Python RPC

4 阅读11分钟

在现代分布式系统中,远程过程调用(RPC)是实现服务间通信的核心技术之一。Python生态提供了多种RPC实现方案,从简单的JSON-RPC到高性能的gRPC,开发者还可以根据需求自定义RPC框架。本文将深入探讨这三种实现方式,帮助你理解RPC的核心原理并选择适合的方案。

一、JSON-RPC:轻量级的RPC方案

1.1 基本概念

JSON-RPC是一种基于JSON的轻量级远程过程调用协议。它使用HTTP作为传输协议,JSON作为数据格式,具有简单易用的特点。

1.2 实现示例

# 服务端
from jsonrpcserver import method, Success, serve


@method
def add(x: int, y: int) -> Success:
    return Success(x + y)


@method
def greet(name: str) -> Success:
    return Success(f"Hello, {name}!")


if __name__ == "__main__":
    serve("localhost", 5000)
# 客户端
import requests


def test_add():
    payload = {
        "jsonrpc": "2.0",
        "method": "add",
        "params": {"x": 5, "y": 3},
        "id": 1
    }
    response = requests.post(
        "http://localhost:5000",
        json=payload,
        headers={"Content-Type": "application/json"}
    ).json()
    print("服务器返回:", response)


def test_greet():
    payload = {
        "jsonrpc": "2.0",
        "method": "greet",
        "params": {"name": "World"},
        "id": 2
    }
    response = requests.post(
        "http://localhost:5000",
        json=payload
    ).json()
    print("服务器返回:", response)


if __name__ == "__main__":
    test_add()
    test_greet()
    # 服务器返回: {'jsonrpc': '2.0', 'result': 8, 'id': 1}
    # 服务器返回: {'jsonrpc': '2.0', 'result': 'Hello, World!', 'id': 2}

1.3 优缺点分析

优点:

  • 简单易用,上手门槛低
  • 跨语言支持良好
  • 人类可读的JSON格式便于调试

缺点:

  • 性能较低,不适合高并发场景
  • 缺乏强类型检查
  • 功能相对简单,缺少高级特性

二、gRPC:高性能的工业级解决方案

2.1 核心特性

gRPC是Google开发的高性能RPC框架,主要特点包括:

  • 基于HTTP/2协议
  • 使用Protocol Buffers作为接口定义语言(IDL)
  • 支持双向流式通信
  • 提供多种语言的实现

2.2 实现示例

# proto文件
syntax = "proto3";  
  
package calculator;  
  
service Calculator {  
rpc Add(AddRequest) returns (AddResponse) {}  
rpc Greet (GreetRequest) returns (GreetResponse) {}  
}  
  
message AddRequest {  
int32 x = 1;  
int32 y = 2;  
}  
  
message AddResponse {  
int32 result = 1;  
}  
  
message GreetRequest {  
string name = 1;  
}  
  
message GreetResponse {  
string greeting = 1;  
}

python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. calculator.proto

# 服务端
import grpc
from concurrent import futures
import calculator_pb2
import calculator_pb2_grpc


class CalculatorServicer(calculator_pb2_grpc.CalculatorServicer):
    def Add(self, request, context):
        result = request.x + request.y
        return calculator_pb2.AddResponse(result=result)

    def Greet(self, request, context):
        return calculator_pb2.GreetResponse(
            greeting="Hello, {}".format(request.name)
        )


def serve():
    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
    calculator_pb2_grpc.add_CalculatorServicer_to_server(
        CalculatorServicer(), server
    )
    server.add_insecure_port('[::]:50051')
    server.start()
    print('Server running on port 50051...')
    server.wait_for_termination()


if __name__ == '__main__':
    serve()
# 客户端
import grpc
import calculator_pb2
import calculator_pb2_grpc


