Gunicorn启动Thrift服务
来源:互联网 发布:python exchange 邮件 编辑:程序博客网 时间:2024/05/17 01:42
用Gunicorn启动Thrift服务
开始写博客,记录,总结,分享。
今天写一下关于Gunicorn和thrift的使用,也是最近做项目时用到的技术。关于thrift,网上都有很多介绍,不必多说。thrift支持很多语言,我们现在需要用到python做一些服务的事情,所以下面主要说的是thrift server端的内容。虽说是针对thrift的内容,但是thrift只是问题所在,主要的解决方式是使用gunicorn,所以后面说的主要是gunicorn的内容。
Thrift
Thrift里面有介绍各种server类型以及如何启动,类似于以下这种(摘自于thrift/tutorial/py源码):
handler = CalculatorHandler() processor = Calculator.Processor(handler) transport = TSocket.TServerSocket(port=9090) tfactory = TTransport.TBufferedTransportFactory() pfactory = TBinaryProtocol.TBinaryProtocolFactory() server = TServer.TSimpleServer(processor, transport, tfactory, pfactory) server.serve()
Thrift支持多线程的server用于提高并发处理能力,如 TServer.TThreadedServer 和 TServer.TThreadPoolServer。但仅这些离实际的分布式系统使用还不够,还需做一些工作。受限于GIL,python多线程并不能很好的利用多核CPU。可以对此做测试,即使用TThreadPoolServer启动server,在我8核电脑环境下,cpu使用率也上不了100%,好尴尬啊。
对于高并发后台服务,还需要考虑以下问题:
- 集群下如何部署启动;
- 服务挂了如何自动恢复以保持可用;
- client端如何找到server地址及端口,也就是服务发现的问题。
Thrift并没有在这方面做什么内容,或者说这些并不算它应包含的范畴之内,但是实际的后台应用都需要涉及这些内容。
Gunicorn
还好有gunicorn!
如果您有“gunicorn是什么”等类似的问题,还是请先自行百度吧。为什么用gunicorn,它使用了master worker模型,这是一个多进程并发模型,解决python多线程的困难啊!!!另外,对于python web,gunicorn基本上是必需品。
那么,gunicorn + thrift?在github早有人实现了:gunicorn_thrift(https://github.com/eleme/gunicorn_thrift,饿了么的项目,赞)。
我是从gunicorn_thrift的代码开始,认识到了gunicorn。。。有点和别人轨迹不一样啊,囧。看gunicorn_thrift的代码,主要是实现了thrift的workers,剩下的主体还都是gunicorn的逻辑,所以把gunicorn过了一遍。
下面主要讲讲gunicorn原理及结构,最关键的是咋用。看网上都说推荐看它的源码,写的非常好。
master-worker模型
就像我刚刚说的,gunicorn使用了master worker模型,一个主进程master负责接收信号,管理worker进程,真正监听处理网络事件的是worker进程。
master维护worker进程的数量,有worker挂掉了,master能够自动新建一个worker进程。master还负责接收信号,就是 un*x系统里的信号signal(SIGTERM,SIGINT,SIGHUP,SIGQUIT,SIGTTIN,SIGTTOU等),用于处理退出逻辑,worker数量增减等。另外,socket是在master创建的,这也意味着,worker进程继承了socket,每个worker都会监听端口请求,但是一个请求只会被一个worker处理。
worker则是真正实现了用户定义的worker class。gunicorn提供了同步sync和异步async的eventlet、gevent以及tornado、gthread、gaiohttp等worker种类。worker进程接收(accept),解析(parse),处理(handle)客户端发起的请求(request),真正对请求作出响应(response)的是用户使用的app应用程序,通常就是web应用定义的app。由此可以看出,worker其实相当于对app进行了一次封装,所有client的请求先经过gunicorn,由worker进程接收,再交给app进行响应。
看下源码(部分主要结构,精简了一下):
class Arbiter(object): def run(self): # 程序入口,在master进程中,start创建socket,manage管理worker进程 self.start() self.manage_workers() def start(self): self.LISTENERS = create_sockets() # 在master中创建socket,监听端口 def manage_workers(self): self.spawn_workers() # master进程管理worker def spawn_workers(self): self.spawn_worker() def spawn_worker(self): # 创建worker类的对象,worker_class就是用户选择的worker种类,包括sync、gevent等 worker = self.worker_class(***, self.LISTENERS, self.app, ***) pid = os.fork() # fork出worker进程 worker.init_process() # worker进程初始化,开始运行
说了这么多,master-worker模型有啥好处?
多进程当然比单进程能支撑更多请求,而且由于是多进程模型,每个进程各自处理请求,某个进程挂了不会影响其他进程的服务。而且master会随时监控worker进程的状态,具有自动恢复工作进程的能力。
- Settings
毕竟大多数时候,我们更需要关注如何使用gunicorn。在官网或其他介绍性的文章里,都有不少对使用gunicorn的介绍。我这里就简单一些,并且把我认为值得注意的地方提一下。
gunicorn --workers=2 test:app
这是官方找出的使用方法,当然可选的参数设置还有:
-c CONFIG, --config=CONFIG, 指定config文件-b BIND, --bind=BIND, 指定绑定端口-k WORKERCLASS, --worker-class=WORKERCLASS,指定worker-class类型...
网上列举了大概6、7个参数设置说明,但是,但是,但是,如果你看gunicorn的源码,从config里面可以找到60+个Setting子类,简直了。不过这些参数设置可以进行归类,包括以下几个方面:
Config File、Server Socket、Worker Processes、Security、Debugging、Server Mechanics、Logging、Process Naming、Django、Server Hooks、SSL
当然,有些参数设置并不常用,但是,还是说一下需要注意的一些内容吧:
首先,建议启动gunicorn的时候,都把配置写到用户自定义的conf.py文件里,毕竟这么多内容,都写在命令行,不容易看也不容易改啊~~~,所以最简单的就是,gunicorn -c conf.py test:app,然后所有其他选项都写在conf.py里。
然后,对于conf文件,如果有,程序在启动的时候会执行用户配置的conf.py文件load_config_from_file,然后对配置做一些参数检查等操作。建议看看gunicorn的源码config.py,里面的参数设置类都继承自Setting类,并且都用SettingMeta(元类MetaClass)一个个给__new__出来的,动态创建设置类,并控制这些类创建时的行为,简直是艺术!
最后,提一下Server Hooks配置。从源码可以看到,Setting的子类里面,有一部分定义了一些方法,但是没有实现。刚一开始我很奇怪,函数里都写着pass,怎么用啊。后来查看gunicorn的example才明白。以when_ready例。在Arbiter的start里面,有调用到cfg.when_ready方法,明显就是调用setting里配置的方法。对于这些server hooks,可以在自己定义的conf文件里重新实现这些方法,用来控制自己需要的一些行为。下面是gunicorn的一个example片段:
bing = '127.0.0.1:8000'workers = 1worker_class = 'sync'loglevel = 'info'def post_fork(server, worker): server.log.info("Worker spawned (pid : %s)", worker.pid)def when_ready(server): server.log.info("Server is ready. Spawning workers")
可以看到,对于server hooks的配置,可以由用户在conf.py中自己定义具体实现,来控制程序的行为。这一点有时非常重要,后面会讲到!!!
Gunicorn on Thrift
前面提到的那个开源项目gunicorn_thrift登场了。
用gunicorn启动thrift的使用方法,如同gunicorn启动其它server的方式一样,就不再赘述了。不同的就是worker类的选择,选好thrift实现的worker类,选好thrift自身包含的Protocol(协议)、Transport(传输层)等参数,其它的和gunicorn都一致。下面主要说一下,对于上面thrift part部分提到的问题,用gunicorn_thrift如何解决。
对于故障自动恢复问题,主要是关注gunicorn的master进程监控的问题。因为对于worker进程来讲,master会自动监控worker进程,所以不需要再另外关注。进程监控,有很多工具,如god,supervisor等。
在集群上部署(或者说分布式服务),说白了就是在多个服务器上部署相同的service,对外提供一致服务。其实单机和多机的部署是一样的,主要的区别是多服务器下,client端请求如何指向到可用的service地址,将请求分散到不同机器上。对于web应用来说,也就是如何做路由和负载均衡的问题,对于thrift server,同样也有这个问题。其实对于这个问题,可以拆分成两个问题,一个是动态提供集群的可用服务器地址,另一个是在此基础上做负载均衡。对于第一个问题,也就是服务发现的问题。
服务发现,也就是集群管理(个人理解),有很多解决的利器,常用的有zookeeper、etcd等,下面以zookeeper举例。当利用集群提供服务,必须知道每台服务器的状态,如果有一个机器上的server挂掉,需要让client端知道并且把请求分流到其它的server端;如果新加一台server到集群中,同样的道理,client端请求也可以知道并且把请求流量加到新的服务节点上。如果利用zookeeper做服务发现,一般是用server在zookeeper上创建EPHEMERAL(临时)节点,这样当server挂掉的时候,临时节点也自动删除,能够感知server的存在。
gunicorn_thrift提供了这种逻辑!
class ThriftApplication(Application): def run(self): if self.cfg.service_register_cls: service_register_cls = utils.load_obj(self.cfg.service_register_cls) self.service_watcher = service_register_cls(self.cfg.service_register_conf, self) instances = [] for i in self.cfg.address: port = i[1] instances.append({'port':{'main':port}, 'meta': None, 'state': 'up'}) self.service_watcher.register_instances(instances) super(ThriftApplication, self).run()
在Application正式运行之前,如果实现了注册类,那么进行注册的逻辑。但是,gunicorn_thrift只是提供了逻辑,没有具体实现service_register_cls(注册类)、service_register_conf(注册配置)和register_instances(注册逻辑),这些都交给了使用者自己实现。我自己实现了一下基于zookeeper的注册逻辑,感兴趣的同学可以看下我的代码 https://github.com/wangyibo360/gunicorn_thrift(当然源分支没有把我的代码merge,因为注册的实现有太多方式,zookeeper只是一种)。
在我的代码实现里,把server的ip和端口写到了zookeeper的临时节点里,供对外查找服务地址使用。当然这只是一个简易版,真正在公司使用可能还需复杂一些,除ip和port外,节点路径可能包括${cluster},${service},${package},${version}信息等,视业务具体情况而定。
还有一点我有些异议,在gunicorn_thrift项目里,注册是写在run之前的,对此,我可能更倾向于写在server hooks里的when_ready函数中。因为run之前注册好服务地址,对于客户端就视为可见了。但是此时application并没有执行逻辑,也就是app并没有做好服务的准备,master、worker都没有运行起来,如果此时有故障挂掉,那么将不能对client提供服务,而请求还不断向这个地址发送。以上是我个人的一点理解,也有可能我理解的有误。
当然,除了实现注册逻辑之外,我还实现了主动取消注册的逻辑。zookeeper的临时节点因为server进程挂掉而自动删除,但是我发现有一定的时间滞后性。在滞后的这段时间,有可能还有请求向这个server地址发送,还有一种可能是在监控进程发现server挂掉滞后自动修复,重新实现地址注册,此时原有的临时节点还没有删除掉,导致新的无法实现注册,而临时节点在滞后一定时间后删除,导致服务已经restart了,但是没有注册到zookeeper上,无法提供服务。所以,在on_exit中,我实现了取消注册的逻辑。on_exit是在halt中调用的,目的就是为了实现客户的程序退出逻辑,因此写在这里比较合适。halt是master在检测到退出信号量时做出的动作,包括SIGTERM、SIGINT、SIGQUIT等。
到此,可以先告一段落了。用gunicorn实现对thrift server的启动运行就介绍到这里。总结一下,用gunicorn能带来性能上的好处,同时能提供服务发现等方面的功能,可以说是有益的补充了thrift在这方面的不足。
最后,在上文中我提到,服务发现,同时还提到了负载均衡。这方面还需要客户端去实现,这方面以后有机会再说吧。
- Gunicorn启动Thrift服务
- django gunicorn启动配置
- HBase 单台服务器启动多个 thrift 服务
- 用gunicorn+gevent启动Flask项目
- 服务开发框架 Thrift
- 搭建thrift服务
- hbase开启thrift服务
- hbase的thrift服务
- Hive的thrift服务
- Hive 的Thrift服务
- Storm thrift服务入坑记
- 聊聊Thrift(三) thrift 服务篇-TThreadPoolServer
- 聊聊Thrift(四) thrift 服务篇-TNonblockingServer
- Nginx+Gunicorn+Django+Supervisor搭建web服务环境
- 启动thrift接口后,在这个服务上写一个test方法去测试,获取thrift中常量方法中的静态变量
- Thrift协议的服务模型
- Hive thrift服务--beeline使用
- Thrift服务搭建和调用
- Swift 不完全函数第 1 部分:如何避免
- [CSAPP笔记][第一章计算机系统漫游]
- 12.链表中倒数第k个结点
- DIV+CSS中的相对定位和绝对定位
- /基本命令/启动shell
- Gunicorn启动Thrift服务
- hdu 2203 亲和串
- 0002-创建数据库和表空间
- CNN的近期进展与实用技巧(上)&&人脸识别简史与近期进展
- Swift 不完全函数第 2 部分:捕获前置条件错误
- C++第4次上机实验
- opencv霍夫直线检测和圆检测
- iOS机型
- 野指针--内存泄漏--缓存区溢出--栈溢出