第二期|苑志国:odoo架构技术实践

September 30, 2020 by
苑志国
9月26日,开源技术爱好者苑志国给我们带来了关于「odoo技术架构实践」的分享。

内容围绕Odoo结合开源框架可以应对的几个典型场景展开:高并发场景、复杂搜索场景、复杂长事务处理、流数据处理场景、复杂关系场景、以及多文件、图片存储场景。

点击链接回看>>>【直播分享回放】第二期:odoo架构技术实践


一  odoo + SANIC…应对高并发场景


Odoo在高并发的场景下,性能并不是太好,会成为系统的瓶颈。为了解决这个问题,我们可以在数据库的层面引入 SANIC 的框架,通过 SANIC 框架对外提供服务(如上流程图)。
Python 的 web 框架很多,比较知名的有 Django、Flask 等框架。但在综合排名里,SANIC 的高发性能排名比较靠前。因此在工作中,会引入 SANIC 来处理一些高并发场景。

  • SANIC 实现与 odoo 一致的用户登录验证机制,验证通过后签发 JWT

SANIC 会与 odoo 实现一致的用户登录验证机制,但 SANIC 只是充当了一个微服务的架构,它会签发一个 JWT 的 token,也就是说用户在请求第一次登录或者获取一个 token 的时候,它要把自己的用户名和密码传输过来,这个验证机制与 odoo 是完全一致的,而且是通过 odoo 的 Create Context 的方式进行的验证。验证通过后,签发 token。
基本上这是一个前后端分离的架构,前端可以用 VUE.js, Angular.js, React.js 等等其他的方式进行页面的渲染实现,用户凭 token 获取后端的资源。关键点是 SANIC 要实现一个JWT 的 token(图中红框标注的这一环节)。

  • SANIC 实现与 odoo 一致的用户注册及修改密码机制

SANIC 实现的用户注册及修改密码机制也是与 odoo 一致的,功能的核心方法就是 odoo 的 def _crypt_context(),实现用户的验证(见图中代码的截图)。SANIC 只是在用户通过验证之后签发了一个 JWT 的 token。通过一个简单的改造,让并发有数量级的提升。

SANIC 是一个并发的非状态的框架,可以做横向的拓展。在一些特殊场景下,odoo 可以作为系统的后端,进行 orm 或者model 层面的管理;而 SANIC 可以提供业务用户在高并发场景下请求的处理。由此,可以实现高并发的 B2B 的网站,或者“秒杀”这些高并发的场景。

基本上在这个架构里,SANIC 是作为一个高并发的微服务的中间层,odoo 是在后台里起到了系统管理的功能,脱离了 ERP 的概念。除了 SANIC 之外,按照这个思路还可以提升很多高并发的框架。这个改造主要是为了应对高并发的场景。


二  odoo + Elasticsearch应对复杂搜索场景


如果做复杂搜索场景(如全文搜索、费用排序等)时,输出的结果也比较复杂。用关系型数据库处理的时候,效率往往比较低。所以,针对工作实践,在 odoo 上做了一个增强。

  • odoo 增加 json 兼容的输出,用于 ES 索引发布

odoo 的 search_read 的方法可以进行几个数据的搜索并输出结果,但输出的结果并不完全是一个兼容 json 的格式,所以这里在 odoo 上增加了一个 search_read_json 的方法,做了一个简单的改写,让它的输出兼容 json。兼容 json 后,Elasticsearch 输出的完全是 json 的 document,这样就能很容易地把数据模型发布到 Elasticsearch。

  • 通过 odoo 定时任务定期更新索引

利用定时任务这一方式,可以通过 odoo 把数据定时地更新到Elasticsearch。

  • 通过 odoo+Celery 或者 odoo 的 server action 准实时更新索引

这个方法基本上没有改写 create,也没有改写 write,而是用了 server action。通过 Celery 这种异步的方式,或者 odoo 的server action 的方式,可以在数据变化时,准实时更新索引。当然,在实践中还有其他的方式可以使用。比如,在数据库层面增加一个 cdc(change data capture)框架来做发布的更新。大部分情况下,odoo+Celery 在效率上已经够用了。

引入 Elasticsearch 后,可以提高搜索的效率,进行大量数据的更新。再加上“准实时”,可以对一些实时性要求比较高的数据进行更新。
这里增强的只是 json 的兼容的输出。在有些产品下,json 的嵌套可能不是两级的嵌套,而是多级的。我们也可以用 pg(PostgreSQL)的 json array 来构造这个查询方式。还可以写一个sql的查询,直接输出 json 的格式,这样在构造索引的时候会比较方便。目前这个模块已经开源。