def run():
    with grpc.insecure_channel('localhost:50051') as channel:
        stub = calculator_pb2_grpc.CalculatorStub(channel)

        # 普通调用
        add_response = stub.Add(calculator_pb2.AddRequest(x=1, y=2))
        print("1 + 2 = %d" % add_response.result)

        # 带元数据的调用
        metadata = [('client-id', 'python-client')]
        greet_response = stub.Greet(
            calculator_pb2.GreetRequest(name='Alice'),
            metadata=metadata
        )
        print(greet_response.greeting)


if __name__ == '__main__':
    run()

2.4 优缺点分析

优点:

  • 高性能,基于HTTP/2和二进制协议
  • 强类型接口定义
  • 支持双向流式通信
  • 丰富的生态和工具链

缺点:

  • 学习曲线较陡峭
  • 调试相对复杂
  • 对动态语言不够友好

三、自定义RPC框架:深入理解RPC本质

3.1 设计思路

我们将实现一个包含以下特性的自定义RPC框架:

  1. 基于TCP协议的二进制通信
  2. 自定义消息格式
  3. 同步/异步支持
  4. 简单的服务注册与发现

3.2 核心实现

消息协议设计:

+--------+--------+--------+--------+---------------+
| 魔数(2) | 版本(1)| 类型(1)| 长度(4)| 数据(变长)    |
+--------+--------+--------+--------+---------------+

3.3 实现示例

# 服务端
import struct
import socket
import threading
import json

from enum import IntEnum


class MessageType(IntEnum):
    REQUEST = 1
    RESPONSE = 2
    ERROR = 3


class RPCServer:
    MAGIC_NUMBER = 0xFAFA
    VERSION = 1

    def __init__(self, host='localhost', port=8000):
        self.host = host
        self.port = port
        self.services = {}
        self.running = False

    def register_service(self, instance):
        service_name = instance.__class__.__name__
        self.services[service_name] = instance

    def _process_request(self, data):
        try:
            request = json.loads(data.decode('utf-8'))
            service_name = request['service']
            method_name = request['method']
            args = request.get('args', [])
            kwargs = request.get('kwargs', {})

            service = self.services.get(service_name)
            if not service:
                raise ValueError(f'Service {service_name} not found')

            method = getattr(service, method_name)
            result = method(*args, **kwargs)

            return (MessageType.RESPONSE,
                    json.dumps({'result': result}).encode('utf-8'))
        except Exception as e:
            return (MessageType.ERROR,
                    json.dumps({'error': str(e)}).encode('utf-8'))

    def _handle_connection(self, conn):
        try:
            while self.running:
                # 读取消息头
                header = conn.recv(8)
                if len(header) < 8:
                    break

                magic, version, msg_type, length = struct.unpack('>HBBL', header)
                if magic != self.MAGIC_NUMBER or version != self.VERSION:
                    break

                # 读取消息体
                data = conn.recv(length)
                if len(data) < length:
                    break

                response_type, response_data = self._process_request(data)

                header = struct.pack('>HBBL', self.MAGIC_NUMBER, self.VERSION, response_type, len(response_data))
                conn.sendall(header + response_data)
        finally:
            conn.close()

    def start(self):
        self.running = True
        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.socket.bind((self.host, self.port))
        self.socket.listen(5)
        print(f'Server started on {self.host}:{self.port}')

        try:
            while self.running:
                conn, addr = self.socket.accept()
                thread = threading.Thread(
                    target=self._handle_connection,
                    args=(conn,)
                )
                thread.start()
        except KeyboardInterrupt:
            self.stop()

    def stop(self):
        self.running = False
        self.socket.close()


class CalculatorService:

    def add(self, x, y):
        return x + y

    def multiply(self, x, y):
        return x * y


class GreeterService:

    def greet(self, name):
        return f'Hello, {name}!'


if __name__ == '__main__':
    server = RPCServer(port=8000)
    server.register_service(CalculatorService())
    server.register_service(GreeterService())
    server.start()
# 客户端
import struct
import socket
import json
from enum import IntEnum


class MessageType(IntEnum):
    REQUEST = 1
    RESPONSE = 2
    ERROR = 3


