设计如下:
构成组件:
watcher
(长连接,监听):
1.监听业务操作etcd产生的事件,打毫秒戳来记录,写入central queue(保证对象的历史事件不丢失)
2.写入central queue后,记录此次监听的mod revision
scheduler
(短连接,定时列举):
1.从队列读出事件,分发给alive handler
2.将dead handler未消费的事件写回central queue
3.如果对象已绑定binding handler,则分发该对象的后续事件给binding handler
4.新handler成员等待,直到分配新对象(未绑定handler的对象)的事件
handler
(短连接,定时列举):
1.基于统一调度,handler成员只读取分配给自己的事件(handler 成员增减时,避免对central queue对象事件的竞争)
2.对同一对象根据事件类型合并,处理完成后删除事件
部署状态:
单实例
(多进程,多机器):watcher&scheduler(running实例工作,waiting实例等待)
多实例
(多进程,多机器):handler(多个alive实例同时工作,dead实例事件会回收)
目录结构:
实现代码:
client.py
import time
from concurrent import futures
from threading import Thread
import etcd3
from etcd3 import events
class Etcd:
def __init__(self,
host="127.0.0.1",
port=2379,
timeout=20,
max_txn_ops=2**14,
max_message_length=512 * 1024 * 1024):
self.client = etcd3.client(host=host,
port=port,
timeout=timeout,
grpc_options=[('grpc.max_send_message_length', max_message_length),
('grpc.max_receive_message_length', max_message_length)])
self.max_txn_ops = max_txn_ops
def wait_lock(self, name: str, ttl=10) -> etcd3.Lock:
"""try register global lock, then refresh it until process dead"""
lock = self.client.lock(name=name, ttl=ttl)
def wait_acquire():
lock.acquire(timeout=None)
def refresh():
while True:
if not lock.is_acquired():
wait_acquire()
continue
lock.refresh()
time.sleep(ttl // 2)
wait_acquire()
refresher = Thread(target=refresh)
refresher.start()
return lock
def watch_prefix(self, key_prefix=None, start_revision=1, prev_kv=True):
events_iterator, _ = self.client.watch_prefix(key_prefix, start_revision=start_revision, prev_kv=prev_kv)
for ev in events_iterator:
key = ev.key.decode()
value = ev.value.decode()
revision = ev.mod_revision
event_type = "create" if isinstance(ev, events.PutEvent) else "delete"
yield key, value, revision, event_type
def read_key(self, key, meta=False):
value, kv_meta_data = self.client.get(key)
if value is not None:
value = value.decode()
if meta is False:
return value
return value, kv_meta_data
def read_keys(self, key_prefix):
keys = list()
for _, meta in self.client.get_prefix(key_prefix):
keys.append(meta.key.decode())
return keys
def read_items(self, key_prefix, order_func=None):
items = list()
for value, meta in self.client.get_prefix(key_prefix):
items.append((meta.key.deco