mirror of
https://github.com/DIYgod/RSSHub.git
synced 2026-10-16 07:44:44 +08:00
perf: use only one WASM alloc for xxhash
This commit is contained in:
@@ -4,6 +4,8 @@ import xxhash from 'xxhash-wasm';
|
|||||||
|
|
||||||
import cacheModule from '@/utils/cache/index';
|
import cacheModule from '@/utils/cache/index';
|
||||||
|
|
||||||
|
const { h64ToString } = await xxhash();
|
||||||
|
|
||||||
const QuerySchema = z.object({
|
const QuerySchema = z.object({
|
||||||
requestPath: z.string().openapi({
|
requestPath: z.string().openapi({
|
||||||
param: {
|
param: {
|
||||||
@@ -62,7 +64,6 @@ const handler: RouteHandler<typeof route> = async (ctx) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { requestPath } = ctx.req.valid('query');
|
const { requestPath } = ctx.req.valid('query');
|
||||||
const { h64ToString } = await xxhash();
|
|
||||||
const key = 'rsshub:koa-redis-cache:' + h64ToString(requestPath + ':rss');
|
const key = 'rsshub:koa-redis-cache:' + h64ToString(requestPath + ':rss');
|
||||||
const cached = await cacheModule.globalCache.has(key);
|
const cached = await cacheModule.globalCache.has(key);
|
||||||
|
|
||||||
|
|||||||
+15
-7
@@ -7,6 +7,8 @@ import type { Data } from '@/types';
|
|||||||
import cacheModule from '@/utils/cache/index';
|
import cacheModule from '@/utils/cache/index';
|
||||||
|
|
||||||
const bypassList = new Set(['/', '/robots.txt', '/logo.png', '/favicon.ico']);
|
const bypassList = new Set(['/', '/robots.txt', '/logo.png', '/favicon.ico']);
|
||||||
|
|
||||||
|
const { h64ToString } = await xxhash();
|
||||||
// only give cache string, as the `!` condition tricky
|
// only give cache string, as the `!` condition tricky
|
||||||
// XXH64 is used to shrink key size
|
// XXH64 is used to shrink key size
|
||||||
// plz, write these tips in comments!
|
// plz, write these tips in comments!
|
||||||
@@ -19,13 +21,18 @@ const middleware: MiddlewareHandler = async (ctx, next) => {
|
|||||||
const requestPath = ctx.req.path;
|
const requestPath = ctx.req.path;
|
||||||
const format = `:${ctx.req.query('format') || 'rss'}`;
|
const format = `:${ctx.req.query('format') || 'rss'}`;
|
||||||
const limit = ctx.req.query('limit') ? `:${ctx.req.query('limit')}` : '';
|
const limit = ctx.req.query('limit') ? `:${ctx.req.query('limit')}` : '';
|
||||||
const { h64ToString } = await xxhash();
|
|
||||||
const key = 'rsshub:koa-redis-cache:' + h64ToString(requestPath + format + limit);
|
const key = 'rsshub:koa-redis-cache:' + h64ToString(requestPath + format + limit);
|
||||||
const controlKey = 'rsshub:path-requested:' + h64ToString(requestPath + format + limit);
|
const controlKey = 'rsshub:path-requested:' + h64ToString(requestPath + format + limit);
|
||||||
|
|
||||||
const isRequesting = await cacheModule.globalCache.get(controlKey);
|
let value = await cacheModule.globalCache.get(key);
|
||||||
|
|
||||||
if (isRequesting === '1') {
|
// Doesn't hit the cache? Try to become the fetcher and let others know!
|
||||||
|
let claimed = false;
|
||||||
|
if (!value) {
|
||||||
|
claimed = await cacheModule.globalCache.claim(controlKey, config.cache.requestTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!value && !claimed) {
|
||||||
let retryTimes = process.env.NODE_ENV === 'test' ? 1 : 10;
|
let retryTimes = process.env.NODE_ENV === 'test' ? 1 : 10;
|
||||||
let bypass = false;
|
let bypass = false;
|
||||||
while (retryTimes > 0) {
|
while (retryTimes > 0) {
|
||||||
@@ -41,10 +48,9 @@ const middleware: MiddlewareHandler = async (ctx, next) => {
|
|||||||
if (!bypass) {
|
if (!bypass) {
|
||||||
throw new RequestInProgressError('This path is currently fetching, please come back later!');
|
throw new RequestInProgressError('This path is currently fetching, please come back later!');
|
||||||
}
|
}
|
||||||
|
value = await cacheModule.globalCache.get(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
const value = await cacheModule.globalCache.get(key);
|
|
||||||
|
|
||||||
if (value) {
|
if (value) {
|
||||||
ctx.status(200);
|
ctx.status(200);
|
||||||
ctx.header('RSSHub-Cache-Status', 'HIT');
|
ctx.header('RSSHub-Cache-Status', 'HIT');
|
||||||
@@ -53,8 +59,10 @@ const middleware: MiddlewareHandler = async (ctx, next) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Doesn't hit the cache? We need to let others know!
|
if (!claimed) {
|
||||||
await cacheModule.globalCache.set(controlKey, '1', config.cache.requestTimeout);
|
// waited out a stale claim without finding a cache entry, take over the fetch
|
||||||
|
await cacheModule.globalCache.set(controlKey, '1', config.cache.requestTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
// let routers control cache
|
// let routers control cache
|
||||||
ctx.set('cacheKey', key);
|
ctx.set('cacheKey', key);
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import { parseDate } from '@/utils/parse-date';
|
|||||||
|
|
||||||
const link = 'https://www.economist.com/the-world-in-brief';
|
const link = 'https://www.economist.com/the-world-in-brief';
|
||||||
|
|
||||||
|
const { h64ToString } = await xxhash();
|
||||||
|
|
||||||
export const route: Route = {
|
export const route: Route = {
|
||||||
path: '/espresso',
|
path: '/espresso',
|
||||||
categories: ['traditional-media'],
|
categories: ['traditional-media'],
|
||||||
@@ -37,7 +39,6 @@ export const route: Route = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
async function handler() {
|
async function handler() {
|
||||||
const { h64ToString } = await xxhash();
|
|
||||||
const nextData = await cache.tryGet(
|
const nextData = await cache.tryGet(
|
||||||
link,
|
link,
|
||||||
async () => {
|
async () => {
|
||||||
|
|||||||
Vendored
+35
@@ -11,10 +11,16 @@ const globalCache: {
|
|||||||
get: (key: string) => Promise<string | null | undefined> | string | null | undefined;
|
get: (key: string) => Promise<string | null | undefined> | string | null | undefined;
|
||||||
has: (key: string) => Promise<boolean> | boolean;
|
has: (key: string) => Promise<boolean> | boolean;
|
||||||
set: (key: string, value?: string | Record<string, any>, maxAge?: number) => any;
|
set: (key: string, value?: string | Record<string, any>, maxAge?: number) => any;
|
||||||
|
/**
|
||||||
|
* Atomically set `key` to '1' and return true, unless it is already '1' (return false).
|
||||||
|
* A get-then-set in the caller races: two same-tick requests would both read "not '1'".
|
||||||
|
*/
|
||||||
|
claim: (key: string, maxAge: number) => Promise<boolean> | boolean;
|
||||||
} = {
|
} = {
|
||||||
get: () => null,
|
get: () => null,
|
||||||
has: () => false,
|
has: () => false,
|
||||||
set: () => null,
|
set: () => null,
|
||||||
|
claim: () => true,
|
||||||
};
|
};
|
||||||
|
|
||||||
const noopCacheModule: CacheModule = {
|
const noopCacheModule: CacheModule = {
|
||||||
@@ -54,6 +60,13 @@ if (isWorker) {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
globalCache.set = cacheModule.set;
|
globalCache.set = cacheModule.set;
|
||||||
|
globalCache.claim = async (key, maxAge) => {
|
||||||
|
if (!key || !cacheModule.status.available || !redisClient) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const result = await redisClient.eval("if redis.call('GET', KEYS[1]) == '1' then return 0 end redis.call('SET', KEYS[1], '1', 'EX', ARGV[1]) return 1", 1, key, maxAge);
|
||||||
|
return result === 1;
|
||||||
|
};
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'http':
|
case 'http':
|
||||||
@@ -75,6 +88,17 @@ if (isWorker) {
|
|||||||
return cacheModule.set(key, value, maxAge);
|
return cacheModule.set(key, value, maxAge);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
globalCache.claim = async (key, maxAge) => {
|
||||||
|
if (!key || !cacheModule.status.available) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// best effort: the HTTP cache protocol has no atomic operation
|
||||||
|
if ((await cacheModule.get(key, false)) === '1') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
await cacheModule.set(key, '1', maxAge);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
break;
|
break;
|
||||||
case 'memory': {
|
case 'memory': {
|
||||||
cacheModule = memory;
|
cacheModule = memory;
|
||||||
@@ -102,6 +126,17 @@ if (isWorker) {
|
|||||||
return memoryCache.set(key, value, { ttl: maxAge * 1000 });
|
return memoryCache.set(key, value, { ttl: maxAge * 1000 });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
// fully synchronous, so nothing can interleave between the read and the write
|
||||||
|
globalCache.claim = (key, maxAge) => {
|
||||||
|
if (!key || !cacheModule.status.available || !memoryCache) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (memoryCache.get(key, { updateAgeOnGet: false }) === '1') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
memoryCache.set(key, '1', { ttl: maxAge * 1000 });
|
||||||
|
return true;
|
||||||
|
};
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
|||||||
Reference in New Issue
Block a user