class RPCClient:
    MAGIC_NUMBER = 0xFAFA
    VERSION = 1
    TIMEOUT = 5

    def __init__(self, host='localhost', port=8000):
        self.host = host
        self.port = port

    def __getattr__(self, name):
        return RPCServiceProxy(self, name)

    def _call(self, service, method, *args, **kwargs):
        request = {
            'service': service,
            'method': method,
            'args': args,
            'kwargs': kwargs
        }
        request_data = json.dumps(request).encode('utf-8')

        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
            sock.settimeout(self.TIMEOUT)
            sock.connect((self.host, self.port))

            # 发送请求
            header = struct.pack('>HBBL', self.MAGIC_NUMBER,
                                 self.VERSION, MessageType.REQUEST,
                                 len(request_data))
            sock.sendall(header + request_data)

            # 接收响应头
            header = sock.recv(8)
            if len(header) < 8:
                raise ConnectionError('Incomplete header received')

            magic, version, msg_type, length = struct.unpack('>HBBL', header)
            if magic != self.MAGIC_NUMBER or version != self.VERSION:
                raise ConnectionError('Invalid protocol header')

            # 接收响应体
            data = bytearray()
            while len(data) < length:
                packet = sock.recv(length - len(data))
                if not packet:
                    raise ConnectionError('Incomplete data received')
                data.extend(packet)

            response = json.loads(data.decode('utf-8'))
            if msg_type == MessageType.ERROR:
                raise RuntimeError(response.get('error', 'Unknown error'))
            return response.get('result')


class RPCServiceProxy:
    def __init__(self, client, service_name):
        self.client = client
        self.service_name = service_name

    def __getattr__(self, method_name):
        return lambda *args, **kwargs: self.client._call(
            self.service_name, method_name, *args, **kwargs
        )


if __name__ == '__main__':
    client = RPCClient(port=8000)

    result = client.CalculatorService.add(5, 3)
    print(f'5 + 3 = {result}')

    greeting = client.GreeterService.greet('Alice')
    print(greeting)

四、进阶

  • 基于发布/订阅模式的服务注册与发现
  • 引入连接池,实现异步IO
  • 功能增强实现服务发现与负载均衡,熔断与降级机制

4.1 基础协议与常量定义

import time
from enum import IntEnum
from typing import Dict, Any
from dataclasses import dataclass, field
import logging

# 配置日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger('custom_rpc')

# 协议常量
MAGIC_NUMBER = 0xFAFA
VERSION = 1
HEADER_LENGTH = 8


class MessageType(IntEnum):
    REQUEST = 1
    RESPONSE = 2
    ERROR = 3
    HEARTBEAT = 4


class LoadBalanceStrategy(IntEnum):
    ROUND_ROBIN = 1
    RANDOM = 2
    LEAST_CONN = 3


@dataclass
class ServiceInstance:
    service_name: str
    host: str
    port: int
    metadata: Dict[str, Any] = field(default_factory=dict)
    last_heartbeat: float = field(default_factory=time.time)
    is_healthy: bool = True

4.2 连接池

import asyncio
import time
from collections import defaultdict
from typing import Optional

from rpc_test.best_rpc.constant import logger


