您的位置:首页 > 数据库 > Redis

利用多写Redis实现分布式锁原理与实现分析

2016-10-02 08:26 651 查看
在我写这篇文章的时候,其实我还是挺纠结的,因为我这个方案本身也是雕虫小技拿出来显眼肯定会被贻笑大方,但是我最终还是拿出来与大家分享,我本着学习的态度和精神,希望大家能够给与我指导和改进方案。

一、关于分布式锁

关于分布式锁,可能绝大部分人都会或多或少涉及到。

我举二个例子:

场景一:从前端界面发起一笔支付请求,如果前端没有做防重处理,那么可能在某一个时刻会有二笔一样的单子同时到达系统后台。

场景二:在App中下订单的时候,点击确认之后,没反应,就又点击了几次。在这种情况下,如果无法保证该接口的幂等性,那么将会出现重复下单问题。

在接收消息的时候,消息推送重复。如果处理消息的接口无法保证幂等,那么重复消费消息产生的影响可能会非常大。

类似这种场景,我们有很多种方法,可以使用幂等操作,也可以使用锁的操作。

我们先来解释一下什么是幂等操作:

所谓幂等,简单地说,就是对接口的多次调用所产生的结果和调用一次是一致的。扩展一下,这里的接口,可以理解为对外发布的HTTP接口或者Thrift接口,也可以是接收消息的内部接口,甚至是一个内部方法或操作。

在分布式环境中,网络环境更加复杂,

因前端操作抖动、网络故障、消息重复、响应速度慢等原因,对接口的重复调用概率会比集中式环境下更大,尤其是重复消息在分布式环境中很难避免。Tyler Treat也在《You Cannot Have Exactly-Once Delivery》一文中提到:

Within the context of a distributed system, you cannot have exactly-once message delivery.

分布式环境中,有些接口是天然保证幂等性的,如查询操作。有些对数据的修改是一个常量,并且无其他记录和操作,那也可以说是具有幂等性的。其他情况下,所有涉及对数据的修改、状态的变更就都有必要防止重复性操作的发生。通过间接的实现接口的幂等性来防止重复操作所带来的影响,成为了一种有效的解决方案。

于是我们根据以上内容就可以讲一下使用分布式锁的方法有哪些。

1、使用数据库乐观锁,包括主键防重,版本号控制。但是这两种方法各有利弊。

使用主键冲突的策略进行防重,在并发量非常高的情况下对数据库性能会有影响,尤其是应用数据表和主键冲突表在一个库的时候,表现更加明显。其实针对是否会对数据库性能产生影响这个话题,我也和一些专业的DBA同学讨论过,普遍认可的是在mysql数据库中采用主键冲突防重,在大并发情况下有可能会造成锁表现象,比较好的办法是在程序中生产主键进行防重。

使用版本号策略

这个策略源于mysql的mvcc机制,使用这个策略其实本身没有什么问题,唯一的问题就是对数据表侵入较大,我们要为每个表设计一个版本号字段,然后写一条判断sql每次进行判断。

2、Zookeeper防重策略

利用ZK确实是一个不错的方案,流程如下:



以前的版本中普遍传言说它的性能不好,但是后续的版本性能得到了较大提高,经过系统压测还是能够支撑较大并发量的,经过压测三台Zookeeper能搞住20000tps。

用zookeeper的优点大概有:高可用、公平锁、心跳保持锁。

3、Redis防重策略

关于主从Redis方案最简单的实现流程如下:



表面来看,这个方案似乎很管用,但是这里存在一个问题:在我们的系统架构里存在一个单点故障,如果Redis的master节点宕机了怎么办呢?有人可能会说:加一个slave节点!在master宕机时用slave就行了!但是其实这个方案明显是不可行的,因为这种方案无法保证第1个安全互斥属性,因为Redis的复制是异步的。 总的来说,这个方案里有一个明显的竞争条件(race condition),举例来说:

客户端A在master节点拿到了锁。

master节点在把A创建的key写入slave之前宕机了。

slave变成了master节点

B也得到了和A还持有的相同的锁(因为原来的slave里还没有A持有锁的信息)

于是我就在想,我该如何做才能让Redis在分布式锁这一块能够达到高可用呢?

于是基于Tedis的思想(http://www.oschina.net/p/tedis) 我自己写了一套针对分布式锁的双写Redis框架。

二、双写Redis的架构图



说明:

组件名叫YeeRedisGroup,基本服务主要有四个,当数据到来的时候,会分别插入二个Redis服务,这二个Redis服务采用的是异地双活的方案,当其中一个Redis服务挂了以后,会将这个Redis服务从可用队列中摘除,放入重试队列中,另一个Redis则会继续使用。同样读取Redis的时候只会从可用队列中读取第一个Redis服务继续读取。

三、双写Redis的类图结构



说明:这个图其实没什么可说的,大家自己看就可以了。

四、双写Redis的时序图



说明:这个图主要就是说明了整体系统交互流程是怎样的。

五、故障容错流程图



六、故障重试流程图



七、主动通知与主动查询流程图



八、Redis可用队列与重试队列结构图



九、项目阐述

上面项目的最终压测结果还没有出来,并且项目也还没有最终开源,在此我是非常想和大家进行交流,我觉得可能的缺点是客户端相对比较重,有大量的逻辑都在客户端上面进行,从我个人是本着交流与学习的态度勇于接受各种批评与指正,谢谢。

注:本文参考了蒋谞的《分布式系统互斥性与幂等性问题的分析与解决》
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  redis 分布式