在很多小伙伴的印象中,Redis就是一个用来做缓存的工具。
但如果你只把它当作缓存,那真是“杀鸡用牛刀”了。
Redis的强大远不止于此,它的高级特性可以让你的系统在性能、扩展性、可靠性上直接起飞。
今天这篇文章就跟大家聊聊Redis中10种高级用法,希望对你会有所帮助。
更多项目实战在项目实战网:java突击队
有些小伙伴在做高并发系统时,最怕的就是缓存穿透——大量请求直接打穿缓存,压垮数据库。
布隆过滤器就是解决这个问题的利器。
布隆过滤器是一种空间效率极高的概率型数据结构,用于判断一个元素是否存在于一个集合中。
它的特点是:
极省内存:存储1亿个元素,只需要约100MB内存
存在误判:判断“不在”一定准确;判断“在”可能误判(小概率)
不可删除:不支持删除元素
布隆过滤器的原理如图:
查询数据的流程如下图:
typescript体验AI代码助手代码解读复制代码@Component publicclass BloomFilterService { @Autowired private RedissonClient redissonClient; private RBloomFilter bloomFilter; @PostConstruct public void init() { bloomFilter = redissonClient.getBloomFilter("user:bloom"); // 初始化:预计插入100万条数据,误判率0.01 bloomFilter.tryInit(1000000L, 0.01); } // 添加白名单数据 public void addUser(Long userId) { bloomFilter.add(userId.toString()); } // 查询前进行拦截 public User getUserById(Long userId) { // 如果不在布隆过滤器中,直接返回空,避免查库 if (!bloomFilter.contains(userId.toString())) { returnnull; } // 存在则查库(缓存兜底) return queryFromDB(userId); } }优点:内存占用极小,能有效防止缓存穿透。
缺点:存在误判,不支持删除(如需删除可用计数布隆过滤器)。
适用场景:防止恶意请求穿透缓存、垃圾邮件过滤、爬虫URL去重。
Redis实现分布式锁,很多人还在用SET NX EX,但这在复杂场景下存在诸多隐患。
Redisson的分布式锁提供了更完善的解决方案。
简单SET NX的问题:
锁过期时间设置不当,可能导致锁提前释放
释放锁时未校验持有者,可能误删他人锁
无法自动续期,长任务执行一半锁就没了
Redisson使用Lua脚本保证原子性,并内置了看门狗机制自动续期。
scss体验AI代码助手代码解读复制代码@Service publicclass OrderService { @Autowired private RedissonClient redissonClient; public void processOrder(String orderId) { RLock lock = redissonClient.getLock("order:lock:" + orderId); try { // 尝试加锁,最多等待10秒,锁有效期30秒(自动续期) if (lock.tryLock(10, 30, TimeUnit.SECONDS)) { // 执行业务逻辑 doProcess(orderId); } else { thrownew RuntimeException("获取锁失败"); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); thrownew RuntimeException("中断", e); } finally { // 必须释放锁 if (lock.isHeldByCurrentThread()) { lock.unlock(); } } } }优点:自动续期,避免死锁;可重入;支持读写锁、红锁等。
缺点:引入Redisson依赖,比原生Redis略重。
适用场景:分布式任务调度、库存扣减、订单创建等需要互斥的场景。
很多场景需要延迟处理,比如订单30分钟未支付自动取消。
传统方案用定时任务扫表,效率低下且存在延迟。
Redisson的延迟队列基于Redis的Sorted Set实现,精准可靠。
typescript体验AI代码助手代码解读复制代码@Component public class DelayQueueService { @Autowired private RedissonClient redissonClient; private RBlockingQueue blockingQueue; private RDelayedQueue delayedQueue; @PostConstruct public void init() { blockingQueue = redissonClient.getBlockingQueue("order:queue"); delayedQueue = redissonClient.getDelayedQueue(blockingQueue); } // 添加延迟任务 public void addOrder(Order order, long delay, TimeUnit unit) { delayedQueue.offer(order, delay, unit); } // 消费者(单独线程) @Async public void startConsumer() { while (true) { try { Order order = blockingQueue.take(); // 处理延迟到期的订单 processExpiredOrder(order); } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } } } }优点:精准定时、分布式、自动持久化。
缺点:依赖Redis,消息消费失败需自行补偿。
适用场景:订单超时关闭、延迟通知、定时任务调度。
面对突发流量,限流是保护系统的关键。
Redis + Lua可以实现高性能的令牌桶算法。
swift体验AI代码助手代码解读复制代码@Component publicclass RateLimiterService { @Autowired private RedisTemplate redisTemplate; privatestaticfinal String LUA_SCRIPT = "local key = KEYS[1]\n" + "local limit = tonumber(ARGV[1])\n" + "local interval = tonumber(ARGV[2])\n" + "local current = redis.call('get', key)\n" + "if current and tonumber(current) >= limit then\n" + " return 0\n" + "else\n" + " redis.call('incr', key)\n" + " redis.call('expire', key, interval)\n" + " return 1\n" + "end"; public boolean tryAcquire(String key, int limit, int intervalSec) { DefaultRedisScript script = new DefaultRedisScript<>(LUA_SCRIPT, Long.class); Long result = redisTemplate.execute(script, Collections.singletonList(key), limit, intervalSec); return result != null && result == 1L; } }优点:支持平滑突发流量,实现简单。
缺点:需要结合滑动窗口做更精细的限流。
适用场景:API限流、防刷、秒杀入口控制。
位图(Bitmap)是Redis中一种非常高效的数据结构,适合统计布尔型数据。
它适合做:海量数据的极简统计。
统计日活用户:用用户ID作为偏移量,1表示活跃
签到记录:一年365天,一个用户只需365个bit
typescript体验AI代码助手代码解读复制代码@Component publicclass BitmapService { @Autowired private RedisTemplate redisTemplate; // 用户签到 public void signIn(Long userId, LocalDate date) { String key = "sign:" + date.toString(); redisTemplate.opsForValue().setBit(key, userId, true); } // 统计某天签到人数 public Long countSignIn(LocalDate date) { String key = "sign:" + date.toString(); return redisTemplate.execute( (RedisCallback) connection -> connection.bitCount(key.getBytes()) ); } // 统计连续签到天数(需结合Lua) public Long continuousSignDays(Long userId, LocalDate date) { // 使用BITFIELD命令获取连续签到天数 // 实现略 } }优点:内存占用极低(1亿用户只需12MB),运算速度快。
缺点:只能表示0/1状态,不适合复杂统计。
适用场景:用户签到、在线状态、布隆过滤器实现。
它是去重统计的“省内存神器”。
如果需要统计UV(独立访客),但数据量极大(千万级),用Set会占用大量内存。
HyperLogLog是解决方案。
HyperLogLog是一种概率算法,用固定内存(约12KB)统计海量数据的基数,误差在0.81%左右。
typescript体验AI代码助手代码解读复制代码@Component publicclass UVService { @Autowired private RedisTemplate redisTemplate; // 添加访问记录 public void addVisit(String date, String userId) { String key = "uv:" + date; redisTemplate.opsForHyperLogLog().add(key, userId); } // 统计UV public Long countUV(String date) { String key = "uv:" + date; return redisTemplate.opsForHyperLogLog().size(key); } // 合并多天UV(如周活) public Long countWeeklyUV(List dates) { String destKey = "uv:weekly:" + LocalDate.now(); for (String date : dates) { redisTemplate.opsForHyperLogLog().union(destKey, "uv:" + date); } return redisTemplate.opsForHyperLogLog().size(destKey); } }优点:固定内存,适合超大规模去重。
缺点:不精确,不能单独判断某个元素是否存在。
适用场景:UV统计、网站访问量、大规模去重。
它在附近的人、门店查询功能中常用。
Redis 3.2开始支持地理位置功能,基于Sorted Set实现,可以轻松计算距离、搜索附近位置。
使用GeoHash编码经纬度,以Score形式存储,支持按半径、按矩形范围搜索。
typescript体验AI代码助手代码解读复制代码@Component publicclass GeoService { @Autowired private RedisTemplate redisTemplate; // 添加门店位置 public void addStore(Long storeId, double lng, double lat) { String key = "stores:geo"; redisTemplate.opsForGeo().add(key, new Point(lng, lat), storeId.toString()); } // 搜索附近门店 public List findNearbyStores(double lng, double lat, double radiusKm) { String key = "stores:geo"; Circle circle = new Circle(new Point(lng, lat), new Distance(radiusKm, Metrics.KILOMETERS)); GeoResults<RedisGeoCommands.GeoLocation> results = redisTemplate.opsForGeo().radius(key, circle); List stores = new ArrayList<>(); for (GeoResult<RedisGeoCommands.GeoLocation> result : results) { // 解析结果,构造Store对象 } return stores; } // 计算两点距离 public Distance distanceBetweenStores(Long storeId1, Long storeId2) { String key = "stores:geo"; return redisTemplate.opsForGeo().distance(key, storeId1.toString(), storeId2.toString()); } }优点:功能丰富,支持半径、矩形搜索,计算距离准确。
缺点:精度受GeoHash影响,索引更新需要重建。
适用场景:外卖骑手派单、附近店铺推荐、打车匹配。
Redis 5.0引入的Stream是一种强大的消息队列数据结构,支持消费组、消息确认、历史回溯等特性,可替代部分场景下的MQ。
它是轻量级MQ的替代方案。
typescript体验AI代码助手代码解读复制代码@Component publicclass StreamService { @Autowired private RedissonClient redissonClient; private RStream stream; @PostConstruct public void init() { stream = redissonClient.getStream("order-stream"); // 创建消费组(如果不存在) stream.createGroup("order-group", StreamMessageId.AUTO); } // 生产者 public void publish(String orderId) { Map data = new HashMap<>(); data.put("orderId", orderId); data.put("timestamp", String.valueOf(System.currentTimeMillis())); stream.add(data); } // 消费者(批量拉取) @Async public void consume() { while (true) { Map<StreamMessageId, Map> messages = stream.readGroup("order-group", "consumer-1", 10); for (Map.Entry<StreamMessageId, Map> entry : messages.entrySet()) { // 处理消息 process(entry.getValue()); // 确认消费 stream.ack("order-group", entry.getKey()); } // 无消息时短暂休眠 if (messages.isEmpty()) { try { Thread.sleep(1000); } catch (InterruptedException e) { break; } } } } }优点:支持消息持久化、消费组、消息确认、消息回溯。
缺点:相比专业MQ,功能较简单,适合轻量级场景。
适用场景:任务队列、日志收集、消息通知。
有些复杂操作需要多个Redis命令原子执行,Lua脚本是最佳方案。
扣减库存:先检查库存,再扣减,防止超卖
设置带条件的锁
复杂数据聚合
typescript体验AI代码助手代码解读复制代码@Component publicclass LuaScriptService { @Autowired private RedisTemplate redisTemplate; // Lua脚本:库存扣减 privatestaticfinal String DECREASE_STOCK_SCRIPT = "local key = KEYS[1]\n" + "local count = tonumber(ARGV[1])\n" + "local stock = redis.call('get', key)\n" + "if not stock or tonumber(stock) < count then\n" + " return 0\n" + "else\n" + " redis.call('decrby', key, count)\n" + " return 1\n" + "end"; public boolean decreaseStock(String productId, int count) { DefaultRedisScript script = new DefaultRedisScript<>(DECREASE_STOCK_SCRIPT, Long.class); Long result = redisTemplate.execute(script, Collections.singletonList("stock:" + productId), count); return result != null && result == 1L; } // 带超时的分布式锁 privatestaticfinal String LOCK_SCRIPT = "if redis.call('setnx', KEYS[1], ARGV[1]) == 1 then\n" + " redis.call('expire', KEYS[1], ARGV[2])\n" + " return 1\n" + "else\n" + " return 0\n" + "end"; public boolean lock(String key, String value, int expireSec) { DefaultRedisScript script = new DefaultRedisScript<>(LOCK_SCRIPT, Long.class); Long result = redisTemplate.execute(script, Collections.singletonList(key), value, expireSec); return result != null && result == 1L; } }优点:原子性、网络传输少、性能高。
缺点:调试困难,脚本需谨慎编写。
适用场景:库存扣减、限流、复杂条件更新。
RedisJSON模块支持将JSON文档直接存储在Redis中,并可对文档内的字段进行增删改查。
RedisJSON需要作为模块加载(Redis Stack已包含),或单独编译安装。
typescript体验AI代码助手代码解读复制代码@Component publicclass RedisJsonService { @Autowired private RedisTemplate redisTemplate; // 存储JSON对象 public void saveUser(Long userId, User user) { String key = "user:" + userId; redisTemplate.opsForValue().set(key, user); // 实际上RedisJSON需要专门命令,这里演示思路,真实使用时需使用Lettuce的JSON命令集 } // 更新字段 public void updateUserAge(Long userId, int age) { // 使用JSON.SET命令 // redisTemplate.execute(connection -> connection.execute("JSON.SET", key, "$.age", String.valueOf(age))); } }优点:直接操作JSON内部字段,支持索引和查询(配合RediSearch)。
缺点:需要额外安装模块,内存占用较高。
适用场景:用户配置、会话信息、动态表单。
更多项目实战在项目实战网:java突击队
以上10种高级用法,涵盖了缓存之外Redis的诸多强大能力:
| 用法 | 核心优势 | 典型场景 |
|---|---|---|
| 布隆过滤器 | 内存极省,防穿透 | 缓存击穿防护、垃圾邮件 |
| Redisson分布式锁 | 自动续期,可重入 | 分布式互斥 |
| 延迟队列 | 精准定时 | 订单超时、延迟通知 |
| 令牌桶限流 | 平滑突发流量 | API限流 |
| 位图 | 极省内存 | 签到、在线状态 |
| HyperLogLog | 固定内存 | UV统计 |
| GEO | 地理位置搜索 | 附近的人 |
| Stream | 轻量MQ | 任务队列 |
| Lua脚本 | 原子操作 | 库存扣减 |
| RedisJSON | 文档存储 | 用户配置 |
在实际项目中,合理组合这些高级特性,可以让你用极小的成本实现高性能、高可用的分布式系统。
需要注意的是,Redis依然是内存数据库,数据量大的场景要考虑内存成本,必要时结合持久化方案。
希望这篇文章能帮你打开Redis高级用法的大门。