1. 问题描述
在使用 Egg.js 作为后端接口服务,做 Login with Twitter 功能的时候,需要把 Twitter OAth 认证获取到的oauth_token 以及 oauth_token_secret进行缓存,在进行 Twitter 接口调用回调的时候需要用于oauth_access_token以及oauth_access_token_secret获取的参数。
无论是使用node-cache进行数据的缓存,还是用一个对象临时存储数据到内存中,当在回调接口里去获取oauth_token_secret的时候,均获取不到!
2. 问题定位
当写两个接口,一个用于固定数据的缓存,一个接口用于获取缓存数据并输出日志,测试结果为一次请求获取不到数据,一次请求能获取到数据!
在本地测试时,无论如何访问都能够获取到数据!
从测试结果现象来看,一次请求获取不到数据,一次请求能获取到数据!,说明服务一定是存在集群化部署的。
通过官方文档,找到了关于多进程模型和进程间通讯的描述说明。
单个 Node.js 实例在单线程环境下运行。为了更好地利用多核环境,用户有时希望启动一批 Node.js 进程用于加载。 集群化模块使得你很方便地创建子进程,以便于在服务端口之间共享。
由于框架的多进程模型,我们存储在内存中的数据在不同进程中并不是共享的。这就导致了一次请求能获取数据,一次请求获取不到数据。
3. 解决方案
既然了解了问题的根源,那解决问题就简单了。
两种问题解决方案:
- 实现进程之间的数据共享
- 持久化数据,而不是存储在内存中,每次访问的时候都重新查询
此处我们主要实现的是进程之间的数据共享。
3.1 Egg 服务创建
$ npm init egg
3.2 添加设置缓存的 Service
创建 app/srvice/cache.js文件,并写入如下内容:
const NodeCache = require('node-cache');
const cache = new NodeCache();
module.exports = {
get cache() {
return cache;
},
set(key, value, ttl) {
cache.set(key, value, ttl || 15 * 60);
},
};
此处使用的是node-cache进行数据缓存的管理。
npm install node-cache --save
3.3 多进程之间缓存数据的更新
创建 app/app.js文件,并写入如下内容:
module.exports = app => {
app.messenger.on('cache_set', data => {
const ctx = app.createAnonymousContext();
ctx.runInBackground(async () => {
await ctx.service.cache.set(data.key, data.value, data.ttl);
});
});
};
app.js 的代码会执行在 Worker 进程上,他们通过框架封装的 messenger 对象进行进程间通讯(IPC)。
oauth_token以及oauth_token_secret存储:
在controller的方法里,我们使用ctx.app.messenger.sendToApp来进行消息的发送,发送给所有的 app 进程。
async index() {
const { ctx } = this;
const oauth_token = 'QThhDwAAAAABn36WAAASDFSDFFV';
const oauth_token_secret = 'EI8MYe1AJexCVgWgxqweqwetQWEDGT';
ctx.app.messenger.sendToApp('cache_set', {
key: oauth_token,
value: { oauth_token_secret },
});
ctx.body = 'hi, egg';
}
获取缓存数据:
通过ctx.service.cache来进行缓存数据的获取。
async testCache() {
const { ctx } = this;
const myCache = ctx.service.cache.cache;
const oauth_token = 'QThhDwAAAAABn36WAAASDFSDFFV';
const { oauth_token_secret } = myCache.get(oauth_token) || {};
ctx.body = {
code: 0,
message: 'SUCCESS'
};
}
至此,我们便能够实现了在多进程之间进行数据的共享!