5 Star 40 Fork 12

lycclsltt / sunyata

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README
MIT

目录

简介

sunyata是一个Python3 RPC框架,client和server既可以直连,也可以通过Consul或ETCD做服务注册发现。

特性

  • 像本地函数一样调用
  • 使用简单,用户只需要关注业务即可
  • 支持HTTP/UDP/TCP协议
  • 支持异步async/await
  • 支持通过consul或etcd的服务注册发现
  • 支持所有数据类型,包括自定义的类对象等都可作为参数

安装

Python 版本 >= 3.6

pip install sunyata

快速开始

创建文件myservice.py

from sunyata.rpc import rpc

@rpc
def hello(name):
    return 'hello ' + name

启动:

sunyata --run myservice

详细介绍

TCP RPC 服务端

下面是一个TCP协议的服务端例子。

  • 创建一个TcpRpcServer对象, 指定服务端监听地址和端口
  • 通过@rpc装饰器注册需要被客户端请求的方法
  • 调用serve()方法,开始处理客户端请求
from sunyata.rpc.server import TcpRpcServer, rpc
import asyncio

@rpc
class TestService:

    def hello(self, name):
        return "Hello, {}!".format(name)

    async def add(self, a, b, c):
        asyncio.sleep(1)
        return a + b + c

@rpc
def hello(name):
    return "Hello, {}!".format(name)

server = TcpRpcServer('0.0.0.0', 9988)
server.serve()

TCP RPC 客户端

  • 创建TcpRpcClient对象,指定RPC服务端地址
  • 通过call()方法,指定服务端方法名称和参数(注意:如果方法名不存在,或者服务端未调用@rpc装饰器注册,那么call()方法将抛出异常)
  • call() 方法的返回值和在本地调用一样,原来是什么返回类型,就还是什么(例如返回字典、列表、对象甚至内置类型,经过序列化后,不会发生改变)
from sunyata.rpc.client import TcpRpcClient

cli = TcpRpcClient('127.0.0.1', 9988, timeout = 2)

resp = cli.TestService.hello('xiaoming')
print(resp)

#或者使用call方法
resp = cli.call('TestService.add', a=1, b=2, c=3)
print(resp)

resp = cli.call('hello', name = 'xiaoming')
print(resp)

指定多个服务端地址

  • 通过servers参数,你可以创建一个指定多个服务端地址的client对象,默认采用轮询的负载均衡策略,将请求转发到多个server上,如果请求其中一个server出现了失败,那么会自动重试。
from sunyata.rpc.client import TcpRpcClient

c = TcpRpcClient(servers = ['127.0.0.1:9988', '127.0.0.1:9989'])
resp = c.call('hello', 'zhangsan')
print(resp)

HTTP RPC 服务端

底层是基于内置web框架实现的,使用起来非常简单,和TcpRpcServer的用法类似:

from sunyata.rpc.server import HttpRpcServer, rpc

@rpc
def sayHello(name):
    return 'hello ' + name

s = HttpRpcServer('0.0.0.0', 9988, workers=1)
s.serve()

HTTP RPC 客户端

客户端使用对应的HttpRpcClient对象:

from sunyata.rpc.client import HttpRpcClient

c = HttpRpcClient('127.0.0.1', 9988)
resp = c.call('sayHello', 'zhangsan')
print(resp)

UDP RPC 服务端

将TcpRpcServer替换为UdpRpcServer即可。

  • 创建UdpRpcServer对象,指定监听的地址和端口
  • 调用regist()方法,将需要被客户端请求的方法注册进去
  • 调用serve()方法开始处理客户端请求
  • 返回的内容和调用本地方法没有差别,框架内部通过序列化和反序列化,将数据转化为程序内的对象(字典、列表、内置类型、各种类对象等等)
from sunyata.rpc.server import UdpRpcServer, rpc

@rpc
def sayHello(name): 
    return 'hello ' + name

server = UdpRpcServer('0.0.0.0', 9988)
server.serve()

UDP RPC 客户端

  • 创建UdpRpcClient对象,指定服务端地址和端口
  • 调用call()方法,并指定服务端的方法名称和参数
  • 返回的内容和调用本地方法没有差别,框架内部通过序列化和反序列化,将数据转化为程序内的对象(字典、列表、内置类型、各种类对象等等)
