1、背景介绍

最近接手了一个项目,项目是使用Python开发的,其中使用到了Etcd,但是项目之前开发的方式,只能够支持单节点连接Etcd,不能够在Etcd节点发生故障时,自动转移。因此需要基于现有etcd sdk 开发一个能够实现故障转移的功能,或者更换etcd sdk来实现故障转移等功能。

先来看看项目之前使用到的 etcd 库,即 python-etcd3,通过给出的示例,没有看到可以连接多节点的方式,深入到源码后,也没有发现可以连接多节点的方式,基本上可以断定之前使用到的 etcd sdk 不支持集群方式了。因为项目中不仅仅是使用到了简单的 get、put、delete 等功能,还用到了 watch、lock等功能,所以最好是找到一个可以替换的 sdk,这样开发周期可以缩短,并且也可以减少工作量。

2、寻找可替换的SDK

网上搜了下,发现用的比较多的几个库都不支持集群方式连接,而且也蛮久没有更新了。比如: etcd3-pypython-etcd3

那重新找一个 etcd 的sdk 吧,然后在 github 上面搜索,最开始按照默认推荐顺序看了好几源代码,都是不支持集群方式连接的。

都有点心灰意冷了,突然想到可以换一下 github 的推荐顺序,换成最近有更新的,然后我换成了 Recently updated搜索,然后从前往后看,在第二页看到了一个库,点击去看了下源代码,发现是通过 grpc 方式调用的 etcd server,点进去看 client.py 文件,看到有一个类是: MultiEndpointEtcd3Client,突然眼前一亮,难道可以,然后更加文档安装了对于的 sdk ,测试发现可以集群连接。

发现可以集群连接后,接下来就是看看项目中用到的其他功能,可以正常使用不,比如: watch、lock 。测试发现都可以正常使用。

接下来就是集成到项目中了,这里就不仔细介绍,大家根据自己实际情况自行调整。

3、etcd-sdk-python 连接集群

官方教程

etcd-sdk-python 连接集群方式比较简单,需要先创建 Endpoint,然后作为参数,传给 MultiEndpointEtcd3Client。

from pyetcd import MultiEndpointEtcd3Client, Endpoint
from pyetcd.exceptions import ConnectionFailedError # time_retry 的意思是,当这个节点连接失败后,多少秒后再次去尝试连接
e1 = Endpoint(host="192.168.91.66", port=12379, secure=False, time_retry=30)
e2 = Endpoint(host="192.168.91.66", port=22379, secure=False, time_retry=30)
e3 = Endpoint(host="192.168.91.66", port=32379, secure=False, time_retry=30) # failover 的意思是,当节点发生故障时,是否进行故障转移,这个参数一定要设置为True,否则当一个节点发生故障时,会报错
c = MultiEndpointEtcd3Client([e1, e2, e3], failover=True) l = c.lease(10) data = {"data": 8000}
c.put("/test_ttl", json.dumps(data).encode("utf-8"), lease=l) time.sleep(5)
b = c.get("/test_ttl")
print(dir(b)) print(dir(b[0]))
print(dir(b[1]))
print(b[1].lease_id)

4、实现一个简约的自动续约的分布式锁

