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

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

一、关于分布式锁

关于分布式锁,可能绝大部分人都会或多或少涉及到。 我举二个例子: 场景一:从前端界面发起一笔支付请求,如果前端没有做防重处理,那么可能在某一个时刻会有二笔一样的单子同时到达系统后台。

场景二:在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确实是一个不错的方案,流程如下:

Paste_Image.png

以前的版本中普遍传言说它的性能不好,但是后续的版本性能得到了较大提高,经过系统压测还是能够支撑较大并发量的,经过压测三台Zookeeper能搞住20000tps。 用zookeeper的优点大概有:高可用、公平锁、心跳保持锁。

3、Redis防重策略 关于主从Redis方案最简单的实现流程如下:

Paste_Image.png

表面来看,这个方案似乎很管用,但是这里存在一个问题:在我们的系统架构里存在一个单点故障,如果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的架构图

Paste_Image.png

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

三、双写Redis的类图结构

Paste_Image.png

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

四、双写Redis的时序图

Paste_Image.png

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

五、故障容错流程图

Paste_Image.png

六、故障重试流程图

Paste_Image.png

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

Paste_Image.png

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

Paste_Image.png

九、项目阐述

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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏小特工作室

工作流组件示例(全部开源)

1.概述 1.1简介 本文档旨在帮助开发人员快速使用工作流组件,完成OA或审批等涉及到工作流组件的系统开发工作 1.2组件构成 1.2.1组件层次图 ? 组...

70710
来自专栏月色的自留地

Mac电脑C语言开发的入门帖

2625
来自专栏做全栈攻城狮

零基础开发高大上精美网站,一步步带你建设全能网站

不管你水平如何,请你仔细看下去。因为这份技能你值得拥有。网站建设完毕之后,下一系列讲解如何建设公众号。一起带你飞。

1981
来自专栏大前端开发

从编程小白到全栈开发:服务端的一些概念

幕后英雄总是容易被大家所忽略。在这看脸的世界,大家都以为脸就是全部了,你总是相信你所看到的就是事物的全部,可是你往往看到的只是冰山的一角而已。

1082
来自专栏IT大咖说

vSAN架构解析与6.7功能介绍

内容来源:2018 年 7 月 17 日,VMware大中华区原厂高级技术讲师史峻在“VMware直播分享 第二期”进行《vSAN架构解析与6.7功能介绍》演讲...

2253
来自专栏一“技”之长

iOS10通知框架UserNotification理解与应用

        关于通知,无论与远程Push还是本地通知,以往的iOS系统暴漏给开发者的接口都是十分有限的,开发者只能对标题和内容进行简单的定义,至于UI展示和...

1282
来自专栏友弟技术工作室

Python 资源大全中文版

GitHub 上有一个 Awesome - XXX 系列的资源整理,资源非常丰富,涉及面非常广。awesome-python 是 vinta 发起维护的 Pyt...

6315
来自专栏杨建荣的学习笔记

简单分析percona-zabbix-templates(r10笔记第6天)

当Zabbix和Percona两者相遇,会擦出不少的开源火花来,众人拾柴火焰高,最终受益的还是大部分运维人员。 我很早就用过Percona提供的...

3706
来自专栏Java架构师学习

如何在ELK中解析各类日志文件

作为一个日志中心,它会收集各种各样的日志,可以用于问题排查,数据监控,统计分析等等。那么对于繁多的日志,它们都有各自的存储格式,我们如何来区分它们,对于不同的日...

1.6K4
来自专栏程序员的SOD蜜

分布式计算,WCF+JSON+实体对象与WebService+DataSet效率大比拼

最近做公司项目,我们要整合所有业务系统的客户数据,各业务系统的数据库有的Oracle,有的是SQLSERVER,而且表结构也不相同,如何整合不同系统之间的客户数...

22710

扫码关注云+社区