本文目录导读:

在Python中实现分布式同步节点分配,主要有以下几种常见方案:
基于Redis的分布式锁
安装依赖
pip install redis
实现代码
import redis
import time
import uuid
from typing import List, Dict
class DistributedNodeAllocator:
def __init__(self, redis_host='localhost', redis_port=6379):
self.redis_client = redis.Redis(
host=redis_host,
port=redis_port,
decode_responses=True
)
self.node_id = str(uuid.uuid4())
def register_node(self, node_name: str, ttl: int = 60):
"""注册节点并获取锁"""
lock_key = f"node_lock:{node_name}"
lock_value = self.node_id
# 尝试获取锁
acquired = self.redis_client.setnx(lock_key, lock_value)
if acquired:
# 设置过期时间
self.redis_client.expire(lock_key, ttl)
print(f"节点 {node_name} 已分配成功(ID: {self.node_id})")
return True
# 检查是否自己的锁
current_owner = self.redis_client.get(lock_key)
if current_owner == self.node_id:
self.redis_client.expire(lock_key, ttl) # 续期
return True
return False
def release_node(self, node_name: str):
"""释放节点"""
lock_key = f"node_lock:{node_name}"
# 使用Lua脚本确保原子性
lua_script = """
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
else
return 0
end
"""
self.redis_client.eval(lua_script, 1, lock_key, self.node_id)
# 使用示例
allocator = DistributedNodeAllocator()
if allocator.register_node("worker_1"):
# 执行任务
time.sleep(30)
allocator.release_node("worker_1")
基于ZooKeeper的分布式协调
安装依赖
pip install kazoo
实现代码
from kazoo.client import KazooClient
from kazoo.recipe.lock import Lock
import json
class ZKNodeAllocator:
def __init__(self, zk_hosts='localhost:2181'):
self.zk = KazooClient(hosts=zk_hosts)
self.zk.start()
def create_node_group(self, group_name: str, max_nodes: int = 5):
"""创建节点组"""
base_path = f"/node_groups/{group_name}"
# 确保路径存在
self.zk.ensure_path(base_path)
# 创建最大节点数控制
self.zk.ensure_path(f"{base_path}/max_nodes")
self.zk.set(f"{base_path}/max_nodes", str(max_nodes).encode())
def allocate_node(self, group_name: str, node_id: str):
"""分配节点"""
base_path = f"/node_groups/{group_name}"
# 获取当前节点数
nodes = self.zk.get_children(base_path)
max_nodes = int(self.zk.get(f"{base_path}/max_nodes")[0])
if len(nodes) >= max_nodes:
return False
# 创建临时节点
node_path = f"{base_path}/{node_id}"
try:
self.zk.create(
node_path,
value=json.dumps({"status": "active", "timestamp": time.time()}).encode(),
ephemeral=True,
sequence=False
)
return True
except:
return False
def get_active_nodes(self, group_name: str):
"""获取当前活动节点"""
base_path = f"/node_groups/{group_name}"
nodes = self.zk.get_children(base_path)
return [node for node in nodes if node != "max_nodes"]
# 使用示例
allocator = ZKNodeAllocator()
allocator.create_node_group("workers", max_nodes=10)
if allocator.allocate_node("workers", "worker_001"):
print("节点分配成功")
基于gRPC的节点通信
定义protobuf文件 (node_allocator.proto)
syntax = "proto3";
service NodeAllocator {
rpc RegisterNode (NodeRequest) returns (NodeResponse);
rpc Heartbeat(HeartbeatRequest) returns (HeartbeatResponse);
rpc ReleaseNode (ReleaseRequest) returns (ReleaseResponse);
}
message NodeRequest {
string node_id = 1;
string node_type = 2;
int32 port = 3;
}
message NodeResponse {
bool success = 1;
string message = 2;
string assigned_task = 3;
}
message HeartbeatRequest {
string node_id = 1;
int64 timestamp = 2;
}
message HeartbeatResponse {
bool active = 1;
}
message ReleaseRequest {
string node_id = 1;
}
message ReleaseResponse {
bool success = 1;
}
服务器端实现
import grpc
from concurrent import futures
import node_allocator_pb2
import node_allocator_pb2_grpc
import threading
class NodeAllocatorService(node_allocator_pb2_grpc.NodeAllocatorServicer):
def __init__(self):
self.nodes = {}
self.lock = threading.Lock()
self.heartbeat_timeout = 30 # 秒
def RegisterNode(self, request, context):
with self.lock:
node_id = request.node_id
if node_id in self.nodes:
return node_allocator_pb2.NodeResponse(
success=False,
message="Node already registered"
)
self.nodes[node_id] = {
'type': request.node_type,
'port': request.port,
'last_heartbeat': time.time()
}
return node_allocator_pb2.NodeResponse(
success=True,
message="Node registered successfully",
assigned_task="task_001"
)
def Heartbeat(self, request, context):
node_id = request.node_id
if node_id in self.nodes:
self.nodes[node_id]['last_heartbeat'] = time.time()
return node_allocator_pb2.HeartbeatResponse(active=True)
return node_allocator_pb2.HeartbeatResponse(active=False)
def check_stale_nodes(self):
"""检查并清理过期节点"""
while True:
time.sleep(10)
current_time = time.time()
with self.lock:
stale_nodes = [
node_id for node_id, info in self.nodes.items()
if current_time - info['last_heartbeat'] > self.heartbeat_timeout
]
for node_id in stale_nodes:
del self.nodes[node_id]
print(f"Node {node_id} removed (timeout)")
# 启动服务器
def serve():
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
service = NodeAllocatorService()
node_allocator_pb2_grpc.add_NodeAllocatorServicer_to_server(service, server)
server.add_insecure_port('[::]:50051')
server.start()
# 启动心跳检查线程
threading.Thread(target=service.check_stale_nodes, daemon=True).start()
print("Server started on port 50051")
server.wait_for_termination()
if __name__ == '__main__':
serve()
客户端实现
import grpc
import node_allocator_pb2
import node_allocator_pb2_grpc
import time
import uuid
class NodeClient:
def __init__(self, server_address='localhost:50051'):
self.channel = grpc.insecure_channel(server_address)
self.stub = node_allocator_pb2_grpc.NodeAllocatorStub(self.channel)
self.node_id = str(uuid.uuid4())
def register(self, node_type='worker', port=8080):
request = node_allocator_pb2.NodeRequest(
node_id=self.node_id,
node_type=node_type,
port=port
)
try:
response = self.stub.RegisterNode(request)
return response.success, response.message, response.assigned_task
except grpc.RpcError as e:
return False, str(e), None
def send_heartbeat(self):
request = node_allocator_pb2.HeartbeatRequest(
node_id=self.node_id,
timestamp=int(time.time())
)
try:
response = self.stub.Heartbeat(request)
return response.active
except:
return False
def run(self):
# 注册节点
success, message, task = self.register()
if success:
print(f"Node {self.node_id} registered, assigned task: {task}")
# 定期发送心跳
while True:
time.sleep(10)
if not self.send_heartbeat():
print("Heartbeat failed, trying to reconnect...")
self.register()
# 使用客户端
client = NodeClient()
client.run()
基于消息队列的方案 (RabbitMQ)
安装依赖
pip install pika
实现代码
import pika
import json
import uuid
import threading
class RabbitMQNodeAllocator:
def __init__(self, host='localhost'):
self.connection = pika.BlockingConnection(
pika.ConnectionParameters(host)
)
self.channel = self.connection.channel()
self.node_id = str(uuid.uuid4())
# 声明交换机和队列
self.channel.exchange_declare(
exchange='node_allocator',
exchange_type='direct'
)
# 每个节点监听自己的专用队列
result = self.channel.queue_declare(queue=self.node_id, exclusive=True)
self.queue_name = result.method.queue
self.channel.queue_bind(
exchange='node_allocator',
queue=self.queue_name,
routing_key=self.node_id
)
def request_node_assignment(self, task_type: str):
"""请求节点分配"""
request = {
'action': 'request_assignment',
'node_id': self.node_id,
'task_type': task_type,
'callback_queue': self.queue_name
}
# 发送请求到分配器
self.channel.basic_publish(
exchange='',
routing_key='allocator_queue',
body=json.dumps(request)
)
# 等待响应
def callback(ch, method, properties, body):
response = json.loads(body)
if response['node_id'] == self.node_id:
print(f"Node assigned: {response['assigned_node']}")
ch.stop_consuming()
self.channel.basic_consume(
queue=self.queue_name,
on_message_callback=callback,
auto_ack=True
)
self.channel.start_consuming()
最佳实践建议
-
选择合适的方案:
- 小规模系统:Redis方案足够
- 需要强一致性:ZooKeeper
- 需要自定义协议:gRPC
- 异步通信:消息队列
-
处理异常情况:
- 实现心跳机制检测节点存活
- 设置超时时间自动释放资源
- 实现重试机制
-
安全性考虑:
- 使用SSL/TLS加密通信
- 实现身份验证
- 限制节点访问权限
-
性能优化:
- 使用连接池管理网络连接
- 实现批量操作减少网络开销
- 使用异步IO提高吞吐量
这些方案都可以根据具体需求进行定制,选择最适合你项目的方案。