Python脚本如何分配分布式同步节点

wen python案例 34

本文目录导读:

Python脚本如何分配分布式同步节点

  1. 基于Redis的分布式锁
  2. 基于ZooKeeper的分布式协调
  3. 基于gRPC的节点通信
  4. 基于消息队列的方案 (RabbitMQ)
  5. 最佳实践建议

在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()

最佳实践建议

  1. 选择合适的方案

    • 小规模系统:Redis方案足够
    • 需要强一致性:ZooKeeper
    • 需要自定义协议:gRPC
    • 异步通信:消息队列
  2. 处理异常情况

    • 实现心跳机制检测节点存活
    • 设置超时时间自动释放资源
    • 实现重试机制
  3. 安全性考虑

    • 使用SSL/TLS加密通信
    • 实现身份验证
    • 限制节点访问权限
  4. 性能优化

    • 使用连接池管理网络连接
    • 实现批量操作减少网络开销
    • 使用异步IO提高吞吐量

这些方案都可以根据具体需求进行定制,选择最适合你项目的方案。

抱歉,评论功能暂时关闭!