不想看推导过程的小伙伴,直接看结尾一句话也就够了
背景和痛点
投行交易系统交易排行榜,存在以下痛点:
- 需要实时排行榜
- 当交易量排名相同时怎么解决?要避免同分跳变
设计思路
Redis可以实时显示排行榜
ZSET数据结构: 可以实现存储不重复的数据,包含score和value
如何解决同分僵局?
(✅推荐)方案1:整数部分保持不变(实时交易量),小数部分:采用时间戳
交易量相同的时候,按照最后更新时间进行判断
1)加入更新时间戳。作为小数部分
2)1-小数部分的更新时间戳
score = volume(交易量) + (1 - update_time(更新时间戳)/ 10的12次方)
交易id | 交易量 | 时间戳 | score |
---|---|---|---|
1001 | 110 | 1740979215805 | 110.8259020784195 |
1002 | 110 | 1740979215806 | 110.8259020784194 |
1003 | 120 | 1740979215812 | 120.8259020784188 |
1004 | 110 | 1740979215807 | 110.8259020784193 |
这种方案的好处有两个:
- 直接在redis中可以进行排序,保证排行的实时性能
- 不需要应用层的参与
(❌不推荐)方案2:应用层进行二次排序
没什么好说的,需要从Redis拿到数据之后,再根据业务字段进行排序,性能不好,不要选!
上代码
注意: Redis ZSET格式的添加和返回的数据,不要放在一个dto中,分为两个DTO
DTO
@Data
public class TradeVolumeRequest {
// 在Redis中 score = volume(交易量) + (1 - update_time(更新时间戳)/ 10的12次方)
private String stockId;
private Integer volume;
// 更新时间
private Timestamp updateTime;
}
@Data
public class TradeRanking {
private String stockId;
private Double rankingzVolume;
}
controller
@RestController
@Slf4j
public class TradeRankingController {
@Resource
private StringRedisTemplate stringRedisTemplate;
private static final String TRADE_RANKING_KEY = "trade:volume:ranking";
@PostMapping("/addTradeVolume")
public String addTradeVolume(@RequestBody List<TradeVolumeRequest> tradeVolumeRequests) {
tradeVolumeRequests.forEach(tradeVolumeRequest -> {
double rankingScore = tradeVolumeRequest.getVolume() + (1 - System.currentTimeMillis() / 10e12);
stringRedisTemplate.opsForZSet()
.add(TRADE_RANKING_KEY, tradeVolumeRequest.getStockId(), rankingScore);
});
return "存入数据成功, key:" + TRADE_RANKING_KEY;
}
@GetMapping("/getTopN")
public List<TradeRanking> getTopN(@RequestParam(value = "topN", defaultValue = "3") int topN) {
List<TradeRanking> tradeRankingList = new ArrayList<>();
Set<ZSetOperations.TypedTuple<String>> typedTuples = stringRedisTemplate.opsForZSet()
.reverseRangeWithScores(TRADE_RANKING_KEY, 0, topN - 1);
for (ZSetOperations.TypedTuple<String> typedTuple : typedTuples) {
TradeRanking tradeRanking = new TradeRanking();
tradeRanking.setStockId(typedTuple.getValue());
tradeRanking.setRankingzVolume(typedTuple.getScore());
tradeRankingList.add(tradeRanking);
}
return tradeRankingList;
}
}
测试结果
数据存入Redis中,score按照上述的公式
获取topN
一句话
通过Redis ZSET加更新时间戳,解决百万交易排行榜实时显示 同分难题