Redis核心使用场景
以下是Redis在7个核心场景中的具体应用举例、代码实现(以Java + Jedis为例)及优缺点分析,覆盖数据结构选型和实际落地细节:
1. 数据缓存(String结构)
场景:缓存数据库中高频查询的静态数据(如商品信息、分类列表),减少DB访问压力。
举例:电商商品详情页,用户查询商品时优先从Redis获取,未命中则查DB并同步到Redis。
代码实现
public class ProductCache {private Jedis jedis = new Jedis("localhost", 6379);private ProductDAO productDAO = new ProductDAO();// 获取商品信息(缓存优先)public Product getProduct(Long productId) {String key = "cache:product:" + productId;// 1. 查缓存String productJson = jedis.get(key);if (productJson != null) {return JsonUtils.fromJson(productJson, Product.class); // 缓存命中}// 2. 缓存未命中,查DBProduct product = productDAO.getById(productId);if (product != null) {// 3. 写入缓存,设置10分钟过期(避免缓存 stale)jedis.setex(key, 600, JsonUtils.toJson(product));}return product;}// 更新商品后删除缓存(保证一致性)public void updateProduct(Product product) {productDAO.update(product);jedis.del("cache:product:" + product.getId()); // 删缓存而非更新(避免并发覆盖)}
}
优点
- 响应速度提升10-100倍(从DB的毫秒级到Redis的微秒级)。
- 降低DB读写压力,尤其适合高并发场景(如促销活动)。
- 支持过期自动清理(
setex),无需手动维护。
缺点
- 缓存一致性问题:DB更新后若缓存未及时删除,会出现数据不一致(需通过“更新DB后删缓存”或“延迟双删”解决)。
- 缓存穿透:查询不存在的key(如
productId=-1)会直击DB(可通过布隆过滤器过滤无效key)。 - 内存限制:缓存数据量受Redis内存大小限制,需合理设置淘汰策略(如
maxmemory-policy allkeys-lru)。
2. 会话存储(String/Hash结构)
场景:分布式系统中共享用户登录会话(Session),解决多服务器间会话不互通问题。
举例:微服务架构中,用户登录后Session存Redis,所有API服务器均可通过SessionID获取用户信息。
代码实现
public class SessionManager {private Jedis jedis = new Jedis("localhost", 6379);private static final int SESSION_TTL = 86400; // 24小时过期// 创建会话(用户登录时)public String createSession(User user) {String sessionId = UUID.randomUUID().toString();String key = "session:" + sessionId;// 用Hash存储用户多字段信息(比String更灵活)jedis.hset(key, "userId", user.getId().toString());jedis.hset(key, "username", user.getUsername());jedis.hset(key, "role", user.getRole());jedis.expire(key, SESSION_TTL); // 设置过期时间return sessionId; // 返给客户端(存Cookie/Token)}// 获取会话(用户访问时)public UserSession getSession(String sessionId) {String key = "session:" + sessionId;Map<String, String> sessionData = jedis.hgetAll(key);if (sessionData.isEmpty()) {return null; // 会话过期或不存在}// 延长有效期(用户活跃时续期)jedis.expire(key, SESSION_TTL);return new UserSession(Long.parseLong(sessionData.get("userId")),sessionData.get("username"),sessionData.get("role"));}// 销毁会话(用户登出时)public void destroySession(String sessionId) {jedis.del("session:" + sessionId);}
}
优点
- 分布式共享:解决“用户在服务器A登录,访问服务器B需重登”的问题。
- 减轻服务器内存压力:Session从本地内存转移到Redis,避免单节点内存溢出。
- 自动清理:过期会话自动删除,无需手动维护。
缺点
- 依赖Redis可用性:Redis宕机会导致所有会话失效(需部署主从+哨兵集群保证高可用)。
- 安全风险:SessionID泄露可能导致会话劫持(需用HTTPS传输,或定期刷新SessionID)。
3. 实时排行榜(ZSET结构)
场景:基于分数(如积分、点击量、销量)实时排序,支持“Top N”查询。
举例:游戏积分榜(按用户积分排名)、短视频平台热门视频榜(按播放量排名)。
代码实现
public class GameRanking {private Jedis jedis = new Jedis("localhost", 6379);private static final String RANK_KEY = "rank:game:score";// 新增/更新用户分数public void updateScore(Long userId, int score) {// ZADD:若用户已存在则更新分数,否则新增(分数即排序依据)jedis.zadd(RANK_KEY, score, userId.toString());}// 获取用户排名(从高到低)public Long getUserRank(Long userId) {// ZREVRANK:返回用户排名(0表示第1名)return jedis.zrevrank(RANK_KEY, userId.toString());}// 获取Top 10用户(含分数)public List<RankUser> getTop10() {// ZREVRANGEWithScores:按分数倒序取前10,包含分数Set<Tuple> tuples = jedis.zrevrangeWithScores(RANK_KEY, 0, 9);List<RankUser> result = new ArrayList<>();for (Tuple tuple : tuples) {result.add(new RankUser(Long.parseLong(tuple.getElement()),(int) tuple.getScore()));}return result;}
}
优点
- 高效排序:ZSET底层基于跳表,新增/更新分数复杂度O(logN),查询Top N复杂度O(N),适合高频率更新场景。
- 实时性强:分数更新后立即反映到排名,无需后台定时计算。
缺点
- 内存占用较高:ZSET需存储元素和分数,数据量大时(如千万级用户)会占用较多内存。
- 不支持复杂排序规则:仅支持单一分数排序,无法实现多维度排序(如“分数相同按时间排序”需额外处理)。
4. 分布式锁(String结构)
场景:分布式系统中控制并发访问共享资源(如秒杀库存扣减、订单创建),避免超卖或重复操作。
举例:秒杀活动中,多服务实例抢单时,通过Redis锁保证同一时间只有一个实例能扣减库存。
代码实现
public class DistributedLock {private Jedis jedis = new Jedis("localhost", 6379);// 尝试获取锁(原子操作)public boolean tryLock(String lockKey, String requestId, int expireMs) {// SET key value NX PX:仅当key不存在时设置,过期时间ms(防止死锁)String result = jedis.set(lockKey, requestId, "NX", "PX", expireMs);return "OK".equals(result);}// 释放锁(Lua脚本保证原子性,避免误删他人锁)public boolean releaseLock(String lockKey, String requestId) {String lua = "if redis.call('get', KEYS[1]) == ARGV[1] then " +"return redis.call('del', KEYS[1]) " +"else return 0 end";Long result = (Long) jedis.eval(lua, 1, lockKey, requestId);return result == 1;}// 秒杀扣减库存示例public boolean seckill(Long productId, String requestId) {String lockKey = "lock:seckill:" + productId;try {// 尝试获取锁,过期时间10秒(需大于业务执行时间)boolean locked = tryLock(lockKey, requestId, 10000);if (!locked) return false;// 业务逻辑:查库存->扣减int stock = InventoryDAO.getStock(productId);if (stock <= 0) return false;InventoryDAO.decreaseStock(productId);return true;} finally {releaseLock(lockKey, requestId); // 无论成败都释放锁}}
}
优点
- 实现简单:基于Redis原子命令,无需依赖ZooKeeper等复杂组件。
- 高性能:单节点QPS可达10万+,适合高并发抢锁场景。
缺点
- 锁超时风险:若业务执行时间超过锁过期时间,锁会被释放,导致并发问题(需用“看门狗”线程续期锁)。
- 集群一致性问题:主从架构下,主节点宕机未同步锁信息到从节点,可能导致“锁丢失”(可考虑Redlock算法,但性能降低)。
5. 消息队列(List/Stream结构)
场景:实现简单异步通信,解耦系统组件(如订单创建后通知库存、物流系统)。
举例:用户下单后,订单系统发送消息到队列,库存系统异步扣减库存,避免主流程阻塞。
代码实现(基于List结构)
// 生产者:订单服务
public class OrderProducer {private Jedis jedis = new Jedis("localhost", 6379);private static final String QUEUE_KEY = "queue:order";public void sendOrderMsg(String orderId) {// LPUSH:向队列左侧添加消息(生产者)jedis.lpush(QUEUE_KEY, orderId);}
}// 消费者:库存服务
public class InventoryConsumer {private Jedis jedis = new Jedis("localhost", 6379);private static final String QUEUE_KEY = "queue:order";public void startConsume() {while (true) {try {// BRPOP:阻塞等待消息(超时0表示永久阻塞)List<String> msg = jedis.brpop(0, QUEUE_KEY);if (msg != null && msg.size() == 2) {String orderId = msg.get(1); // 消息内容processOrder(orderId); // 处理订单(扣减库存)}} catch (Exception e) {// 异常重试try { Thread.sleep(1000); } catch (InterruptedException ie) {}}}}private void processOrder(String orderId) {// 业务逻辑:扣减库存、更新订单状态等}
}
优点
- 轻量易集成:无需部署独立MQ(如RabbitMQ),复用Redis即可,降低架构复杂度。
- 支持异步解耦:生产者无需等待消费者处理,提升主流程响应速度。
缺点
- 可靠性低:List无消息确认机制,消费者崩溃会导致消息丢失(需业务层实现重试,或用Stream的ACK机制)。
- 无持久化保障:依赖Redis持久化,宕机未持久化则消息丢失。
- 不支持复杂功能:无死信队列、延迟队列等高级特性(需额外开发)。
6. 计数器与限流(String结构)
场景:基于INCR原子操作实现高频计数(如访问量、点赞数)或接口限流。
举例:限制某IP每分钟最多调用100次接口,或统计文章实时阅读量。
代码实现(接口限流)
public class ApiLimiter {private Jedis jedis = new Jedis("localhost", 6379);private static final int LIMIT = 100; // 每分钟最多100次private static final int TTL = 60; // 60秒过期public boolean allowRequest(String ip) {String key = "limit:api:" + ip;// INCR:原子计数+1(避免并发计数错误)Long count = jedis.incr(key);// 首次计数时设置过期时间(仅执行1次)if (count == 1) {jedis.expire(key, TTL);}// 判断是否超过限制return count <= LIMIT;}
}// 阅读量统计示例
public class ReadCounter {public void incrementReadCount(Long articleId) {String key = "counter:article:read:" + articleId;jedis.incr(key); // 每阅读一次+1jedis.expire(key, 86400 * 7); // 保留7天数据(定期同步到DB)}
}
优点
- 原子性:
INCR命令单线程执行,天然避免并发计数误差(无需加锁)。 - 高性能:支持每秒数万次操作,远超数据库的并发能力。
缺点
- 数据丢失风险:Redis未开启持久化时,宕机后计数丢失(适合可容忍短期误差的场景)。
- 过期时间误差:Redis过期删除是“惰性+定期”,可能存在几秒误差(不影响核心逻辑)。
7. 发布订阅(PUB/SUB机制)
场景:实现消息广播(一对多通信),适用于实时通知(如系统公告、聊天房间)。
举例:直播平台中,主播发送弹幕,所有观众实时接收;系统发布公告,所有在线用户立即收到。
代码实现
// 发布者:系统公告服务
public class AnnouncementPublisher {private Jedis jedis = new Jedis("localhost", 6379);private static final String CHANNEL = "channel:announcement";public void publish(String content) {// PUBLISH:向频道发布消息jedis.publish(CHANNEL, content);}
}// 订阅者:用户客户端
public class UserSubscriber extends JedisPubSub {@Overridepublic void onMessage(String channel, String message) {// 接收消息并处理(如弹窗显示公告)System.out.println("收到公告:" + message);}// 启动订阅public void startSubscribe() {Jedis jedis = new Jedis("localhost", 6379);jedis.subscribe(this, "channel:announcement"); // 阻塞监听频道}
}
优点
- 实时性强:消息发布后立即推送给所有订阅者,延迟低(毫秒级)。
- 实现简单:无需复杂配置,通过
PUBLISH/SUBSCRIBE命令即可使用。
缺点
- 消息不持久化:订阅者离线期间的消息会丢失(无法回溯历史消息)。
- 无确认机制:无法保证订阅者已接收消息(适合非关键通知场景)。
总结
Redis的核心价值在于高性能(内存操作)+ 多数据结构,但需根据场景权衡优缺点:
- 缓存/计数器/排行榜:优先用,优势明显;
- 分布式锁:适合中小规模场景,大规模需考虑Redlock或专业组件;
- 消息队列/发布订阅:适合简单场景,可靠性要求高则选RabbitMQ/Kafka;
- 所有场景均需保证Redis高可用(主从+哨兵)和合理持久化策略(AOF+RDB)。
