【大数据】Zookeeper 数据写入与分布式锁

news2024/12/24 11:39:22

Zookeeper 数据写入与分布式锁

  • 1.数据是怎么写入的
  • 2.基于 Zookeeper 实现分布式锁

1.数据是怎么写入的

无论是 Zookeeper 自带的客户端 zkCli.sh,还是使用 Python(或者其它语言)实现的客户端,本质上都是连接至集群,然后往里面读写数据。那么问题来了,集群在收到来自客户端的写请求时,是怎么写入数据的呢?

另外客户端在访问集群的时候,本质上是访问集群内的某一个节点,而根据访问的节点是领导者还是追随者,写入数据的过程也会有所不同。

先来看看当 访问的节点是领导者 的情况:

在这里插入图片描述
这里面有一个关键的地方,就是 Leader 不会等到所有的 Follower 都写完,只要有一半的 Follower 写完,就会告知客户端。还是半数机制,一半的 Follower 加上 Leader 正好刚过半数。而这么做的原因也很简单,就是为了快速响应。

再来看另一种情况,如果客户端 访问的节点是追随者,情况会怎么样呢?其实很简单,由于追随者没有写权限,那么会先将写请求转发给领导者,然后接下来的步骤和上面类似,只是最后一步不同。

当 Leader 发现有半数的 Follower 写完,就认为写数据成功,于是返回 ack。但这个 ack 不会返回给客户端,因为客户端访问的不是领导者,最终领导者会将 ack 返回给客户端访问的追随者,再由这个追随者将 ack 返回给客户端,告知写请求已执行完毕。

2.基于 Zookeeper 实现分布式锁

关于分布式锁,我之前介绍过如何基于 Redis 实现分布式锁,里面对分布式锁做了比较详细的解析。下面来聊一聊如何基于 Zookeeper 实现分布式锁。

先来说一下原理,当客户端需要操作共享资源时,需要先往 Zookeeper 集群中创建一个临时顺序节点。然后查看对应的编号,如果没有比它小的,说明最先创建,我们就认为客户端拿到了分布式锁。

如果客户端发现节点的编号不是最小的,说明已经有人先创建了,也就是锁已经被别的客户端拿走了。那么该客户端会对前一个节点进行监听,等待释放。

在这里插入图片描述

所以从概念上还是很好理解的,然后我们来编程实现一下。

from typing import List
import queue
from kazoo.client import KazooClient

class DistributedLock:

    def __init__(self, hosts: List[str]):
        """
        :param hosts: 'ip1:port1,...'
        """
        self.client = KazooClient(",".join(hosts))
        self.client.start()
        # 要在 /lock 节点下面创建临时顺序节点
        # 所以先保证 /lock 节点存在
        if not self.client.exists("/lock"):
            self.client.create("/lock")

        # 要创建的临时顺序节点
        self.cur_node = None
        # 要监听的节点(也就是上一个节点)
        self.prev_node = None
        # 本地队列
        self.q = queue.Queue()

    def acquire(self):
        """
        获取锁
        :return:
        """
        self.cur_node = self.client.create(
            "/lock/seq-",
            # 临时顺序节点
            ephemeral=True,
            sequence=True
        )
        # create 方法会返回创建的节点名称
        # 需要判断编号是不是最小的
        # 因此要拿到所有的节点
        nodes = self.client.get_children("/lock")
        # nodes: ["seq-000..0", "seq-000...1"]
        nodes.sort()
        if len(nodes) == 1:
            return True
        elif "/lock/" + nodes[0] == self.cur_node:
            # 如果 nodes 里面的最小值和 node 相等
            # 说明该客户端创建的节点的编号最小
            # 于是我们就认为它拿到了分布式锁
            return True
        # 否则说明不是最小,因此要找到它的上一个节点
        # 也就是要监听的节点
        index = nodes.index(self.cur_node.split("/")[-1])
        self.prev_node = "/lock/" + nodes[index - 1]
        # 对上一个节点进行监听
        self.client.get(self.prev_node, watch=self.watch)
        # 这一步不是阻塞的,但程序必须要拿到锁之后才可以执行
        # 所以我们要显式地让程序阻塞在这里
        self.q.get()
        return True

    def release(self):
        """
        释放锁
        :return:
        """
        self.client.delete(self.cur_node)

    def watch(self, event):
        """
        监听函数,参数 event 是一个 namedtuple
        kazoo.protocol.states.WatchedEvent
        里面有三个字段:type、state、path

        监听节点的值被改变时,type 为 "CHANGED"
        监听节点被删除时,type 为 "DELETED"

        path 就是监听的节点本身

        state 表示客户端和服务端之间的连接状态
        建立连接时,状态为 LOST
        连接建立成功,状态为 CONNECTED
        如果在整个会话的生命周期里,伴随着网络闪断、服务端异常
        或者其他什么原因导致客户端和服务端连接断开,状态为 SUSPENDED
        与此同时,KazooClient 会不断尝试与服务端建立连接,直至超时
        如果连接建立成功了,那么状态会再次切换到 CONNECTED
        """
        if event.type == "DELETED" and \
            self.prev_node == event.path:
            # 往队列里面扔一个元素
            # 让下一个节点解除阻塞
            self.q.put(None)