这里的挑战在于增加 json 的格式,以及如何构建索引。因为在一些特定的场景下做一个特定查询时,需要考虑如何构建索引才能达到想要的检索效果,所以索引的设计是一个挑战。在数据同步层面,odoo 的 search_read 方式,加上改造过的 search_read_json,输出 json 格式的结果发布到这个索引上,是一个天然的结构。当然,在数据量大的情况下,可能会考虑一些更好的方式。

在 Elasticsearch 上可以进行一些非常复杂的检索,它的全文检索比数据库层面的全文检索要强许多。Elasticsearch 可以进行复杂的查询,可以有 must,must not,should,filter 等等方式的构造。在新版上,Elasticsearch 还支持了对等的sql的翻译。所以,如果是复杂查询的场景,且并发量比较高的情况下,可以尝试用 Elasticsearch 进行拓展。


三  odoo + Celery应对复杂长事务处理场景



odoo 的长事务处理上,让许多用户比较诟病的地方是确认销售订单的用户体验,一旦行数过多,耗时就比较久。这里引入了 Celery 这样一个应对方式,把一些长事务的处理放到后台进行。也就是说,用户点击 confirm 的时候产生一个异步的任务,然后交到后台进行处理。本质上并不是提高了系统处理的能力,因为仍旧使用的是 odoo 的方法,只是改善了用户体验。

  • 赋予 odoo 分布式任务处理能力

引入 Celery 之后还可以带来一些其他好处。通常我们在部署 odoo 的时候,特别是在一些大型应用的情况下,我们不会仅仅做单机部署,而是可能会部署在两台,甚至三台、四台上面。那其中会有1—2台的服务器是面对用户的,即用户会集中地登录到这些服务器上来进行业务的处理。同时,我可能会留1—2台的服务器用于后端的数据处理,去跑定时任务,它会跑这个 Celery 的 Worker,来进行数据的处理。

  • 在社区模块的基础上增强:Celery Worker 按照配置文件在 odoo 进程内启动

之前社区里有一个 queue_job 的异步处理的框架,但在目前多应用服务器(Application server)的情况下,它会造成一个混乱,因为它无法形成一个很好的队列的方式来处理。引入 Celery 框架之后,借助 Celery 比较好的处理机制,就解决了这个问题。因为只要有 Celery Worker 接受了任务,其他的 Celery Worker 就不会再接收这个任务。这个模块是社区已有的,我们在此基础上做了一些增强。

在目前的 Celery 实践中,broker 这一层使用了 Rabbit MQ,然后把计算结果放在 Redis 上,这样会有比较好的性能。在一些复杂长事务的处理上,比较好的实现了集群的分布式事务处理。这个模块也做了一个分享,是基于odoo13版本上的改造。→ odoo_celery模块


四  odoo + MQ Consumer应对流数据处理场景

从本质上来说,关系型数据库是把数据沉淀下去了,但是数据的产生本身是无穷无尽的,从这个角度来说,所有的数据都不是静止的,都是没有终止的。在关系型数据库处理数据的过程中,会有大量、反复的 IO 的读写。也就是说,这个数据录到数据库里面之后,它被一次又一次的搬运,然后附加不同的状态。这是一个非常消耗性能、消耗时间的处理方式。

如果有了流数据的处理方式,我们是不是可以在消息过来的时候,把它在内存中经过流式处理之后再存储。基于这样的一个设想,在实践上引入了 Rabbit MQ。即,外部有很多的数据源、数据通道(APP1、2、3),把它们放到 Rabbit MQ 里面,然后产生消息队列。有了消息队列后,odoo 就可以启动 Worker,接入数据之后会有一个 callback 的处理方法,可以指定任意的用 Python 写的方法。这样,我们就实现了数据流式的处理。也就是说,这个数据是持续的产生,那我就持续地消费、持续地处理。然后把处理好的数据沉淀到不同的地方去,有沉淀到数据库的,有发布到 Redis 里面的,有发布到 Elasticsearch 上面的。有了 MQ consumer,再加上 callback 的方法,我们可以做很多流数据的处理。

目前这个模块已经做到可配置监听多个消息队列。通过 odoo 的 Server Action 做到后台可以配置,即定义任意多的消息队列,每个消息队列可以启动自己对应的 callback 处理方法,也就是说定义到 Server Action 里的方法(一个消息队列对应一个server action)。基本原理是用了 Python 的 multi_process + with api.Environment.manage()。

以上是一个尝试。对于流数据处理的成熟的框架,最近比较热的是 Flink 框架,在流数据处理上已经比较完备。Flink 也实现了一个 Python 的 udf,就是 user define function 的处理,可以重点关注。

