Docker 下的Zookeeper以及.ne core 的分布式锁

Stella981
• 阅读 817

单节点

1.拉取镜像:docker pull zookeeper

2.运行容器

a.我的容器同一放在/root/docker下面,然后创建相应的目录和文件,

mkdir zookeeper
cd zookeeper
mkdir data
mkdir datalog
mkdir conf
cd conf
touch zoo.cfg

其中zoo.cfg(这里是默认的主要延时怪哉文件)如下:

tickTime=2000
initLimit=10
syncLimit=5
dataDir=/data
dataLogDir=/datalog
clientPort=2181
maxClientCnxns=60

Docker 下的Zookeeper以及.ne core 的分布式锁

这里也设置了zookeeper默认的环境变量

Docker 下的Zookeeper以及.ne core 的分布式锁

b.运行实例,切换到/root/docker/zookeeper下载执行(不知道为什么这里zoo.cfg一定要用相对路径,用绝对路径提示docker-entrypoint.sh: line 15: /conf/zoo.cfg: Is a directory)

docker run --name zookeeper --restart always -d -v$(pwd)/data:/data  -v$(pwd)/datalog:/datalog -v $(pwd)/conf/zoo.cfg:/conf/zoo.cfg  -p 2181:2181  -p 2888:2888 -p 3888:3888  zookeeperp 2181:2181  -p 2888:2888 -p 3888:3888  zookeeper
# 2181端口号是zookeeper client端口
# 2888端口号是zookeeper服务之间通信的端口
# 3888端口是zookeeper与其他应用程序通信的端口
#用绝对路径 docker run --name zookeeper --restart always -d -v/root/docker/zookeep/data:/data  -v/root/docker/zookeep/datalog:/datalog -v /root/docker/zookeep/conf/zoo.cfg:/conf/zoo.cfg  -p 2181:2181  -p 2888:2888 -p 3888:3888  zookeeper
#提示/docker-entrypoint.sh: line 15: /conf/zoo.cfg: Is a directory
docker run --name zookeeper --restart always -d -v/root/docker/zookeep/data:/data  -v/root/docker/zookeep/datalog:/datalog -v /root/docker/zookeep/conf/:/conf/  -p 2181:2181  -p 2888:2888 -p 3888:388 zookeeper #正确的用法是不指定文件

c.zookeeper常规操作,首先执行以下指令进入zookeeper客服端:

docker exec -it zookeeper zkCli.sh -server 192.168.100.5:2181 #如果是集群server用逗号分割 -server 192.168.100.5:2181,192.168.100.6:2182
 
create /zk "zkval1" #创建zk节点
create /zk/test1 "testval1" #创建zk/test1节点
create /zk/test2 "testval2" #创建zk/test2节点
#create /test/node "node1" 失败,不支持递归创建,多级时,必须一级一级创建
#create /zk/test2/ null 节点不能以 / 结尾,会直接报错
ls -s /zk #查看zk节点信息
set /zk/test1 "{1111}" #修改节点数据
get /zk/test1 #查看节点数据
delete /zk #删除时,须先清空节点下的内容,才能删除节点
delete /zk/test2

Docker 下的Zookeeper以及.ne core 的分布式锁

Docker 下的Zookeeper以及.ne core 的分布式锁

集群搭建

我这里搞了很久,最后还是用官网的配置 创建docker-compose.yml文件如下:

version: '3.1'
 
services:
  zoo1:
    image: zookeeper
    restart: always
    hostname: zoo1
    ports:
      - 2181:2181
    environment:
      ZOO_MY_ID: 1
      ZOO_SERVERS: server.1=0.0.0.0:2888:3888;2181 server.2=zoo2:2888:3888;2181 server.3=zoo3:2888:3888;2181
 
  zoo2:
    image: zookeeper
    restart: always
    hostname: zoo2
    ports:
      - 2182:2181
    environment:
      ZOO_MY_ID: 2
      ZOO_SERVERS: server.1=zoo1:2888:3888;2181 server.2=0.0.0.0:2888:3888;2181 server.3=zoo3:2888:3888;2181
 
  zoo3:
    image: zookeeper
    restart: always
    hostname: zoo3
    ports:
      - 2183:2181
    environment:
      ZOO_MY_ID: 3
      ZOO_SERVERS: server.1=zoo1:2888:3888;2181 server.2=zoo2:2888:3888;2181 server.3=0.0.0.0:2888:3888;2181

最后运行docker-compose up指令,最后验证,

docker exec -it zookeeper_zoo1_1 zkCli.sh -server 192.168.100.5:2181
create /zk "test"
quit #退出容器1
 
docker exec -it zookeeper_zoo2_1 zkCli.sh -server 192.168.100.5:2182
get /zk #在容器2获取值
quit
 
docker exec -it zookeeper_zoo3_1 zkCli.sh -server 192.168.100.5:2183
get /zk #在容器3获取值
quit

分布式锁

ZooKeeper 分布式锁是基于 临时顺序节点 来实现的,锁可理解为 ZooKeeper 上的一个节点,当需要获取锁时,就在这个锁节点下创建一个临时顺序节点。当存在多个客户端同时来获取锁,就按顺序依次创建多个临时顺序节点,但只有排列序号是第一的那个节点能获取锁成功,其他节点则按顺序分别监听前一个节点的变化,当被监听者释放锁时,监听者就可以马上获得锁。而且用临时顺序节点的另外一个用意是如果某个客户端创建临时顺序节点后,自己意外宕机了也没关系,ZooKeeper 感知到某个客户端宕机后会自动删除对应的临时顺序节点,相当于自动释放锁。