# 测试函数
def test(lock, name):
    lock.acquire()
    print(f"{name}获得锁,其它人等着吧")
    print(f"{name}处理业务······")
    print(f"{name}处理完毕,释放锁")
    lock.release()

if __name__ == '__main__':
    import threading
    hosts = [
        "82.157.146.194:2181",  
        "121.37.165.252:2181",  
        "123.60.7.226:2181",    
    ]
    # 创建三把锁
    lock1 = DistributedLock(hosts)
    lock2 = DistributedLock(hosts)
    lock3 = DistributedLock(hosts)
    threading.Thread(
        target=test, args=(lock1, "客户端1")
    ).start()
    threading.Thread(
        target=test, args=(lock2, "客户端2")
    ).start()
    threading.Thread(
        target=test, args=(lock3, "客户端3")
    ).start()

"""
客户端1获得锁,其它人等着吧
客户端1处理业务······
客户端1处理完毕,释放锁
客户端3获得锁,其它人等着吧
客户端3处理业务······
客户端3处理完毕,释放锁
客户端2获得锁,其它人等着吧
客户端2处理业务······
客户端2处理完毕,释放锁
"""

实现起来不是很难,并且使用 Zookeeper 的好处就是,我们不需要担心死锁的问题。因为客户端宕掉之后,临时节点会自动删除,但缺点是性能没有 Redis 高。

另外值得一提的是,kazoo 已经帮我们实现好了分布式锁,开箱即用,我们就不需要再手动实现了。

# 创建客户端
client = KazooClient(",".join(hosts))
client.start()
# 此时需要自己手动给一个唯一标识
lock = client.Lock("/lock", "unique-identifier")
# 获取锁
lock.acquire()
# 处理业务逻辑
...
# 释放锁
lock.release()
# 或者也可以使用上下文管理器
with lock:
    ...

显然就优雅多了,借助于 kazoo 实现好的分布式锁,可以减轻我们的心智负担。此外 kazoo 还提供了 读锁写锁

  • client.ReadLock
  • client.WriteLock

我们一般使用 client.Lock 就行,可以自己测试一下。


关于 Zookeeper 的基础内容就介绍到这里,但伴随着 Zookeeper 还有一系列的协议,比如 Paxos 协议ZAB 协议CAP 定理 等等,这些可谓是分布式系统的重中之重。我们后续来逐一介绍。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1362701.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux 上 Nginx 配置访问 web 服务器及配置 https 访问配置过程记录

目录 一、前言说明二、配置思路三、开始修改配置四、结尾 一、前言说明 最近自己搭建了个 Blog 网站,想把网站部署到服务器上面,本文记录一下搭建过程中 Nginx 配置请求转发的过程。 二、配置思路 web项目已经在服务器上面运行起来了,运行的端…

Qt pro文件

1. 项目通常结构 2.pri文件 pri文件可定义通用的宏,例如创建一个COMMON.pri文件内容为 COMMON_PATH D:\MyData 然后其它pri或者pro文件如APPTemplate.pro文件中通过添加include(Common.pri) ,QtCreator就会自动在项目结构树里面创建对应的节点 3.变量…

gitlab 配置 二

一 环境说明 群晖Nas DS418DELL XPS serverGitlab ce 二 需要实现的功能 外网可以访问,gitlab使用https的方式访问。wiki issue 等都可以上传图片和附件。 三 操作步骤 因为群晖上有证书,并且由群晖做转发功能。因此证书上,采用群晖的证书…

Ant Design 使用出现 Error_ Can‘t resolve ‘_antd_dist_antd.css‘

推荐阅读 智能化校园:深入探讨云端管理系统设计与实现(一) 智能化校园:深入探讨云端管理系统设计与实现(二) 文章目录 推荐阅读问题描述问题解决方法一:进行版本回退,安装指定版本方…

nodejs01

nodejs作用 Node.js 是一个免费的、开源的、跨平台的 JavaScript 运行时环境,允许开发人员在浏览器之外编写命令行工具和服务器端脚本. 是javascript的一个运行环境,,, nodejs stream 是前端工程化的基础 nodejs可以作为中间层&…

基于FFmpeg的短视频编辑工具Cut

前言 最近在学习FFmpeg和音视频的相关知识,为了加强对FFmpeg的认识和了解,于是撸了一个短视频编辑软件Cut。 效果图先行: 技术点 启动页优化 但启动app的时候会有一个短暂的黑屏或者白屏。为什么呢? 是因为在App启动时&#x…

497 蓝桥杯 成绩分析 简单

497 蓝桥杯 成绩分析 简单 //C风格解法1&#xff0c;*max_element&#xff08;&#xff09;与*min_element&#xff08;&#xff09;求最值 //时间复杂度O(n)&#xff0c;通过率100% #include <bits/stdc.h> using namespace std;using ll long long; const int N 1e4 …

