Odoo事件驱动架构:WebSocket实时通讯与消息队列实现

Odoo事件驱动架构:WebSocket实时通讯与消息队列实现

【免费下载链接】odoo Odoo. Open Source Apps To Grow Your Business. 【免费下载链接】odoo 项目地址: https://gitcode.com/GitHub_Trending/od/odoo

架构概览

Odoo采用事件驱动架构实现实时通讯,核心通过WebSocket协议和消息队列机制处理跨模块、跨客户端的实时数据交换。该架构主要包含以下组件:

WebSocket连接建立流程

Odoo的实时通讯从客户端WebSocket连接建立开始,完整流程如下:

mermaid

关键实现代码位于addons/bus/static/src/services/bus_service.jsensureWorkerStarted函数,负责创建WebSocket连接并进行身份验证:

async function ensureWorkerStarted() {
    if (!connectionInitializedDeferred) {
        connectionInitializedDeferred = new Deferred();
        let uid = Array.isArray(session.user_id) ? session.user_id[0] : user.userId;
        await workerService.ensureWorkerStarted();
        await workerService.registerHandler(handleMessage);
        workerService.send("BUS:INITIALIZE_CONNECTION", {
            websocketURL: `${params.serverURL.replace("http", "ws")}/websocket?version=${session.websocket_worker_version}`,
            db: session.db,
            debug: odoo.debug,
            lastNotificationId: legacyMultiTab.getSharedValue("last_notification_id", 0),
            uid,
            startTs: startedAt.valueOf(),
        });
    }
    await connectionInitializedDeferred;
}

消息队列实现机制

Odoo利用PostgreSQL的NOTIFY/LISTEN机制实现轻量级消息队列,避免了独立消息队列服务的部署复杂性。核心实现位于addons/bus/models/bus.py

