大家好,我是苏三,又跟大家见面了。
前言
在很多小伙伴的印象中,Redis就是一个用来做缓存的工具。
但如果你只把它当作缓存,那真是“杀鸡用牛刀”了。
Redis的强大远不止于此,它的高级特性可以让你的系统在性能、扩展性、可靠性上直接起飞。
今天这篇文章就跟大家聊聊Redis中10种高级用法,希望对你会有所帮助。
更多项目实战在项目实战网:java突击队
一、布隆过滤器
有些小伙伴在做高并发系统时,最怕的就是缓存穿透——大量请求直接打穿缓存,压垮数据库。
布隆过滤器就是解决这个问题的利器。
1.1 什么是布隆过滤器
布隆过滤器是一种空间效率极高的概率型数据结构,用于判断一个元素是否存在于一个集合中。
它的特点是:
- 极省内存:存储1亿个元素,只需要约100MB内存
- 存在误判:判断“不在”一定准确;判断“在”可能误判(小概率)
- 不可删除:不支持删除元素
1.2 原理
布隆过滤器的原理如图:
查询数据的流程如下图:
1.3 代码示例(使用Redisson)
@Component
publicclass BloomFilterService {
@Autowired
private RedissonClient redissonClient;
private RBloomFilter<String> 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去重。
二、Redisson分布式锁
Redis实现分布式锁,很多人还在用SET NX EX,但这在复杂场景下存在诸多隐患。
Redisson的分布式锁提供了更完善的解决方案。
2.1 为什么不用SET NX
简单SET NX的问题:
- 锁过期时间设置不当,可能导致锁提前释放
- 释放锁时未校验持有者,可能误删他人锁
- 无法自动续期,长任务执行一半锁就没了
2.2 Redisson分布式锁原理
Redisson使用Lua脚本保证原子性,并内置了看门狗机制自动续期。
2.3 代码示例
@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略重。
适用场景:分布式任务调度、库存扣减、订单创建等需要互斥的场景。
三、Redisson延迟队列
很多场景需要延迟处理,比如订单30分钟未支付自动取消。
传统方案用定时任务扫表,效率低下且存在延迟。
Redisson的延迟队列基于Redis的Sorted Set实现,精准可靠。
3.1 原理图
3.2 代码示例
@Component
public class DelayQueueService {
@Autowired
private RedissonClient redissonClient;
private RBlockingQueue<Order> blockingQueue;
private RDelayedQueue<Order> 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可以实现高性能的令牌桶算法。
4.1 令牌桶原理
4.2 Lua脚本实现
@Component
publicclass RateLimiterService {
@Autowired
private RedisTemplate<String, Object> 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<Long> 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中一种非常高效的数据结构,适合统计布尔型数据。
它适合做:海量数据的极简统计。
5.1 应用场景
- 统计日活用户:用用户ID作为偏移量,1表示活跃
- 签到记录:一年365天,一个用户只需365个bit
5.2 代码示例
@Component
publicclass BitmapService {
@Autowired
private RedisTemplate<String, Object> 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<Long>) connection -> connection.bitCount(key.getBytes())
);
}
// 统计连续签到天数(需结合Lua)
public Long continuousSignDays(Long userId, LocalDate date) {
// 使用BITFIELD命令获取连续签到天数
// 实现略
}
}
优点:内存占用极低(1亿用户只需12MB),运算速度快。
缺点:只能表示0/1状态,不适合复杂统计。
适用场景:用户签到、在线状态、布隆过滤器实现。
六、HyperLogLog
它是去重统计的“省内存神器”。
如果需要统计UV(独立访客),但数据量极大(千万级),用Set会占用大量内存。
HyperLogLog是解决方案。
6.1 原理
HyperLogLog是一种概率算法,用固定内存(约12KB)统计海量数据的基数,误差在0.81%左右。
6.2 代码示例
@Component
publicclass UVService {
@Autowired
private RedisTemplate<String, Object> 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<String> dates) {
String destKey = "uv:weekly:" + LocalDate.now();
for (String date : dates) {
redisTemplate.opsForHyperLogLog().union(destKey, "uv:" + date);
}
return redisTemplate.opsForHyperLogLog().size(destKey);
}
}
优点:固定内存,适合超大规模去重。
缺点:不精确,不能单独判断某个元素是否存在。
适用场景:UV统计、网站访问量、大规模去重。
七、GEO地理位置
它在附近的人、门店查询功能中常用。
Redis 3.2开始支持地理位置功能,基于Sorted Set实现,可以轻松计算距离、搜索附近位置。
7.1 原理
使用GeoHash编码经纬度,以Score形式存储,支持按半径、按矩形范围搜索。
7.2 代码示例
@Component
publicclass GeoService {
@Autowired
private RedisTemplate<String, Object> 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<Store> 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<Object>> results =
redisTemplate.opsForGeo().radius(key, circle);
List<Store> stores = new ArrayList<>();
for (GeoResult<RedisGeoCommands.GeoLocation<Object>> 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影响,索引更新需要重建。
适用场景:外卖骑手派单、附近店铺推荐、打车匹配。
八、Stream消息队列
Redis 5.0引入的Stream是一种强大的消息队列数据结构,支持消费组、消息确认、历史回溯等特性,可替代部分场景下的MQ。
它是轻量级MQ的替代方案。
8.1 架构图
8.2 代码示例(使用Redisson)
@Component
publicclass StreamService {
@Autowired
private RedissonClient redissonClient;
private RStream<String, String> stream;
@PostConstruct
public void init() {
stream = redissonClient.getStream("order-stream");
// 创建消费组(如果不存在)
stream.createGroup("order-group", StreamMessageId.AUTO);
}
// 生产者
public void publish(String orderId) {
Map<String, String> 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<String, String>> messages =
stream.readGroup("order-group", "consumer-1", 10);
for (Map.Entry<StreamMessageId, Map<String, String>> entry : messages.entrySet()) {
// 处理消息
process(entry.getValue());
// 确认消费
stream.ack("order-group", entry.getKey());
}
// 无消息时短暂休眠
if (messages.isEmpty()) {
try { Thread.sleep(1000); } catch (InterruptedException e) { break; }
}
}
}
}
优点:支持消息持久化、消费组、消息确认、消息回溯。
缺点:相比专业MQ,功能较简单,适合轻量级场景。
适用场景:任务队列、日志收集、消息通知。
九、Lua脚本
有些复杂操作需要多个Redis命令原子执行,Lua脚本是最佳方案。
9.1 典型场景
- 扣减库存:先检查库存,再扣减,防止超卖
- 设置带条件的锁
- 复杂数据聚合
9.2 代码示例
@Component
publicclass LuaScriptService {
@Autowired
private RedisTemplate<String, Object> 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<Long> 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<Long> script = new DefaultRedisScript<>(LOCK_SCRIPT, Long.class);
Long result = redisTemplate.execute(script, Collections.singletonList(key), value, expireSec);
return result != null && result == 1L;
}
}
优点:原子性、网络传输少、性能高。
缺点:调试困难,脚本需谨慎编写。
适用场景:库存扣减、限流、复杂条件更新。
十、RedisJSON
RedisJSON模块支持将JSON文档直接存储在Redis中,并可对文档内的字段进行增删改查。
10.1 安装
RedisJSON需要作为模块加载(Redis Stack已包含),或单独编译安装。
10.2 代码示例(使用Lettuce)
@Component
publicclass RedisJsonService {
@Autowired
private RedisTemplate<String, Object> 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高级用法的大门。
如果你有更好的Redis实践,欢迎评论区分享!
更多项目实战在项目实战网:java突击队