现在包括 Spark、kafka、pulsar 等框架上,基本都推出了流数据的概念。甚至前段时间我们国内分享的涛思数据的 TDengine 上面,也有流数据的概念。


五  odoo + Dgraph 应对复杂关系场景

Dgraph 是一个分布式的图形数据库。图形数据库是一种非关系型数据库,它应用图形理论存储实体之间的关系信息。相对于关系数据库中的各种关联表,图形数据库中的关系可以通过关系能够包含属性这一功能来提供更为丰富的关系展现方式。比如商圈内各商户之间的价格表要 join 10个以上的表,性能较差,关系型数据库的查询效率远低于图数据库。

通过 Dgraph 赋予 odoo GraphQL 的能力后,可以约定进行一个 json 的查询。比方说要查询这个数据,拋一个低一层的关系:我要查询这个人的姓名是什么,他的朋友关系是什么,朋友的朋友的关系是什么?可以实现这样逐层的查询,而且这个构造天然是支持的。基于 GraphQL 查询可以提供更灵活的 restful 接口。一方面,提高了接口获取数据的能力;另一方面,开发的效率也会有很大的提升。

这个查询的层次结构实际上是一个 json 的格式,约定好这个格式之后,可以无限地往下查询,只要数据有支撑这样的数据结构。

目前这个模块也做了封装,可以查看。(*该模块开发时间较早,而 Dgraph 的迭代也比较快,可能需要做一些测试才能使用)→ odoo_dgraph模块


六  odoo + Seaweeds 应对多文件,图片存储场景


Odoo 目前在存储这些附件图片的时候,会指定静态文件的地址,存储在硬盘上面。无论对于读取,还是管理,都不太方便。尤其 odoo 不单纯是一个内部应用的时候。所以,在这个地方做了一个增强,引入 Seaweeds。Seaweeds 是一个高效的图片、文件存储系统,有比较高效的检索能力、高并发能力;存储的图片可以指定任意大小的格式进行传输;有分布式的系统。

在实践中,我们定义了一个 ImageMixin 的方法,使用到了Server Action。就是数据、图片在 Mixin 这里被继承之后,在任意的 model 上面,只要你有图片这个字段,在进行配置之后就可以发布到 Seaweeds 上。这是第一个抽象。第二个抽象是对文件做的抽象,把这个文件绑定到 lr.attachment 上,只要你在 lr.attachment 上面上传了这个文件,它就天然地可以发布到 Seaweeds 服务器上。按照这个原理的话,我们可以进一步地拓展,比方说阿里的 OSS 存储,或者国内有名的七牛,或者腾讯的存储,都可以使用这个方式。这样一来,在一个高并发的场景下,这些文件、图片的读取,就不会成为系统的瓶颈。
odoo_seaweeds模块



Q & A(部分)


01

有专门配置权限的模块吗?

其实这是odoo的框架里比较重要的一个功能,它有group这种概念,有权限的概念。定义好group之后,给它相应地绑定资源,比方说展示的资源,像菜单、视图,它就可以控制这个资源。然后再给它数据库的资源和访问权限,它有记录规则、访问数据的过滤,那这些权限就可以颗粒度非常好的进行定义。
在这里的话,我推荐社区里的一个base_user_role,在group上面会有一个角色的概念,比较适合产品化的开发。如果是做平台的开发,那大家可以尝试这个模块。它实际上是在group的基础上,又增加了一层,这样可以把许多的group绑定在一起形成一个角色,比方说销售经理,采购经理。那实际上odoo的销售经理、采购经理的权限,在一些大型的系统里比较复杂,如果用了角色这个概念,可以颗粒度比较好的定义。group的话,我们就可以分成有权限的group、菜单的group,还有这个模型的group等,然后通过role这个角色把它聚合起来。我建议如果是权限管理,可以用这个。


02

SANIC部署起来的难易程度如何?

SANIC真的是一个非常轻巧、高效的框架。部署起来非常快。如果你熟悉flask的话,很容易就起来了。

03

SANIC在这里是API网关的概念吗?odoo作为web server?

在那个结构上,基本就不用odoo来提供服务。odoo它有自己controller那个写法,但odoo本身并发的性能并不太好,所以odoo实际上在这里就是一个系统后台。但是SANIC实现了odoo同等的验证的机制。在这里,你可以把SANIC理解成一个网关。

04

对于odoo学习,对新手有什么建议吗?

新手的话,odoo官方的文档就已经非常全了。对新手没什么特别好的建议,就是动手去写代码,学会在写的过程中去问问题,这是我的建议。只要你有Python的基础,应该能比较快地学会。甚至你没有Python的基础,你有java其他一些语言的基础,入门也非常快。


往期推荐

Jeff Wang:基于开源的企业信息化服务生态