class ConnectionPool:
    def __init__(self, max_size=10, idle_timeout=300, connect_timeout=5):
        self.max_size = max_size
        self.idle_timeout = idle_timeout
        self.connect_timeout = connect_timeout
        self._pool = defaultdict(list)
        self._lock = asyncio.Lock()

    async def get_connection(self, host: str, port: int) -> Optional[asyncio.StreamWriter]:
        """获取连接"""
        async with self._lock:
            key = f'{host}:{port}'

            # 1. 首先尝试复用空闲连接
            while self._pool[key]:
                writer, last_used = self._pool[key].pop()

                # 检查连接是否过期
                if time.time() - last_used >= self.idle_timeout:
                    writer.close()
                    await writer.wait_closed()
                    continue

                # 检查连接是否仍然有效
                try:
                    writer.write(b'\x00')  # 发送心跳字节
                    await asyncio.wait_for(writer.drain(), timeout=1)
                    return writer
                except (ConnectionError, asyncio.TimeoutError) as e:
                    logger.debug(f"Connection test failed: {e}")
                    writer.close()
                    await writer.wait_closed()
                    continue

            # 2. 创建新连接
            if len(self._pool[key]) < self.max_size:
                for attempt in range(3):  # 重试3次
                    try:
                        # 添加连接超时
                        reader, writer = await asyncio.wait_for(
                            asyncio.open_connection(host, port),
                            timeout=self.connect_timeout
                        )
                        logger.debug(f"Created new connection to {key}")
                        return writer
                    except Exception as e:
                        logger.warning(f"Connection attempt {attempt + 1} failed to {key}: {e}")
                        if attempt == 2:  # 最后一次尝试仍然失败
                            return None
                        await asyncio.sleep(0.5)  # 等待后重试

            return None

    async def release_connection(self, host: str, port: int, writer: asyncio.StreamWriter):
        """释放连接回池"""
        async with self._lock:
            key = f'{host}:{port}'
            if writer.is_closing():
                return

            if len(self._pool[key]) < self.max_size:
                self._pool[key].append((writer, time.time()))
            else:
                writer.close()
                await writer.wait_closed()

    async def close_all(self):
        """关闭所有连接"""
        async with self._lock:
            for connections in self._pool.values():
                for writer, _ in connections:
                    if not writer.is_closing():
                        writer.close()
                        await writer.wait_closed()
            self._pool.clear()

4.3 服务发现与负载均衡

import asyncio
import time
from typing import Callable, Dict, List

from rpc_test.best_rpc.constant import ServiceInstance, logger
from rpc_test.best_rpc.event_bus import EventBus


class ServiceRegistry:

    def __init__(self, heartbeat_timeout: int = 30):
        self._services: Dict[str, List[ServiceInstance]] = {}
        self._event_bus = EventBus()
        self.heartbeat_timeout = heartbeat_timeout
        self._lock = asyncio.Lock()

    async def register(self, instance: ServiceInstance):
        """注册服务"""
        async with self._lock:
            if instance.service_name not in self._services:
                self._services[instance.service_name] = []

            # 避免重复注册
            existing = next(
                (inst for inst in self._services[instance.service_name]
                if inst.host == instance.host and inst.port == instance.port),
                None
            )

            if existing:
                existing.last_heartbeat = time.time()
                existing.is_healthy = True
                existing.metadata = instance.metadata
                logger.info(f"Updated instance {instance.service_name} at {instance.host}:{instance.port}")
            else:
                self._services[instance.service_name].append(instance)
                logger.info(f"Registered new instance {instance.service_name} at {instance.host}:{instance.port}")

            # 通知服务变更
            await self._event_bus.publish(
                f'service_change:{instance.service_name}',
                {'action': 'registered', 'instance': instance}
            )

    async def deregister(self, service_name: str, host: str, port: int):
        """注销服务实例"""
        async with self._lock:
            if service_name not in self._services:
                return False

            removed = False
            self._services[service_name] = [
                inst for inst in self._services[service_name]
                if not (inst.host == host and inst.port == port)
            ]

            if removed:
                logger.info(f"Deregistered instance {service_name} at {host}:{port}")
                await self._event_bus.publish(
                    f'service_change:{service_name}',
                    {'action': 'deregistered', 'host': host, 'port': port}
                )
            return removed

    async def heartbeat(self, service_name: str, host: str, port: int):
        """
        处理心跳
        :return:
        """
        async with self._lock:
            if service_name not in self._services:
                return False

            for inst in self._services[service_name]:
                if inst.host == host and inst.port == port:
                    inst.last_heartbeat = time.time()
                    if not inst.is_healthy:
                        inst.is_healthy = True
                        logger.info(f"Marked instance {service_name} at {host}:{port} as healthy")
                        await self._event_bus.publish(
                            f'service_change:{service_name}',
                            {'action': 'health_changed', 'instance': inst, 'healthy': True}
                        )
                    return True
            return False

    async def discover(self, service_name: str, filter_healthy: bool = True) -> List[ServiceInstance]:
        """发现服务实例"""
        async with self._lock:
            if service_name not in self._services:
                return []

            instances = self._services[service_name]

            if filter_healthy:
                return [inst for inst in instances if inst.is_healthy]
            return instances.copy()

    async def check_health(self):
        """服务健康检测"""
        async with self._lock:
            now = time.time()
            for service_name, instances in self._services.items():
                for instance in instances:
                    if now - instance.last_heartbeat > self.heartbeat_timeout:
                        if instance.is_healthy:
                            instance.is_healthy = False
                            logger.warning(
                                f'Instance {service_name} at {instance.host}:{instance.port} is unhealthy'
                            )
                            await self._event_bus.publish(
                                f'service_change:{service_name}',
                                {'action': 'health_changed', 'instance': instance, 'healthy': False}
                            )

    async def subscribe(self, service_name: str, callback: Callable):
        """订阅服务变更事件"""
        await self._event_bus.subscribe(f'service_change:{service_name}', callback)

    async def unsubscribe(self, service_name: str, callback: Callable):
        """取消订阅服务变更事件"""
        await self._event_bus.unsubscribe(f'service_change:{service_name}', callback)

