☕ Java 使用 Redis

Java Redis 客户端概述

Java 生态系统中有多个优秀的 Redis 客户端库,每个都有其特点和适用场景。选择合适的客户端对于项目的性能和开发效率至关重要。

🔧 Jedis

Redis 官方推荐的 Java 客户端,简单易用,API 直观。

✅ 优点
  • API 简单直观
  • 官方支持
  • 文档完善
  • 社区活跃
❌ 缺点
  • 线程不安全
  • 功能相对简单
  • 需要连接池管理

🚀 Lettuce

基于 Netty 的异步 Redis 客户端,支持响应式编程。

✅ 优点
  • 线程安全
  • 支持异步操作
  • 响应式编程
  • 连接复用
❌ 缺点
  • 学习曲线较陡
  • 配置复杂
  • 依赖较多

⚡ Redisson

功能丰富的 Redis Java 客户端,提供分布式对象和服务。

✅ 优点
  • 功能丰富
  • 分布式锁
  • 集合操作
  • 高级特性
❌ 缺点
  • 体积较大
  • 复杂度高
  • 性能开销
  • 环境准备

    Maven 依赖

    redis.clients jedis 4.4.3 io.lettuce lettuce-core 6.2.6.RELEASE org.redisson redisson 3.23.4 org.apache.commons commons-pool2 2.11.1

    Jedis 使用详解

    基础连接和操作

    import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; public class JedisExample { public static void main(String[] args) { // 创建 Jedis 连接 Jedis jedis = new Jedis("localhost", 6379); try { // 测试连接 String pong = jedis.ping(); System.out.println("连接测试: " + pong); // 字符串操作 jedis.set("name", "张三"); String name = jedis.get("name"); System.out.println("姓名: " + name); // 设置过期时间 jedis.setex("session:123", 3600, "user_data"); // 哈希操作 jedis.hset("user:1001", "name", "李四"); jedis.hset("user:1001", "age", "25"); jedis.hset("user:1001", "city", "北京"); String userName = jedis.hget("user:1001", "name"); System.out.println("用户名: " + userName); // 列表操作 jedis.lpush("tasks", "任务1", "任务2", "任务3"); String task = jedis.rpop("tasks"); System.out.println("取出任务: " + task); // 集合操作 jedis.sadd("tags", "Java", "Redis", "Spring"); boolean isMember = jedis.sismember("tags", "Java"); System.out.println("是否包含Java: " + isMember); // 有序集合操作 jedis.zadd("leaderboard", 1500, "player1"); jedis.zadd("leaderboard", 2000, "player2"); jedis.zadd("leaderboard", 1800, "player3"); // 获取排行榜前3名 var topPlayers = jedis.zrevrange("leaderboard", 0, 2); System.out.println("排行榜前3名: " + topPlayers); } finally { jedis.close(); } } }

    连接池配置

    import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; import redis.clients.jedis.Jedis; public class JedisPoolExample { private static JedisPool jedisPool; static { // 连接池配置 JedisPoolConfig config = new JedisPoolConfig(); config.setMaxTotal(100); // 最大连接数 config.setMaxIdle(20); // 最大空闲连接数 config.setMinIdle(5); // 最小空闲连接数 config.setMaxWaitMillis(3000); // 最大等待时间 config.setTestOnBorrow(true); // 获取连接时测试 config.setTestOnReturn(true); // 归还连接时测试 config.setTestWhileIdle(true); // 空闲时测试 // 创建连接池 jedisPool = new JedisPool(config, "localhost", 6379, 2000); } public static Jedis getJedis() { return jedisPool.getResource(); } public static void closePool() { if (jedisPool != null) { jedisPool.close(); } } // 使用示例 public static void example() { Jedis jedis = getJedis(); try { jedis.set("pool:test", "连接池测试"); String value = jedis.get("pool:test"); System.out.println(value); } finally { jedis.close(); // 归还连接到池中 } } }

    工具类封装

    public class RedisUtil { private static final JedisPool jedisPool; static { JedisPoolConfig config = new JedisPoolConfig(); config.setMaxTotal(100); config.setMaxIdle(20); config.setMinIdle(5); config.setMaxWaitMillis(3000); config.setTestOnBorrow(true); jedisPool = new JedisPool(config, "localhost", 6379); } /** * 执行 Redis 操作 */ public static T execute(Function action) { Jedis jedis = jedisPool.getResource(); try { return action.apply(jedis); } finally { jedis.close(); } } /** * 设置字符串值 */ public static void set(String key, String value) { execute(jedis -> { jedis.set(key, value); return null; }); } /** * 获取字符串值 */ public static String get(String key) { return execute(jedis -> jedis.get(key)); } /** * 设置带过期时间的值 */ public static void setex(String key, int seconds, String value) { execute(jedis -> { jedis.setex(key, seconds, value); return null; }); } /** * 删除键 */ public static void del(String key) { execute(jedis -> jedis.del(key)); } /** * 检查键是否存在 */ public static boolean exists(String key) { return execute(jedis -> jedis.exists(key)); } }

    集群模式配置

    import redis.clients.jedis.JedisCluster; import redis.clients.jedis.HostAndPort; import redis.clients.jedis.JedisPoolConfig; import java.util.HashSet; import java.util.Set; public class JedisClusterExample { private static JedisCluster jedisCluster; static { // 集群节点 Set nodes = new HashSet<>(); nodes.add(new HostAndPort("192.168.1.100", 7000)); nodes.add(new HostAndPort("192.168.1.100", 7001)); nodes.add(new HostAndPort("192.168.1.101", 7000)); nodes.add(new HostAndPort("192.168.1.101", 7001)); nodes.add(new HostAndPort("192.168.1.102", 7000)); nodes.add(new HostAndPort("192.168.1.102", 7001)); // 连接池配置 JedisPoolConfig config = new JedisPoolConfig(); config.setMaxTotal(100); config.setMaxIdle(20); config.setMinIdle(5); config.setMaxWaitMillis(3000); config.setTestOnBorrow(true); // 创建集群连接 jedisCluster = new JedisCluster(nodes, 2000, 5000, 5, config); } public static void example() { try { // 集群操作与单机操作相同 jedisCluster.set("cluster:test", "集群测试"); String value = jedisCluster.get("cluster:test"); System.out.println("集群值: " + value); // 哈希操作 jedisCluster.hset("cluster:user:1001", "name", "集群用户"); String name = jedisCluster.hget("cluster:user:1001", "name"); System.out.println("集群用户名: " + name); } catch (Exception e) { e.printStackTrace(); } } public static void close() { if (jedisCluster != null) { try { jedisCluster.close(); } catch (Exception e) { e.printStackTrace(); } } } }

    实际应用场景

    1. 缓存管理

    public class CacheManager { private static final int DEFAULT_EXPIRE = 3600; // 1小时 /** * 缓存用户信息 */ public static void cacheUser(Long userId, User user) { String key = "user:" + userId; String userJson = JSON.toJSONString(user); RedisUtil.setex(key, DEFAULT_EXPIRE, userJson); } /** * 获取缓存的用户信息 */ public static User getUser(Long userId) { String key = "user:" + userId; String userJson = RedisUtil.get(key); if (userJson != null) { return JSON.parseObject(userJson, User.class); } return null; } /** * 缓存查询结果 */ public static void cacheQueryResult(String queryKey, Object result, int expireSeconds) { String resultJson = JSON.toJSONString(result); RedisUtil.setex(queryKey, expireSeconds, resultJson); } /** * 获取缓存的查询结果 */ public static <T> T getCachedResult(String queryKey, Class<T> clazz) { String resultJson = RedisUtil.get(queryKey); if (resultJson != null) { return JSON.parseObject(resultJson, clazz); } return null; } }

    2. 分布式锁实现

    public class DistributedLock { private static final String LOCK_PREFIX = "lock:"; private static final String UNLOCK_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then " + "return redis.call('del', KEYS[1]) " + "else return 0 end"; /** * 获取分布式锁 */ public static boolean tryLock(String lockKey, String requestId, int expireTime) { String key = LOCK_PREFIX + lockKey; return RedisUtil.execute(jedis -> { String result = jedis.set(key, requestId, "NX", "EX", expireTime); return "OK".equals(result); }); } /** * 释放分布式锁 */ public static boolean releaseLock(String lockKey, String requestId) { String key = LOCK_PREFIX + lockKey; return RedisUtil.execute(jedis -> { Object result = jedis.eval(UNLOCK_SCRIPT, Collections.singletonList(key), Collections.singletonList(requestId)); return Long.valueOf(1).equals(result); }); } /** * 使用锁执行业务逻辑 */ public static <T> T executeWithLock(String lockKey, int expireTime, Supplier<T> business) { String requestId = UUID.randomUUID().toString(); boolean locked = tryLock(lockKey, requestId, expireTime); if (!locked) { throw new RuntimeException("获取锁失败: " + lockKey); } try { return business.get(); } finally { releaseLock(lockKey, requestId); } } }

    3. 会话管理

    public class SessionManager { private static final String SESSION_PREFIX = "session:"; private static final int SESSION_TIMEOUT = 1800; // 30分钟 /** * 创建会话 */ public static String createSession(Long userId) { String sessionId = UUID.randomUUID().toString(); String key = SESSION_PREFIX + sessionId; Map<String, String> sessionData = new HashMap<>(); sessionData.put("userId", userId.toString()); sessionData.put("createTime", String.valueOf(System.currentTimeMillis())); sessionData.put("lastAccessTime", String.valueOf(System.currentTimeMillis())); RedisUtil.execute(jedis -> { jedis.hmset(key, sessionData); jedis.expire(key, SESSION_TIMEOUT); return null; }); return sessionId; } /** * 获取会话信息 */ public static Map<String, String> getSession(String sessionId) { String key = SESSION_PREFIX + sessionId; return RedisUtil.execute(jedis -> { Map<String, String> session = jedis.hgetAll(key); if (!session.isEmpty()) { // 更新最后访问时间 jedis.hset(key, "lastAccessTime", String.valueOf(System.currentTimeMillis())); jedis.expire(key, SESSION_TIMEOUT); } return session; }); } /** * 删除会话 */ public static void removeSession(String sessionId) { String key = SESSION_PREFIX + sessionId; RedisUtil.del(key); } /** * 验证会话是否有效 */ public static boolean isValidSession(String sessionId) { String key = SESSION_PREFIX + sessionId; return RedisUtil.exists(key); } }

    4. 计数器和限流

    public class CounterAndRateLimit { /** * 简单计数器 */ public static long increment(String counterKey) { return RedisUtil.execute(jedis -> jedis.incr(counterKey)); } /** * 带过期时间的计数器 */ public static long incrementWithExpire(String counterKey, int expireSeconds) { return RedisUtil.execute(jedis -> { long count = jedis.incr(counterKey); if (count == 1) { jedis.expire(counterKey, expireSeconds); } return count; }); } /** * 滑动窗口限流 */ public static boolean isAllowed(String key, int limit, int windowSeconds) { long now = System.currentTimeMillis(); long windowStart = now - windowSeconds * 1000L; return RedisUtil.execute(jedis -> { // 移除过期的记录 jedis.zremrangeByScore(key, 0, windowStart); // 获取当前窗口内的请求数 long count = jedis.zcard(key); if (count < limit) { // 添加当前请求 jedis.zadd(key, now, String.valueOf(now)); jedis.expire(key, windowSeconds); return true; } return false; }); } /** * 令牌桶限流 */ public static boolean tryAcquire(String bucketKey, int capacity, int refillRate) { String script = "local bucket = KEYS[1] " + "local capacity = tonumber(ARGV[1]) " + "local refillRate = tonumber(ARGV[2]) " + "local now = tonumber(ARGV[3]) " + "local bucket_data = redis.call('HMGET', bucket, 'tokens', 'lastRefill') " + "local tokens = tonumber(bucket_data[1]) or capacity " + "local lastRefill = tonumber(bucket_data[2]) or now " + "local elapsed = now - lastRefill " + "local tokensToAdd = math.floor(elapsed * refillRate / 1000) " + "tokens = math.min(capacity, tokens + tokensToAdd) " + "if tokens >= 1 then " + "tokens = tokens - 1 " + "redis.call('HMSET', bucket, 'tokens', tokens, 'lastRefill', now) " + "redis.call('EXPIRE', bucket, 3600) " + "return 1 " + "else " + "redis.call('HMSET', bucket, 'tokens', tokens, 'lastRefill', now) " + "redis.call('EXPIRE', bucket, 3600) " + "return 0 " + "end"; return RedisUtil.execute(jedis -> { Object result = jedis.eval(script, Collections.singletonList(bucketKey), Arrays.asList(String.valueOf(capacity), String.valueOf(refillRate), String.valueOf(System.currentTimeMillis()))); return Long.valueOf(1).equals(result); }); } }

    💡 最佳实践

    • 连接管理:使用连接池,避免频繁创建连接
    • 异常处理:妥善处理网络异常和超时
    • 键命名:使用有意义的键名,建立命名规范
    • 过期时间:合理设置过期时间,避免内存泄漏
    • 序列化:选择合适的序列化方式(JSON、Protobuf等)
    • 监控:监控连接数、响应时间、错误率等指标

    ⚠️ 注意事项

    • 线程安全:Jedis 实例不是线程安全的,需要使用连接池
    • 资源释放:及时关闭连接,避免连接泄漏
    • 大键值:避免存储过大的值,影响性能
    • 批量操作:使用 Pipeline 或事务进行批量操作
    • 网络延迟:考虑网络延迟对性能的影响