当前位置: 首页 > news >正文

Redis | 基于 Redis 实现机器列表 Token 缓存的 Java 实现

关注:CodingTechWork

引言

  在分布式系统中,Token 缓存是一种常见的需求。它可以帮助我们快速验证用户身份,减少对数据库的频繁访问,提高系统的性能和响应速度。本文将介绍如何使用 Redis 来实现机器列表的 Token 缓存,在 Kubernetes Pod 部署的环境中,为了避免多个 Pod 同时执行相同的定时任务(如刷新缓存 Token),我们需要引入分布式锁机制。以下是基于RedisTemplate分布式锁实现的分布式刷新缓存 Token 的完整 Java 示例代码。

为什么选择 Redis

  Redis 是一个高性能的键值存储数据库,它提供了丰富的数据结构和极高的读写速度。以下是选择 Redis 实现 Token 缓存的原因:

  • 高性能:Redis 的读写速度非常快,能够轻松应对高并发场景下的 Token 验证请求。
  • 持久化支持:虽然 Redis 是内存数据库,但它支持多种持久化方式,可以保证数据在机器故障时不会丢失。
  • 易于使用:Redis 提供了丰富的客户端库,方便在各种编程语言中使用。

设计思路

  1. Token 的生成与存储:使用 UUID 生成唯一的 Token,并通过 RedisTemplate 存储到 Redis 中,同时设置过期时间
  2. Token 的验证:通过 RedisTemplate 从 Redis 中获取 Token,并检查其是否存在和是否过期。
  3. 分布式锁:使用 Redis 实现分布式锁,确保在分布式环境中只有一个 Pod 能够执行 Token 刷新任务。
  4. 定时刷新 Token:使用 Spring 的 @Scheduled注解实现定时任务,结合分布式锁确保同一时间只有一个 Pod 执行 Token 刷新操作。(当然,我们也可以引入xxl-job组件来实现定时任务)

Java 实现

引入依赖

在 Maven 项目中,需要引入 Spring Boot Starter Data Redis`` 和 Spring Boot Starter Scheduling 的依赖。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-scheduling</artifactId>
</dependency>

Redis 配置类

创建一个 Redis 配置类,用于配置 RedisTemplate。

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);

        // 设置键的序列化方式
        template.setKeySerializer(new StringRedisSerializer());
        // 设置值的序列化方式
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());

        return template;
    }
}

分布式锁工具类

创建一个分布式锁工具类,用于获取和释放锁。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

@Component
public class RedisDistributedLock {
    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    private static final String LOCK_PREFIX = "scheduled_task_lock:";
    // 锁过期时间(秒)
    private static final int LOCK_EXPIRE_TIME = 30; 