4.4 熔断器

mport time

from rpc_test.best_rpc.error_enum import CircuitOpenError


class CircuitBreaker:

    def __init__(self,
                 failure_threshold: int = 5,
                 recovery_timeout: int = 30,
                 half_open_attempts: int = 2):
        self.failure_threshold = failure_threshold
        self.recovery_timeout = recovery_timeout
        self.half_open_attempts = half_open_attempts
        self._state = 'closed'
        self._failure_count = 0
        self._last_failure_time = None
        self._half_open_success = 0

    async def execute(self, func, *args, **kwargs):
        """执行受保护的调用"""
        current_time = time.time()
        if self._state == 'open':
            if current_time - self._last_failure_time > self.recovery_timeout:
                self._state = 'half-open'
                self._half_open_success = 0
            else:
                raise CircuitOpenError('Circuit breaker is open')

        try:
            result = await func(*args, **kwargs)
            if self._state == 'half-open':
                self._half_open_success += 1
                if self._half_open_success >= self.half_open_attempts:
                    self._state = 'closed'
                    self._failure_count = 0
            return result
        except Exception as e:
            self._failure_count += 1
            if (self._state == 'closed' and self._failure_count >= self.failure_threshold) or (self._state == 'half-open'):
                self._state = 'open'
                self._last_failure_time =time.time()
            raise

4.5 发布订阅

4.5.1 异步事件总线

import asyncio
from collections import defaultdict
from typing import Any, Callable, Dict, List

from rpc_test.best_rpc.constant import logger


class EventBus:

    """异步事件总线"""
    def __init__(self):
        self._subscribers: Dict[str, List[Callable]] = defaultdict(list)
        self._lock = asyncio.Lock()

    async def publish(self, event_type: str, data: Any = None):
        """发布事件"""
        async with self._lock:
            listeners = self._subscribers.get(event_type, [])
            logger.debug(f'Publishing {event_type} event to {len(listeners)} listeners')

            if listeners:
                await asyncio.gather(
                    *(listener(data) for listener in listeners)
                )

    async def subscribe(self, event_type: str, callback: Callable):
        """订阅事件"""
        async with self._lock:
            self._subscribers[event_type].append(callback)
        logger.debug(f"New subscriber for {event_type} events")

    async def unsubscribe(self, event_type: str, callback: Callable):
        """取消订阅"""
        async with self._lock:
            if callback in self._subscribers[event_type]:
                self._subscribers[event_type].remove(callback)

4.5.2 服务提供者

import asyncio
import os
from typing import Any, Dict

