玩会吧微服务改造

1. 基于 redis 实现 RPC 开发框架

市面上没有基于 redis 实现的 Python 异步 RPC 框架。
干脆自己写了一个最简版的。

1.1 客户端

连接 redis 服务器:

1
2
async def connect(self):
self.redis = await redis.from_url(self.url)

将远程方法序列化:

1
2
3
def make_request(name, *args, **kwargs):
req_id = uuid.uuid4().hex
return req_id, {'id': req_id, 'name': name, 'args': args, 'kwargs': kwargs}

每次调用都应该有一个唯一 id,用此 id 作为回复队列的 key

远程调用:

1
2
3
async def call(self, name, *args, **kwargs):
req_id, req = make_request(name, *args, **kwargs)
...

以 redis list 实现请求队列,将序列化后的请求字符串 push 到队尾:

1
await self.redis.rpush(self.queue, json.dumps(req))

然后在回复队列上阻塞等待:

1
2
3
4
_, elem = await self.redis.blpop(f'{self.queue}:{req_id}')
elem = elem.decode()
result = json.loads(elem)
return result['error'], result['result']

1.2 服务端

run 起来之后,首先同客户端一样,连接到 redis 服务器。

在请求队列上阻塞等待并解包:

1
2
3
4
5
6
7
8
9
10
11
12
13
while True:
_, elem = await self.redis.blpop(self.queue)
req = json.loads(elem.decode())
name = req['name']
req_id = req['id']
if name not in self.tasks:
# handle error
continue
func = self.tasks[name]
args = req['args']
kwargs = req['kwargs']
result = await func(*args, **kwargs)
await self.response(req_id, Error.OK.value, result)

push 结果到回复队列:

1
2
3
async def response(self, req_id, error, result):
data = {'error': error, 'result': result}
await self.redis.rpush(f"{self.queue}:{req_id}", json.dumps(data))

方法注册装饰器:

1
2
def task(self, func):
self.tasks[func.__name__] = func

1.3 下载

pypi:https://pypi.org/project/asyncredisrpc/

源码:https://github.com/LiangZuoting/aioredisrpc

2. 项目结构改造

改造后的目录结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
.
├── common
│ ├── __init__.py
├── microservices
│ ├── auth
│ │ ├── Dockerfile
│ │ ├── docker_build.bat
│ │ ├── requirements.txt
│ │ └── src
│ └── iptv
│ ├── Dockerfile
│ ├── docker_build.bat
│ ├── requirements.txt
│ └── src
├── webapi
│ ├── Dockerfile
│ ├── docker_build.bat
│ ├── requirements.txt
│ └── src
└── websocket
├── Dockerfile
├── docker_build.bat
├── requirements.txt
└── src
  • common:通用能力模块,由其它模块导入使用
  • microservices:rpc 服务
  • webapi:web 网关,sanic 框架实现。通过 rpc 协议与几个 rpc 服务通信
  • websocket:websocket 服务,同样通过 sanic 框架实现

3. 通过 Docker 部署

3.1 创建镜像

照惯例,Dockerfilerequirements.txt 还有创建镜像的批处理脚本都放在了每个服务的根目录下。
但这样有一个问题,Dockerfile 不允许出现上层目录,即 ..,也就是说没法把 common copy 到镜像。

需要在 docker build 时指定工作目录,以 webapi 为例:

1
docker build -t liangzuoting/wanhuiba:webapi -f ./Dockerfile ../

最后的一个参数就是这个作用,把工作目录上移一层,即 common 所在目录。

同时,Dockerfile 中所有源路径都应该是相对于此工作目录的。比如:

1
2
3
COPY ./webapi/requirements.txt /webapi/

COPY ./common/ /common/

3.2 添加 PYTHONPATH

因为 common 模块在服务模块的上层目录里,按 python 默认的模块搜索规则找不到。
需要将 common 在镜像中的路径添加到环境变量 PYTHONPATH 中:

1
2
# Dockerfile 中设置环境变量
ENV PYTHONPATH "${PYTHONPATH}:/"

4. 已知问题

4.1 日志服务

没有找到一个轻量、免费、易用的日志服务。 sentry 偏重错误跟踪;graylog、logstash 又太重。
可能还是要用自己的 rpc 框架搞一个简易的日志服务。

评论