我曾经在一些公众场合说过心中的优秀Python开发者。Flask和Requests的作者就不说了,21世纪最缺的就是idea,他们不仅有而且还都用非常优美的方式做出来了。另外我还提到了Celery作者Ask
Solem,并不是因为Celery很有名它的主要作者就优秀了,我对ask的欣赏,完全是看Celery及其相关依赖的源代码的时候产生的。
有多年后台开发的工程师想必清楚,Celery本身涉及到的技术点其实在业界应用是很广泛的。Celery能这么流行,我们先排除没有进行技术深入下的盲从,和它诞生的非常早以外,我认为这和项目的内部设计的非常好也是有关的。
接下来的几篇文章我将分析Celery使用的Kombu库中的一些设计实现让大家对这个优秀项目更了解,并从中学习可扩展开发的实践。
Kombu是什么?
当一个项目变得越来越复杂,就要考虑只保留核心,并把其他部分分拆到不同的项目中以便减少未来的维护和开发的成本。Flask、IPython都是这样做的。
Kombu是一个把消息传递封装成统一接口的库。
Celery一开始先支持的RabbitMQ,也就是使用AMQ协议。由于要支持越来越多的消息代理,但是这些消息代理是不支持AMQ协议的,需要一个东西把所有的消息代理的处理方式统一起来,甚至可以理解为把它们「伪装成支持AMQ协议」。Kombu的最初的实现叫做carrot,
后来经过重构才成了Kombu。
registry
registry也就是「注册」,有按需加入的意思,在Python标准库和一些优秀开源项目中都有应用。我们先看个django的场景,为了减少篇幅我没有列出CheckRegistry类中其他方法:
1 |
|
可以看到每次用registry.register都能动态的添加新的tag,最后还用register =
registry.register
这样的方式列了个别名。执行结果如下:
1 |
|
kombu库包含对消息的序列化和反序列化工作的实现,可以同时支持多种序列化方案,如pickle、json、yaml和msgpack。假如你从前没有写过这样可扩展的项目,可能想的是每种的方案的loads和dumps都封装一遍,然后用一个大的if/elif/else来控制最后的序列化如何执行。
那么在kombu里面是怎么用的呢?我简化下它的实现:
1 |
|
其实kombu还实现了unregister限于篇幅我就不展开了。现在我们想添加yaml的支持,只需要加这样一个函数:
1 |
|
这样就支持yaml了。如果希望默认使用yaml来序列化,可以执行:
1 |
|
是不是非常好扩展,如果哪天我希望去掉对pickle(安全问题),就可以直接注释对应的函数就好了。写个小例子试验下:
1 |
|
运行的结果就是:
1 |
|
entrypoint
在我的书里面介绍过如果使用标准库自带的pkg_resources.iter_entry_points实现一个简单的插件系统。这在kombu上面也有应用,在序列化实现模块的最后加了这么几句:
1 |
|
这是什么东西呢?pkg_resources是一个用于包发现和资源访问的模块,我们可以实现不同的kombu扩展,如果在这个扩展项目的setup.py里面设置对应的entry_points,在安装之后,运行上述代码的时候就会自动找到这些扩展,并注册进来。这就是一个扩展系统。Flake8就是最好的这个扩展玩法的范例。
kombu的扩展不多,我选择[kombu-fernet-serializers](https://github.com/heroku/kombu-
fernet-serializers)来进行介绍。首先看一下它的setup.py文件:
1 |
|
注意到了吧,这个entry点就是kombu.serializers,安装之后就多了4个序列化方案,我们看一下fernet_json的实现:
1 |
|
而fernet_yaml也被放进了模块的方式,其实和在函数内殊途同归:
1 |
|
事实上,我们并不需要了解fernet_encode和fernet_decode是如何对消息做对称加密的,只是感受下这样添加扩展的方式是不是很优雅呢?
版权声明:本文由 董伟明 原创,未经作者授权禁止任何微信公众号和向掘金(juejin.im)转载,技术博客转载采用 保留署名-非商业性使用-禁止演绎 4.0-国际许可协议
python