第 6 课:Redis 常见应用场景(下)
本课重点学习 Redis 在企业项目中的高频业务场景:
- 计数器
- 排行榜
- 购物车
- 分布式限流
6.1 计数器
Redis 的原子性操作非常适合实现各种计数器功能。
常见场景:
- 文章阅读量
- 点赞数
- 网站访问量(PV)
- 用户登录次数
- 商品浏览次数
代码示例
@Service
public class CounterService {
@Resource
private RedisUtil redisUtil;
// 文章阅读量自增
public Long incrementViewCount(Long articleId) {
String key = "article:view:" + articleId;
return redisUtil.increment(key);
}
// 获取文章阅读量
public Long getViewCount(Long articleId) {
String key = "article:view:" + articleId;
Object count = redisUtil.get(key);
return count == null
? 0
: Long.parseLong(count.toString());
}
// 点赞数自增
public Long incrementLikeCount(Long articleId) {
String key = "article:like:" + articleId;
return redisUtil.increment(key);
}
// 取消点赞
public Long decrementLikeCount(Long articleId) {
String key = "article:like:" + articleId;
return redisUtil.increment(key, -1);
}
// 网站访问量统计
public Long incrementSiteVisit() {
return redisUtil.increment("site:visit:count");
}
}
Redis 计数器优势
| 优势 | 说明 |
|---|---|
| 原子性 | 不会出现并发问题 |
| 高性能 | 单机可达十万级 QPS |
| 实现简单 | INCR 即可完成 |
| 易扩展 | 支持分布式部署 |
6.2 排行榜
Redis 的 ZSet(有序集合)天生适合实现排行榜。
每个成员都有:
member + score
Redis 会自动按照 score 排序。
应用场景
- 游戏排行榜
- 热搜排行榜
- 商品销量排行
- 用户积分排行
- 直播打赏榜
代码示例
@Service
public class RankService {
@Resource
private RedisUtil redisUtil;
// 添加用户分数
public void addScore(Long userId, double score) {
String key = "game:rank";
redisUtil.zadd(
key,
userId.toString(),
score
);
}
// 获取排行榜前N名
public List<RankVO> getTopN(int n) {
String key = "game:rank";
Set<ZSetOperations.TypedTuple<Object>> tuples =
redisUtil.zrevrangeWithScores(
key,
0,
n - 1
);
List<RankVO> rankList =
new ArrayList<>();
int rank = 1;
for (ZSetOperations.TypedTuple<Object> tuple : tuples) {
RankVO rankVO = new RankVO();
rankVO.setUserId(
Long.parseLong(
tuple.getValue().toString()
)
);
rankVO.setScore(
tuple.getScore()
);
rankVO.setRank(rank++);
rankList.add(rankVO);
}
return rankList;
}
// 获取用户排名
public Long getUserRank(Long userId) {
String key = "game:rank";
Long rank =
redisUtil.zrevrank(
key,
userId.toString()
);
return rank == null
? null
: rank + 1;
}
// 获取用户分数
public Double getUserScore(Long userId) {
String key = "game:rank";
return redisUtil.zscore(
key,
userId.toString()
);
}
}
排行榜核心命令
# 添加分数
zadd rank 100 user1
# 查询排行榜
zrevrange rank 0 9
# 查询排名
zrevrank rank user1
# 查询分数
zscore rank user1
6.3 购物车
Redis Hash 非常适合购物车业务。
数据结构设计
cart:1001
├── 2001 -> 3
├── 2002 -> 5
└── 2003 -> 1
含义:
用户1001购物车:
商品2001 数量3
商品2002 数量5
商品2003 数量1
代码示例
@Service
public class CartService {
@Resource
private RedisUtil redisUtil;
// 添加商品
public void addToCart(
Long userId,
Long productId,
int quantity
) {
String key = "cart:" + userId;
Object currentQuantity =
redisUtil.hget(
key,
productId.toString()
);
if (currentQuantity != null) {
quantity += Integer.parseInt(
currentQuantity.toString()
);
}
redisUtil.hset(
key,
productId.toString(),
quantity
);
}
// 更新商品数量
public void updateCartItem(
Long userId,
Long productId,
int quantity
) {
String key = "cart:" + userId;
redisUtil.hset(
key,
productId.toString(),
quantity
);
}
// 删除商品
public void removeCartItem(
Long userId,
Long productId
) {
String key = "cart:" + userId;
redisUtil.hdel(
key,
productId.toString()
);
}
// 获取购物车
public Map<Long, Integer> getCart(
Long userId
) {
String key = "cart:" + userId;
Map<Object, Object> entries =
redisUtil.hgetAll(key);
Map<Long, Integer> cart =
new HashMap<>();
for (Map.Entry<Object, Object> entry
: entries.entrySet()) {
Long productId =
Long.parseLong(
entry.getKey().toString()
);
Integer quantity =
Integer.parseInt(
entry.getValue().toString()
);
cart.put(productId, quantity);
}
return cart;
}
// 清空购物车
public void clearCart(Long userId) {
String key = "cart:" + userId;
redisUtil.delete(key);
}
}
Hash 实现购物车优势
| 优势 | 说明 |
|---|---|
| 节省内存 | 一个用户一个 Key |
| 查询快 | O(1) |
| 修改方便 | 单个商品独立更新 |
| 支持扩展 | 可以增加价格、规格等 |
6.4 分布式限流
限流是高并发系统的核心保护机制。
应用场景
- 接口防刷
- 登录防暴力破解
- 秒杀保护
- API 调用限制
限流流程
用户请求
↓
Redis计数
↓
超过限制?
┌────Yes────→ 拒绝访问
│
No
│
↓
正常放行
Lua 实现限流
@Service
public class RateLimitService {
@Resource
private RedisUtil redisUtil;
/**
* 限流
*
* @param key 限流Key
* @param limit 最大次数
* @param period 时间窗口
*/
public boolean isAllowed(
String key,
int limit,
int period
) {
String script =
"local key = KEYS[1] " +
"local limit = tonumber(ARGV[1]) " +
"local period = tonumber(ARGV[2]) " +
"local current = tonumber(redis.call('get', key) or '0') " +
"if current + 1 > limit then " +
" return 0 " +
"else " +
" redis.call('incr', key) " +
" redis.call('expire', key, period) " +
" return 1 " +
"end";
Long result =
(Long) redisUtil.execute(
script,
Collections.singletonList(key),
limit,
period
);
return Long.valueOf(1)
.equals(result);
}
}
Controller 使用示例
@GetMapping("/api/test")
public String test(
HttpServletRequest request
) {
String ip =
request.getRemoteAddr();
String key =
"rate:limit:" + ip;
// 每分钟最多访问10次
if (!rateLimitService.isAllowed(
key,
10,
60
)) {
return "请求过于频繁,请稍后再试";
}
return "success";
}
本课知识总结
| 模块 | Redis数据结构 |
|---|---|
| 计数器 | String |
| 排行榜 | ZSet |
| 购物车 | Hash |
| 限流 | String + Lua |
Redis 场景总结
| 场景 | 推荐数据结构 |
|---|---|
| 用户缓存 | String / Hash |
| 商品详情 | Hash |
| 阅读量统计 | String |
| 点赞数统计 | String |
| 排行榜 | ZSet |
| 标签系统 | Set |
| 消息队列 | List |
| 购物车 | Hash |
| 限流 | String + Lua |
| 分布式锁 | String + Lua |
课后练习
练习 1
实现文章阅读量统计:
功能:
- 阅读量 +1
- 查询阅读量
练习 2
实现点赞系统:
功能:
- 点赞
- 取消点赞
- 查询点赞数
练习 3
实现游戏排行榜:
功能:
- 添加分数
- 查询排名
- 查询 Top10
- 查询用户积分
练习 4
实现完整购物车:
功能:
- 添加商品
- 修改数量
- 删除商品
- 查询购物车
- 清空购物车
练习 5
实现 IP 限流:
要求:
- 每分钟最多访问 10 次
- 使用 Redis + Lua
- 支持分布式部署
- 支持高并发测试