Redis 常见使用场景全解析:从基础到实战
Redis 凭借其高性能、丰富的数据结构和灵活的部署方式,已成为现代应用架构中的关键组件。除了大家熟知的缓存功能,Redis 在众多业务场景中都能发挥重要作用。本文将系统梳理 Redis 的常见使用场景,深入分析每种场景的技术实现和最佳实践,帮助开发者充分发挥 Redis 的价值。
一、热点数据缓存
这是 Redis 最经典的应用场景。在电商、社交等高频访问场景中,将热点数据(如商品详情、用户信息)缓存到 Redis,可大幅降低数据库压力,提升系统响应速度。
实现要点:
- 缓存策略:采用 Cache-Aside 模式(先查缓存,未命中再查数据库并更新缓存)
- 过期控制:设置合理的 TTL(生存时间),避免缓存数据长期不更新
- 缓存键设计:使用业务前缀 + 唯一标识,如product:info:1001
- 空值处理:对查询不到的数据缓存空值(短 TTL),防止缓存穿透
kotlin
// 商品信息缓存示例
public ProductDTO getProductInfo(Long productId) {
String cacheKey = "product:info:" + productId;
// 尝试从缓存获取
ProductDTO product = (ProductDTO) redisTemplate.opsForValue().get(cacheKey);
if (product != null) {
return product;
}
// 缓存未命中,查询数据库
ProductDO productDO = productMapper.selectById(productId);
if (productDO != null) {
product = convertToDTO(productDO);
// 写入缓存,设置30分钟过期
redisTemplate.opsForValue().set(cacheKey, product, 30, TimeUnit.MINUTES);
} else {
// 缓存空值,设置5分钟过期
redisTemplate.opsForValue().set(cacheKey, new ProductDTO(), 5, TimeUnit.MINUTES);
}
return product;
}
二、分布式锁
在分布式系统中,多个服务实例需要协同操作共享资源时,分布式锁是保证数据一致性的关键机制。Redis 的SET NX命令天然适合实现分布式锁。
实现要点:
- 加锁:使用SET key value NX PX 30000(不存在则设置,过期时间 30 秒)
- 解锁:通过 Lua 脚本原子性判断并删除锁,避免误删
- 续期:对长时间运行的任务,需定时延长锁的有效期
- 重试机制:获取锁失败时,实现合理的重试策略
typescript
// 分布式锁核心实现
public boolean tryLock(String lockKey, long expireTime, TimeUnit unit) {
String identifier = UUID.randomUUID().toString();
Boolean success = redisTemplate.opsForValue().setIfAbsent(
lockKey, identifier, expireTime, unit
);
if (Boolean.TRUE.equals(success)) {
// 启动定时续期任务
startLockRenewer(lockKey, identifier, expireTime, unit);
return true;
}
return false;
}
// 解锁Lua脚本
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";
三、计数器与限流
Redis 的原子递增命令INCR非常适合实现计数器功能,结合过期时间可实现接口限流。
典型应用:
- 文章阅读量、视频播放次数统计
- 接口访问频率限制(如每分钟最多 100 次请求)
- 秒杀活动中的库存计数
java
// 接口限流实现
public boolean isAllowed(String userId, String action, int maxCount, int period) {
String key = "rate_limit:" + action + ":" + userId;
Long count = redisTemplate.opsForValue().increment(key);
if (count != null && count == 1) {
// 首次访问,设置过期时间
redisTemplate.expire(key, period, TimeUnit.SECONDS);
}
return count != null && count <= maxCount;
}
// 阅读量统计
public long incrementReadCount(Long articleId) {
String key = "article:read_count:" + articleId;
return redisTemplate.opsForValue().increment(key);
}
四、排行榜系统
Redis 的 Sorted Set(有序集合)数据结构完美适配排行榜需求,支持按分数实时排序。
适用场景:
- 商品销量榜、用户积分榜
- 游戏排行榜(如分数、通关时间)
- 实时投票结果展示
arduino
// 排行榜服务
public class RankingService {
// 添加/更新分数
public void updateScore(String rankKey, String member, double score) {
redisTemplate.opsForZSet().add(rankKey, member, score);
}
// 获取Top N排名
public List<RankingItem> getTopRank(String rankKey, int topN) {
Set<ZSetOperations.TypedTuple<Object>> tuples = redisTemplate.opsForZSet()
.reverseRangeWithScores(rankKey, 0, topN - 1);
List<RankingItem> result = new ArrayList<>();
int rank = 1;
for (ZSetOperations.TypedTuple<Object> tuple : tuples) {
result.add(new RankingItem(
tuple.getValue().toString(),
tuple.getScore(),
rank++
));
}
return result;
}
// 获取用户排名
public Long getUserRank(String rankKey, String member) {
return redisTemplate.opsForZSet().reverseRank(rankKey, member);
}
}
五、分布式会话
在分布式系统中,使用 Redis 存储用户会话信息,可实现多服务实例间的会话共享。
实现优势:
- 会话信息集中存储,服务水平扩展无压力
- 支持设置过期时间,自动清理无效会话
- 可实现会话共享,提升用户体验
java
// 分布式会话管理
public class RedisSessionManager {
private static final String SESSION_PREFIX = "session:";
private static final int SESSION_EXPIRE_HOURS = 2;
// 创建会话
public String createSession(UserDTO user) {
String sessionId = UUID.randomUUID().toString();
String key = SESSION_PREFIX + sessionId;
redisTemplate.opsForValue().set(key, user, SESSION_EXPIRE_HOURS, TimeUnit.HOURS);
return sessionId;
}
// 获取会话
public UserDTO getSession(String sessionId) {
if (sessionId == null) return null;
String key = SESSION_PREFIX + sessionId;
UserDTO user = (UserDTO) redisTemplate.opsForValue().get(key);
if (user != null) {
// 刷新过期时间
redisTemplate.expire(key, SESSION_EXPIRE_HOURS, TimeUnit.HOURS);
}
return user;
}
}
六、消息队列
利用 Redis 的 List 数据结构可实现简单的消息队列,适合对消息可靠性要求不高的场景。
实现方式:
- 生产者:使用LPUSH将消息推入队列
- 消费者:使用BRPOP阻塞式获取消息(避免轮询)
- 支持多个消费者协同工作,实现负载均衡
typescript
// 简单消息队列实现
public class RedisMessageQueue {
private String queueKey;
public RedisMessageQueue(String queueKey) {
this.queueKey = queueKey;
}
// 发送消息
public Long sendMessage(String message) {
return redisTemplate.opsForList().leftPush(queueKey, message);
}
// 接收消息(阻塞式)
public String receiveMessage(long timeout, TimeUnit unit) {
List<Object> messages = redisTemplate.opsForList().rightPop(queueKey, timeout, unit);
return messages != null && !messages.isEmpty() ? messages.get(0).toString() : null;
}
// 消息监听
public void listenMessage(Consumer<String> consumer) {
new Thread(() -> {
while (true) {
try {
String message = receiveMessage(1, TimeUnit.MINUTES);
if (message != null) {
consumer.accept(message);
}
} catch (Exception e) {
// 异常处理
}
}
}).start();
}
}
七、位图应用
Redis 的 Bitmap(位图)是一种节省空间的二进制数据结构,适合存储大量布尔值信息。
典型场景:
- 用户签到记录(如一年内的签到情况)
- 活跃用户统计(如最近 7 天活跃用户)
- 权限标记(如用户拥有的权限集合)
ini
// 签到功能实现
public class CheckInService {
// 用户签到
public boolean checkIn(Long userId) {
String key = "checkin:" + userId + ":" + LocalDate.now().getYear();
int dayOfYear = LocalDate.now().getDayOfYear() - 1; // 从0开始
return redisTemplate.opsForValue().setBit(key, dayOfYear, true);
}
// 获取当月签到次数
public long getMonthlyCheckInCount(Long userId) {
LocalDate today = LocalDate.now();
String key = "checkin:" + userId + ":" + today.getYear();
int lastDay = today.lengthOfMonth();
return redisTemplate.execute(
(RedisCallback<Long>) connection -> connection.bitCount(key.getBytes(), 0, lastDay)
);
}
// 获取连续签到天数
public int getContinuousCheckInDays(Long userId) {
LocalDate today = LocalDate.now();
String key = "checkin:" + userId + ":" + today.getYear();
int dayOfYear = today.getDayOfYear() - 1;
int continuousDays = 0;
// 从今天开始往前查,直到遇到未签到的日期
for (int i = 0; i <= dayOfYear; i++) {
Boolean isCheckIn = redisTemplate.opsForValue().getBit(key, dayOfYear - i);
if (Boolean.TRUE.equals(isCheckIn)) {
continuousDays++;
} else {
break;
}
}
return continuousDays;
}
}
八、地理信息存储
Redis 的 GEO 数据结构专门用于存储和操作地理坐标信息,适合 LBS(基于位置的服务)场景。
应用场景:
- 附近的人 / 店铺查询
- 地理位置标记与距离计算
- 区域内的实体搜索
java
// 附近店铺查询
public class NearbyStoreService {
private static final String GEO_KEY = "stores:locations";
// 添加店铺位置
public void addStoreLocation(Long storeId, double longitude, double latitude) {
redisTemplate.opsForGeo().add(GEO_KEY, new Point(longitude, latitude), storeId.toString());
}
// 搜索附近的店铺
public List<StoreLocation> findNearbyStores(double longitude, double latitude, double radius) {
// 搜索半径radius公里内的店铺,返回前20名
GeoResults<GeoLocation<Object>> results = redisTemplate.opsForGeo()
.radius(GEO_KEY, new Circle(new Point(longitude, latitude), new Distance(radius, Metrics.KILOMETERS)),
RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs().includeDistance().limit(20));
List<StoreLocation> stores = new ArrayList<>();
for (GeoResult<GeoLocation<Object>> result : results) {
StoreLocation location = new StoreLocation();
location.setStoreId(Long.valueOf(result.getContent().getName()));
location.setDistance(result.getDistance().getValue());
stores.add(location);
}
return stores;
}
}
九、HyperLogLog 统计
Redis 的 HyperLogLog 是一种概率性数据结构,用于高效统计基数(不重复元素数量)。
适用场景:
- 网站独立访客(UV)统计
- 页面浏览用户去重统计
- 大型活动参与人数统计
vbnet
// UV统计实现
public class UvStatService {
// 记录访问用户
public void recordVisit(Long userId) {
String key = "uv:stat:" + LocalDate.now().toString();
redisTemplate.opsForHyperLogLog().add(key, userId);
}
// 获取当日UV
public long getTodayUv() {
String key = "uv:stat:" + LocalDate.now().toString();
return redisTemplate.opsForHyperLogLog().size(key);
}
// 获取指定日期范围的UV
public long getUvByDateRange(LocalDate start, LocalDate end) {
List<String> keys = new ArrayList<>();
LocalDate date = start;
while (!date.isAfter(end)) {
keys.add("uv:stat:" + date.toString());
date = date.plusDays(1);
}
String destKey = "uv:stat:temp:" + System.currentTimeMillis();
// 合并多个HyperLogLog
redisTemplate.opsForHyperLogLog().union(destKey, keys.toArray(new String[0]));
long total = redisTemplate.opsForHyperLogLog().size(destKey);
// 删除临时key
redisTemplate.delete(destKey);
return total;
}
}
十、总结与最佳实践
Redis 的多样化数据结构使其能够适应多种业务场景,在实际应用中,还需注意以下最佳实践:
- 合理选择数据结构:根据业务特点选择最适合的数据结构,如计数器用 String,排行榜用 Sorted Set
- 键设计规范:采用 "业务:模块:标识" 的命名方式,如user:info:1001,便于管理和统计
- 内存管理:设置合理的过期时间,避免内存溢出;根据数据特性选择合适的淘汰策略
- 性能优化:批量操作使用 Pipeline,避免频繁的网络交互;合理使用连接池
- 监控与告警:实时监控 Redis 的内存使用率、命中率、响应时间等关键指标
- 高可用部署:生产环境采用主从复制 + 哨兵模式或 Redis Cluster,保证服务可用性
通过灵活运用 Redis 的各种特性,不仅能解决实际业务问题,还能显著提升系统性能和可扩展性。在选择使用场景时,需结合业务需求和 Redis 的特性综合考量,才能充分发挥其价值。