【ArchSummit架构师峰会】探讨数据与人工智能相互驱动的关系>>> 了解详情
写点什么

zookeeper 实现分布式锁安全用法

  • 2019-11-06
  • 本文字数:4615 字

    阅读完需:约 15 分钟

zookeeper 实现分布式锁安全用法

分布式锁现在用的越来越多,通常用来协调多个并发任务。在一般的应用场景中存在一定的不安全用法,不安全用法会带来多个 master 在并行执行,业务或数据可能存在重复计算带来的副作用,在没有拿到 lock 的情况下扮演者 master 等诸如此类。


要想准确的拿到分布式锁,并且准确的捕获在分布式情况下锁的动态转移状态,需要处理网络变化带来的连锁反应。比如常见的 session expire、connectionLoss,在设置 lock 状态的时候我们如何保证准确拿到 lock。


在设计任务的时候我们需要具有 stop point 的策略,这个策略是用来在感知到 lock 丢失后能够交付执行权的机制。但是是否需要这么严肃的处理这个问题还取决于业务场景,比如下游的任务已经做好幂等也无所谓重复计算。 但是在有些情况下确实需要严肃精准控制。

ConnectionLoss 链接丢失

先说第一个场景,connectionLoss 事件,此事件表示提交的 commit 有可能执行成功也有可能执行失败,成功是指在 zookeeper broker 中执行成功但是返回的时候 tcp 断开了,导致未能拿到返回的状态。失败是指根本就没有提交到 zookeper broker 中链接就断开了。


所以在我们获取 lock 的时候需要做 connectionLoss 事件处理,我们看个例子。


