mirror of
https://gh-proxy.org/https://github.com/klinsmaya/gateway.git
synced 2026-11-08 00:57:59 +08:00
extend redis client backend to support redis specific functionality
This commit is contained in:
+16
-22
@@ -6,6 +6,8 @@ import Redis from 'ioredis';
|
||||
|
||||
import { CacheBackend, CacheEntry, CacheOptions, CacheStats } from '../types';
|
||||
|
||||
type RedisClient = Redis;
|
||||
|
||||
// Using console.log for now to avoid build issues
|
||||
const logger = {
|
||||
debug: (msg: string, ...args: any[]) =>
|
||||
@@ -18,22 +20,6 @@ const logger = {
|
||||
console.error(`[RedisCache] ${msg}`, ...args),
|
||||
};
|
||||
|
||||
// Redis client interface matching ioredis
|
||||
interface RedisClient {
|
||||
get(key: string): Promise<string | null>;
|
||||
set(
|
||||
key: string,
|
||||
value: string,
|
||||
expiryMode?: string | any,
|
||||
time?: number | string
|
||||
): Promise<'OK' | null>;
|
||||
del(...keys: string[]): Promise<number>;
|
||||
exists(...keys: string[]): Promise<number>;
|
||||
keys(pattern: string): Promise<string[]>;
|
||||
flushdb(): Promise<'OK'>;
|
||||
quit(): Promise<'OK'>;
|
||||
}
|
||||
|
||||
export class RedisCacheBackend implements CacheBackend {
|
||||
private client: RedisClient;
|
||||
private dbName: string;
|
||||
@@ -52,12 +38,6 @@ export class RedisCacheBackend implements CacheBackend {
|
||||
this.dbName = dbName;
|
||||
}
|
||||
|
||||
private getFullKey(key: string, namespace?: string): string {
|
||||
return namespace
|
||||
? `${this.dbName}:${namespace}:${key}`
|
||||
: `${this.dbName}:default:${key}`;
|
||||
}
|
||||
|
||||
private serializeEntry<T>(entry: CacheEntry<T>): string {
|
||||
return JSON.stringify(entry);
|
||||
}
|
||||
@@ -70,6 +50,12 @@ export class RedisCacheBackend implements CacheBackend {
|
||||
return entry.expiresAt !== undefined && entry.expiresAt <= Date.now();
|
||||
}
|
||||
|
||||
getFullKey(key: string, namespace?: string): string {
|
||||
return namespace
|
||||
? `${this.dbName}:${namespace}:${key}`
|
||||
: `${this.dbName}:default:${key}`;
|
||||
}
|
||||
|
||||
async get<T = any>(
|
||||
key: string,
|
||||
namespace?: string
|
||||
@@ -216,6 +202,14 @@ export class RedisCacheBackend implements CacheBackend {
|
||||
}
|
||||
}
|
||||
|
||||
async script(mode: 'LOAD' | 'EXISTS', script: string): Promise<unknown> {
|
||||
return await this.client.script('LOAD', script);
|
||||
}
|
||||
|
||||
async evalsha(sha: string, keys: string[], args: string[]): Promise<unknown> {
|
||||
return await this.client.evalsha(sha, keys.length, ...keys, ...args);
|
||||
}
|
||||
|
||||
async cleanup(): Promise<void> {
|
||||
// Redis handles TTL automatically, so this is mostly a no-op
|
||||
// We could scan for entries with manual expiration and clean them up
|
||||
|
||||
Vendored
+4
@@ -253,6 +253,10 @@ export class CacheService {
|
||||
}));
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
getClient(): CacheBackend {
|
||||
return this.backend;
|
||||
}
|
||||
}
|
||||
|
||||
// Default cache instances for different use cases
|
||||
|
||||
+9
-7
@@ -1,5 +1,6 @@
|
||||
import { Redis, Cluster } from 'ioredis';
|
||||
import { RateLimiterKeyTypes } from '../../../../globals';
|
||||
import { RedisCacheBackend } from '../backends/redis';
|
||||
|
||||
const RATE_LIMIT_LUA = `
|
||||
local tokensKey = KEYS[1]
|
||||
@@ -81,7 +82,7 @@ return {allowed, waitTime, currentTokens}
|
||||
`;
|
||||
|
||||
class RedisRateLimiter {
|
||||
private redis: Redis | Cluster;
|
||||
private redis: RedisCacheBackend;
|
||||
private capacity: number;
|
||||
private windowSize: number;
|
||||
private tokensKey: string;
|
||||
@@ -92,7 +93,7 @@ class RedisRateLimiter {
|
||||
private key: string;
|
||||
|
||||
constructor(
|
||||
redisClient: Redis | Cluster,
|
||||
redisClient: RedisCacheBackend,
|
||||
capacity: number,
|
||||
windowSize: number,
|
||||
key: string,
|
||||
@@ -103,8 +104,8 @@ class RedisRateLimiter {
|
||||
const tag = `{rate:${key}}`; // ensures same hash slot
|
||||
this.capacity = capacity;
|
||||
this.windowSize = windowSize;
|
||||
this.tokensKey = `${tag}:tokens`;
|
||||
this.lastRefillKey = `${tag}:lastRefill`;
|
||||
this.tokensKey = `default:default:${tag}:tokens`;
|
||||
this.lastRefillKey = `default:default:${tag}:lastRefill`;
|
||||
this.keyTTL = windowSize * ttlFactor; // dynamic TTL
|
||||
this.keyType = keyType;
|
||||
this.key = key;
|
||||
@@ -126,12 +127,12 @@ class RedisRateLimiter {
|
||||
const sha = await this.loadOrGetScriptSha();
|
||||
|
||||
try {
|
||||
return await this.redis.evalsha(sha, keys.length, ...keys, ...args);
|
||||
return await this.redis.evalsha(sha, keys, args);
|
||||
} catch (error: any) {
|
||||
if (error.message.includes('NOSCRIPT')) {
|
||||
// Script not loaded on target node - load it and retry with same SHA
|
||||
await this.redis.script('LOAD', RATE_LIMIT_LUA);
|
||||
return await this.redis.evalsha(sha, keys.length, ...keys, ...args);
|
||||
return await this.redis.evalsha(sha, keys, args);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
@@ -171,7 +172,8 @@ class RedisRateLimiter {
|
||||
}
|
||||
|
||||
async getToken(): Promise<string | null> {
|
||||
return this.redis.get(this.tokensKey);
|
||||
const cacheEntry = await this.redis.get(this.tokensKey);
|
||||
return cacheEntry ? cacheEntry.value : null;
|
||||
}
|
||||
|
||||
async decrementToken(
|
||||
|
||||
Reference in New Issue
Block a user