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 的依赖。

xml 复制代码
<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。

java 复制代码
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;
    }
}

分布式锁工具类

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

java 复制代码
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 的方法。

java 复制代码
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 执行该任务。

java 复制代码
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 刷新任务已被其他节点执行");
        }
    }
}

启动类

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

java 复制代码
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 刷新,还可以扩展到其他需要分布式定时任务的场景。

相关推荐
xyliiiiiL19 分钟前
从责任链模式聊到aware接口
java·开发语言
码农老起3 小时前
与Aspose.pdf类似的jar库分享
java·pdf·jar
程序猿小D4 小时前
第三百八十九节 JavaFX教程 - JavaFX WebEngine
java·eclipse·intellij-idea·vr·javafx
self-discipline6346 小时前
【Java】Java核心知识点与相应面试技巧(七)——类与对象(二)
java·开发语言·面试
wei3872452326 小时前
java笔记02
java·开发语言·笔记
zjj5876 小时前
Docker使用ubuntu
java·docker·eureka
士别三日&&当刮目相看6 小时前
JAVA学习*简单的代理模式
java·学习·代理模式
ShareBeHappy_Qin7 小时前
设计模式——设计模式理念
java·设计模式
Chandler249 小时前
Redis:事务
数据库·redis·缓存
程序猿大波9 小时前
基于Java,SpringBoot,Vue,HTML高校社团信息管理系统设计
java·vue.js·spring boot