通用场景设计
2026/1/15大约 3 分钟
通用场景设计
场景一:分布式限流
需求:API 接口限流,防止恶意请求。
令牌桶算法
// Redis + Lua 实现令牌桶
String script =
"local key = KEYS[1] " +
"local rate = tonumber(ARGV[1]) " + // 每秒生成令牌数
"local capacity = tonumber(ARGV[2]) " + // 桶容量
"local now = tonumber(ARGV[3]) " +
"local requested = tonumber(ARGV[4]) " + // 请求令牌数
"local tokens = redis.call('hget', key, 'tokens') " +
"local last_time = redis.call('hget', key, 'last_time') " +
"tokens = tokens and tonumber(tokens) or capacity " +
"last_time = last_time and tonumber(last_time) or now " +
// 计算新增令牌
"local delta = math.max(0, now - last_time) " +
"local new_tokens = math.min(capacity, tokens + delta * rate / 1000) " +
"if new_tokens >= requested then " +
" redis.call('hset', key, 'tokens', new_tokens - requested) " +
" redis.call('hset', key, 'last_time', now) " +
" return 1 " +
"end " +
"return 0";
public boolean tryAcquire(String key, int rate, int capacity) {
Long result = redisTemplate.execute(
new DefaultRedisScript<>(script, Long.class),
Collections.singletonList(key),
String.valueOf(rate),
String.valueOf(capacity),
String.valueOf(System.currentTimeMillis()),
"1"
);
return result == 1;
}滑动窗口
// 滑动窗口限流
public boolean isAllowed(String key, int limit, int windowSeconds) {
long now = System.currentTimeMillis();
long windowStart = now - windowSeconds * 1000;
String zsetKey = "ratelimit:" + key;
// 移除窗口外的请求
redisTemplate.opsForZSet().removeRangeByScore(zsetKey, 0, windowStart);
// 统计窗口内请求数
Long count = redisTemplate.opsForZSet().count(zsetKey, windowStart, now);
if (count < limit) {
// 添加当前请求
redisTemplate.opsForZSet().add(zsetKey, UUID.randomUUID().toString(), now);
redisTemplate.expire(zsetKey, windowSeconds, TimeUnit.SECONDS);
return true;
}
return false;
}场景二:分布式 ID
雪花算法
┌─────────────────────────────────────────────────────────────────┐
│ 0 │ 41位时间戳 │ 5位数据中心 │ 5位机器ID │ 12位序列号 │
└─────────────────────────────────────────────────────────────────┘号段模式
// 数据库存储号段
CREATE TABLE id_segment (
biz_tag VARCHAR(64) PRIMARY KEY,
max_id BIGINT NOT NULL,
step INT NOT NULL,
version INT NOT NULL
);
// 获取号段
@Transactional
public long[] getSegment(String bizTag) {
// 获取当前号段
IdSegment segment = segmentMapper.selectByTag(bizTag);
// 更新号段
int rows = segmentMapper.updateMaxId(bizTag, segment.getMaxId() + segment.getStep(), segment.getVersion());
if (rows == 0) {
throw new RuntimeException("获取号段失败");
}
return new long[]{segment.getMaxId() + 1, segment.getMaxId() + segment.getStep()};
}
// 本地缓存号段
public class SegmentIdGenerator {
private AtomicLong current;
private long max;
public synchronized long nextId() {
if (current.get() >= max) {
// 获取新号段
long[] segment = getSegment("order");
current = new AtomicLong(segment[0]);
max = segment[1];
}
return current.getAndIncrement();
}
}场景三:分布式锁
Redisson 实现
@Autowired
private RedissonClient redissonClient;
public void doWithLock(String lockKey, Runnable task) {
RLock lock = redissonClient.getLock(lockKey);
try {
// 尝试加锁,等待10秒,锁定30秒
if (lock.tryLock(10, 30, TimeUnit.SECONDS)) {
task.run();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}红锁(多节点)
RLock lock1 = redisson1.getLock("lock");
RLock lock2 = redisson2.getLock("lock");
RLock lock3 = redisson3.getLock("lock");
RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);
redLock.lock();
try {
// 业务逻辑
} finally {
redLock.unlock();
}场景四:延迟任务
Redis ZSet
// 添加延迟任务
public void addDelayTask(String taskId, long executeTime) {
redisTemplate.opsForZSet().add("delay:tasks", taskId, executeTime);
}
// 消费延迟任务
@Scheduled(fixedRate = 1000)
public void consumeDelayTasks() {
long now = System.currentTimeMillis();
Set<String> tasks = redisTemplate.opsForZSet()
.rangeByScore("delay:tasks", 0, now);
for (String taskId : tasks) {
// 原子移除并处理
Long removed = redisTemplate.opsForZSet().remove("delay:tasks", taskId);
if (removed > 0) {
processTask(taskId);
}
}
}场景五:排行榜
// 更新分数
public void updateScore(Long userId, double score) {
redisTemplate.opsForZSet().add("leaderboard", userId, score);
}
// 增加分数
public void incrScore(Long userId, double delta) {
redisTemplate.opsForZSet().incrementScore("leaderboard", userId, delta);
}
// 获取排名(从0开始)
public Long getRank(Long userId) {
return redisTemplate.opsForZSet().reverseRank("leaderboard", userId);
}
// 获取 Top N
public List<RankItem> getTopN(int n) {
Set<ZSetOperations.TypedTuple<Long>> tuples = redisTemplate.opsForZSet()
.reverseRangeWithScores("leaderboard", 0, n - 1);
return tuples.stream()
.map(t -> new RankItem(t.getValue(), t.getScore()))
.collect(Collectors.toList());
}
// 获取用户周围排名
public List<RankItem> getAroundRank(Long userId, int range) {
Long rank = getRank(userId);
long start = Math.max(0, rank - range);
long end = rank + range;
return redisTemplate.opsForZSet()
.reverseRangeWithScores("leaderboard", start, end);
}场景六:短链接
// 生成短链
public String shorten(String longUrl) {
// 1. 查询是否已存在
String existing = redisTemplate.opsForValue().get("url:long:" + longUrl);
if (existing != null) {
return existing;
}
// 2. 生成短码
long id = idGenerator.nextId();
String shortCode = Base62.encode(id);
// 3. 存储映射
redisTemplate.opsForValue().set("url:short:" + shortCode, longUrl);
redisTemplate.opsForValue().set("url:long:" + longUrl, shortCode);
return "https://short.url/" + shortCode;
}
// 解析短链
public String resolve(String shortCode) {
return redisTemplate.opsForValue().get("url:short:" + shortCode);
}
// Base62 编码
public class Base62 {
private static final String CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
public static String encode(long num) {
StringBuilder sb = new StringBuilder();
while (num > 0) {
sb.insert(0, CHARS.charAt((int)(num % 62)));
num /= 62;
}
return sb.toString();
}
}