【AICon】AI 大模型超全落地场景&最佳实践 了解详情
写点什么

zookeeper 实现分布式锁安全用法

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

    阅读完需:约 15 分钟

zookeeper 实现分布式锁安全用法

8 月 16 - 19 日,与零一万物李开复、蔚来李斌、面壁智能李大海,及工商银行、交通银行、华夏银行等 100+ 行业专家相聚 FCon x AICon

分布式锁现在用的越来越多,通常用来协调多个并发任务。在一般的应用场景中存在一定的不安全用法,不安全用法会带来多个 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:251251

评论

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

Kotlin学习手记——注解(1),2021年字节跳动74道高级程序员面试

android 程序员 移动开发

Native开发工具之应用开发编辑器&打包发布(一),kotlin构造器

android 程序员 移动开发

ObjectBox 集成指南,建议细读

android 程序员 移动开发

OkHttp 的 IO 操作和进度监听,android应用开发实训总结

android 程序员 移动开发

Meterial Design常见控件的使用(八),【面试总结】

android 程序员 移动开发

Kotlin下的5种单例模式,一招教你看懂Netty

android 程序员 移动开发

LeakCanary源码分析以及ContentProvider的优化方案,android移动开发

android 程序员 移动开发

《个人信息保护法》正式实施,企业如何保证数据安全合规?

腾讯安全云鼎实验室

数据安全

Kotlin写一个解释器(2)---语法分析,安卓项目开发范例大全

android 程序员 移动开发

lambda表达式(3)-shawn,挑战大厂重燃激情

android 程序员 移动开发

Meterial Design常见控件的使用(五),移动端h5开发框架

android 程序员 移动开发

MVVM系列之三:ViewModel,最新Android开发进阶

android 程序员 移动开发

OkHttp踩坑记:为何 response,androidui设计

android 程序员 移动开发

Go语言HTTPServer开发的六种实现

FunTester

HTTP Fasthttp Server Go 语言 FunTester

livedatabus详解,阿里是如何用他来做淘宝架构的?,android开发视频百度云

android 程序员 移动开发

offer求比较+部分大厂Android面经+真题解析,给2021的Android一些建议

android 程序员 移动开发

Kotlin学习手记--泛型、泛型约束、泛型型变,天呐

android 程序员 移动开发

Kotlin学习手记——注解,flutter下拉加载

android 程序员 移动开发

Kotlin(五)深入理解Kotlin类与接口,androidndk开发视频

android 程序员 移动开发

MaterialDesign系列文章(十一)Google2018年大会新出的控件汇总集合

android 程序员 移动开发

LeakCanary源码学习二:LeakCanary,sw实战营文件下载

android 程序员 移动开发

Linux编程之权限系统与工具使用(二),【大牛系列教学】

android 程序员 移动开发

万人逐鹿、十强争霸!华为云GaussDB数据库两大重量级赛事圆满落幕

华为云数据库小助手

GaussDB 大赛 华为云数据库

Meterial Design常见控件的使用(一),安卓面试题2018中高级

android 程序员 移动开发

MVPArms官方快速组件化方案开源,来自5K star的信赖,安卓性能优化和内存优化

android 程序员 移动开发

Native开发工具之交叉编译移植(五),android开发基础教程视频

android 程序员 移动开发

Okio—— 更加高效易用的IO库,一线互联网架构师Android框架体系架构

android 程序员 移动开发

Kotlin协程它不香吗?,kotlin开发游戏

android 程序员 移动开发

Kotlin的自定义View,实现带弧形的进度条,androidjetpack详解

android 程序员 移动开发

Kotlin系列三:空指针检查,万字长文总结Android多进程

android 程序员 移动开发

offer求比较+部分大厂Android面经+真题解析(1),覆盖所有面试知识点

android 程序员 移动开发

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