前言:
话说至今为止我还是不太清除事件存在的意义,然后各种纠缠 Geemo 兄,然后他给我讲了一大顿,各种洗脑。最后我一直保持我智障的状态,然后他甩了一个demo给我,我定睛一看,我勒个去,似曾相识的东东,这尼玛不就是我朴大大,写的那个事件解决雪崩问题的demo升级版么。
回顾(朴灵大大):
雪崩问题是在缓存失效的情景下,大并发高访问量同时涌入数据库中查询,数据库无法同时承受如此大的查询请求,进而往前影响到网站整体响应缓慢。那么在Node.js中如何应付这种情景呢。
var select = function (callback) { db.select("SQL", function (results) { callback(results); }); };
以上是一句数据库查询的调用,如果站点刚好启动,这时候缓存中是不存在数据的,而如果访问量巨大,同一句SQL会被发送到数据库中反复查询,影响到服务的整体性能。一个改进是添加一个状态锁。
var status = "ready"; var select = function (callback) { if (status === "ready") { status = "pending"; db.select("SQL", function (results) { callback(results); status = "ready"; }); } };
但是这种情景,连续的多次调用select发,只有第一次调用是生效的,后续的select是没有数据服务的。所以这个时候引入事件队列吧:
var proxy = new EventProxy(); var status = "ready"; var select = function (callback) { proxy.once("selected", callback); if (status === "ready") { status = "pending"; db.select("SQL", function (results) { proxy.emit("selected", results); status = "ready"; }); } };
这里利用了EventProxy对象的once方法,将所有请求的回调都压入事件队列中,并利用其执行一次就会将监视器移除的特点,保证每一个回调只会被执行一次。对于相同的SQL语句,保证在同一个查询开始到结束的时间中永远只有一次,在这查询期间到来的调用,只需在队列中等待数据就绪即可,节省了重复的数据库调用开销。由于Node.js单线程执行的原因,此处无需担心状态问题。这种方式其实也可以应用到其他远程调用的场景中,即使外部没有缓存策略,也能有效节省重复开销。此处也可以用EventEmitter替代EventProxy,不过可能存在侦听器过多,引发警告,需要调用setMaxListeners(0)移除掉警告,或者设更大的警告阀值。
理解:
之前看这个一头雾水,首先状态锁的问题,状态锁是不是在锁一个查询。乍一看,感觉并无什么卵用。然后事件怎么能通过事件返回了所有并发的结果而进行一次查询。自己画了一张图,来解释下,首先这依赖于单线程和异步,状态锁在并发状态下被改变即是在所有异步并发中被改变(单线程);那么我们假设第一个异步访问,他执行了以上代码,当执行完查询,他会在当前时间线以前以后的状态锁改变(跳过数据库查询步骤),阻止其他异步并发的查询,触发所有事件,返回查询的内容给所有异步并发。下一阶段,新的并发请求来到,再次进行查询。这样面对很大的并发时候之后有很少量的数据查询,极大的减轻了数据库鸭梨。 画了一张图:
后记:
这个东西显然勇于相同请求的,如载入首页内容。如果执行不同的数据库查询,在大并发情况下,将会导致最新结束的查询结果被copy至后面的一切请求。用了一下午时间折腾懂了,但是...我还是不知道事件有什么卵用。