mirror of
https://github.com/DIYgod/RSSHub.git
synced 2026-10-16 21:02:57 +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';
|
||||
|
||||
const { h64ToString } = await xxhash();
|
||||
|
||||
const QuerySchema = z.object({
|
||||
requestPath: z.string().openapi({
|
||||
param: {
|
||||
@@ -62,7 +64,6 @@ const handler: RouteHandler<typeof route> = async (ctx) => {
|
||||
}
|
||||
|
||||
const { requestPath } = ctx.req.valid('query');
|
||||
const { h64ToString } = await xxhash();
|
||||
const key = 'rsshub:koa-redis-cache:' + h64ToString(requestPath + ':rss');
|
||||
const cached = await cacheModule.globalCache.has(key);
|
||||
|
||||
|
||||
+15
-7
@@ -7,6 +7,8 @@ import type { Data } from '@/types';
|
||||
import cacheModule from '@/utils/cache/index';
|
||||
|
||||
const bypassList = new Set(['/', '/robots.txt', '/logo.png', '/favicon.ico']);
|
||||
|
||||
const { h64ToString } = await xxhash();
|
||||
// only give cache string, as the `!` condition tricky
|
||||
// XXH64 is used to shrink key size
|
||||
// plz, write these tips in comments!
|
||||
@@ -19,13 +21,18 @@ const middleware: MiddlewareHandler = async (ctx, next) => {
|
||||
const requestPath = ctx.req.path;
|
||||
const format = `:${ctx.req.query('format') || 'rss'}`;
|
||||
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 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 bypass = false;
|
||||
while (retryTimes > 0) {
|
||||
@@ -41,10 +48,9 @@ const middleware: MiddlewareHandler = async (ctx, next) => {
|
||||
if (!bypass) {
|
||||
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) {
|
||||
ctx.status(200);
|
||||
ctx.header('RSSHub-Cache-Status', 'HIT');
|
||||
@@ -53,8 +59,10 @@ const middleware: MiddlewareHandler = async (ctx, next) => {
|
||||
return;
|
||||
}
|
||||
|
||||
// Doesn't hit the cache? We need to let others know!
|
||||
await cacheModule.globalCache.set(controlKey, '1', config.cache.requestTimeout);
|
||||
if (!claimed) {
|
||||
// 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
|
||||
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 { h64ToString } = await xxhash();
|
||||
|
||||
export const route: Route = {
|
||||
path: '/espresso',
|
||||
categories: ['traditional-media'],
|
||||
@@ -37,7 +39,6 @@ export const route: Route = {
|
||||
};
|
||||
|
||||
async function handler() {
|
||||
const { h64ToString } = await xxhash();
|
||||
const nextData = await cache.tryGet(
|
||||
link,
|
||||
async () => {
|
||||
|
||||
Vendored
+35
@@ -11,10 +11,16 @@ const globalCache: {
|
||||
get: (key: string) => Promise<string | null | undefined> | string | null | undefined;
|
||||
has: (key: string) => Promise<boolean> | boolean;
|
||||
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,
|
||||
has: () => false,
|
||||
set: () => null,
|
||||
claim: () => true,
|
||||
};
|
||||
|
||||
const noopCacheModule: CacheModule = {
|
||||
@@ -54,6 +60,13 @@ if (isWorker) {
|
||||
return false;
|
||||
};
|
||||
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;
|
||||
}
|
||||
case 'http':
|
||||
@@ -75,6 +88,17 @@ if (isWorker) {
|
||||
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;
|
||||
case 'memory': {
|
||||
cacheModule = memory;
|
||||
@@ -102,6 +126,17 @@ if (isWorker) {
|
||||
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;
|
||||
}
|
||||
default:
|
||||
|
||||
Reference in New Issue
Block a user