from sunyata.rpc.client import UdpRpcClient
cli = UdpRpcClient('127.0.0.1', 9988)
resp = cli.call('sayHello', name = 'xiaoming' )
print(resp)

服务发现

除了客户端与服务端直连,也支持服务注册发现(客户端与服务端直连的例子,请参考上面的TcpRpcServer部分)。 目前支持基于Consul 或 etcd 进行服务注册发现, 下面的例子先以Consul为例。

基于Consul的服务注册发现

基于Consul的Check机制,服务注册后,自动添加一个定期的检查任务。默认为TCP端口检查,支持TCP/HTTP RPC服务端,UDP服务端暂不支持。一旦服务进程挂掉,那么客户端会请求到其他健康的服务端节点上。

快速开始

  • 第一步,你需要定义一个DiscoverConfig对象。 指定用于服务注册发现的Consul的地址和端口。同时通过serviceName参数指定一个全局唯一的服务名称(用于标记服务端服务)。同时指定服务端监听的地址和端口。
from sunyata.rpc.discovery import DiscoveryConfig

disconf = DiscoveryConfig(
    consulHost = '192.168.19.103',
    consulPort = 8500,
    serviceName = 'test-rpc-server',
    serviceHost = local_ip(),
    servicePort = 9988
)

说明: 1.consulHost 和 consulPort 参数指定Consul的地址和端口 2.ServiceName 参数用于标记服务端名称,并通过服务名称进行服务发现,需要保证全局唯一 3.serviceHost和servicePort参数指定服务端监听的端口和地址

  • 第二步、调用setDiscoverConfig()方法将DiscoveryConfig对象传入
  • 第三步,调用serve()方法,开始处理请求
s = TcpRpcServer('0.0.0.0', 9988)
s.regist(sayHello)
disconf = DiscoveryConfig(
    consulHost = '192.168.19.103',
    consulPort = 8500,
    serviceName = 'test-rpc-server',
    serviceHost = local_ip(),
    servicePort = 9988
)
s.setDiscoverConfig(disconf)
s.serve()

完整的服务端示例 (UDP/HTTP调用方式相同)

from sunyata.rpc.server import TcpRpcServer, rpc
from sunyata.rpc.discovery import DiscoveryConfig
from sunyata.util import local_ip

@rpc
def sayHello(name): 
    return 'hello ' + name

disconf = DiscoveryConfig(
    consulHost = '192.168.19.103',
    consulPort = 8500,
    consulToken = 'd8ba9c48-c01a-a78e-ce8d-b65593a56419',
    serviceName = 'UserService',
    serviceHost = local_ip(),
    servicePort = 9988,
)

server = TcpRpcServer('0.0.0.0', 9988)
server.setDiscoverConfig(disconf)
server.serve()

完整的客户端示例(UDP/HTTP调用方式相同)

  • 创建DiscoveryConfig对象,指定Consul的地址端口(serviceName参数和服务端的保持一致,且全局唯一)
  • 调用setDiscoveryConfig()方法传入服务发现配置
from sunyata.rpc.client import TcpRpcClient
from sunyata.rpc.discovery import DiscoveryConfig
cli = TcpRpcClient()
disconf = DiscoveryConfig(
    consulHost= '192.168.19.103',
    consulPort= 8500,
    serviceName='test-rpc-server'
)
cli.setDiscoveryConfig(disconf)
resp = cli.call('sayHello', name = 'mary')
print(resp)

基于Etcd的服务注册发现

说明: 1.etcdHost 和 etcdPort 参数指定etcd的地址和端口 2.ServiceName 参数用于标记服务端名称,并通过服务名称进行服务发现,需要保证全局唯一 3.serviceHost和servicePort参数指定服务端监听的端口和地址

  • 第二步、调用setDiscoverConfig()方法将DiscoveryConfig对象传入
  • 第三步,调用serve()方法,开始处理请求

完整的服务端示例

from sunyata.rpc.server import HttpRpcServer
from sunyata.rpc.discovery import DiscoveryConfig
from sunyata.util import local_ip

def sayHello(name): 
    return 'hello ' + name