Docker 下的Zookeeper以及.ne core 的分布式锁

如上图:ClientA 和 ClientB 同时想获取锁,所以都在 locks 节点下创建了一个临时节点 1 和 2,而 1 是当前 locks 节点下排列序号第一的节点,所以 ClientA 获取锁成功,而 ClientB 处于等待状态,这时 ZooKeeper 中的 2 节点会监听 1 节点,当 1节点锁释放(节点被删除)时,2 就变成了 locks 节点下排列序号第一的节点,这样 ClientB 就获取锁成功了。如下是c#代码:

创建 .NET Core 控制台程序

Nuget 安装 ZooKeeperNetEx.Recipes

创建 ZooKeeper Client, ZooKeeprLock代码如下:

namespace ZookeeperDemo
{
    using org.apache.zookeeper;
    using org.apache.zookeeper.recipes.@lock;
    using System;
    using System.Diagnostics;
    using System.Threading.Tasks;
    public class ZooKeeprLock
    {
        private const int CONNECTION_TIMEOUT = 50000;
        private const string CONNECTION_STRING = "192.168.100.5:2181,192.168.100.5:2182,192.168.100.5:2183";
 
        /// <summary>
        /// 加锁
        /// </summary>
        /// <param name="key">加锁的节点名</param>
        /// <param name="lockAcquiredAction">加锁成功后需要执行的逻辑</param>
        /// <param name="lockReleasedAction">锁释放后需要执行的逻辑,可为空</param>
        /// <returns></returns>
        public async Task Lock(string key, Action lockAcquiredAction, Action lockReleasedAction = null)
        {
            // 获取 ZooKeeper Client
            ZooKeeper keeper = CreateClient();
            // 指定锁节点
            WriteLock writeLock = new WriteLock(keeper, $"/{key}", null);
 
            var lockCallback = new LockCallback(() =>
            {
                lockAcquiredAction.Invoke();
                writeLock.unlock();
            }, lockReleasedAction);
            // 绑定锁获取和释放的监听对象
            writeLock.setLockListener(lockCallback);
            // 获取锁(获取失败时会监听上一个临时节点)
            await writeLock.Lock();
        }
 
        private ZooKeeper CreateClient()
        {
            var zooKeeper = new ZooKeeper(CONNECTION_STRING, CONNECTION_TIMEOUT, NullWatcher.Instance);
            Stopwatch sw = new Stopwatch();
            sw.Start();
            while (sw.ElapsedMilliseconds < CONNECTION_TIMEOUT)
            {
                var state = zooKeeper.getState();
                if (state == ZooKeeper.States.CONNECTED || state == ZooKeeper.States.CONNECTING)
                {
                    break;
                }
            }
            sw.Stop();
            return zooKeeper;
        }
 
        class NullWatcher : Watcher
        {
            public static readonly NullWatcher Instance = new NullWatcher();
            private NullWatcher() { }
            public override Task process(WatchedEvent @event)
            {
                return Task.CompletedTask;
            }
        }
 
        class LockCallback : LockListener
        {
            private readonly Action _lockAcquiredAction;
            private readonly Action _lockReleasedAction;
 
            public LockCallback(Action lockAcquiredAction, Action lockReleasedAction)
            {
                _lockAcquiredAction = lockAcquiredAction;
                _lockReleasedAction = lockReleasedAction;
            }
 
            /// <summary>
            /// 获取锁成功回调
            /// </summary>
            /// <returns></returns>
            public Task lockAcquired()
            {
                _lockAcquiredAction?.Invoke();
                return Task.FromResult(0);
            }
 
            /// <summary>
            /// 释放锁成功回调
            /// </summary>
            /// <returns></returns>
            public Task lockReleased()
            {
                _lockReleasedAction?.Invoke();
                return Task.FromResult(0);
            }
        }
 
    }
}

测试代码:

namespace ZookeeperDemo
{
    using System;
    using System.Threading;
    using System.Threading.Tasks;
    class Program
    {
        static void Main(string[] args)
        {       
            Parallel.For(1, 10, async (i) =>
            {
                await new ZooKeeprLock().Lock("locks", () =>
                {
                    Console.WriteLine($"第{i}个请求,获取锁成功:{DateTime.Now},线程Id:{Thread.CurrentThread.ManagedThreadId}");
                    Thread.Sleep(1000); // 业务逻辑...
                }, () =>
                {
                    Console.WriteLine($"第{i}个请求,释放锁成功:{DateTime.Now},线程Id:{Thread.CurrentThread.ManagedThreadId}");
                    Console.WriteLine("-------------------------------");
                });
            });
            Console.ReadKey();
        }
    }
}

运行结果:

Docker 下的Zookeeper以及.ne core 的分布式锁

关于分布式锁, 我们也可以采用数据库和redis来实现, 各有优缺点。

参考:

ZooKeeper 实现分布式锁

七张图彻底讲清楚ZooKeeper分布式锁的实现原理

面试请不要再问我Redis分布式锁的实现原理

Docker下Zookeeper集群搭建

zookeeper Docker Official

zookeeper客户端命令详解

How To Install and Configure an Apache ZooKeeper Cluster on Ubuntu 18.04

docker zookeeper 集群搭建

CSharpKit 微服务工具包

Redis实现分布式锁

分布式锁的几种使用方式(redis、zookeeper、数据库)

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Stella981 Stella981
3年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
10个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这