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

相关推荐
速易达网络6 分钟前
基于Java TCP 聊天室
java·开发语言·tcp/ip
沿着路走到底19 分钟前
JS事件循环
java·前端·javascript
爱笑的眼睛1130 分钟前
超越 `cross_val_score`:深度解析Scikit-learn交叉验证API的架构、技巧与陷阱
java·人工智能·python·ai
今晚务必早点睡1 小时前
Redis——快速入门第二课:Redis 常用命令 + 能解决实际问题
数据库·redis·bootstrap
❀͜͡傀儡师2 小时前
SpringBoot 扫码登录全流程:UUID 生成、状态轮询、授权回调详解
java·spring boot·后端
a努力。2 小时前
国家电网Java面试被问:Spring Boot Starter 制作原理
java·spring boot·面试
一 乐2 小时前
酒店预约|基于springboot + vue酒店预约系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端
guslegend2 小时前
Tomact高级使用及原理剖析
java
Code blocks2 小时前
SpringBoot从0-1集成Minio对象存储
java·spring boot·后端