from rpc_test.best_rpc.constant import ServiceInstance, logger
from rpc_test.best_rpc.service_registry import ServiceRegistry


class ServiceProvider:
    """服务提供者"""
    def __init__(self, registry: ServiceRegistry, host: str = 'localhost', port: int = 8000):
        self.registry = registry
        self.host = host
        self.port = port
        self._services: Dict[str, Any] = {}
        self._heartbeat_task = None
        self._running = False

    def register_service(self, service_name: str, service_instance: Any):
        """注册服务实现"""
        self._services[service_name] = service_instance

    async def start(self):
        """启动服务提供者"""
        if self._running:
            return

        self._running = True
        # 注册所有服务
        for service_name in self._services:
            instance = ServiceInstance(
                service_name=service_name,
                host=self.host,
                port=self.port,
                metadata={'pid': str(os.getpid())}
            )
            await self.registry.register(instance)

        # 启动心跳任务
        self._heartbeat_task = asyncio.create_task(self._send_heartbeats())
        logger.info(f"Service provider started on {self.host}:{self.port}")

    async def stop(self):
        """停止服务提供者"""
        if not self._running:
            return

        self._running = False
        if self._heartbeat_task:
            self._heartbeat_task.cancel()
            try:
                await self._heartbeat_task
            except asyncio.CancelledError:
                pass

        # 注销所有服务
        for service_name in self._services:
            await self.registry.deregister(service_name, self.host, self.port)

        logger.info('Service provider stopped')

    async def _send_heartbeats(self):
        """定期发送心跳"""
        while self._running:
            try:
                for service_name in self._services:
                    await self.registry.heartbeat(service_name, self.host, self.port)
            except asyncio.CancelledError:
                break
            except Exception as e:
                logger.error(f'Heartbeat failed: {e}')
                await asyncio.sleep(1)

4.5.3 服务消费者

import asyncio
from typing import Callable, Dict, List

from rpc_test.best_rpc.constant import ServiceInstance, logger
from rpc_test.best_rpc.service_registry import ServiceRegistry


class ServiceConsumer:
    """服务消费者"""

    def __init__(self, registry: ServiceRegistry):
        self.registry = registry
        self._service_cache: Dict[str, List[ServiceInstance]] = {}
        self._callbacks: Dict[str, List[Callable]] = {}

    async def discover(self, service_name: str) -> List[ServiceInstance]:
        """发现服务实例并缓存"""
        instances = await self.registry.discover(service_name)
        self._service_cache[service_name] = instances
        return instances

    async def watch(self, service_name: str, callback: Callable):
        """监听服务的变更"""
        if service_name not in self._callbacks:
            self._callbacks[service_name] = []
        self._callbacks[service_name].append(callback)

        # 初始发现
        instances = await self.discover(service_name)
        callback({'action': 'init', 'instances': instances})

        # 订阅变更
        def wrapped_callback(event):
            if event['action'] in ['registered', 'deregistered', 'health_changed']:
                asyncio.create_task(self._handle_service_change(service_name, event))

        self.registry.subscribe(service_name, wrapped_callback)

    async def _handle_service_change(self, service_name: str, event: Dict):
        """处理服务变更"""
        instances = await self.discover(service_name)
        self._service_cache[service_name] = instances

        # 通知回调
        for callback in self._callbacks.get(service_name, []):
            try:
                callback({'action': 'update', 'instances': instances, 'event': event})
            except Exception as e:
                logger.error(f'Callback failed:{e}')

    def unwatch(self, service_name: str, callback: Callable = None):
        """取消监听"""
        if callback:
            if service_name in self._callbacks in self._callbacks[service_name]:
                self._callbacks[service_name].remove(callback)
                if not self._callbacks[service_name]:
                    del self._callbacks[service_name]
        elif service_name in self._callbacks:
            del self._callbacks[service_name]

4.5.4 定义示例服务

class CalculatorService:
    def add(self, a, b):
        return a + b
    
    async def multiply(self, a, b):
        await asyncio.sleep(0.1)
        return a * b