    /**
     * 尝试获取分布式锁
     * @param taskName 任务名称
     * @param nodeIdentifier 节点标识
     * @return 是否获取到锁
     */
    public boolean tryAcquireLock(String taskName, String nodeIdentifier) {
        String lockKey = LOCK_PREFIX + taskName;
        String lockValue = String.valueOf(System.currentTimeMillis() + LOCK_EXPIRE_TIME * 1000);
        if (Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, LOCK_EXPIRE_TIME, TimeUnit.SECONDS))) {
            return true;
        } else {
            String currentValue = redisTemplate.opsForValue().get(lockKey);
            if (currentValue != null && Long.parseLong(currentValue) < System.currentTimeMillis()) {
                String oldValue = redisTemplate.opsForValue().getAndSet(lockKey, lockValue);
                if (oldValue != null && oldValue.equals(currentValue)) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * 释放分布式锁
     * @param taskName 任务名称
     * @param nodeIdentifier 节点标识
     */
    public void releaseLock(String taskName, String nodeIdentifier) {
        String lockKey = LOCK_PREFIX + taskName;
        redisTemplate.delete(lockKey);
    }
}

Token 缓存类

创建一个 Token 缓存类,用于生成、存储和验证 Token,同时提供刷新 Token 的方法。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

@Component
public class TokenCache {
    private static final String TOKEN_PREFIX = "token:";
    // Token 过期时间(秒)可以配置化
    private static final long EXPIRE_TIME = 60 * 60; 
    // 提前 5 分钟刷新 Token可以配置化
    private static final long REFRESH_THRESHOLD = 60 * 5; 

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 生成 Token
     */
    public String generateToken() {
        return java.util.UUID.randomUUID().toString();
    }

    /**
     * 存储 Token
     * @param token Token
     * @param machineId 机器 ID
     */
    public void storeToken(String token, String machineId) {
        redisTemplate.opsForValue().set(TOKEN_PREFIX + token, machineId, EXPIRE_TIME, TimeUnit.SECONDS);
    }

    /**
     * 验证 Token
     * @param token Token
     * @return 验证结果(true 表示有效,false 表示无效)
     */
    public boolean verifyToken(String token) {
        Object machineId = redisTemplate.opsForValue().get(TOKEN_PREFIX + token);
        return machineId != null;
    }

    /**
     * 刷新 Token
     * @param token Token
     */
    public void refreshToken(String token) {
        redisTemplate.expire(TOKEN_PREFIX + token, EXPIRE_TIME, TimeUnit.SECONDS);
    }
}

定时任务类

创建一个定时任务类,用于定期检查并刷新即将过期的 Token。使用分布式锁确保同一时间只有一个 Pod 执行该任务。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.util.Set;

@Component
public class TokenRefreshTask {
    private static final String TASK_NAME = "refreshTokenTask";
    // 节点标识,可以根据实际情况动态生成
    private static final String NODE_IDENTIFIER = "node-1"; 

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    @Autowired
    private TokenCache tokenCache;
    @Autowired
    private RedisDistributedLock redisDistributedLock;

    /**
     * 定时任务:刷新即将过期的 Token
     */
    @Scheduled(fixedRate = 60 * 1000) // 每分钟执行一次
    public void refreshTokenTask() {
        if (redisDistributedLock.tryAcquireLock(TASK_NAME, NODE_IDENTIFIER)) {
            try {
                Set<String> keys = redisTemplate.keys(TokenCache.TOKEN_PREFIX + "*");
                if (keys != null) {
                    for (String key : keys) {
                        Long ttl = redisTemplate.getExpire(key, TimeUnit.SECONDS);
                        if (ttl != null && ttl <= TokenCache.REFRESH_THRESHOLD) {
                            tokenCache.refreshToken(key.replace(TokenCache.TOKEN_PREFIX, ""));
                            System.out.println("Token 刷新成功: " + key);
                        }
                    }
                }
            } finally {
                redisDistributedLock.releaseLock(TASK_NAME, NODE_IDENTIFIER);
            }
        } else {
            System.out.println("Token 刷新任务已被其他节点执行");
        }
    }
}

启动类

确保启动类中启用了定时任务。

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableScheduling
public class TokenCacheApplication {
    public static void main(String[] args) {
        SpringApplication.run(TokenCacheApplication.class, args);
    }
}

运行结果

在 Kubernetes 环境中部署多个 Pod 后,运行程序,观察输出结果:

Token 刷新成功: token:0f5d3f6e-7f8b-4d9b-8a1b-4d8c7f6e8d9b
Token 刷新任务已被其他节点执行

从结果可以看出,只有获取到分布式锁的 Pod 会执行 Token 刷新任务,其他 Pod 会跳过该任务。

总结

  通过引入分布式锁机制,我们成功解决了在 Kubernetes Pod 部署环境下分布式刷新缓存 Token 的问题。使用 Redis 实现的分布式锁确保了同一时间只有一个 Pod 能够执行 Token 刷新任务,避免了重复执行的问题。这种机制不仅适用于 Token 刷新,还可以扩展到其他需要分布式定时任务的场景。

相关文章:

  • CSS——变换、过度与动画
  • 当贝AI知识库评测 AI如何让知识检索快人一步
  • 《数据库原理》SQLServer期末复习_题型+考点
  • Vue3当中el-tree树形控件使用
  • MyBatisPlus 中,模糊查询
  • SQL Server安装进度卡在 57%:Windows Update 服务异常
  • 基于大模型的自发性气胸全方位预测与诊疗方案研究
  • 数字人分身生成50语种发布会视频技术架构深度解析
  • 【蓝桥杯】单片机设计与开发,PWM
  • 面试的时候问到了HTML5的新特性有哪些
  • eBay多账号安全运营技术体系:从环境隔离到智能风控的工程化实践
  • 百度文库标题生成器 v2.0:高效创作,一键生成文章优质标题生成器
  • 如何验证极端工况下的系统可靠性?
  • 无人机DSP处理器工作要点!
  • 3.28日,NBA,欧篮联全扫盘,太阳VS森林狼
  • 使用git-lfs管理大文件
  • Giteki 认证:无线产品进入日本市场的关键保障
  • STM32通用定时器结构框图
  • Linux常见使用场景
  • c# ftp上传下载 帮助类
  • 百度推广需要什么条件/网站seo排名培训
  • 网站建制作/东莞网站快速排名提升
  • 微网站建设公司哪家好/谷歌优化培训
  • 国家税务总局网站官网发票查询/关键词优化方法有什么步骤
  • 苏州网站建设哪家效果好/seo优化软件
  • 商业政府网站cms/百度推广代理公司