c++ 读写锁_如何用Redis 实现分布式锁的思考

本文介绍了锁的概念及其在解决并发问题中的作用,特别是在分布式环境下的应用场景。通过具体案例,如多人在线编辑平台,展示了分布式锁如何确保数据一致性,并提供了一种基于Redis的分布式锁实现方案。

锁和分布式锁

在计算机中,锁的作用是解决在并发状态下的共享资源互斥问题,保证在同一时间只有一个进程/线程可以掌握资源的控制权。

例如以下几种情况:

文件锁的实现是为了解决不同用户同时读写同一文件的并发问题而出现的,防止导致文件的内容被破坏。

使用数组实现的队列,在 push 操作的地方一般需要加锁来解决槽位的争夺问题,防止出现多次 push 冲突从而导致数据丢失问题。

对于12306来说,火车票就是他的资源,最终放票的时候需要锁来保证票、人、座位唯一对应。

……

上面的例子中其实就包含了我们通常讲的传统单机锁和我要讲的分布式锁。

单机环境下,资源竞争者都是来自机器内部((进程/线程),那么实现锁的方案只需要借助单机资源就可以了,比如借助磁盘、内存、寄存器来实现。

但是对于分布式环境下,资源竞争者生存环境更复杂了,原有依赖单机的方案不再发挥作用,这时候就需要一个大家都认可的协调者出来,帮助解决竞争问题,那这个协调者称之为分布式锁。

上面这个例子就像两个职员产生的矛盾,只要公司的领导出面就可以解决。而当两个公司产生竞争矛盾的时候,就需要司法机关出面,是同一个道理。

简单的说,分布式锁就是解决分布式环境下资源竞争问题的手段。

分布式锁的应用场景

所有分布式环境下会出现资源竞争的地方都需要分布式锁的协调,除了上面介绍的 12306 放票,还有类似共享文档平台编辑问题、王者荣耀选择英雄、全局自增主键等应用需要用到。简单介绍一下在类似公司内部 Wiki 等多人协作编辑平台的使用场景。

Wiki 中的多人在线编辑

场景1:清明节前,团队要求我们在 Wiki 登记自己的休假情况,假设我们在 id=1 这个文档上记录我们的休假时间和联系电话。A、C 两个同学同时开始编辑,并且 A 和 C 在同一时间提交了结果,他们在提交前文档是空的。服务需要如何处理这两个请求呢?以谁的为准呢?会不会产生覆盖现象导致 A 的记录丢失了?

场景2:另一个 case,我是 Z 同学,在我前面别人都已经填完了,我有一个陋习,喜欢在保存的时候连续按3-5下 Ctrl+s,而每一个 Ctrl+s 都会触发一个请求,但是每个请求处理大概1s钟,但是实际请求都在 20ms 内发出去了。

问题同上面,如何保证不重复的追加记录呢?

假设你的存储服务和存储架构是这样的:

f804cdd1a67420253e2520419d3e6196.png

一般的处理代码是这样的:

//根据docid获取文件内容,从分布式文件系统取,时间不可控nowFileContent = getFileByDocId(docId)//do something,类似diff,追加操作newFileContent = doSomeThing()//存储到文件系统setNewFileContent(docId,newFileContent)

对于场景1讲到的 A、C 两个请求同时到达代码段,但是由于网络原因,A 先拿到文档内容,C 在 A 写入前读到文件内容,所以最终的结果是两者会丢失一个写入。

5c42f9dbf58cebe9948dd9dbcfedfed2.png

所以需要对读写操作做一次加锁,保证事务的完整、一致。

下图是《现代操作系统》中的插图,这里的效果也希望如此。

ef7c8faa5a850919b7c47ac1c9b5a5ae.png

Wiki 这类场景属于长耗时事务的资源处理问题,锁的出现保证不会因为事务中的读写间跨度耗时大导致写覆盖的情况,使得请求排队,顺序处理。

解决方案选择

我遇到的问题也是类 Wiki 这类长事务的问题,遇到问题第一想法是去看网上的解决方案。

网上 MySQL、ZK、Redis 各种实现方式很多,我需要选择哪种?怎么选择?我需要权衡哪些方面?

以前看分布式书的时候,一个被提到很多次的词是:trade-off,我理解是取舍或者是权衡吧。

作为一个 Web 开发者,我需要考虑的主要包含下面几个部分:

实现我的功能是否 OK,耗时是否满足在线需求?

实现难度、学习成本;

运维成本。

那么按照这几个标准来看一下现在的可选方案:

实现方式功能要求实现难度学习成本运维成本

MySQL 的方案借助表锁/行锁实现满足基本要求不难熟悉小量OK、大量影响现有业务、1主多从架构,不方便扩容

通过 ZK 创建数据节点的方式实现满足要求熟悉 ZK API 即可需要学习重,需要堆机器,有跨机房请求

Redis 使用 setnxex基本要求不难熟悉扩容方便、现有服务

MySQL 单主架构,写都会到 master,有瓶颈。ZK 的方式需要自己搭建、运维,而且需要堆机器,利用率不高。最终采用了 Redis 来实现,流量/存储都可以扩容,运维也不需要自己。

实现

选好了方案,下面就是实现了。如果我们最终实现了这个锁,对它的要求是什么呢?

lock 实现必须要是原子操作,同时保证任何时候只有一个竞争者是独占的;

unlock 必须是原子的,同时保证只有自己可以解锁自己;

不能出现死锁,当进程挂掉之后不影响其他的加锁行为;

支持 Twemproxy 模式下的架构和单机;

耗时可以接受。

基于上述要求我的实现如下(只提供了大致,删除了敏感信息):

_objRedis = RedisFactory::getRedis();$this->_redisKey =self::COMMON_REDISKEY_PREFIX.$ukey;$this->_unLockTime = $unlockTime ;//为单次加锁生成唯一guid$this->_guid = genGuid(); }/** *@brief对给定的key进行加锁处理 * *@return* * true 表示加锁成功 * * 抛出异常则表示加锁未成功,根据业务选择自己的care的级别 * 异常错误码 : * 1.网络错误: ErrorCodes::REDIS_ERROR 视业务严谨度,这个错误是否忽略 * 2.锁被占用: ErrorCodes::LOCK_IS_USED 明确确定锁被别人占有 */publicfunctionlock(){/*

* 设置锁的过程需要是原子的,所以采用了set来操作

* SET key value [EX seconds] [PX milliseconds] [NX|XX]

* Redis 2.6.12 版本开始支持通过set 指定参数完成setexnx功能

*

* php 语法 : $redis->set('key', 'value', Array('xx', 'px'=>1000));

*

*/$setRet =$this->_objRedis->set($this->_redisKey,$this->_guid,array('nx','ex'=>$this->_unLockTime));//返回false表示请求锁失败if(false=== $setRet){//锁被占用,抛异常thrownewException("get Lock Failed!Locking

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值