import math
from threading import Thread
import time from pyetcd import MultiEndpointEtcd3Client, Endpoint
from pyetcd.exceptions import ConnectionFailedError e1 = Endpoint(host="192.168.91.66", port=12379, secure=False, time_retry=2)
e2 = Endpoint(host="192.168.91.66", port=22379, secure=False, time_retry=2)
e3 = Endpoint(host="192.168.91.66", port=32379, secure=False, time_retry=2) c = MultiEndpointEtcd3Client([e1, e2, e3], failover=True) class EtcdGlobalMutex(object): def __init__(self, etcd_client, lock_key, ttl=5, acquire_timeout=2):
""" :param etcd_client: 已连接的etcd客户端
:param lock_key: 分布式锁key
:param ttl: key的有效期
:param acquire_timeout: 尝试获取锁的最长等待时间
"""
self.etcd_client = etcd_client
self.lock_key = lock_key
self.ttl = ttl if ttl else 5
self.acquire_timeout = acquire_timeout if acquire_timeout else 2
self.locker = etcd_client.lock(lock_key, ttl) def acquire(self):
self.locker.acquire(timeout=self.acquire_timeout) def refresh_lock(self):
"""
刷新lock,本质上就是更新 key 的ttl
:return:
"""
# 向上取整
seconds = math.ceil(self.ttl / 2)
if seconds == 1 and self.ttl == 1:
seconds = 0.5 while True:
try:
self.locker.refresh()
except ConnectionFailedError as e:
# 测试发现,当etcd集群一个节点故障时,可能会出现这个错误
print(f"refresh_lock. lock_key:{self.lock_key}. ConnectionFailedError, err:{e}") except Exception as e1:
# 非期望错误,退出,防止线程不能退出
print(f"refresh_lock. lock_key:{self.lock_key}. unexpected error. err:{e1}")
return time.sleep(seconds) def try_lock(self):
"""
尝试获取锁,当获取不到锁时,会监听对应的key,当key消失时,会再次尝试获取锁
:return:
"""
try:
self.acquire()
except ConnectionFailedError as e:
print(f"try_lock. lock_key:{self.lock_key}. ConnectionFailedError. err:{e}")
time.sleep(1) self.try_lock() if self.locker.is_acquired():
print(f"try_lock. lock_key:{self.lock_key}. Lock acquired successfully")
# 启动刷新锁的线程
t1 = Thread(target=self.refresh_lock)
t1.start()
else:
print(f"try_lock. lock_key:{self.lock_key}. Failed to acquire lock")
self._watch_key() def _watch_key(self):
"""
监听 key
:return:
"""
# 写入etcd的key
real_key = f"/locks/{self.lock_key}"
cancel = None
try:
print(f"watch_key. lock_key:{self.lock_key}")
# watch 需要捕获异常,这样当一个etcd节点挂掉后,还能够正常 watch
events_iterator, cancel = self.etcd_client.watch(real_key)
for event in events_iterator:
print(f"watch_key. lock_key:{self.lock_key}. event: {event}")
cancel()
break
except ConnectionFailedError as e:
print(f"watch_key. lock_key:{self.lock_key}, ConnectionFailedError err:{e}")
if cancel:
cancel()
time.sleep(1) self.etcd_client._clear_old_stubs() self._watch_key() self.try_lock() def main():
name = 'lock_name'
e = EtcdGlobalMutex(c, name, ttl=10)
e.try_lock() while True:
print("Main thread sleeping")
time.sleep(2) if __name__ == "__main__":
main()

5、watch key 如何实现?

如果只是单纯的实现一个 watch key 功能,没啥好说的,看看官方给的 api 就可以,因为测试的时候,发现如果一个 etcd 节点挂掉,而这个节点有正好是连接的节点,会出现报错,这个时候需要做一些异常捕获处理。

import math
from threading import Thread
import time from pyetcd import MultiEndpointEtcd3Client, Endpoint
from pyetcd.exceptions import ConnectionFailedError
from pyetcd.events import PutEvent e1 = Endpoint(host="192.168.91.66", port=12379, secure=False, time_retry=2)
e2 = Endpoint(host="192.168.91.66", port=22379, secure=False, time_retry=2)
e3 = Endpoint(host="192.168.91.66", port=32379, secure=False, time_retry=2) c = MultiEndpointEtcd3Client([e1, e2, e3], failover=True) look_key = "look_key" def watch(self):
print('MonitorEqp is watching')
cancel = None
try:
events_iterator, cancel = c.watch_prefix(look_key) self.watch_key(events_iterator)
except ConnectionFailedError as e:
# 重点就是这里的异常处理
print(f"MonitorEqp. ConnectionFailedError, err:{e}")
if cancel:
cancel()
time.sleep(1) c._clear_old_stubs()
watch()
except Exception as e1:
# 非期望错误,退出,防止线程不能退出
print(f"MonitorEqp. unexpected error. err:{e1}")
if cancel:
cancel() return def watch_key(self, events_iterator):
print("coming watch_key")
for watch_msg in events_iterator:
print(watch_msg)
if type(watch_msg) != PutEvent:
# 如果不是watch响应的Put信息, 忽略
continue # xxx 处理监听到的信息

通过上面的学习,对 python 连接etcd集群有一个基础的认识。

