[eggjs/egg][Feature Request] 全局变量的一些想法

2025-11-04 197 views
2
建议 利用 Agent 作为全局变量的存取

messenger 的 send 和 on 机制能否像 http 通讯一样。 Agent作为服务器 ,Worker 是客户端。 Worker 发送请求 (send) 后等待服务器(Agent)返回结果,Agent 接受数据(on)处理并返回,Workersend接收到返回的数据并后续处理。 利用 Agent 唯一的特性,把数据挂载到 Agent 的 app 上作为数据的 Store 来使用。从而达到全局变量的目的。

目前 messenger send 后返回自身,并不等待返回 Agent on 中的 return

Background

全局变量总是一个绕不开的话题,因为 eggjs 多进程模型导致各个 Worker 之间共享动态的全局变量变得不可能。也导致一些很简单的事情变得复杂。 例如目前我遇到的一个需求。 需要判断一大堆摄像头是否在线状态,需要请求一个第三方的反代服务器去获得 hls 流。然后判断 hls 流是否能 Get 到来判定在线及延迟。

// app.js   初始化后从第三方平台获取一个非常大的摄像头地址列表
class AppBootHook {
    async serverDidReady() {
       const bigList = await axios.get('xxx.com/camear/list');
       this.app.my = {
              currentChecker: 0, // 当前正在检查的数量
              maxChecker: 10,   // 同时检查的最大数量
              concurrency: 2 // 每次检查的并发数量
              nextCheckNum: 0 // 下一个需要检测的序号
              bigList : bigList , // 检查列表
       }
    }
}
module.exports = AppBootHook;

// schedule/check.js 创建一个任务计划
module.exports = {
    schedule: {
        type: 'worker',
        interval: “10s”,
        immediate: true,
    },
    async task(ctx) {
        const check = ctx.app.my;
        try {
          if (check.nextCheckNum > check.needCheckArr.length - 1) {
                    // 如果当前 bigList 中所有摄像头更新完了。更新 bigList 
          }else{
            if (check.currentChecker >= check.maxChecker) return;
                // 每次并发出异步的检查请求 更新 currentChecker 的数量 
               //  检查时间也许很长 1分钟以上等
                while (i <= check.concurrency ) {
                    let id = check.bigList [check.nextCheckNum].id;
                    check.currentChecker++;
                    axios.get(`/checkCameraById?id=${id}`).then().catch().finally(() => {
                        check.currentChecker--;
                    });
                    i++;
                    check.nextCheckNum++;
            }
          }
        } catch (error) {
            ctx.app.logger.error(error);
        }
    },
};

如果只是单线程,以上十分简单。维护挂载在 app.my 上面这个全局变量即可。 多线程的 egg.js 目前看到的 issues 推荐的方案都是把全局变量放到 Redis 或者 数据库 里面去~ 但是我感觉这不是杀鸡用牛刀么~ 只是这么小一个全局变量就需要那么重的外部存储。 而且不管 Redis 还是 数据库,多一个外部的东西就多一份不可控的因素,不太能接受。

Proposal

我目前采用的办法是通过worker广播消息的方式来维护全局变量。 某个worker全局变量改变后 将最新的全局变量推送给其他 Worker。这样相当于每个worker都存储了一份全局变量。不太划算。不知道有更好的最佳实践没

其他

https://github.com/eggjs/egg/issues/2673 多数人理解应用就是整个程序吧, 这里有歧义。

文档方面是不是增加多进程注意事项的 FAQ。 毕竟本地开发环境是单进程,到生产环境就变成多进程。全局变量 app等不共享这个没注意的话坑挺大的。

回答

3

分布式架构没有全局变量一说,两台服务器的时候是如何考虑的。

3

@popomore 但是大部分的个人开发者不会是分布式的对吧。 只是想开发点个人应用 没必要 Redis 什么的都整上。增加复杂度。 是不是能在改动不大的情况下 提升一下个人开发者的友好度

我想的是只要Worker -->Agent 这种1对1的情况下,如果 messenger send 过去能拿到 on 的 return。有些事情就方便很多。 例如自行在 eggjs 内实现 Store

1

这个场景非常小,一旦运用了这个就可能导致无法把服务部署到2台以上服务器。 可以写一个插件来做 egg-memory-store 之类的.

2

@ngot Worker send 拿到 Agent on 的return 为什么会导致 '无法把服务部署到2台以上服务器'呢?

如果可以实现以下伪代码, 应该就可以通过把 Agent 作为 Store。通过 messenger 进行传递,对外封装 get set 等简单 Api 来做这个插件。

// agent.js
class AgentBootHook {
    constructor(agent) {
        this.agent = agent;
    }
    configWillLoad() {
          this.agent.myData = {a:123,b:233}
    }
    async serverDidReady() {
        this.agent.messenger.on('getData', () => {
             return this.agent.myData; // *1
        });
        this.agent.messenger.on('setData', (data) => {
             this.agent.myData = Object.assign(this.agent.myData,data);
             return "ok"; // *2
        });
    }
}

// some Controller

class DataController extends Controller {
    async getGlobalData() {
       // ↓ ---伪代码  promise 返回 *1 的 agnet.myData  {a:123,b:233}
       const data = await this.app.messenger.sendToAgent('getData');        
       this.ctx.body = data;
    }

    async setGlobalData() {
        const {data} = this.ctx.request.body;
        // ↓ ---伪代码  promise 返回 *2 的 "ok"
        const state = await this.app.messenger.sendToAgent('setData',data); 
        this.ctx.body = {
            code: 200,
            msg: 'ok',
            result: this.app.my.Monitor.check,
        };
    }
}
1

by the way

app.messenger.sendToAgent('msg1');    
app.messenger.sendToAgent('msg2');    

agent 收 msg1 msg2,agent不死的情况下。一定能收到并且保证接收顺序是 msg1 msg2 么~ 我是不是问的太极端了~ 异步的事说不清楚的吧?

3

这种自己做就好了

5

@atian25 这个插件是我自己做。但是实现这个插件的前提是 send 能拿 on 的 return。 即这一条 伪代码 能实现。

const data = await this.app.messenger.sendToAgent('getData');  

目前我的理解,这个需要框架层面的支持 我自己做插件是办不到的。 所以我就来问问 有没有 可能 支持上面的伪代码的实现。

1

这个不需要框架支持,你可以自己封装一个 app.sendXX 的方法,里面自己处理下事件就可以了。

建议:不要在单机上花太多时间搞这种 hack,意义不大。

4

看来全局变量是不行啦,redis走起