tooz整体设计架构分析
引言:
tooz本身聚焦了python项目中的两个痛点,一个是群组管理(也就是分布式集群的概念),另一个是分布式锁。
其中群组管理,主要关注了两个方面,一个是一致性哈希(可用于负载均衡),另一个是leader选举。
tooz中有许多driver做为后端来实现,例如redis,mariadb,zookeeper等。通过抽象类或者父类来定义公共的接口,
而具体的Driver做为子类,例如redis,mariadb,zookeeper等具体实现这些接口,从而实现了群组管理,分布式锁等功能。
1 tooz类信息
1.1 RedisDriver类
RedisDriver(coordination.CoordinationDriverCachedRunWatchers, coordination.CoordinationDriverWithExecutor)
作用:
构造redis客户端,然后生成redis的lua模板,包含create_group,delete_group,update_capabilities
为每个脚本调用register_script生成可执行的脚本对象,最后记录到脚本字典中;
为当前组成员调用redis的PSETEX name ttl_ms value创建一个默认30秒的过期的键值对。
设置某个组成员的心跳id,默认30秒,会向redis调用PSETEX name ttl_ms value
的形式给该组成员设置一个键为: <beat_prefix>:<member_id>的字符串,返回超时时间,默认30秒。
获取'create_group'对应的lua脚本,然后使用线程池执行器futurist._futures.ThreadPoolExecutor去执行_creage_group方法
_create_group方法中则创建group实际是调用lua脚本去做的,具体是:
如果待创建group名称已经在redis中存在,则直接返回;
否则则调用redis的sadd方法将当前待创建group名称加入到group集合中,
然后调用hset方法将键值对<"__created__", "1">写入到group名称的hash表中。
成员变量:
_scripts: 一个字典,包含redis的lua脚本名称到脚本内容redis.client.Script的键值对
形如: {'delete_group': <redis.client.Script object at 0x7ff8c5dbde50>, 'create_group': <redis.client.Script object at 0x7ff8c5df7690>, 'update_capabilities': <redis.client.Script object at 0x7ff8c5dbded0>}
_member_id: 组成员id
...
成员方法:
1 _make_client(cls, parsed_url, options, default_socket_timeout):
根据解析后的url,选项列表和超时时间初始化redis.StrictRedis客户端
传入的参数实际是:
{'socket_timeout': 30, 'host': 'localhost', 'port': 6379}
2 _start(self):
作用: 构造redis客户端,然后生成redis的lua模板,包含create_group,delete_group,update_capabilities
为每个脚本调用register_script生成可执行的脚本对象,最后记录到脚本字典中;
为当前组成员调用redis的PSETEX name ttl_ms value创建一个默认30秒的过期的键值对。
具体过程:
步骤1:
调用_make_client(cls, parsed_url, options, default_socket_timeout):
根据解析后的url,选项列表和超时时间初始化redis.StrictRedis客户端
传入的参数实际是:
{'socket_timeout': 30, 'host': 'localhost', 'port': 6379}
步骤2:
用参数字典: {'group_existence_key': u'__created__', 'group_existence_value': u'1'}
去填充redis的lua模板,然后对每个填充后的lua脚本,执行:
StrictRedis.register_script方法注册得到一个可执行的脚本对象
3个脚本内容分别如下:
delete_group脚本对应内容如下:
-- Extract *all* the variables (so we can easily know what they are)...
local namespaced_group_key = KEYS[1]
local all_groups_key = KEYS[2]
local no_namespaced_group_key = ARGV[1]
if redis.call("exists", namespaced_group_key) == 0 then
return -1
end
if redis.call("sismember", all_groups_key, no_namespaced_group_key) == 0 then
return -2
end
if redis.call("hlen", namespaced_group_key) > 1 then
return -3
end
-- First remove from the set (then delet