Redis分布式锁是老生常谈的话题,网上已经一堆关于它的介绍和说明,做起来貌似也简单,但感觉更像是为了应对面试的方案,多少人真的在公司的系统里加上分布式锁然后让测试压测过呢?本文也聊聊这个话题,不同的是我准备结合做过的业务中使用的Redis分布式锁,说说我们之前是怎么做的。
简述
什么是分布式锁?单应用的锁延伸到分布式系统中就是分布式锁,锁的是多个节点应用对同一资源或者数据的访问权限,每次仅能有一个节点获得相应的处理权限,其他节点需要等待该节点处理完成后才可再次竞争锁。
常见问题
1、正确的锁key和适合业务的超时时间非常关键
那我使用之前做的支付系统来举例,用户在支付的过程中不允许在短时间内提交两边重复的订单,就像在双11买东西的时候,大家就会碰到“订单正在处理中,请勿重复提交的提示”,这种限制操作虽然前端可以限制,但是后端的限制也必不可少。
这里暂时不提redission,在上面的简单代码中,我们使用订单ID作为锁key,将超时时间设置为1分钟,那么就要求下面的payOrder方法在1分钟之类就必须要有返回,无论是正常返回还是抛异常。超过一分钟后,锁被其他节点拿到后,重复访问数据或者资源的情况就不可避免。
这是在支付订单的业务中,这种业务场景往往都是比较快的。那么如果在保险的前置系统系统呢,用户购买保单的行为在业务上是复杂的,往往有试算、风控、再次核算、出单的流程,即使在每个流程节点使用异步处理,但是每个业务节点就不会慢,当出现一些并发量的时候,业务节点的耗时也不可控。所以预估并发量并在上线之前进行合理压测,取得一个合适的超时时间也是必须流程。
购买保险的流程中,每个业务系统对key的选择也是不同的,前置也许是用身份证ID来限制并发,后面试算环境可能用用户ID,出单环节可能用的又是投保单ID,保单打印环节可能用的又是保单ID。
2、分布式锁影响性能怎么办
在上面的代码中try和catch之间的业务逻辑可以很多也可以很少,可以当营销活动开始并发量陡然开始上升那该怎么做?即使payOrder耗时不是太高,排队的请求也会大量积压。我们这里暂不考虑熔断,因为熔断的体验感也不是很好的。扩展下上面的代码:
上面的代码可以看出我们在锁里面做了三件事,初始化订单、调用支付、更新订单。其中调用支付系统涉及三方系统耗时最长。在这里我们是不是可以只要锁住newOrder()就可以了。后面的方法只要使用newOrder生成的payId做更新操作即可,完全可以不用放在锁内。甚至是不是不用锁newOrder(),我们只用锁一个payId的生成即可,前端拿到payId后再次调用支付方法,这里只有更新操作根本不用上锁。
3、Redis宕机了怎么办?
我遇到Redis宕机的次数不多,无论是金融行业还在互联网公司的那段时间,都无法保证Redis绝对可用,无论是哨兵还是集群,在后端研发的角度上都需要要个backup方案。
简单粗暴方案,Redis宕机则系统不可用。这也是我遇到最多的方案,Redis同时还负责生成各种ID的职责,当ID生成失败则业务异常不再执行。
如果正在执行中的任务遇到了Redis突然宕机了,第一步是在数据库上一定要有乐观锁,防止数据重复更新,失败了可以重试,但是错误了就是大问题。第二步是数据要有完善的状态机,在执行一些害怕出问题的关键节点(如调用三方支付系统)之前切记保存一个临时状态。
总结
分布式锁自然不一定要用Redis,但是分布式锁的范围无论在什么业务场景上我都建议一定要小,一定不要锁复杂耗时逻辑。在设计系统时,分布式锁不可能是100%的可靠,备用的数据安全方案一定要提前想好。