server = HttpRpcServer('0.0.0.0', 10031)
disconf = DiscoveryConfig(
    etcdHost='192.168.19.103',
    etcdPort=2379,
    serviceName = 'test-http-rpc-server-etcd',
    serviceHost = local_ip(),
    servicePort = 10031
)
server.setDiscoverConfig(disconf)
server.regist(sayHello)
server.serve()

完整的客户端示例

from sunyata.rpc.client import HttpRpcClient
from sunyata.rpc.discovery import DiscoveryConfig

client = HttpRpcClient()
disconf = DiscoveryConfig(
    etcdHost='192.168.19.103',
    etcdPort=2379,
    serviceName = 'myservice'
)
client.setDiscoveryConfig(disconf)
resp = client.sayHello('xiaoming')
assert (resp == 'hello xiaoming')

数据压缩

默认采用lz4进行压缩、解压缩(经过测试,它的压缩效果和gzip, zlib比较接近,压缩、解压缩性能是zlib的10倍左右)。 在数据传输大于4KB时,自动开启进行压缩。对端根据一个标记位进行判断,自动进行解压缩处理(或不处理,未经过压缩的情况)。开发者无需关心 数据的压缩、解压缩过程,经过测试对性能的影响极低(由于采用了level1级别的压缩),最高可减少75%的网络IO。

内置Web框架

sunyata也可以作为一个web框架来使用, HttpRpcServer在此基础上构建。

from sunyata.http.server import HttpServer, route

@route('/hello', methods=['GET'])
def hello(request):
    name = request.data.get('name', '')
    return 'Hello ' + name

hs = HttpServer(bind='0.0.0.0', port=9989)
hs.serve()

中间件

可以通过继承Middleware类,重写handle方法,添加中间件。所有请求的都会先经过多个中间件,可用于身份验证的场景,下面是一个例子。

from sunyata.http.server import HttpServer, route
from sunyata.http.middleware import Middleware
import logging


class LogMiddleware(Middleware):
    
    def handle(self, request):
        logging.info('path:' + request.uri + ' method:' + request.method)


class AuthMiddleware(Middleware):

    def handle(self, request):
        if request.headers.get('token') != 'abc':
            self.abort(403, 'invalid token')


@route('/api/v1/getUserName', methods=['POST', 'GET'])
def getUserName(request):
    return 'tom'

app = HttpServer(port=9990, accessLog=True)
app.middlewares = [
    LogMiddleware(),
    AuthMiddleware(),
]
app.serve()

微服务案例

下面是一个dns查询场景的微服务案例,dns_service.py负责提供底层dns查询服务,dns_web.py启动http api负责提供对外接口。dns_web与dns_service之间通过rpc进行通信。

dns_service.py

from sunyata.rpc.server import HttpRpcServer, rpc

@rpc
class DnsService(object):

    def query(self, domain):
        print('domain', domain)
        return domain + ' A IN 192.168.1.1'

rpcServer = HttpRpcServer(host='0.0.0.0', port=9988)
rpcServer.serve()

dns_web.py

from sunyata.http.server import HttpServer, route
from sunyata.rpc.client import HttpRpcClient

@route('/query', methods=['GET'])
def query(request):
    domain = request.data.get('domain')
    cli = HttpRpcClient('127.0.0.1', 9988)
    ip = cli.DnsService.query(domain)
    return ip

app = HttpServer()
app.serve()

请求dns_web curl 'http://127.0.0.1:9989/query?domain=www.a.com' 返回 www.a.com A IN 192.168.1.1

局限性

由于底层通过python特有的pickle方式进行序列化,目前只支持python语言编写的服务之间的rpc通信,暂不支持其他语言。好处是不需要 编写protobuf文件。

Stargazers repo roster for @lycclsltt/sunyata

Forkers repo roster for @lycclsltt/sunyata

MIT License Copyright (c) 2021 lycclsltt Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

简介

Light, simple, asynchronous RPC framework for Python 展开 收起
Python 等 2 种语言
MIT
取消

发行版 (3)

全部

贡献者

全部

近期动态

加载更多
不能加载更多了
Python
1
https://gitee.com/lycclsltt/sunyata.git
git@gitee.com:lycclsltt/sunyata.git
lycclsltt
sunyata
sunyata
master

搜索帮助