设为首页 收藏本站
查看: 1026|回复: 0

[经验分享] jsGen技术总结之:在Node.js中构建redis同步缓存

[复制链接]

尚未签到

发表于 2015-7-21 08:06:15 | 显示全部楼层 |阅读模式
  以前版本的jsGen直接利用Node.js的Buffer内存缓存数据,这样带来的一个问题是无法开启Cluster,多个Node.js进程的内存都是相互独立的,不能相互访问,不能及时更新数据变动。
  新本(0.6.0)jsGen使用了第三方内存数据库redis作为缓存,如此以来多进程或多机运行jsGen成为可能。redis作为内存缓存的唯一缺陷就是——异步驱动,读取或写入数据都得callback!。



var myData;
redisCache.get(key, function (err, data) {
// callback读取缓存数据
myData = data;
});
redisCache.put(key, myData, function (err, reply) {
// 写入缓存,callback确认写入结果
});
  
  那么,有没有办法构建一个“同步”的redis缓存呢,使得读取、写入缓存像下面一样简单:



// 从缓存读取数据
var myData = redisCache.data;
// 往缓存写入数据
redisCache.data = myData;
  

redis同步缓存原理
  我采用JavaScript的getter、setter和闭包构建了这个redis同步缓存:
  利用闭包创建一个缓存数据镜像,读取缓存时,getter从镜像读取;写入缓存时,setter把值写入镜像,再写入redis数据库。
  如果开启多进程,缓存镜像仍然是分布在各个进程中,是相互独立的。如果一个进程更新了缓存数据,如何及时更新其它进程的缓存镜像呢?这就用到了redis的Pub/Sub系统,setter更新缓存时,更新数据写入数据库后,发布更新通知,其它redis进程收到通知就从redis数据库读取数据来更新镜像。
  各进程的缓存虽然不是真正的同步更新,但也算及时更新了,可以满足一般业务需要。缺点是多消耗了一倍的内存。对于频繁访问更新的小数据,如config数据,很适合采用这个方案。下面是来自jsGen/lib/redia.js的源代码,通过一个config的json数据模板构建一个redis同步缓存的config对象,数据不但写入了redis数据库,还按照一定频率写入MongoDB数据库。

jsGen源代码片段



// clientSub:专用于订阅的redis client
// client[globalCacheDb]:存取数据的redis client
// 异步任务函数then及then.each,见 https://github.com/zensh/then.js
function initConfig(configTpl, callback) {
var config = {},
// 新构建的config缓存
_config = union(configTpl),
// 从configTpl克隆的config闭包镜像
subPubId = MD5('' + Date.now() + Math.random(), 'base64');
// 本进程的唯一识别ID

callback = callback || callbackFn;
var update = throttle(function () {
jsGen.dao.index.setGlobalConfig(_config);
}, 300000); // 将config写入MongoDB,每五分钟内最多执行一次
function updateKey(key) {
// 更新镜像的key键值
return then(function (defer) {
client[globalCacheDb].hget('config.hash', key, defer);
// 从redis读取更新的数据
}).then(function (defer, reply) {
reply = JSON.parse(reply);
_config[key] = typeof _config[key] === typeof reply ? reply : _config[key];
// 数据写入config镜像
defer(null, _config[key]);
}).fail(errorHandler);
}
clientSub.on('message', function (channel, key) {
var ID = key.slice(0, 24);
key = key.slice(24);
// 分离识别ID和key
if (channel === 'updateConfig' && ID !== subPubId) {
// 来自于updateConfig频道且不是本进程发出的更新通知
if (key in _config) {
updateKey(key);  // 更新一个key
} else {
each(_config, function (value, key) {  // 更新整个config镜像
                    updateKey(key);
});
}
}
});
clientSub.subscribe('updateConfig');
// 订阅updateConfig频道

each(configTpl, function (value, key) {
// 从configTpl模板构建getter/setter,利用Hash类型存储config
        Object.defineProperty(config, key, {
set: function (value) {
then(function (defer) {
if ((value === 1 || value === -1) && typeof _config[key] === 'number') {
_config[key] += value;
// 按1递增或递减,更新镜像,再更新redis
client[globalCacheDb].hincrby('config.hash', key, value, defer);
} else {
_config[key] = value;
// 因为redis存储字符串,下面先序列化。
client[globalCacheDb].hset('config.hash', key, JSON.stringify(value), defer);
}
}).then(function () {
// redis数据库更新完成,向其他进程发出更新通知
client[globalCacheDb].publish('updateConfig', subPubId + key);
}).fail(jsGen.thenErrLog);
update();  // 更新MongoDB
            },
get: function () {
return _config[key];
// 从镜像读取数据
            },
enumerable: true,
configurable: true
});
});
// 初始化config对象的值,如重启进程后,如果redis数据库原来存有数据,读取该数据
then.each(Object.keys(configTpl), function (next, key) {
updateKey(key).then(function (defer, value) {
return next ? next() : callback(null, config);
// 异步返回新的config对象,已初始化数据值
}).fail(function (defer, err) {
callback(err);
});
});
return config;  // 同步返回新的config对象
}
  

初始化代码,详见jsGen/app.js



then(function (defer) {
redis.initConfig(jsGen.lib.json.GlobalConfig, defer);
// 异步初始化config缓存
}).then(function (defer, config) {
jsGen.config = config;
// config缓存引用到全局变量jsGen
// ...
}).then(function (defer, config) {
// ...
});
  

调用示例



// 从config缓存取配置值并new一个LRU缓存
jsGen.cache.user = new CacheLRU(jsGen.config.userCache);
// 更新网站访问次数
jsGen.config.visitors = 1; // 网站访问次数+1
  

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-88831-1-1.html 上篇帖子: Redis Java客户端Jedis入门 下篇帖子: 【转】NoSQL初探之人人都爱Redis:(2)Redis API与常用数据类型简介
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表