如何在Python中使用ZeroMQ和Docker构建微服务架构
微服务是什么?
微服务是一种架构风格,它包括多个彼此间进行通信的独立进程。在设计上,这些进程具有高度的可扩展性、相互解耦而且一次只完成一个较小的任务。这些服务都拥有自己的资源以及通过网络实现彼此间通信的进程。
相比于靠后端的单体结构来封装所有服务器逻辑的传统客户端-服务器架构(C/S架构)而言,微服务架构的差异性体现在关注点分离(Separation of concern)。这种设计模式更易于维护,使得灵活性、可扩展性及容错能力更强。但是这种分布式架构所的不足之处体现在如果设计不合理就会使得排错及维护变得复杂。
一个简单微服务的例子
让我们来分析这样的一个场景:你正在使用微服务模式构建一个电子商务网店。
对于一个电商网店上的常见商品,好比说iPhone,其详情页会显示:
- 产品的及基本信息
- 你的购买历史
- 哪些人买了iPhone也买了手机套
- 与苹果手机相关的优惠和折扣
- 店家的数据
- 送货方式
- 推荐商品等等
此外,这个简单的产品详情页的接口将有多个版本的来匹配Web、移动端以及用于第三方应用程序的REST API。
在微服务模式中数据分布在多个服务之间。在这个例子中,服务包括:
- 产品详情服务
- 商家服务
- 支付服务
- 优惠及折扣服务
- 库存服务
- 定价服务
- 回顾服务
- 推荐服务
这些独立的服务是如何被访问的呢?
解决办法是使用一个API网管,它作为所有客户端的单一入口并且根据需求调用分布在整个基础架构中的特定微服务。以上模式的行业应用案例是NetFlix API网关,它具有支持不同设备的多个API客户端。你可以点击此处了解更多。
构建一个简单的微服务
目前有很多方法可以用于构建你的微服务。
在本文中我们将使用ZeroMQ来创建两个进程之间的通信。ZeroMQ提供了用于在套接字之上开发可扩展、分布式systed的构建块。它使用椭圆曲线密码体制(第四版)来实现安全性,并提供了即刻开启的通讯模式。
关于ZMQ,还有很多优点。MQ即是针对异步工作而设计的线程化消息队列。谈论太多zeroMQ的内容已经超出了本文的范畴,你可以阅读使用zeromq以及zeromq用于分布式系统。
我们要使用的另一个工具是Docker。本文假设读者对Docker已经有了基础的了解。
ZeroMQ有很多种通讯模式,为了开始我们的工作,让我们用ZeroMQ和Flask来配置一个简单的PUB-SUB。下图展示了组件之间的关系和数据流。
- 1&3 - 一个flask服务器运行在5000端口上而且其URL是
/downcase/
。该URL用来接受(GET)请求,而所有格式为的请求将收到回应:答谢字符将会转换为小写字符并返回。 - 2 - 回应的消息也被发送给同一个容器中的ZMQ发布者(Publisher)
- 4,5 - ZMQ订阅者(subscriber)持续监听并将来自ZMQ服务器的消息保存到名为
subscriber.log
的文件中
创建服务器
首先看一下我们的Dockerfile
FROM ubuntu:14.04 RUN apt-get update RUN apt-get install -y --force-yes python python-dev python-setuptools software-properties-common gcc python-pip RUN apt-get clean all RUN pip install pyzmq RUN pip install Flask ADD zmqserver.py /tmp/zmqserver.py # # Flask Port EXPOSE 5000 # # Zmq Sub Server EXPOSE 4444 CMD ["python","/tmp/zmqserver.py"]
我们选择Ubuntu 14.04作为容器操作系统。我们安装了基本的软件包。通过pip,我们安装pyzmq(zeromq的Python绑定)同时也安装了Flask。接着我们导出端口5000(flask服务器)和4444(发布者运行的端口)。此外,我们复制了包含所有flask及zeromq pythond代码的脚本文件zmqserver.py
并运行它。
现在我们来看一下zmqserver.py的内容:
# server.py import time import zmq HOST = '127.0.0.1' PORT = '4444' _context = zmq.Context() _publisher = _context.socket(zmq.PUB) url = 'tcp://{}:{}'.format(HOST, PORT) def publish_message(message): try: _publisher.bind(url) time.sleep(1) _publisher.send(message) except Exception as e: print "error {}".format(e) finally: _publisher.unbind(url) from flask import Flask from flask import request app = Flask(__name__) @app.route("/downcase/", methods=['GET']) def lowerString(): _strn = request.args.get('param') response = 'lower case of {} is {}'.format(_strn, _strn.lower()) publish_message(response) return response if __name__ == '__main__': app.run(host='0.0.0.0', debug=False)
ZMQ发布者运行在4444端口上。我们创建了一个context并且声明了URL。我们运行了flask app,它通过URL /downcase/
把GET获得的参数Param
转换成小写字符,这就是服务的应答。应答的字符串是published
,它作为一个消息把相同的字符串返回给浏览器。
为了构建以上的Docker映像(image),我们执行以下的命令:sudo docker build -t docker-zmq-pub
并且在该映像之上执行:docker run --name docker-pub-server -p 5000:5000 -p 4444:4444 -t docker-zmq-pub
。
我们把容器中的端口5000和4444映射到这台主机上,于是无论客户端在哪里,它们都可以订阅这个发布者。
订阅者客户端
# client.py import zmq import sys import time import logging import os HOST = '127.0.0.1' PORT = '4444' logging.basicConfig(filename='subscriber.log', level=logging.INFO) class ZClient(object): def __init__(self, host=HOST, port=PORT): """Initialize Worker""" self.host = host self.port = port self._context = zmq.Context() self._subscriber = self._context.socket(zmq.SUB) print "Client Initiated" def receive_message(self): """Start receiving messages""" self._subscriber.connect('tcp://{}:{}'.format(self.host, self.port)) self._subscriber.setsockopt(zmq.SUBSCRIBE, b"") while True: print 'listening on tcp://{}:{}'.format(self.host, self.port) message = self._subscriber.recv() print message logging.info( '{} - {}'.format(message, time.strftime("%Y-%m-%d %H:%M"))) if __name__ == '__main__': zs = ZClient() zs.receive_message()
我们声明了发布者的IP地址及端口,当前它运行在同一个的主机上因此地址是127开头。我们在URL tcp://IP:PORT
上进行监听。一旦我们收到一个消息,就将其附上时间戳后记录到名为subscriber.log
的文件中。运行客户端要做的所有工作就是执行 python <name_of_client_file>.py
。如果你在以上的架构上进行构建,它可以很好地充当近实时的日志聚合引擎。
我在Unbuntu主机上对以上的代码进行了测试。这里所用的代码保管在GitHub上。这是一个如何配置ZMQ、Docker和Python服务器的基础讲解,在我的下一片文章中我们会使用我们已经学习的东西构建简单的微服务。
希望这篇文章对你有所帮助。
原
posted on 2019-04-25 18:11 ExplorerMan 阅读(1255) 评论(0) 编辑 收藏 举报