Python连接Etcd集群基础教程的更多相关文章

  1. 【hadoop】——window下elicpse连接hadoop集群基础超详细版

    1.Hadoop开发环境简介 1.1 Hadoop集群简介 Java版本:jdk-6u31-linux-i586.bin Linux系统:CentOS6.0 Hadoop版本:hadoop-1.0.0 ...

  2. python连接mongodb集群

    一 安装模块pymongo pip3 install pymongo 二 创建一个MongoClient conn=MongoClient('mongodb://cbi:pass@ip1:20000, ...

  3. python操作redis集群

    strictRedis对象方法用于连接redis 指定主机地址,port与服务器连接,默认db是0,redis默认数据库有16个,在配置文件中指定database 16 上代码 .对redis的单实例 ...

  4. 15.9,python操作redis集群

      上代码 .对redis的单实例进行连接操作 python3 >>>import redis >>>r = redis.StrictRedis(host=, db ...

  5. 如何设置一个生产级别的高可用etcd集群

    在之前的文章中,我们详细介绍了K3s的架构以及部署场景,给尚未了解K3s的朋友提供了一个很好的入门方向.那么,在本文中我们将探索如何配置一个3节点的etcd集群,它将会被用于高可用.多节点的K3s集群 ...

  6. 003.etcd集群部署-静态发现

    一 etcd集群概述 1.1 概述 静态启动etcd集群要求每个成员都知道集群中的另一个成员.Etcd运行在集群的每个coreos节点上,可以保证coreos集群的稳定,可靠的运行.当集群网络出现动荡 ...

  7. 安装etcd集群

    kuberntes 系统使用 etcd 存储所有数据,是最重要的组件之一,注意 etcd集群只能有奇数个节点(1,3,5...),本文档使用3个节点做集群. 一.基础环境 软件包 etcd下载地址:h ...

  8. 彻底搞懂 etcd 系列文章(三):etcd 集群运维部署

    0 专辑概述 etcd 是云原生架构中重要的基础组件,由 CNCF 孵化托管.etcd 在微服务和 Kubernates 集群中不仅可以作为服务注册与发现,还可以作为 key-value 存储的中间件 ...

  9. K8s二进制部署单节点 etcd集群,flannel网络配置 ——锥刺股

    K8s 二进制部署单节点 master    --锥刺股 k8s集群搭建: etcd集群 flannel网络插件 搭建master组件 搭建node组件 1.部署etcd集群 2.Flannel 网络 ...

  10. python脚本实现集群检测和管理

    python脚本实现集群检测和管理 场景是这样的:一个生产机房,会有很多的测试机器和生产机器(也就是30台左右吧),由于管理较为混乱导致了哪台机器有人用.哪台机器没人用都不清楚,从而产生了一个想法-- ...

随机推荐

  1. [ML] 可视化编写运行 Python 脚本的工具 Jupyter

    Jupyter 提供了可视化的编写和运行 python 程序的 Web 界面. https://jupyter.org/install 使用只需要两步: $ pip install jupyterla ...

  2. 函数编程:强大的 Stream API

    函数编程:强大的 Stream API 每博一文案 只要有人的地方,世界就不会是冰冷的,我们可以平凡,但绝对不可以平庸. ------ <平凡的世界> 人活着,就得随时准备经受磨难.他已经 ...

  3. vim 使用clang-format 格化C/C++/Java/JavaScript

    vim 使用clang-format 格化C/C++/Java/JavaScript 参考信息 官方参考https://clang.llvm.org/docs/ClangFormat.html 安装 ...

  4. 微分流形Loring Tu 习题21.2解答

    今天的作业,随手写到博客吧. \(Proof.\)对于任意的\(p \in M\),有p附近的坐标卡\((U,x^{1},\ldots,x^{n})\), 由引理\(21.4\),$$dx^{1}\w ...

  5. Android项目自动生成uml图(以小米便签为例)

    摘要 最近在学习软件工程,需要完成小米便签的精读,任务中需要详细类图,由于项目巨大,手动画图打咩,试了一下午的android studio,试了诸如code iris,SequenceDigram(只 ...

  6. vue+js实现点击图片,图片放大

    1.首先在template中插入image,并赋予点击事件(这个时候是小图) <template> <div> <img src="@/assets/image ...

  7. WEB集群 - LNMT集群架构部署zrlog

    目录 1. 集群环境说明 2. NFS部署 3. mysql部署 4. redis部署 5. tomcat部署 6. nginx负载均衡部署 7. 客户端访问 8. tomcat+redis实现会话共 ...

  8. WEB服务与NGINX(12)-NGINX的变量

    目录 1. nginx的变量 1.1 内置变量 1.2 自定义变量 1. nginx的变量 nginx的变量可以在配置文件中引用,作为功能判断或日志等场景使用,变量可以分为内置变量和自定义变量. 内置 ...

  9. nginx中目录浏览配置

    root方式配置:(会自动加目录名) #开放本地目录-root server { listen 81; server_name localhost 127.0.0.1 0.0.0.0; charset ...

  10. kettle使用2-增量插入

    1.新建转换 2.在DB连接中,新建2个数据库连接 3.在输入中,新建:表输入 4.在输入中,新建:表输入 5.在输出中,新建:表输出