目录
[1. 优惠券秒杀系统](#1. 优惠券秒杀系统)
[1.1 全局唯一ID生成](#1.1 全局唯一ID生成)
[1.2 秒杀下单基础实现](#1.2 秒杀下单基础实现)
[1.3 一人一单限制](#1.3 一人一单限制)
[2. 分布式锁](#2. 分布式锁)
[2.1 分布式锁基本概念](#2.1 分布式锁基本概念)
[2.2 Redis分布式锁基础实现](#2.2 Redis分布式锁基础实现)
[2.3 解决锁误删问题](#2.3 解决锁误删问题)
[2.4 Lua脚本保证原子性](#2.4 Lua脚本保证原子性)
[2.5 Redisson分布式锁](#2.5 Redisson分布式锁)
[2.6 多级锁(MultiLock)](#2.6 多级锁(MultiLock))
[3. 异步秒杀优化](#3. 异步秒杀优化)
[3.1 秒杀优化思路](#3.1 秒杀优化思路)
[3.2 Redis快速校验](#3.2 Redis快速校验)
[3.3 异步下单实现](#3.3 异步下单实现)
[4. Redis消息队列](#4. Redis消息队列)
[4.1 消息队列基本概念](#4.1 消息队列基本概念)
[4.2 基于List的消息队列](#4.2 基于List的消息队列)
[4.3 基于PubSub的消息队列](#4.3 基于PubSub的消息队列)
[4.4 基于Stream的消息队列](#4.4 基于Stream的消息队列)
[4.5 Stream实现异步秒杀](#4.5 Stream实现异步秒杀)
[5. 点赞与关注功能](#5. 点赞与关注功能)
[5.1 点赞功能实现](#5.1 点赞功能实现)
[5.2 关注功能实现](#5.2 关注功能实现)
[6. Feed流实现](#6. Feed流实现)
[6.1 Feed流模式](#6.1 Feed流模式)
[6.2 基于推模式的Feed流](#6.2 基于推模式的Feed流)
[6.3 滚动分页查询](#6.3 滚动分页查询)
[7. 附近商户搜索](#7. 附近商户搜索)
[7.1 GEO数据结构](#7.1 GEO数据结构)
[7.2 商户数据导入GEO](#7.2 商户数据导入GEO)
[7.3 附近商户查询](#7.3 附近商户查询)
[8. 用户签到](#8. 用户签到)
[8.1 BitMap数据结构](#8.1 BitMap数据结构)
[8.2 签到功能实现](#8.2 签到功能实现)
[9. UV统计](#9. UV统计)
[9.1 HyperLogLog](#9.1 HyperLogLog)
[9.2 UV统计实现](#9.2 UV统计实现)
本文介绍了基于Redis的优惠券秒杀系统实现方案,重点讲解了分布式锁、异步秒杀优化和Redis高级功能应用。系统采用Redis分布式ID生成器解决ID冲突问题,通过Redis+Lua脚本实现原子操作,优化了秒杀流程。文章详细阐述了Redis分布式锁的实现与优化方案,包括锁误删问题解决、Lua脚本原子操作和Redisson集成。在秒杀优化方面,提出异步下单方案,使用Redis快速校验资格,通过消息队列处理订单。此外,还介绍了Redis在社交功能(点赞、关注、Feed流)、地理位置搜索、用户签到和UV统计等场景的应用实践,展示了Redis作为高性能数据存储的多样化解决方案。
1. 优惠券秒杀系统
1.1 全局唯一ID生成
数据库自增ID的问题:
-
ID规律性明显,易猜测
-
受单表数据量限制
-
分库分表时ID冲突
Redis分布式ID生成器:
java
@Component
public class RedisIdWorker {
private static final long BEGIN_TIMESTAMP = 1640995200L; // 开始时间戳
private static final int COUNT_BITS = 32; // 序列号位数
public long nextId(String keyPrefix) {
// 1.生成时间戳
LocalDateTime now = LocalDateTime.now();
long nowSecond = now.toEpochSecond(ZoneOffset.UTC);
long timestamp = nowSecond - BEGIN_TIMESTAMP;
// 2.生成序列号
String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));
long count = stringRedisTemplate.opsForValue()
.increment("icr:" + keyPrefix + ":" + date);
// 3.拼接并返回
return timestamp << COUNT_BITS | count;
}
}
1.2 秒杀下单基础实现
下单流程:
-
查询优惠券信息
-
判断秒杀时间
-
判断库存
-
扣减库存
-
创建订单
基础代码:
java
public Result seckillVoucher(Long voucherId) {
// 1.查询优惠券
SeckillVoucher voucher = seckillVoucherService.getById(voucherId);
// 2.判断秒杀时间
if (voucher.getBeginTime().isAfter(LocalDateTime.now())) {
return Result.fail("秒杀尚未开始!");
}
if (voucher.getEndTime().isBefore(LocalDateTime.now())) {
return Result.fail("秒杀已经结束!");
}
// 3.判断库存
if (voucher.getStock() < 1) {
return Result.fail("库存不足!");
}
// 4.扣减库存(乐观锁)
boolean success = seckillVoucherService.update()
.setSql("stock = stock - 1")
.eq("voucher_id", voucherId)
.gt("stock", 0)
.update();
if (!success) {
return Result.fail("库存不足!");
}
// 5.创建订单
VoucherOrder voucherOrder = new VoucherOrder();
voucherOrder.setId(redisIdWorker.nextId("order"));
voucherOrder.setUserId(UserHolder.getUser().getId());
voucherOrder.setVoucherId(voucherId);
save(voucherOrder);
return Result.ok(voucherOrder.getId());
}
1.3 一人一单限制
实现思路:
-
查询用户是否已下单
-
使用 synchronized 保证单机线程安全
-
使用分布式锁解决集群环境问题
单机版实现:
java
@Transactional
public synchronized Result createVoucherOrder(Long voucherId) {
Long userId = UserHolder.getUser().getId();
// 判断是否已购买
int count = query().eq("user_id", userId)
.eq("voucher_id", voucherId).count();
if (count > 0) {
return Result.fail("用户已经购买过一次!");
}
// ... 扣减库存和创建订单逻辑
}
2. 分布式锁
2.1 分布式锁基本概念
分布式锁要求:
-
可见性:多个进程都能看到锁状态
-
互斥性:同一时刻只有一个进程持有锁
-
高可用:不易崩溃
-
高性能:加锁释放锁性能高
-
安全性:防止死锁
实现方案对比:
-
MySQL:性能一般
-
Redis:常用方案,基于 setnx
-
ZooKeeper:企业级方案
2.2 Redis分布式锁基础实现
基本操作:
java
// 获取锁
public boolean tryLock(long timeoutSec) {
String threadId = Thread.currentThread().getId();
Boolean success = stringRedisTemplate.opsForValue()
.setIfAbsent(KEY_PREFIX + name, threadId, timeoutSec, TimeUnit.SECONDS);
return Boolean.TRUE.equals(success);
}
// 释放锁
public void unlock() {
stringRedisTemplate.delete(KEY_PREFIX + name);
}
2.3 解决锁误删问题
问题:线程A锁超时自动释放,线程B获取锁,线程A恢复后误删线程B的锁
解决方案:在锁中存储线程标识,释放时验证
改进代码:
java
private static final String ID_PREFIX = UUID.randomUUID().toString(true) + "-";
public boolean tryLock(long timeoutSec) {
String threadId = ID_PREFIX + Thread.currentThread().getId();
Boolean success = stringRedisTemplate.opsForValue()
.setIfAbsent(KEY_PREFIX + name, threadId, timeoutSec, TimeUnit.SECONDS);
return Boolean.TRUE.equals(success);
}
public void unlock() {
String threadId = ID_PREFIX + Thread.currentThread().getId();
String id = stringRedisTemplate.opsForValue().get(KEY_PREFIX + name);
if (threadId.equals(id)) {
stringRedisTemplate.delete(KEY_PREFIX + name);
}
}
2.4 Lua脚本保证原子性
问题:判断锁标识和删除锁不是原子操作
Lua脚本解决方案:
Lua
-- 释放锁的Lua脚本
if (redis.call('GET', KEYS[1]) == ARGV[1]) then
return redis.call('DEL', KEYS[1])
end
return 0
Java调用:
java
private static final DefaultRedisScript<Long> UNLOCK_SCRIPT;
static {
UNLOCK_SCRIPT = new DefaultRedisScript<>();
UNLOCK_SCRIPT.setLocation(new ClassPathResource("unlock.lua"));
UNLOCK_SCRIPT.setResultType(Long.class);
}
public void unlock() {
stringRedisTemplate.execute(
UNLOCK_SCRIPT,
Collections.singletonList(KEY_PREFIX + name),
ID_PREFIX + Thread.currentThread().getId()
);
}
2.5 Redisson分布式锁
Redisson优势:
-
可重入锁
-
可重试机制
-
超时续期(看门狗机制)
-
主从一致性支持
使用示例:
java
@Resource
private RedissonClient redissonClient;
public Result seckillVoucher(Long voucherId) {
Long userId = UserHolder.getUser().getId();
RLock lock = redissonClient.getLock("lock:order:" + userId);
boolean isLock = lock.tryLock();
if (!isLock) {
return Result.fail("不允许重复下单");
}
try {
// 业务逻辑
return createVoucherOrder(voucherId);
} finally {
lock.unlock();
}
}
2.6 多级锁(MultiLock)
解决主从一致性问题:
-
写入所有Redis节点,所有节点成功才算加锁成功
-
提高锁的可靠性,防止主节点宕机导致锁丢失
3. 异步秒杀优化
3.1 秒杀优化思路
传统流程问题:
-
查询优惠券
-
判断库存
-
查询订单
-
校验一人一单
-
扣减库存
-
创建订单
优化方案:
-
Redis快速校验(库存、一人一单)
-
异步下单
-
消息队列处理订单
3.2 Redis快速校验
Lua脚本实现原子操作:
Lua
-- 秒杀资格判断Lua脚本
local voucherId = ARGV[1]
local userId = ARGV[2]
local stockKey = 'seckill:stock:' .. voucherId
local orderKey = 'seckill:order:' .. voucherId
-- 判断库存
if(tonumber(redis.call('get', stockKey)) <= 0) then
return 1
end
-- 判断是否重复下单
if(redis.call('sismember', orderKey, userId) == 1) then
return 2
end
-- 扣减库存,保存订单
redis.call('incrby', stockKey, -1)
redis.call('sadd', orderKey, userId)
return 0
3.3 异步下单实现
阻塞队列方案:
java
// 创建阻塞队列
private BlockingQueue<VoucherOrder> orderTasks = new ArrayBlockingQueue<>(1024 * 1024);
// 异步处理线程
private class VoucherOrderHandler implements Runnable {
@Override
public void run() {
while (true) {
try {
VoucherOrder voucherOrder = orderTasks.take();
handleVoucherOrder(voucherOrder);
} catch (Exception e) {
log.error("处理订单异常", e);
}
}
}
}
// 下单方法
public Result seckillVoucher(Long voucherId) {
Long userId = UserHolder.getUser().getId();
// 执行Lua脚本
Long result = stringRedisTemplate.execute(SECKILL_SCRIPT, ...);
if (result != 0) {
return Result.fail(result == 1 ? "库存不足" : "不能重复下单");
}
// 创建订单对象
VoucherOrder voucherOrder = new VoucherOrder();
voucherOrder.setId(redisIdWorker.nextId("order"));
voucherOrder.setUserId(userId);
voucherOrder.setVoucherId(voucherId);
// 放入阻塞队列
orderTasks.add(voucherOrder);
return Result.ok(voucherOrder.getId());
}
4. Redis消息队列
4.1 消息队列基本概念
消息队列角色:
-
消息队列:存储管理消息
-
生产者:发送消息
-
消费者:获取并处理消息
优点:解耦、异步、削峰
4.2 基于List的消息队列
基本操作:
-
LPUSH + RPOP 或 RPUSH + LPOP
-
BRPOP/BLPOP 实现阻塞
优缺点:
-
优点:数据安全、有序
-
缺点:消息丢失、单消费者
4.3 基于PubSub的消息队列
基本操作:
-
SUBSCRIBE:订阅频道
-
PUBLISH:发布消息
-
PSUBSCRIBE:模式订阅
优缺点:
-
优点:多生产多消费
-
缺点:无持久化、可能丢失消息
4.4 基于Stream的消息队列
Stream优势:
-
消息可回溯
-
支持多消费者
-
阻塞读取
-
消息确认机制
基本命令:
bash
java
# 添加消息
XADD stream.orders * key1 value1 key2 value2
# 读取消息
XREAD COUNT 1 BLOCK 2000 STREAMS stream.orders 0
# 消费者组
XGROUP CREATE stream.orders g1 0
XREADGROUP GROUP g1 c1 COUNT 1 BLOCK 2000 STREAMS stream.orders >
4.5 Stream实现异步秒杀
优化方案:
-
Lua脚本操作后,将订单信息发送到Stream
-
独立线程消费Stream中的订单
实现代码:
Lua
// Lua脚本添加发送消息
redis.call('xadd', 'stream.orders', '*', 'userId', userId, 'voucherId', voucherId, 'id', orderId)
java
// 消费者组处理订单
private class VoucherOrderHandler implements Runnable {
@Override
public void run() {
while (true) {
try {
List<MapRecord<String, Object, Object>> list = stringRedisTemplate.opsForStream()
.read(Consumer.from("g1", "c1"),
StreamReadOptions.empty().count(1).block(Duration.ofSeconds(2)),
StreamOffset.create("stream.orders", ReadOffset.lastConsumed()));
if (list != null && !list.isEmpty()) {
MapRecord<String, Object, Object> record = list.get(0);
Map<Object, Object> value = record.getValue();
VoucherOrder voucherOrder = BeanUtil.fillBeanWithMap(value, new VoucherOrder(), true);
handleVoucherOrder(voucherOrder);
stringRedisTemplate.opsForStream().acknowledge("stream.orders", "g1", record.getId());
}
} catch (Exception e) {
log.error("处理订单异常", e);
handlePendingList();
}
}
}
}
5. 点赞与关注功能
5.1 点赞功能实现
需求:
-
同一用户只能点赞一次
-
可取消点赞
-
显示点赞数量
-
点赞排行榜
SortedSet实现:
java
// 点赞
public Result likeBlog(Long id) {
Long userId = UserHolder.getUser().getId();
String key = "blog:liked:" + id;
Double score = stringRedisTemplate.opsForZSet().score(key, userId.toString());
if (score == null) {
// 未点赞,点赞数+1,记录用户
boolean success = update().setSql("liked = liked + 1").eq("id", id).update();
if (success) {
stringRedisTemplate.opsForZSet().add(key, userId.toString(), System.currentTimeMillis());
}
} else {
// 已点赞,取消点赞
boolean success = update().setSql("liked = liked - 1").eq("id", id).update();
if (success) {
stringRedisTemplate.opsForZSet().remove(key, userId.toString());
}
}
return Result.ok();
}
// 查询点赞排行榜
public Result queryBlogLikes(Long id) {
String key = "blog:liked:" + id;
Set<String> top5 = stringRedisTemplate.opsForZSet().range(key, 0, 4);
if (top5 == null || top5.isEmpty()) {
return Result.ok(Collections.emptyList());
}
List<Long> ids = top5.stream().map(Long::valueOf).collect(Collectors.toList());
String idStr = StrUtil.join(",", ids);
List<UserDTO> userDTOS = userService.query()
.in("id", ids)
.last("ORDER BY FIELD(id," + idStr + ")")
.list()
.stream()
.map(user -> BeanUtil.copyProperties(user, UserDTO.class))
.collect(Collectors.toList());
return Result.ok(userDTOS);
}
5.2 关注功能实现
关注与取关:
java
// 关注/取关
public Result follow(Long followUserId, Boolean isFollow) {
Long userId = UserHolder.getUser().getId();
String key = "follows:" + userId;
if (isFollow) {
// 关注
Follow follow = new Follow();
follow.setUserId(userId);
follow.setFollowUserId(followUserId);
boolean success = save(follow);
if (success) {
stringRedisTemplate.opsForSet().add(key, followUserId.toString());
}
} else {
// 取关
boolean success = remove(new QueryWrapper<Follow>()
.eq("user_id", userId).eq("follow_user_id", followUserId));
if (success) {
stringRedisTemplate.opsForSet().remove(key, followUserId.toString());
}
}
return Result.ok();
}
// 共同关注
public Result followCommons(Long id) {
Long userId = UserHolder.getUser().getId();
String key1 = "follows:" + userId;
String key2 = "follows:" + id;
Set<String> intersect = stringRedisTemplate.opsForSet().intersect(key1, key2);
if (intersect == null || intersect.isEmpty()) {
return Result.ok(Collections.emptyList());
}
List<Long> ids = intersect.stream().map(Long::valueOf).collect(Collectors.toList());
List<UserDTO> users = userService.listByIds(ids)
.stream()
.map(user -> BeanUtil.copyProperties(user, UserDTO.class))
.collect(Collectors.toList());
return Result.ok(users);
}
6. Feed流实现
6.1 Feed流模式
三种模式:
-
拉模式:用户读取时拉取关注者内容
-
优点:节约空间
-
缺点:延迟高,压力大
-
-
推模式:发布时推送给粉丝
-
优点:时效性好
-
缺点:内存压力大
-
-
推拉结合:普通用户推,大V用户推拉结合
6.2 基于推模式的Feed流
实现方案:
-
发布笔记时推送给所有粉丝
-
使用SortedSet存储,分数为时间戳
-
实现滚动分页查询
发布笔记:
java
public Result saveBlog(Blog blog) {
UserDTO user = UserHolder.getUser();
blog.setUserId(user.getId());
boolean isSuccess = save(blog);
if (!isSuccess) {
return Result.fail("新增笔记失败!");
}
// 查询作者粉丝
List<Follow> follows = followService.query()
.eq("follow_user_id", user.getId()).list();
// 推送给粉丝
for (Follow follow : follows) {
Long userId = follow.getUserId();
String key = "feed:" + userId;
stringRedisTemplate.opsForZSet()
.add(key, blog.getId().toString(), System.currentTimeMillis());
}
return Result.ok(blog.getId());
}
6.3 滚动分页查询
传统分页问题:数据变化导致重复或遗漏
滚动分页方案:
-
记录上次查询的最小时间戳
-
记录偏移量
-
基于SortedSet的ZREVRANGEBYSCORE
实现代码:
java
public Result queryBlogOfFollow(Long max, Integer offset) {
Long userId = UserHolder.getUser().getId();
String key = "feed:" + userId;
// 查询收件箱
Set<ZSetOperations.TypedTuple<String>> typedTuples = stringRedisTemplate.opsForZSet()
.reverseRangeByScoreWithScores(key, 0, max, offset, 2);
if (typedTuples == null || typedTuples.isEmpty()) {
return Result.ok();
}
// 解析数据
List<Long> ids = new ArrayList<>(typedTuples.size());
long minTime = 0;
int os = 1;
for (ZSetOperations.TypedTuple<String> tuple : typedTuples) {
ids.add(Long.valueOf(tuple.getValue()));
long time = tuple.getScore().longValue();
if (time == minTime) {
os++;
} else {
minTime = time;
os = 1;
}
}
os = minTime == max ? os : os + offset;
// 查询博客详情
String idStr = StrUtil.join(",", ids);
List<Blog> blogs = query().in("id", ids)
.last("ORDER BY FIELD(id," + idStr + ")").list();
ScrollResult r = new ScrollResult();
r.setList(blogs);
r.setOffset(os);
r.setMinTime(minTime);
return Result.ok(r);
}
7. 附近商户搜索
7.1 GEO数据结构
基本命令:
-
GEOADD:添加地理坐标
-
GEODIST:计算距离
-
GEOHASH:坐标转哈希
-
GEOPOS:获取坐标
-
GEOSEARCH:范围搜索
7.2 商户数据导入GEO
java
@Test
void loadShopData() {
List<Shop> list = shopService.list();
Map<Long, List<Shop>> map = list.stream()
.collect(Collectors.groupingBy(Shop::getTypeId));
for (Map.Entry<Long, List<Shop>> entry : map.entrySet()) {
Long typeId = entry.getKey();
String key = "shop:geo:" + typeId;
List<Shop> value = entry.getValue();
List<RedisGeoCommands.GeoLocation<String>> locations = new ArrayList<>(value.size());
for (Shop shop : value) {
locations.add(new RedisGeoCommands.GeoLocation<>(
shop.getId().toString(),
new Point(shop.getX(), shop.getY())
));
}
stringRedisTemplate.opsForGeo().add(key, locations);
}
}
7.3 附近商户查询
java
public Result queryShopByType(Integer typeId, Integer current, Double x, Double y) {
if (x == null || y == null) {
// 无坐标查询
Page<Shop> page = query()
.eq("type_id", typeId)
.page(new Page<>(current, DEFAULT_PAGE_SIZE));
return Result.ok(page.getRecords());
}
// 分页参数
int from = (current - 1) * DEFAULT_PAGE_SIZE;
int end = current * DEFAULT_PAGE_SIZE;
// GEO查询
String key = "shop:geo:" + typeId;
GeoResults<RedisGeoCommands.GeoLocation<String>> results = stringRedisTemplate.opsForGeo()
.search(key, GeoReference.fromCoordinate(x, y),
new Distance(5000),
RedisGeoCommands.GeoSearchCommandArgs.newGeoSearchArgs()
.includeDistance().limit(end));
// 解析结果
List<GeoResult<RedisGeoCommands.GeoLocation<String>>> list = results.getContent();
if (list.size() <= from) {
return Result.ok(Collections.emptyList());
}
List<Long> ids = new ArrayList<>(list.size());
Map<String, Distance> distanceMap = new HashMap<>(list.size());
list.stream().skip(from).forEach(result -> {
String shopIdStr = result.getContent().getName();
ids.add(Long.valueOf(shopIdStr));
distanceMap.put(shopIdStr, result.getDistance());
});
// 查询店铺详情
String idStr = StrUtil.join(",", ids);
List<Shop> shops = query().in("id", ids)
.last("ORDER BY FIELD(id," + idStr + ")").list();
for (Shop shop : shops) {
shop.setDistance(distanceMap.get(shop.getId().toString()).getValue());
}
return Result.ok(shops);
}
8. 用户签到
8.1 BitMap数据结构
基本命令:
-
SETBIT:设置位
-
GETBIT:获取位
-
BITCOUNT:统计1的数量
-
BITFIELD:操作位数组
8.2 签到功能实现
java
// 签到
public Result sign() {
Long userId = UserHolder.getUser().getId();
LocalDateTime now = LocalDateTime.now();
String keySuffix = now.format(DateTimeFormatter.ofPattern(":yyyyMM"));
String key = "sign:" + userId + keySuffix;
int dayOfMonth = now.getDayOfMonth();
stringRedisTemplate.opsForValue().setBit(key, dayOfMonth - 1, true);
return Result.ok();
}
// 统计连续签到天数
public Result signCount() {
Long userId = UserHolder.getUser().getId();
LocalDateTime now = LocalDateTime.now();
String keySuffix = now.format(DateTimeFormatter.ofPattern(":yyyyMM"));
String key = "sign:" + userId + keySuffix;
int dayOfMonth = now.getDayOfMonth();
// 获取本月签到记录
List<Long> result = stringRedisTemplate.opsForValue().bitField(
key, BitFieldSubCommands.create()
.get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth))
.valueAt(0));
if (result == null || result.isEmpty()) {
return Result.ok(0);
}
Long num = result.get(0);
if (num == null || num == 0) {
return Result.ok(0);
}
// 统计连续签到
int count = 0;
while (true) {
if ((num & 1) == 0) {
break;
} else {
count++;
}
num >>>= 1;
}
return Result.ok(count);
}
9. UV统计
9.1 HyperLogLog
特点:
-
统计巨大集合的基数
-
内存占用小(<16KB)
-
概率性统计(误差<0.81%)
基本命令:
-
PFADD:添加元素
-
PFCOUNT:统计基数
-
PFMERGE:合并多个HyperLogLog
9.2 UV统计实现
java
// 用户访问时添加
stringRedisTemplate.opsForHyperLogLog().add("uv:" + date, userId.toString());
// 统计UV
Long size = stringRedisTemplate.opsForHyperLogLog().size("uv:" + date);
// 合并多日数据
stringRedisTemplate.opsForHyperLogLog().union("uv:week", "uv:day1", "uv:day2", "uv:day3");
Long weekSize = stringRedisTemplate.opsForHyperLogLog().size("uv:week");
总结
Redis在实际应用中扮演着重要角色,从基础的缓存、会话管理,到复杂的分布式锁、消息队列、地理搜索等高级功能。掌握这些实战技能对于构建高性能、可扩展的系统至关重要。本文涵盖的知识点包括:
-
会话管理:使用Redis实现分布式Session
-
缓存策略:缓存穿透、雪崩、击穿的解决方案
-
分布式锁:基于Redis的分布式锁实现与优化
-
秒杀系统:异步秒杀、库存扣减、一人一单
-
消息队列:List、PubSub、Stream三种实现方案
-
社交功能:点赞、关注、Feed流
-
地理位置:GEO数据结构与附近搜索
-
数据统计:BitMap签到、HyperLogLog UV统计