【ArchSummit】如何通过AIOps推动可量化的业务价值增长和效率提升?>>> 了解详情
写点什么

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:251195

评论

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

DAPP马蹄链智能合约系统丨DAPP马蹄链智能合约系统开发(开发规则))

系统开发咨询1357O98O718

一文上手图数据备份恢复工具 NebulaGraph BR

NebulaGraph

数据库 容灾备份

微信小程序自动化测试方案实践过程

Openlab_cosmoplat

开源社区 微信小程序测试

如何利用分布式算法理解分布式存储

Dinfan

数据库 分布式 分布式存储 分布式算法

全国首个算力互联互通验证平台发布,天翼云推动算力智能调度再提速

天翼云开发者社区

DTALK直播预约 | 深度解析大资管行业数字化转型

袋鼠云数栈

数字化转型

【知识科普】晶振究竟是如何起振的?

元器件秋姐

科普 晶振 元器件 电子

【附下载】艾瑞《全球互联网通信云研究报告》,融云 IM 连续 8 年展现统治力

融云 RongCloud

互联网 IM 社交 融云 元宇宙

虚拟主机和云服务器的区别

天翼云开发者社区

DAPP众筹互助游戏开发详细丨DAPP众筹互助游戏系统开发(开发逻辑及案例)

系统开发咨询1357O98O718

一图读懂工业数据要素高水平应用

Openlab_cosmoplat

工业数据

十分钟读懂火山引擎DataLeap数据治理实践

字节跳动数据平台

大数据 数据研发 企业号 3 月 PK 榜

一文解码:如何在人工智能热潮下实现产业“智”变

加入高科技仿生人

人工智能 AI 低代码 智能化

3 问 6 步,极狐GitLab 帮助企业构建高效、安全、合规的 DevSecOps 文化

极狐GitLab

DevOps DevSecOps 安全测试 极狐GitLab 安全左移

OpenCloudOS 轻量级虚拟化引擎 LiKeX 介绍

OpenCloudOS

Linux 容器 rust

内存耗尽后Redis干了什么

CTO技术共享

GitLab 解析:为什么市场正在转向一体化 DevSecOps 平台?(附Forrester完整报告下载)

极狐GitLab

DevOps DevSecOps 安全测试 极狐GitLab 安全合规

图片动画化应用中的动作分解方法

百度Geek说

深度学习 算法 计算机视觉 企业号 3 月 PK 榜

云原生+新技术,会碰撞出怎样的火花?

墨天轮

数据库 阿里云 Serverless 云原生 华为云

分享一个 hive on spark 模式下使用 HikariCP 数据库连接池造成的资源泄露问题

明哥的IT随笔

大数据 hive

共铸国云智领未来| “码”上呼唤,马上办!

天翼云开发者社区

一文搞懂面试官常问的:SpringBoot自动配置原理

做梦都在改BUG

Java Spring Boot 自动装配

云计算之-弹性伸缩

天翼云开发者社区

软件测试/测试开发丨基于 Spring Boot 的 RESTful API 设计与实现

测试人

Spring Boot 软件测试 测试发开 RESTful API

MetaForce佛萨奇2.0开发规则丨MetaForce佛萨奇2.0系统开发说明及案例

系统开发咨询1357O98O718

SpringBoot整合ElasticSearch

代码的路

Java elasticsearch

华为阅读全新上线高品质男声,带来身临其境般听书体验

叶落便知秋

用友与百度强强联合,以AI深化冶金行业数智化应用场景

用友BIP

如何召开成功高效的项目会议?

PMO实践

项目管理 PMO

用友BIP接入百度文心一言 持续使能企业数智商业创新

用友BIP

从5分钟到60秒,袋鼠云数栈在热重启技术上的提效探索之路

袋鼠云数栈

热重启

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