消息发送流程

  1. 应用模块调用bus.bus._sendone发送消息
  2. 消息被存储到bus_bus表中
  3. 事务提交后触发PostgreSQL NOTIFY [addons/bus/models/bus.py#L148]
@self.env.cr.postcommit.add
def notify():
    payloads = get_notify_payloads(
        list(self.env.cr.postcommit.data.pop("bus.bus.channels"))
    )
    with odoo.sql_db.db_connect("postgres").cursor() as cr:
        for payload in payloads:
            cr.execute(
                SQL(
                    "SELECT %s('imbus', %s)",
                    SQL.identifier(ODOO_NOTIFY_FUNCTION),
                    payload,
                )
            )

消息接收与分发

服务器通过持续监听PostgreSQL的imbus频道接收消息,并分发给相应的WebSocket连接:

def loop(self):
    """ Dispatch postgres notifications to the relevant websockets """
    _logger.info("Bus.loop listen imbus on db postgres")
    with odoo.sql_db.db_connect('postgres').cursor() as cr, \
         selectors.DefaultSelector() as sel:
        cr.execute("listen imbus")
        cr.commit()
        conn = cr._cnx
        sel.register(conn, selectors.EVENT_READ)
        while not stop_event.is_set():
            if sel.select(TIMEOUT):
                conn.poll()
                channels = []
                while conn.notifies:
                    channels.extend(json.loads(conn.notifies.pop().payload))
                # relay notifications to websockets
                websockets = set()
                for channel in channels:
                    websockets.update(self._channels_to_ws.get(hashable(channel), []))
                for websocket in websockets:
                    websocket.trigger_notification_dispatching()

消息总线核心实现

消息总线是Odoo事件驱动架构的核心,负责消息的存储、过滤和分发。主要功能包括:

1. 消息存储与清理

addons/bus/models/bus.py中定义的bus.bus模型负责消息的持久化存储:

class BusBus(models.Model):
    _name = 'bus.bus'
    _description = 'Communication Bus'
    
    channel = fields.Char('Channel')
    message = fields.Char('Message')
    
    @api.autovacuum
    def _gc_messages(self):
        gc_retention_seconds = int(
            self.env["ir.config_parameter"]
            .sudo()
            .get_param("bus.gc_retention_seconds", DEFAULT_GC_RETENTION_SECONDS)
        )
        timeout_ago = fields.Datetime.now() - datetime.timedelta(seconds=gc_retention_seconds)
        # 直接SQL删除过期消息,提高效率
        self.env.cr.execute("DELETE FROM bus_bus WHERE create_date < %s", (timeout_ago,))

2. 消息发送API

_sendone方法提供了发送消息的标准接口,支持事务提交后异步发送:

@api.model
def _sendone(self, target, notification_type, message):
    """Low-level method to send notification to target"""
    self._ensure_hooks()
    channel = channel_with_db(self.env.cr.dbname, target)
    self.env.cr.precommit.data["bus.bus.values"].append({
        "channel": json_dump(channel),
        "message": json_dump({
            "type": notification_type,
            "payload": message,
        })
    })
    self.env.cr.postcommit.data["bus.bus.channels"].add(channel)

3. 消息订阅与过滤

客户端通过_poll方法订阅指定频道的消息,支持基于时间戳和消息ID的过滤:

@api.model
def _poll(self, channels, last=0, ignore_ids=None):
    # first poll return the notification in the 'buffer'
    if last == 0:
        timeout_ago = fields.Datetime.now() - datetime.timedelta(seconds=TIMEOUT)
        domain = [('create_date', '>', timeout_ago)]
    else:  # else returns the unread notifications
        domain = [('id', '>', last)]
    if ignore_ids:
        domain.append(("id", "not in", ignore_ids))
    channels = [json_dump(channel_with_db(self.env.cr.dbname, c)) for c in channels]
    domain.append(('channel', 'in', channels))
    notifications = self.sudo().search_read(domain, ["message"])
    # format and return notifications
    result = []
    for notif in notifications:
        result.append({
            'id': notif['id'],
            'message': json.loads(notif['message']),
        })
    return result

客户端消息处理

客户端接收消息后,通过事件总线分发给相应的处理函数。addons/bus/static/src/services/bus_service.js中的消息处理流程如下:

function handleMessage(messageEv) {
    const { type, data } = messageEv.data;
    switch (type) {
        case "BUS:NOTIFICATION": {
            const notifications = data.map(({ id, message }) => ({ id, ...message }));
            state.lastNotificationId = notifications.at(-1).id;
            legacyMultiTab.setSharedValue("last_notification_id", state.lastNotificationId);
            for (const { id, type, payload } of notifications) {
                notificationBus.trigger(type, { id, payload });
                busService._onMessage(env, id, type, payload);
            }
            break;
        }
        // 其他消息类型处理...
    }
}

客户端可以通过订阅特定事件类型来接收消息:

// 订阅通知
busService.subscribe('mail.notification', (payload) => {
    console.log('收到新邮件通知:', payload);
    renderNotification(payload);
});

多标签页同步机制

Odoo通过共享Worker和本地存储实现多标签页间的状态同步,核心实现位于addons/bus/static/src/multi_tab_shared_worker_service.js

同步流程如下:

  1. 主标签页建立WebSocket连接
  2. 其他标签页通过SharedWorker共享连接
  3. 使用localStorage同步最后消息ID
// 多标签同步实现
legacyMultiTab.setSharedValue("last_notification_id", state.lastNotificationId);

// 从共享存储获取最后消息ID
const lastId = legacyMultiTab.getSharedValue("last_notification_id", 0);

性能优化策略

Odoo的事件驱动架构采用多种优化策略确保高并发场景下的性能:

1. 消息负载拆分

当消息负载过大时,自动拆分消息以适应PostgreSQL NOTIFY的 payload 限制:

def get_notify_payloads(channels):
    """Generates json payloads for imbus NOTIFY, splitting large payloads"""
    if not channels:
        return []
    payload = json_dump(channels)
    if len(channels) == 1 or len(payload.encode()) < NOTIFY_PAYLOAD_MAX_LENGTH:
        return [payload]
    else:
        pivot = math.ceil(len(channels) / 2)
        return (get_notify_payloads(channels[:pivot]) +
                get_notify_payloads(channels[pivot:]))

2. 消息过期清理

自动清理过期消息,避免存储膨胀:

@api.autovacuum
def _gc_messages(self):
    gc_retention_seconds = int(
        self.env["ir.config_parameter"]
        .sudo()
        .get_param("bus.gc_retention_seconds", DEFAULT_GC_RETENTION_SECONDS)
    )
    timeout_ago = fields.Datetime.now() - datetime.timedelta(seconds=gc_retention_seconds)
    self.env.cr.execute("DELETE FROM bus_bus WHERE create_date < %s", (timeout_ago,))

3. 连接池管理

通过连接池复用数据库连接,减少连接建立开销:

with odoo.sql_db.db_connect('postgres').cursor() as cr, \
     selectors.DefaultSelector() as sel:
    cr.execute("listen imbus")
    cr.commit()
    conn = cr._cnx
    sel.register(conn, selectors.EVENT_READ)

实际应用场景

Odoo的事件驱动架构在多个模块中得到应用,典型场景包括:

1. 实时通知系统

addons/mail/模块使用消息总线实现邮件和通知的实时推送:

# 发送邮件通知
self.env['bus.bus']._sendone(
    partner.id, 
    'mail.notification', 
    {'message_id': message.id, 'title': message.subject}
)

2. 库存变动实时更新

addons/stock/模块通过消息总线实时推送库存变动:

# 库存变动通知
self.env['bus.bus']._sendone(
    'stock_channel', 
    'stock.quantity_change',
    {'product_id': product.id, 'new_quantity': product.qty_available}
)

3. 多用户协作编辑

addons/document/模块利用WebSocket实现文档的实时协作编辑。

部署与扩展建议

在生产环境部署Odoo的事件驱动架构时,建议考虑以下几点:

  1. 数据库优化:确保PostgreSQL配置足够的连接数和内存
  2. 负载均衡:使用Redis等外部消息队列替代PostgreSQL NOTIFY以支持更大规模部署
  3. 监控:通过addons/bus/static/src/debug/工具监控消息总线状态
  4. 水平扩展:多服务器部署时确保WebSocket连接粘性会话(sticky session)

总结

Odoo的事件驱动架构通过WebSocket和基于PostgreSQL的消息队列,构建了高效的实时通讯系统。该架构具有以下特点:

  • 松耦合:通过事件总线解耦系统组件,提高可扩展性
  • 实时性:WebSocket协议确保消息低延迟传递
  • 可靠性:消息持久化和重试机制保证消息不丢失
  • 高效性:多种优化策略确保高并发场景下的系统性能

核心实现代码分布在addons/bus/models/bus.pyaddons/bus/static/src/目录下,通过这些组件的协同工作,Odoo实现了跨模块、跨客户端的实时数据交换能力。

【免费下载链接】odoo Odoo. Open Source Apps To Grow Your Business. 【免费下载链接】odoo 项目地址: https://gitcode.com/GitHub_Trending/od/odoo

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值