基于Sanic的微服务基础架构
2019独角兽企业重金招聘Python工程师标准>>>
介绍
使用python做web开发面临的一个最大的问题就是性能,在解决C10K问题上显的有点吃力。有些异步框架Tornado、Twisted、Gevent 等就是为了解决性能问题。这些框架在性能上有些提升,但是也出现了各种古怪的问题难以解决。
在python3.6中,官方的异步协程库asyncio正式成为标准。在保留便捷性的同时对性能有了很大的提升,已经出现许多的异步框架使用asyncio。
使用较早的异步框架是aiohttp,它提供了server端和client端,对asyncio做了很好的封装。但是开发方式和最流行的微框架flask不同,flask开发简单,轻量,高效。
微服务是最近最火开发模式,它解决了复杂性问题,提高开发效率,便于部署等优点。
正是结合这些优点, 以Sanic为基础,集成多个流行的库来搭建微服务。 Sanic框架是和Flask相似的异步协程框架,简单轻量,并且性能很高。
本项目就是以Sanic为基础搭建的微服务框架。
特点
- 使用sanic异步框架,简单,轻量,高效。
- 使用uvloop为核心引擎,使sanic在很多情况下单机并发甚至不亚于Golang。
- 使用asyncpg为数据库驱动,进行数据库连接,执行sql语句执行。
- 使用aiohttp为Client,对其他微服务进行访问。
- 使用peewee为ORM,但是只是用来做模型设计和migration。
- 使用opentracing为分布式追踪系统。
- 使用unittest做单元测试,并且使用mock来避免访问其他微服务。
- 使用swagger做API标准,能自动生成API文档。
使用
项目地址
sanic-ms
Example
Swagger API
Zipkin Server
服务端
使用sanic异步框架,有较高的性能,但是使用不当会造成blocking, 对于有IO请求的都要选用异步库。添加库要慎重。 sanic使用uvloop异步驱动,uvloop基于libuv使用Cython编写,性能比nodejs还要高。
功能说明:
启动前
@app.listener('before_server_start')
async def before_srver_start(app, loop):queue = asyncio.Queue()app.queue = queueloop.create_task(consume(queue, app.config.ZIPKIN_SERVER))reporter = AioReporter(queue=queue)tracer = BasicTracer(recorder=reporter)tracer.register_required_propagators()opentracing.tracer = tracerapp.db = await ConnectionPool(loop=loop).init(DB_CONFIG)
- 创建DB连接池
- 创建Client连接
- 创建queue, 消耗span,用于日志追踪
- 创建opentracing.tracer进行日志追踪
中间件
@app.middleware('request')
async def cros(request):if request.method == 'POST' or request.method == 'PUT':request['data'] = request.jsonspan = before_request(request)request['span'] = span@app.middleware('response')
async def cors_res(request, response):span = request['span'] if 'span' in request else Noneif response is None:return responseresult = {'code': 0}if not isinstance(response, HTTPResponse):if isinstance(response, tuple) and len(response) == 2:result.update({'data': response[0],'pagination': response[1]})else:result.update({'data': response})response = json(result)if span:span.set_tag('http.status_code', "200")if span:span.set_tag('component', request.app.name)span.finish()return response
- 创建span, 用于日志追踪
- 对response进行封装,统一格式
异常处理
对抛出的异常进行处理,返回统一格式
任务
创建task消费queue中对span,用于日志追踪
异步处理
由于使用的是异步框架,可以将一些IO请求并行处理
Example:
async def async_request(datas):# async handler requestresults = await asyncio.gather(*[data[2] for data in datas])for index, obj in enumerate(results):data = datas[index]data[0][data[1]] = results[index]@user_bp.get('/<id:int>')
@doc.summary("get user info")
@doc.description("get user info by id")
@doc.produces(Users)
async def get_users_list(request, id):async with request.app.db.acquire(request) as cur:record = await cur.fetch(""" SELECT * FROM users WHERE id = $1 """, id)datas = [[record, 'city_id', get_city_by_id(request, record['city_id'])][record, 'role_id', get_role_by_id(request, record['role_id'])]]await async_request(datas)return record
get_city_by_id, get_role_by_id是并行处理。
相关连接
sanic
模型设计 & ORM
Peewee is a simple and small ORM. It has few (but expressive) concepts, making it easy to learn and intuitive to use。
ORM使用peewee, 只是用来做模型设计和migration, 数据库操作使用asyncpg。
Example:
# models.pyclass Users(Model):id = PrimaryKeyField()create_time = DateTimeField(verbose_name='create time',default=datetime.datetime.utcnow)name = CharField(max_length=128, verbose_name="user's name")age = IntegerField(null=False, verbose_name="user's age")sex = CharField(max_length=32, verbose_name="user's sex")city_id = IntegerField(verbose_name='city for user', help_text=CityApi)role_id = IntegerField(verbose_name='role for user', help_text=RoleApi)class Meta:db_table = 'users'# migrations.pyfrom sanic_ms.migrations import MigrationModel, info, dbclass UserMigration(MigrationModel):_model = Users# @info(version="v1")# def migrate_v1(self):# migrate(self.add_column('sex'))def migrations():try:um = UserMigration()with db.transaction():um.auto_migrate()print("Success Migration")except Exception as e:raise eif __name__ == '__main__':migrations()
- 运行命令 python migrations.py
- migrate_v1函数添加字段sex, 在BaseModel中要先添加name字段
- info装饰器会创建表migrate_record来记录migrate,version每个model中必须唯一,使用version来记录是否执行过,还可以记录author,datetime
- migrate函数必须以**migrate_**开头
相关连接
peewee
数据库操作
asyncpg is the fastest driver among common Python, NodeJS and Go implementations
使用asyncpg为数据库驱动, 对数据库连接进行封装, 执行数据库操作。
不使用ORM做数据库操作,一个原因是性能,ORM会有性能的损耗,并且无法使用asyncpg高性能库。另一个是单个微服务是很简单的,表结构不会很复杂,简单的SQL语句就可以处理来,没必要引入ORM。使用peewee只是做模型设计
Example:
sql = "SELECT * FROM users WHERE name=$1"
name = "test"
async with request.app.db.acquire(request) as cur:data = await cur.fetchrow(sql, name)async with request.app.db.transaction(request) as cur:data = await cur.fetchrow(sql, name)
- acquire() 函数为非事务, 对于只涉及到查询的使用非事务,可以提高查询效率
- tansaction() 函数为事务操作,对于增删改必须使用事务操作
- 传入request参数是为了获取到span,用于日志追踪
- TODO 数据库读写分离
相关连接
asyncpg benchmarks
客户端
使用aiohttp中的client,对客户端进行了简单的封装,用于微服务之间访问。
Don’t create a session per request. Most likely you need a session per application which performs all requests altogether. A session contains a connection pool inside, connection reusage and keep-alives (both are on by default) may speed up total performance.
Example:
@app.listener('before_server_start')
async def before_srver_start(app, loop):app.client = Client(loop, url='http://host:port')async def get_role_by_id(request, id):cli = request.app.client.cli(request)async with cli.get('/cities/{}'.format(id)) as res:return await res.json()@app.listener('before_server_stop')
async def before_server_stop(app, loop):app.client.close()
对于访问不同的微服务可以创建多个不同的client,这样每个client都会keep-alives
相关连接
aiohttp
日志 & 分布式追踪系统
使用官方logging, 配置文件为logging.yml, sanic版本要0.6.0及以上。JsonFormatter将日志转成json格式,用于输入到ES
Enter OpenTracing: by offering consistent, expressive, vendor-neutral APIs for popular platforms, OpenTracing makes it easy for developers to add (or switch) tracing implementations with an O(1) configuration change. OpenTracing also offers a lingua franca for OSS instrumentation and platform-specific tracing helper libraries. Please refer to the Semantic Specification.
装饰器logger
@logger(type='method', category='test', detail='detail', description="des", tracing=True, level=logging.INFO)
async def get_city_by_id(request, id):cli = request.app.client.cli(request)
- type: 日志类型,如 method, route
- category: 日志类别,默认为app的name
- detail: 日志详细信息
- description: 日志描述,默认为函数的注释
- tracing: 日志追踪,默认为True
- level: 日志级别,默认为INFO
分布式追踪系统
- OpenTracing是以Dapper,Zipkin等分布式追踪系统为依据, 建立了统一的标准。
- Opentracing跟踪每一个请求,记录请求所经过的每一个微服务,以链条的方式串联起来,对分析微服务的性能瓶颈至关重要。
- 使用opentracing框架,但是在输出时转换成zipkin格式。 因为大多数分布式追踪系统考虑到性能问题,都是使用的thrift进行通信的,本着简单,Restful风格的精神,没有使用RPC通信。以日志的方式输出, 可以使用fluentd, logstash等日志收集再输入到Zipkin。Zipkin是支持HTTP输入的。
- 生成的span先无阻塞的放入queue中,在task中消费队列的span。后期可以添加上采样频率。
- 对于DB,Client都加上了tracing
相关连接
opentracing zipkin jaeger
API接口
api文档使用swagger标准。
Example:
from sanic_ms import doc@user_bp.post('/')
@doc.summary('create user')
@doc.description('create user info')
@doc.consumes(Users)
@doc.produces({'id': int})
async def create_user(request):data = request['data']async with request.app.db.transaction(request) as cur:record = await cur.fetchrow(""" INSERT INTO users(name, age, city_id, role_id)VALUES($1, $2, $3, $4, $5)RETURNING id""", data['name'], data['age'], data['city_id'], data['role_id'])return {'id': record['id']}
- summary: api概要
- description: 详细描述
- consumes: request的body数据
- produces: response的返回数据
- tag: API标签
- 在consumes和produces中传入的参数可以是peewee的model,会解析model生成API数据, 在field字段的help_text参数来表示引用对象
- http://host:ip/openapi/spec.json 获取生成的json数据
相关连接
swagger
Response 数据
在返回时,不要返回sanic的response,直接返回原始数据,会在Middleware中对返回的数据进行处理,返回统一的格式,具体的格式可以[查看]
单元测试
单元测试使用unittest。 mock是自己创建了MockClient,因为unittest还没有asyncio的mock,并且sanic的测试接口也是发送request请求,所以比较麻烦. 后期可以使用pytest。
Example:
from sanic_ms.tests import APITestCase
from server import appclass TestCase(APITestCase):_app = app_blueprint = 'visit'def setUp(self):super(TestCase, self).setUp()self._mock.get('/cities/1',payload={'id': 1, 'name': 'shanghai'})self._mock.get('/roles/1',payload={'id': 1, 'name': 'shanghai'})def test_create_user(self):data = {'name': 'test','age': 2,'city_id': 1,'role_id': 1,}res = self.client.create_user(data=data)body = ujson.loads(res.text)self.assertEqual(res.status, 200)
- 其中_blueprint为blueprint名称
- 在setUp函数中,使用_mock来注册mock信息, 这样就不会访问真实的服务器, payload为返回的body信息
- 使用client变量调用各个函数, data为body信息,params为路径的参数信息,其他参数是route的参数
代码覆盖
coverage erase
coverage run --source . -m sanic_ms tests
coverage xml -o reports/coverage.xml
coverage2clover -i reports/coverage.xml -o reports/clover.xml
coverage html -d reports
- coverage2colver 是将coverage.xml 转换成 clover.xml,bamboo需要的格式是clover的。
相关连接
unittest coverage
异常处理
使用 app.error_handler = CustomHander() 对抛出的异常进行处理
Example:
from sanic_ms.exception import ServerError@visit_bp.delete('/users/<id:int>')
async def del_user(request, id):raise ServerError(error='内部错误',code=10500, message="msg")
- code: 错误码,无异常时为0,其余值都为异常
- message: 状态码信息
- error: 自定义错误信息
- status_code: http状态码,使用标准的http状态码
转载于:https://my.oschina.net/songcser/blog/1600616
基于Sanic的微服务基础架构相关推荐
- python sanic orm_基于sanic的微服务框架 - 架构分析
感谢@songcser分享的<基于sanic的微服务基础架构>https://github.com/songcser/sanic-ms 最近正在学习微服务,发现这个repo不错,但不完整, ...
- Aooms_基于SpringCloud的微服务基础开发平台实战_002_工程构建
为什么80%的码农都做不了架构师?>>> 一.关于框架更名的一点说明 最近在做年终总结.明年规划.还有几个项目需要了结.出解决方案,事情还比较多,死了不少脑细胞,距离上一篇文章 ...
- 微服务基础架构的5个关键问题
作者介绍: 杨波,微服务技术专家,曾主导了拍拍贷微服务架构体系建设.任职携程技术研发总监期间,主导了携程大规模SOP体系建设,也是极客时间「微服务架构实战160讲」课程讲师. 一.OAuth2的四种模 ...
- GIAC 2020 全球互联网架构大会演讲实录:基于TarsGo的微服务技术架构实践
2020年8月14日-15日,GIAC 2020 全球互联网架构大会于上周五正式在深圳开幕. GIAC(GLOBAL INTERNET ARCHITECTURE CONFERENCE)是长期关注互联网 ...
- 【微服务】微服务安全 - 如何保护您的微服务基础架构?
在当今行业使用各种软件架构和应用程序的市场中,几乎不可能感觉到您的数据是完全安全的.因此,在使用微服务架构构建应用程序时,安全问题变得更加重要,因为各个服务相互之间以及客户端之间进行通信.因此,在这篇 ...
- 日10亿级处理,基于云的微服务架构
德比软件:基于云的微服务架构 作者:朱攀,德比软件架构师,同济大学研究生,2007 年 2 月加入德比软件(DerbySoft),拥有 10 年以上的软件架构和开发经验.目前主要负责公司数据对接平台的 ...
- 基于 Docker 的微服务架构实践
http://dockone.io/article/4887 前言 基于 Docker 的容器技术是在2015年的时候开始接触的,两年多的时间,作为一名 Docker 的 DevOps,也见证了 Do ...
- docker容器 eureka 集成_微服务:基于 Docker 的微服务架构之分布式企业级实践参考...
编者按:本文分享自CSDN技术博客,作者为 FlyWine,所有权归原著者.若有不妥,联系本头条号以做必要处理. 目录 Microservice 和 Docker 服务发现模式 客户端发现模式 Net ...
- javaweb k8s_K8S微服务核心架构学习指南 ASP.NET Core微服务基于K8S 架构师必备Kubernetes教程...
K8S微服务核心架构学习指南 ASP.NET Core微服务基于K8S 架构师必备Kubernetes教程 课程内容是关于Kubernetes微服务架构学习课程,基于K8S开展ASP.NET核心进行微 ...
最新文章
- “冗余”的参数(变量) —— 提升访问的效率
- CoFun 1612 单词分组(容斥)
- JMetro版本11.5.11和8.5.11发布
- mongodb lbs java_LBS JAVA Spring mongoDB
- matlab三位画图_matlab 3D绘图详解(示例代码)
- 怎么导出oracle库,【DG】怎么从Oracle备库导出数据
- 这操作厉害:怎么样发布你的 Python模块给别人 “pip install”
- python特性有什么_举例介绍Python中的25个隐藏特性
- java txt 分段读取_Java 读取TXT文件的多种方式
- Git(1)——初始版本控制工具
- Kali Linux 暴力破解学校办公室WiFi 总结
- EasyGBS国标视频云服务平台可以获取录像却无法播放是什么原因?
- Neo4j CQL基础
- 计算机电源直接连接哪两根线才能开机,电脑电源开关线是哪两根
- JS事件绑定的几种方式
- List 列表的用法
- 网络规划师上午备考知识点(一)(万能协议篇)
- 全新深度学习,机器学习,模式识别,软件编程,面经资料汇总
- 2023年租房投影仪推荐,出租屋投影仪值得买吗?又该怎么选择?
- OV7670摄像头的相关参数及时序分析
热门文章
- Python3 casefold() 方法
- Swift - UITableView里的cell底部分割线左侧靠边
- 交叉编译Python-2.7.13到ARM(aarch32)平台
- SQL join中on与where区别
- Linux课程实践一:Linux基础实践(SSH)
- 取消chrome浏览器下input和textarea的默认样式
- 格林威治时间(Tue Jan 01 00:00:00 CST 2019)[ Date ]转化 为 [ 2019-01-01 10:10:10 ]
- idea部署项目com.intellij.javaee.oss.admin.jmx.JmxAdminException-未使用最新版本的war包
- HDOJ 2030-汉字统计
- 微信小程序 全局共享数据