protected void runForMaster() {        logger.info("master:run for master.");        AsyncCallback.StringCallback createCallback =                (rc, path, ctx, name) -> {                    switch (KeeperException.Code.get(rc)) {                        case CONNECTIONLOSS:                            checkMaster();//链接失效检查znode设置是否成功                            return;                        case OK:                            isLeader = true;                            logger.info("master:I'm the leader serverId:" + serverId);                            addMasterWatcher();//监控 master znode                            this.takeLeadership();//执行leader权利                            break;                        case NODEEXISTS:                            isLeader = false;                            String serverId = this.getMasterServerId();                            this.takeBackup(serverId);                            break;                    }                };        zk.create(rootPath + "/master", serverId.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE,                CreateMode.EPHEMERAL, createCallback, null);//创建master节点    }    /**     * check master 循环检查     */    private void checkMaster() {        AsyncCallback.DataCallback masterCheckCallback =                (rc, path, ctx, data[], stat) -> {                    switch (KeeperException.Code.get(rc)) {                        case CONNECTIONLOSS:                            checkMaster();                            return;                        case NONODE:                            runForMaster();                            return;                        default: {                            String serverId = this.getMasterServerId();                            isLeader = serverId.equals(this.serverId);                            if (BooleanUtils.isNotTrue(isLeader)) {                                this.takeBackup(serverId);                            } else {                                this.takeLeadership();                            }                        }                        return;                    }                };        zk.getData(masterZnode, false, masterCheckCallback, null);    }
复制代码


这里的 master 表示具有执行权,只有成功拿到 master 角色才能履行 master 权利。


runForMaster 方法一旦发现有 connectionLoss 就发起 checkMaster 进行检查,同时 checkMaster 方法中也进行 connectinLoss 检查,直到拿到明确的状态为止。在此时有可能有另外的节点获取到了 master 角色,那么当前节点就做好 backup 等待机会。


我们需要捕获 zookeeper 所有的状态变化,要知道 master 什么时候失效做好申请准备,当自己是 master 时候会话失效需要释放 master 权利。


/**     * 监控 master znode 做 master/slave 切换     */    private void addMasterWatcher() {
AsyncCallback.StatCallback addMasterWatcher = (rc, path, ctx, stat) -> { switch (KeeperException.Code.get(rc)) { case CONNECTIONLOSS: addMasterWatcher(); break; case OK: if (stat == null) { runForMaster();//master 已经不存在 } else { logger.info("master:watcher master znode ok."); } break; case NONODE: logger.info("master:master znode delete."); runForMaster(); break; } };
zk.exists(masterZnode, MasterExistsWatcher, addMasterWatcher, null); }
复制代码


通过 zookeeper watcher 机制来进行状态监听,保持与网络、zookeeper 状态变化联动。

SessionExpired 会话过期

我们在来看第二个问题,第一个问题是获取 lock 的时候如何保证一定可以准确拿到状态,这里状态是指 master 角色或者 backup 角色。


当我们成功与 zookeeper broker 建立链接,成功获取到 master 角色并且正在履行 master 义务时突然 zookeeper 通知 session 过期,SessionExpired 事件表示 zookeeper 将会删除所有当前会话创建的临时 znode,也就意味这 master znode 将会被其他会话创建。


此时我们需要将自己的 master 权利交出去,也就是我们必须放下目前手上执行的任务,这个停止的状态必须能够反应到全局。此时最容易出现到问题就是,我们已经不是 master 了但是还在偷偷到执行 master 权利,通过 dashboard 会看到很奇怪的问题,不是 master 的服务器还在执行。


case SESSIONEXPIRED:    //执行 stop point 通知    this.stopPoint();    break;
复制代码


所以这里需要我们在设计任务时有 stop point 策略,类似 jvm 的 safe point,随时响应全局停止。

绕开 zookeeper broker 进行状态通知

还有一种常见的使用方式是绕开 zookeeper 来做状态通知。


我们都知道 zookeeper cluster 是由多台实例组成,每个实例都在全国甚至全球的不同地方,leader 到这些节点之间都有很大的同步延迟差异,zookeeper 内部采用法定人数的两阶段提交的方式来完成一次 commit。


比如有 7 个实例构成一套 zookeeper cluster ,当一次 client 写入 commit 只需要集群中有超过半数完成写入就算这次 commit 提交成功了。但是 cleint 得到这个提交成功的响应之后立马执行接下来的任务,这个任务可能是读取某个 znode 下的所有状态数据,此时有可能无法读取到这个状态。


如果是分布式锁的话很有可能是锁在 zk 集群中的转移无法和 client 集群保持一直。所以只要是基于 zookeeper 做集群调度就要完全原来 zookeeper 来做状态通知,不可以绕开 zookeeper 来自行调度。

leader 选举与 zkNode 断开

zookeeper leader 是所有状态变更的串行化器,add、update、delete 都需要 leader 来处理,然后传播给所有 follower、observer 节点。


所有的 session 是保存在 leader 中的,所有的 watcher 是保存在 client 链接的 zookeper node 中的,这里两个场景都会导致状态迁移的通知不准时。


如果 zookeeper 是由多数据中心构成的一套集群,存在异地同步延迟的问题,leader 是肯定会放在写入的数据中心中,同时 zid 应该是最大的,甚至是一组高 zid 的机器都在写入的数据中心中,这样保证 leader 宕机也不会轻易导致 leader 选举到其他数据中心。


但是 follower、observer 都会有 client 在使用,也会有在这些节点进行协调的分布式集群。


先说 leader 选举导致异地节点延迟感知问题,比如当前 zookeeper cluster 有 7 台机器构成:


dataCenter shanghai:zid=100、zid=80、zid=50dataCenter beijing: zid=10、zid=20dataCenter shenzhen:zid=30、zid=40
复制代码


由于网络问题集群发生 leader 选举,zid=100 暂时脱离集群,zid=80 成为 leader,这里不考虑日志新旧问题,优先使用 zid 进行选举。


由于集群中所有的 session 是保存在原来 zid=100 的机器中的,新 leader 没有任何 session 信息,所以将导致所有 session 丢失。


session 的保持时间是取决于我们设置的 sessinoTimeout 时间来的,client 通过 ping 来将心跳传播到所链接的 zkNode,这个 zkNode 可能是任意角色的 node,然后 zkNode 在与 zkleaderNode 进行心跳来保持会话,同时 zkNode 也会通过 ping 来保持会话超时时间。


此时当原有当 client 在重新链接上 zkNode 时会被告知 sessionExpired。sessionExpired 是由 zkNode 通知出来的,当会话丢失或者过期,client 在去尝试链接 zkNode 时候会被 zkNode 告知会话过期。


如果 client 只捕获了 sessionExpired 显然会出现多个 master 运行情况,因为当你与 zkNode 断开到时候,当时还没有收到 sessionExpired 事件时,已经有另外 client 成功创建 master 拿到权利。


这种情况在 zkNode 出现脱离集群当时候也会出现,当 zkNode 断开之后也会出现 sessionExpired 延迟通知问题。所有的 watcher 都是需要在新的 zkNode 上创建才会收到新的事件。

静态扩容、动态扩容

在极端情况下静态扩容可能会导致 zookeeper 集群出现严重的数据不一致问题,比如现有集群:A、B、C,现在需要进行静态扩容,停止 ABC 实例,拉入 DE 实例,此时如果 C 实例是 ABC 中最滞后的实例,如果 AB 启动的速度没有 C 快就会导致 CDE 组成新的集群,新的纪元号会覆盖原来的 AB 日志。当然现在基本上不会接受静态扩容,基本上都是动态扩容。


动态扩容在极端情况下也会出现类似问题,比如现在有三个机房,1、2、3,1 机房方 leader zid=200、100,2 机房 zid=80、50,3 机房 zid=40,假设上次的 commit 是在 zid=200、100、50 之间提交的,此时机房 1 出现断网,2 机房 zid=80、50 与 3 机房 zid=40 开始组成新的集群,新的纪元在 zid=50 上产生。

做好幂等

在使用 zookeeper 来实现分布式锁或者集群调度的时候会出现很多分布式下的问题,为了保证这些问题的出现不会带来业务系统或者业务数据的不一致,我们还是在这些任务上做好幂等性考虑。


比如进行数据的计算,做个时间检查,版本检查之类的。如果本身是基于 zookeeper 实现的一套独立的分布式系统需要的工作会更多点。


作者介绍:


王清培,腾讯云 TVP,沪江资深应用架构师 、微软全球最有价值专家、畅销书《.NET 框架设计-模式、配置、工具》作者、图灵社区专家顾问团专家、51CTO 特邀讲师、云栖社区技术专家。 先后效力 美国新蛋网、携程、找钢网, 十年应用系统开发架构经验,在电商 、交易系统、营销平台有一定的积累。


本文转载自公众号云加社区(ID:QcloudCommunity)。


原文链接:


https://mp.weixin.qq.com/s/tly6H8XwXPJwvcQ3evV_tQ


2019-11-06 18:251201

评论

发布
暂无评论
发现更多内容

华为云瑶光:打通云边端界限,为企业云上业务带来最优解

华为云开发者联盟

华为 云服务

架构训练营学习笔记之五技术选型(一)

于成龙

架构训练营

一个草根的日常杂碎(10月20日)

刘新吾

随笔杂谈 生活记录 社会百态

mongodb 源码实现、调优、最佳实践系列-百万级代码量mongodb内核源码阅读经验分享

杨亚洲(专注MongoDB及高性能中间件)

MySQL mongodb 源码 中间件 分布式数据库mongodb

万物互联的IoT时代,柔性电子会大行其道吗?

脑极体

一个草根的日常杂碎(10月19日)

刘新吾

随笔杂谈 生活记录 社会百态

java week1练习

闷骚程序员

算法分析关键

Geek_0b8195

算法和数据结构

分布式下,我想要一致性

架构师修行之路

分布式 微服务

【线上排查实战】AOP切面执行顺序你真的了解吗

Zhendong

spring aop

Flink窗口算子-6-8

小知识点

scala 大数据 flink

架构师训练营 1 期 -- 第五周作业

曾彪彪

极客大学架构师训练营

透视HTTPS建造固若金汤的堡垒

码哥字节

https 加密解密 HTTP

1分钟带你入门 React 公共逻辑抽离HOC...

Leo

大前端 React Hooks HOC Render Props

游戏数值策划之常用excel函数

吴优秀同学

Excel 游戏

央行数字货币离我们还有多远?

CECBC

数字货币

Linux的上手命令

Linux 常用命令

Java程序员还在为没有项目经验感到苦恼?快来看看GitHub上最火的SpringCloud微服务商城系统开源项目,附全套教程!

Java架构之路

Java 程序员 架构 面试 编程语言

云原生在京东丨云原生时代下的监控:如何基于云原生进行指标采集?

京东科技开发者

云原生

第四周作业

dll

聊聊技术人员如何学习成长

架构精进之路

职业成长

深入java week1-01 字节码、内存、GC、调试工具

闷骚程序员

膜拜!阿里技术总监纯手打的《MySQL笔记》内部资料限时分享

Java架构师迁哥

Nginx 在运维领域中的应用,看这一篇就够了

华章IT

nginx Linux 运维工程师

利用区块链等技术,加强对交通运输信用信息的归集共享和分析应用

CECBC

区块链 交通运输

架构师必备的那些分布式事务解决方案!!

架构师修行之路

分布式 微服务 架构设计

Go语言内存管理三部曲(三)图解GC算法和垃圾回收原理

网管

内存管理 垃圾回收 GC GC算法 Go 语言

一个草根的日常杂碎(10月18日)

刘新吾

随笔杂谈 生活记录 社会百态

二十、深入Python迭代器和生成器

刘润森

Python

甲方日常 35

句子

工作 随笔杂谈 日常

年纪轻轻怎么就卵巢早衰了?试管可帮忙!

Geek_65d32f

试管 三代试管

zookeeper 实现分布式锁安全用法_文化 & 方法_王清培_InfoQ精选文章