1. Redis 多场景应用全景解读
作为从业十年的Java开发者,我深刻体会到Redis在各类业务系统中的重要性。它早已超越简单的缓存角色,成为高并发架构中的瑞士军刀。本文将分享我在电商、社交、金融等不同领域落地Redis时积累的完整代码模板,每个方案都经过生产环境验证。
Redis之所以能覆盖如此多的业务场景,核心在于其丰富的数据结构和对高性能的极致追求。String类型的原子计数器、Hash的对象存储、List的队列特性、Set的去重能力、ZSet的排序功能,配合Lua脚本和管道技术,几乎能解决所有高并发场景下的数据读写难题。
2. 核心数据结构与业务场景匹配
2.1 字符串(String)实战模板
字符串是Redis最基础的数据类型,在以下场景表现尤为出色:
计数器实现(分布式ID生成)
java复制// 分布式自增ID生成器
public class RedisIdGenerator {
private final JedisPool jedisPool;
private final String bizKey;
public Long generateId() {
try (Jedis jedis = jedisPool.getResource()) {
return jedis.incr(bizKey);
}
}
}
关键点:incr命令的原子性保证即使在万级QPS下也不会出现重复ID
缓存雪崩防护方案
java复制// 带随机过期的缓存写入
public void setWithRandomExpire(String key, String value) {
try (Jedis jedis = jedisPool.getResource()) {
int baseExpire = 3600; // 基础过期时间1小时
int randomExpire = new Random().nextInt(600); // 随机10分钟
jedis.setex(key, baseExpire + randomExpire, value);
}
}
2.2 哈希(Hash)实战模板
Hash结构特别适合存储对象数据,我在用户画像系统中大量使用:
用户属性存储方案
java复制// 用户画像存储
public void saveUserProfile(String userId, Map<String, String> profile) {
try (Jedis jedis = jedisPool.getResource()) {
jedis.hset("user:" + userId, profile);
jedis.expire("user:" + userId, 86400); // 24小时过期
}
}
// 批量获取用户属性
public Map<String, String> getUserProfiles(List<String> userIds) {
Pipeline pipeline = jedisPool.getResource().pipelined();
for (String userId : userIds) {
pipeline.hgetAll("user:" + userId);
}
List<Object> results = pipeline.syncAndReturnAll();
// 结果处理逻辑...
}
性能对比:相比String类型分字段存储,Hash结构在字段较多时可减少50%以上的网络开销
2.3 列表(List)实战模板
List结构在消息队列和最新列表场景下表现出色:
最新消息队列实现
java复制// 消息生产者
public void pushNews(String channel, String newsId) {
try (Jedis jedis = jedisPool.getResource()) {
jedis.lpush("news:" + channel, newsId);
jedis.ltrim("news:" + channel, 0, 99); // 只保留最新100条
}
}
// 消息消费者
public List<String> getLatestNews(String channel, int count) {
try (Jedis jedis = jedisPool.getResource()) {
return jedis.lrange("news:" + channel, 0, count-1);
}
}
3. 高级特性实战应用
3.1 分布式锁深度优化
基础版分布式锁存在诸多隐患,以下是经过生产验证的改进方案:
java复制public class RedisDistributedLock {
private static final String LOCK_SUCCESS = "OK";
private static final String SET_IF_NOT_EXIST = "NX";
private static final String SET_WITH_EXPIRE_TIME = "PX";
public boolean tryLock(String lockKey, String requestId, int expireTime) {
try (Jedis jedis = jedisPool.getResource()) {
String result = jedis.set(lockKey, requestId,
SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
return LOCK_SUCCESS.equals(result);
}
}
public boolean unlock(String lockKey, String requestId) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
"return redis.call('del', KEYS[1]) " +
"else return 0 end";
try (Jedis jedis = jedisPool.getResource()) {
Object result = jedis.eval(script,
Collections.singletonList(lockKey),
Collections.singletonList(requestId));
return result.equals(1L);
}
}
}
避坑指南:
- 必须设置唯一requestId避免误删
- 使用Lua脚本保证原子性
- 过期时间建议设置在10-30秒
- 需要实现重试机制和锁续期
3.2 延迟队列精准实现
基于ZSet的延迟队列方案:
java复制public class RedisDelayedQueue {
public void addToQueue(String queueKey, String message, long delaySeconds) {
try (Jedis jedis = jedisPool.getResource()) {
double score = System.currentTimeMillis() + delaySeconds * 1000;
jedis.zadd(queueKey, score, message);
}
}
public List<String> pollReadyMessages(String queueKey, int batchSize) {
try (Jedis jedis = jedisPool.getResource()) {
long now = System.currentTimeMillis();
Set<String> messages = jedis.zrangeByScore(queueKey, 0, now, 0, batchSize);
if (!messages.isEmpty()) {
jedis.zrem(queueKey, messages.toArray(new String[0]));
}
return new ArrayList<>(messages);
}
}
}
4. 性能优化实战技巧
4.1 管道技术批量操作
java复制public void batchUpdateUserStatus(List<String> userIds, String status) {
try (Jedis jedis = jedisPool.getResource()) {
Pipeline pipeline = jedis.pipelined();
for (String userId : userIds) {
pipeline.hset("user:" + userId, "status", status);
}
pipeline.sync();
}
}
性能对比:
| 操作方式 | 1000次操作耗时 |
|---|---|
| 普通模式 | 1200ms |
| 管道模式 | 85ms |
4.2 Lua脚本原子操作
java复制// 库存扣减脚本
public boolean deductStock(String key, int num) {
String script = "local stock = tonumber(redis.call('get', KEYS[1])) " +
"if stock >= tonumber(ARGV[1]) then " +
"return redis.call('decrby', KEYS[1], ARGV[1]) " +
"else return -1 end";
try (Jedis jedis = jedisPool.getResource()) {
Object result = jedis.eval(script,
Collections.singletonList(key),
Collections.singletonList(String.valueOf(num)));
return ((Long)result) >= 0;
}
}
5. 生产环境避坑指南
-
连接池配置要点:
- maxTotal根据业务QPS设置,建议QPS*平均耗时(ms)/1000 + 缓冲数
- maxIdle建议设置为maxTotal的1/3到1/2
- testOnBorrow必须设置为true
-
热点Key发现与处理:
java复制// 使用本地缓存+Redis的多级缓存方案 public class HotKeyCache { private final LoadingCache<String, String> localCache = Caffeine.newBuilder() .maximumSize(10000) .expireAfterWrite(1, TimeUnit.SECONDS) .build(key -> { try (Jedis jedis = jedisPool.getResource()) { return jedis.get(key); } }); } -
大Key拆分策略:
- Hash类型字段超过1000个时考虑分片
- List/Set元素超过5000时考虑拆分
- 单个Value超过10KB时考虑压缩或拆分
-
内存优化技巧:
- 使用Hash结构代替多个String
- 对长文本启用压缩(GZIP)
- 合理设置过期时间(建议不超过24小时)
在金融级系统中,我们还会额外添加以下保障措施:
- 所有写操作记录binlog用于恢复
- 关键操作添加慢查询监控(超过50ms报警)
- 定期执行scan命令查找潜在大Key