class UserService:
    async def get_user(self, user_id):
        await asyncio.sleep(0.2)
        return {"id": user_id, "name": f"user{user_id}"}

4.5.5 健康检查

async def run_health_check(registry: ServiceRegistry, interval: int = 10):
    """定期执行健康检查"""
    while True:
        try:
            await registry.check_health()
            await asyncio.sleep(interval)
        except asyncio.CancelledError:
            break
        except Exception as e:
            logger.error(f"Health check failed: {e}")
            await asyncio.sleep(1)

4.6 执行

4.6.1 服务端实现

async def run_server(host: str, port: int, registry: ServiceRegistry):
    """运行服务提供者"""
    provider = ServiceProvider(registry, host, port)

    # 注册服务实现
    provider.register_service('CalculatorService', CalculatorService())
    provider.register_service('UserService', UserService())

    await provider.start()

    try:
        while True:
            await asyncio.sleep(1)
    except asyncio.CancelledError:
        await provider.stop()

4.6.2 客户端实现

async def run_client(registry: ServiceRegistry):
    """运行服务消费者"""
    consumer = ServiceConsumer(registry)

    # 服务变更回调
    def service_changed(event):
        action = event['action']
        if action == 'init':
            print(f'Initial instances: {[f"{i.host}:{i.port}" for i in event["instances"]]}')
        elif action == 'update':
            print(f'Service changed: {event["event"]["action"]}')
            print(f'Current instances: {[f"{i.host}:{i.port}" for i in event["instances"]]}')

    # 监听calculatorServicer
    await consumer.watch('CalculatorService', service_changed)

    # 模拟RPC调用
    try:
        while True:
            instances = await consumer.discover('CalculatorService')
            if instances:
                instances = instances[0]
                print(f'Calling service at {instances.host}:{instances.port}')
            else:
                print('No available instances')

            await asyncio.sleep(3)
    except asyncio.CancelledError:
        consumer.unwatch('CalculatorService', service_changed)

4.6.3 主函数

async def main():
    # 创建共享的注册中心
    registry = ServiceRegistry()

    # 启动健康检查
    health_task = asyncio.create_task(run_health_check(registry))

    # 使用Event实现服务就绪通知
    service_ready = asyncio.Event()

    # 注册服务就绪回调
    async def on_service_registered(event):
        if event.get("action") == "registered":
            service_ready.set()

    # 注意这里需要使用 await
    await registry.subscribe("CalculatorService", on_service_registered)

    # 启动3个服务实例
    servers = [
        asyncio.create_task(run_server('localhost', 8000 + i, registry))
        for i in range(3)
    ]

    # 创建RPC客户端
    pool = ConnectionPool(max_size=5)
    client = RPCClient(
        registry=registry,
        pool=pool,
        load_balance_strategy=LoadBalanceStrategy.ROUND_ROBIN
    )

    try:
        # 等待服务就绪(带超时)
        try:
            await asyncio.wait_for(service_ready.wait(), timeout=10)
        except asyncio.TimeoutError:
            print("警告:等待服务注册超时,可能服务启动失败")
            return

        # 发起RPC调用
        calculator = client.get_proxy("CalculatorService")
        add_result = await calculator.add(5, 3)
        print(f"[RPC调用结果] 5 + 3 = {add_result}")

        # 保持运行
        client_task = asyncio.create_task(run_client(registry))
        await asyncio.sleep(600)
    except KeyboardInterrupt:
        pass
    finally:
        # 取消订阅
        await registry.unsubscribe("service_change:CalculatorService", on_service_registered)

        # 清理资源
        if 'client_task' in locals():
            client_task.cancel()
        for server in servers:
            server.cancel()
        health_task.cancel()
        await pool.close_all()

        await asyncio.gather(
            *[t for t in [client_task] if 'client_task' in locals()],
            *servers,
            health_task,
            return_exceptions=True
        )

if __name__ == '__main__':
    asyncio.run(main())