记一次积分池问题的解决方案过程
一、积分池需求描述:
3.积分池,100万,多线程访问,不断的减,可能需要加锁,而且是分布式的。减到<=0了就要报错:积分已经消耗完毕!
机构号 + 积分数量,积分是和机构关联的,客户端来访问并发获取积分,积分数就要做减法,减完后再更新到数据库。这种的设计一个技术方案。
4.我分扣光了,别人手上还有分怎么办?
二、积分池问题我想的解决方案:
1.利用数据库的行锁,update t_score set score = score - 1 where dept = 1001,操作放在事务中,会锁定操作的行。此时dept=1001这行是锁定的。但是其他行没有被锁定。
好像不行,因为考虑极端场景下,就取一个机构下面的积分。所有的线程都访问这行数据,并发量也是很大的。
2.我就想用简单一点的方法,利用数据库的行锁机制,在事务中操作。这样就能利用数据库的行锁了,不需要额外的代码。也不需要考虑分布式,因为使用的都是同一个数据库。
三、积分池稳哥的解决方案:
1.先设计数据库表,积分池表:机构号、年份、总积分、剩余积分、创建时间、更新时间。
2.程序开始时,初始化数据,包括初始化数据库表、初始化redis数据。redis中的key设置好,deptSeq:year value:初始值积分
3.扣积分的时候,从redis中进行扣减,使用decrease稳哥写好的方法,调的是lua脚本,这个脚本是在redis中一次性执行的,所以可以保证原子性。
4.写个定时任务,每隔5分钟,将redis中的积分数据,批量存进数据库表中。前端查询积分的时候,就从数据库表中获取。这个获取值可能不是实时性的,但是对实时性要求不高。
5.RedisService这个,只要有redisTemplate就可以用。
6.如果redis服务停了,这个积分池服务也没办法用。
四、稳哥方案的优点:
1.照我的方案的话,使用数据库的行锁可能会造成死锁,事务不提交一直锁住,不好。
2.redis抗并发高。我开始是觉得redis一挂掉数据会丢失,虽然抗并发高,但是不好记录数据。但是稳哥的方案是起一个定时任务,5分钟跑一次,落一次库,这样解决了持久化的问题。
3.我为什么没有很想使用redis,因为需要考虑到原子性问题,而且是分布式的,但是我对分布式锁不熟悉,不会,不懂怎么实现。但是稳哥的方案redis跑lua脚本的方式简单优雅的解决了原子性问题。
4.redis中执行lua脚本,这个脚本执行过程中是一次性发给redis执行的,这个可以保证原子性。
5.可见会的技术多,博学,技术点的打通,就很能帮助你选到良好的技术方案。
6.我的方案还有个缺点就是,想利用数据库的行锁,但是确实是有热点问题,这个是存在的。会有场景下大量访问同一批数据,热点问题,高并发无法真正解决。
--
浙公网安备 33010602011771号