From 349546a89d4f8df2618360d6ca5cfbec48326652 Mon Sep 17 00:00:00 2001 From: theharveyz Date: Tue, 19 Dec 2017 02:42:00 +0800 Subject: [PATCH 1/5] fix lint --- src/middlewares/view_cache.js | 107 +++++++++++++++++++++++++--------- 1 file changed, 80 insertions(+), 27 deletions(-) diff --git a/src/middlewares/view_cache.js b/src/middlewares/view_cache.js index aa23e70..57ca281 100644 --- a/src/middlewares/view_cache.js +++ b/src/middlewares/view_cache.js @@ -74,49 +74,102 @@ function ViewCacheMiddleware(cache, logger) { ttl, headersFilter, namespace, + lockNamespace, hashStrategy } = Object.assign({ ttl: 60, headersFilter: defaultHeadersFilter, hashStrategy: defaultHashStrategy, - namespace: 'view' + namespace: 'view', + lockNamespace: 'view:lock' }, options); + + const getCache = async (ns, key) => await cache.namespace(ns).get(key) || { + headers: [], + body: null + }; + return wrapper(async (req, res, next) => { const cacheKey = requestToCacheKey(req, hashStrategy); - const { - headers: cachedHeaders = [], - body: cachedBody - } = await cache.namespace(namespace).get(cacheKey) - || { - headers: [], - body: null - }; - if (req.query.flush !== 'true' && cachedBody) { + + //加锁避免缓存失效风暴 + const lockTTL = ttl - 1; + + const sendHit = (headers, body) => { logger.debug('View cache hit by key %s', cacheKey); - if (cachedHeaders.length > 0) { - cachedHeaders.forEach(([key, value]) => { + if (headers.length > 0) { + headers.forEach(([key, value]) => { res.setHeader(key, value); }); } res.setHeader('X-View-Cache-Hit', cacheKey); - res.send(cachedBody); + res.send(body); + }; + + //重设send方法 + const resetSend = () => { + res.realSend = res.send; //eslint-disable-line no-param-reassign + res.send = (body) => { //eslint-disable-line no-param-reassign + logger.debug('View cache missed by key %s, creating...', cacheKey); + res.setHeader('X-View-Cache-Miss', cacheKey); + res.setHeader('X-View-Cache-Expire-At', moment().add(ttl, 'minute').format('YYYY-MM-DD HH:mm:ss Z')); + res.setHeader('X-View-Cache-Created-At', moment().format('YYYY-MM-DD HH:mm:ss Z')); + res.realSend(body); + const headers = headersFilter && util.isFunction(headersFilter) ? + headersFilter(res) : defaultHeadersFilter(res); + if (res.statusCode <= 500) { + cache.namespace(namespace).set(cacheKey, { headers, body }, ttl).then((ret) => { + if (ret === null) { + logger.error('View cache set failed for return %s', ret); + } + if (lockTTL > 0) { + cache.namespace(lockNamespace).del(cacheKey); + } + }).catch((e) => { + logger.error('View cache set failed for %s', cacheKey, e); + }); + } else if (lockTTL > 0) { + cache.namespace(lockNamespace).del(cacheKey); + } + }; + }; + + const spinCache = async () => { + const retry = await getCache(namespace, cacheKey); + if (retry && retry.body) { + return sendHit(retry.headers, retry.body); + } + //这里再次判断锁是否存在, 如果锁已经被释放, 代表缓存可能已经过期,则重新设置 + const lockRet = await cache.namespace(lockNamespace).set(cacheKey, 1, lockTTL, 'nx'); + if (lockRet) { + resetSend(); + return next(); + } + + setTimeout(spinCache(), 0); + logger.info('View cache is spinning...'); + return true; + }; + + const { + headers: cachedHeaders, + body: cachedBody + } = await getCache(namespace, cacheKey); + if (req.query.flush !== 'true' && cachedBody) { + sendHit(cachedHeaders, cachedBody); return; } - res.realSend = res.send; //eslint-disable-line no-param-reassign - res.send = (body) => { //eslint-disable-line no-param-reassign - logger.debug('View cache missed by key %s, creating...', cacheKey); - res.setHeader('X-View-Cache-Miss', cacheKey); - res.setHeader('X-View-Cache-Expire-At', moment().add(ttl, 'minute').format('YYYY-MM-DD HH:mm:ss Z')); - res.setHeader('X-View-Cache-Created-At', moment().format('YYYY-MM-DD HH:mm:ss Z')); - res.realSend(body); - const headers = headersFilter && util.isFunction(headersFilter) ? - headersFilter(res) : defaultHeadersFilter(res); - if (res.statusCode <= 500) { - cache.namespace(namespace).set(cacheKey, { headers, body }, ttl).catch((e) => { - logger.error('View cache set failed for %s', cacheKey, e); - }); + + if (lockTTL > 0) { + const lockRet = await cache.namespace(lockNamespace).set(cacheKey, 1, lockTTL, 'nx'); + if (lockRet === null) { + //get cache again + await spinCache(); + return; } - }; + } + + resetSend(); next(); }); }; From 99136fb56e26685189fa0767146e1e7c821a96de Mon Sep 17 00:00:00 2001 From: theharveyz Date: Tue, 19 Dec 2017 02:54:26 +0800 Subject: [PATCH 2/5] fix bug --- src/middlewares/view_cache.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/middlewares/view_cache.js b/src/middlewares/view_cache.js index 57ca281..4dac4f6 100644 --- a/src/middlewares/view_cache.js +++ b/src/middlewares/view_cache.js @@ -146,7 +146,7 @@ function ViewCacheMiddleware(cache, logger) { return next(); } - setTimeout(spinCache(), 0); + setTimeout(spinCache, 0); logger.info('View cache is spinning...'); return true; }; From 02084ad0aaad0ff6594b3070b4c55027641f4832 Mon Sep 17 00:00:00 2001 From: theharveyz Date: Wed, 20 Dec 2017 02:05:32 +0800 Subject: [PATCH 3/5] fix view_cache test --- test/middlewares/view_cache.js | 36 ++++++++++++++++------------------ 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/test/middlewares/view_cache.js b/test/middlewares/view_cache.js index a34578a..c704544 100644 --- a/test/middlewares/view_cache.js +++ b/test/middlewares/view_cache.js @@ -61,23 +61,21 @@ test('Request hash', (t) => { ); }); -//FIXME: this assert not work!! -// test('View cache', (t) => { -// t.plan(1); -// const req = mockRequest({ -// method: 'GET', url: '/' -// }); -// req.route = {}; -// const res = mockResponse(); -// const middleware = DI.get('view_cache')(60); -// res.on('end', () => { -// cache.namespace('view').has('get/unknown:043fe182887af19ba0be0cb494b75c9c').then((v) => { -// t.true(v); -// }); -// }); -// -// middleware(req, res, () => { -// res.send('something'); -// }); -// }); +test('View cache', async (t) => { + const req = mockRequest({ + method: 'GET', url: '/' + }); + req.route = {}; + const res = mockResponse(); + const middleware = DI.get('view_cache')(60); + + await middleware(req, res, () => { + res.send('something'); + }); + + const v = await cache.namespace('view').has('get/unknown/:5bb7eb919a5d177089d48a9ab171fc4e'); + t.true(v); + + t.is(await cache.namespace('view').flush(), 1); +}); From eb467051c46ac021dae268d8836fc50d952a3ef6 Mon Sep 17 00:00:00 2001 From: theharveyz Date: Thu, 21 Dec 2017 02:36:12 +0800 Subject: [PATCH 4/5] =?UTF-8?q?fix=20res.realSend=E5=9C=A8=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E6=97=B6=E5=BE=AA=E7=8E=AF=E5=BC=95=E7=94=A8=E7=9A=84?= =?UTF-8?q?=E9=97=AE=E9=A2=98=20&=20fix=20view=5Fcache=E5=B9=B6=E5=8F=91?= =?UTF-8?q?=E5=9C=BA=E6=99=AF=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nohup.out | 4 ++++ src/middlewares/view_cache.js | 3 ++- test/middlewares/view_cache.js | 40 ++++++++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 nohup.out diff --git a/nohup.out b/nohup.out new file mode 100644 index 0000000..46d377c --- /dev/null +++ b/nohup.out @@ -0,0 +1,4 @@ + +> evaengine@0.5.10 test /Users/harveyz/code/hz/EvaEngine.js +> nyc -a --reporter=lcov --reporter=text --reporter=html npm run ava + diff --git a/src/middlewares/view_cache.js b/src/middlewares/view_cache.js index 4dac4f6..d7f7562 100644 --- a/src/middlewares/view_cache.js +++ b/src/middlewares/view_cache.js @@ -108,7 +108,7 @@ function ViewCacheMiddleware(cache, logger) { //重设send方法 const resetSend = () => { - res.realSend = res.send; //eslint-disable-line no-param-reassign + res.realSend = res.realSend || res.send; //eslint-disable-line no-param-reassign res.send = (body) => { //eslint-disable-line no-param-reassign logger.debug('View cache missed by key %s, creating...', cacheKey); res.setHeader('X-View-Cache-Miss', cacheKey); @@ -146,6 +146,7 @@ function ViewCacheMiddleware(cache, logger) { return next(); } + //自旋 setTimeout(spinCache, 0); logger.info('View cache is spinning...'); return true; diff --git a/test/middlewares/view_cache.js b/test/middlewares/view_cache.js index c704544..45f7773 100644 --- a/test/middlewares/view_cache.js +++ b/test/middlewares/view_cache.js @@ -73,9 +73,49 @@ test('View cache', async (t) => { res.send('something'); }); + //走缓存, 所以next中断言不执行 + await middleware(req, res, () => { + t.true(false); + }); + const v = await cache.namespace('view').has('get/unknown/:5bb7eb919a5d177089d48a9ab171fc4e'); t.true(v); t.is(await cache.namespace('view').flush(), 1); + t.is(await cache.namespace('view:lock').flush(), 0); }); +test('View lock', async (t) => { + const req = mockRequest({ + method: 'GET', url: '/' + }); + req.route = {}; + const res = mockResponse(); + const middleware = DI.get('view_cache')(60); + + await middleware(req, res, () => { + // 不执行send + }); + + const locked = await cache.namespace('view:lock').has('get/unknown/:5bb7eb919a5d177089d48a9ab171fc4e'); + t.true(locked); + + //此时资源被锁定, 无法缓存 + await middleware(req, res, (v) => { + res.send('something'); + }); + let v = await cache.namespace('view').has('get/unknown/:5bb7eb919a5d177089d48a9ab171fc4e'); + t.false(v); + + t.is(await cache.namespace('view:lock').flush(), 1); + + //延迟500ms, 确保当lock 被释放后, 缓存被设置成功 + const s = new Promise(resolve => setTimeout(resolve, 500)); + await s; + + v = await cache.namespace('view').has('get/unknown/:5bb7eb919a5d177089d48a9ab171fc4e'); + t.true(v); + + t.is(await cache.namespace('view').flush(), 1); + +}); From 9816bd722ca4691cfd9c91565427df894c1cdffa Mon Sep 17 00:00:00 2001 From: theharveyz Date: Thu, 21 Dec 2017 02:39:49 +0800 Subject: [PATCH 5/5] rm nohup.out --- .gitignore | 3 ++- nohup.out | 4 ---- 2 files changed, 2 insertions(+), 5 deletions(-) delete mode 100644 nohup.out diff --git a/.gitignore b/.gitignore index b5799bd..3a60306 100644 --- a/.gitignore +++ b/.gitignore @@ -44,4 +44,5 @@ src/config/config.local* src/config/sequelize.json lib/* .nyc_output -/yarn.lock \ No newline at end of file +/yarn.lock +/nohup.out \ No newline at end of file diff --git a/nohup.out b/nohup.out deleted file mode 100644 index 46d377c..0000000 --- a/nohup.out +++ /dev/null @@ -1,4 +0,0 @@ - -> evaengine@0.5.10 test /Users/harveyz/code/hz/EvaEngine.js -> nyc -a --reporter=lcov --reporter=text --reporter=html npm run ava -