【Spring Cloud】组件概念详解

&#x1f389;&#x1f389;欢迎来到我的CSDN主页&#xff01;&#x1f389;&#x1f389; &#x1f3c5;我是Java方文山&#xff0c;一个在CSDN分享笔记的博主。&#x1f4da;&#x1f4da; &#x1f31f;推荐给大家我的专栏《Spring Cloud》。&#x1f3af;&#x1f3af; &am…

CentOS中开启mysql挂载

挂载的作用其实说白了就是备份。防止数据库文件损害或者数据库被误删导致数据丢失。 创建一个文件名为my.cnf内容如下 # Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. # # This program is free software; you can redistribute it and/or modif…

清理windows中git凭证

清理windows中git凭证 控制面板——>用户账户——>凭据管理器——>管理Windows凭据 点开后如下&#xff1a;

面试之线程状态

1.线程有哪些状态 1.1Java线程的六种状态 Java 线程六种状态 新建 当一个线程对象被创建&#xff0c;但还未调用 start 方法时处于新建状态 此时未与操作系统底层线程关联 可运行 调用了 start 方法&#xff0c;就会由新建进入可运行 此时与底层线程关联&#xff0c;由操作…

Hadolint:Lint Dockerfile 的完整指南

想学习如何使用 Hadolint 对 Dockerfile 进行 lint 处理吗&#xff1f;这篇博文将向您展示如何操作。这是关于 Dockerfile linting 的完整指南。 通过对 Dockerfile 进行 lint 检查&#xff0c;您可以及早发现错误和问题&#xff0c;并确保它们遵循最佳实践。 什么是Hadolint…

windows 查看所有端口占用情况

winR&#xff0c;调出cmd窗口&#xff1a; 输入命令 netstat -ano 内容太多&#xff0c;显示不全&#xff0c;怎么办? 输入下面命令 netstat -ano > d:\1.log 在d盘根目录下就产生了 输出文件 打开可以看到如下内容 活动连接协议 本地地址 外部地址 状…

2.3_7 生产者-消费者问题

2.3_7 生产者-消费者问题 系统中有一组生产者进程和一组消费者进程,生产者进程每次生产一个产品放入缓冲区,消费者进程每次从缓冲区中取出一个产品并使用。(注:这里的“产品”理解为某种数据) 生产者、消费者共享一个初始为空、大小为n的缓冲区。 只有缓冲区没满时,生产者才…

Kotlin协程学习之-01

由于协程需要支持挂起、恢复、因此对于挂起点的状态保存就显得机器关键。类似的&#xff0c;线程会因为CPU调度权的切换而被中断&#xff0c;它的中断状态会保存在调用栈当中&#xff0c;因而协程的实现也按照是否开辟相应的调用栈存在以下两种类型&#xff1a; 有栈协程&…

weak_ptr如何能做到解决循环引用又能传递参数呢?

引子&#xff1a;今天在看CLR via C#的时候看到C#的垃圾回收算法--引用跟踪算法的时候想到以下几个问题。 一、引用计数法存在的问题 一般引用计数法存在的问题就是不好处理循环引用的问题&#xff0c;但是C不是有weak_ptr吗&#xff1f; 这个引用跟踪的垃圾回收算法看起来还…

vivado xsim 终端 模拟

只模拟的话直接终端运行会快很多 计数器举例 mkdir srccounter.v module counter(input wire clk,input wire rst_n,output reg[31:0] cnt ); always (posedge clk or negedge rst_n)if(!rst_n)cnt < 31h0;elsecnt < cnt1;endmodule tb.v module tb; wire[31:0] out…

C语言编译器(C语言编程软件)完全攻略

介绍常用C语言编译器的安装、配置和使用。 常用的C语言编译器&#xff08;编程软件&#xff09;介绍&#xff0c;同时附带下载地址、详细的安装教程和使用教程。我们还对比了不同C语言编译器&#xff08;C语言编程软件&#xff09;的优缺点&#xff0c;让初学者知道该如何选择…

WPF 使用矢量字体图标

矢量字体图标 在WPF项目中经常需要显示图标&#xff0c;但是项目改动后&#xff0c;有时候需要替换和修改图标&#xff0c;这样非常麻烦且消耗开发和美工的时间。为了快速开发项目&#xff0c;节省项目时间&#xff0c;使用图标矢量字体图标是一个非常不错的选择。 矢量字体图标…

Java Swing手搓童年坦克大战游戏(I)

前言 业余偶尔对游戏有些兴趣&#xff0c;不过这样的时代&#xff0c;硬件软件飞速进步&#xff0c;2D游戏画面都无比精美&#xff0c;之前的8bit像素游戏时代早就过去了&#xff0c;不过那时候有许多让人印象深刻的游戏比如魂斗罗、超级玛丽、坦克大战(Battle City)等等。 学…