project setup with core files including configuration, package management, and basic structure. Added .gitignore, README, and various TypeScript types for CMS components. Implemented initial components and layouts for the application.
This commit is contained in:
175
middlelayer/utils/redisCache.ts
Normal file
175
middlelayer/utils/redisCache.ts
Normal file
@@ -0,0 +1,175 @@
|
||||
import Redis from "ioredis";
|
||||
import { cacheHits, cacheMisses } from "../monitoring/metrics.js";
|
||||
import { logCacheHit, logCacheMiss } from "../monitoring/logger.js";
|
||||
import { logger } from "../monitoring/logger.js";
|
||||
|
||||
/**
|
||||
* Redis-basierter Cache mit Fallback auf In-Memory
|
||||
*/
|
||||
export class RedisCache<T> {
|
||||
private redis: Redis | null = null;
|
||||
private memoryCache: Map<string, { data: T; expiresAt: number }> = new Map();
|
||||
private defaultTTL: number;
|
||||
private cacheType: string;
|
||||
private useRedis: boolean;
|
||||
|
||||
constructor(defaultTTL: number, cacheType: string) {
|
||||
this.defaultTTL = defaultTTL;
|
||||
this.cacheType = cacheType;
|
||||
this.useRedis = process.env.REDIS_ENABLED === "true";
|
||||
|
||||
if (this.useRedis) {
|
||||
this.initRedis();
|
||||
} else {
|
||||
logger.info(
|
||||
`Redis Cache deaktiviert für ${cacheType}, verwende In-Memory Cache`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async initRedis() {
|
||||
try {
|
||||
this.redis = new Redis({
|
||||
host: process.env.REDIS_HOST || "localhost",
|
||||
port: parseInt(process.env.REDIS_PORT || "6379"),
|
||||
password: process.env.REDIS_PASSWORD,
|
||||
retryStrategy: (times) => {
|
||||
// Bei Fehler: Fallback auf In-Memory nach 3 Versuchen
|
||||
if (times > 3) {
|
||||
logger.warn(
|
||||
"Redis-Verbindung fehlgeschlagen, verwende In-Memory Cache"
|
||||
);
|
||||
this.useRedis = false;
|
||||
return null; // Stoppe Retry
|
||||
}
|
||||
return Math.min(times * 50, 2000);
|
||||
},
|
||||
maxRetriesPerRequest: 3,
|
||||
});
|
||||
|
||||
this.redis.on("error", (error) => {
|
||||
logger.error("Redis-Fehler", { error, cacheType: this.cacheType });
|
||||
// Fallback auf In-Memory bei Fehler
|
||||
this.useRedis = false;
|
||||
});
|
||||
|
||||
this.redis.on("connect", () => {
|
||||
logger.info(`Redis verbunden für ${this.cacheType}`);
|
||||
});
|
||||
|
||||
// Test-Verbindung
|
||||
await this.redis.ping();
|
||||
logger.info(`Redis Cache aktiviert für ${this.cacheType}`);
|
||||
} catch (error) {
|
||||
logger.warn(
|
||||
"Redis-Initialisierung fehlgeschlagen, verwende In-Memory Cache",
|
||||
{
|
||||
error,
|
||||
cacheType: this.cacheType,
|
||||
}
|
||||
);
|
||||
this.useRedis = false;
|
||||
}
|
||||
}
|
||||
|
||||
async set(key: string, data: T, ttl?: number): Promise<void> {
|
||||
const ttlMs = ttl || this.defaultTTL;
|
||||
const serialized = JSON.stringify(data);
|
||||
|
||||
if (this.useRedis && this.redis) {
|
||||
try {
|
||||
// Redis TTL in Sekunden
|
||||
const ttlSeconds = Math.ceil(ttlMs / 1000);
|
||||
await this.redis.setex(key, ttlSeconds, serialized);
|
||||
return;
|
||||
} catch (error) {
|
||||
logger.warn("Redis set fehlgeschlagen, verwende In-Memory", {
|
||||
error,
|
||||
key,
|
||||
});
|
||||
this.useRedis = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: In-Memory
|
||||
const expiresAt = Date.now() + ttlMs;
|
||||
this.memoryCache.set(key, { data, expiresAt });
|
||||
}
|
||||
|
||||
async get(key: string): Promise<T | null> {
|
||||
if (this.useRedis && this.redis) {
|
||||
try {
|
||||
const value = await this.redis.get(key);
|
||||
if (value) {
|
||||
// Cache Hit
|
||||
cacheHits.inc({ cache_type: this.cacheType });
|
||||
logCacheHit(key, this.cacheType);
|
||||
return JSON.parse(value) as T;
|
||||
}
|
||||
// Cache Miss
|
||||
cacheMisses.inc({ cache_type: this.cacheType });
|
||||
logCacheMiss(key, this.cacheType);
|
||||
return null;
|
||||
} catch (error) {
|
||||
logger.warn("Redis get fehlgeschlagen, verwende In-Memory", {
|
||||
error,
|
||||
key,
|
||||
});
|
||||
this.useRedis = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: In-Memory
|
||||
const entry = this.memoryCache.get(key);
|
||||
if (!entry) {
|
||||
cacheMisses.inc({ cache_type: this.cacheType });
|
||||
logCacheMiss(key, this.cacheType);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Date.now() > entry.expiresAt) {
|
||||
this.memoryCache.delete(key);
|
||||
cacheMisses.inc({ cache_type: this.cacheType });
|
||||
logCacheMiss(key, this.cacheType);
|
||||
return null;
|
||||
}
|
||||
|
||||
cacheHits.inc({ cache_type: this.cacheType });
|
||||
logCacheHit(key, this.cacheType);
|
||||
return entry.data;
|
||||
}
|
||||
|
||||
async clear(): Promise<void> {
|
||||
if (this.useRedis && this.redis) {
|
||||
try {
|
||||
// Lösche alle Keys mit unserem Prefix
|
||||
const keys = await this.redis.keys(`${this.cacheType}:*`);
|
||||
if (keys.length > 0) {
|
||||
await this.redis.del(...keys);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.warn("Redis clear fehlgeschlagen", { error });
|
||||
}
|
||||
}
|
||||
this.memoryCache.clear();
|
||||
}
|
||||
|
||||
async delete(key: string): Promise<void> {
|
||||
if (this.useRedis && this.redis) {
|
||||
try {
|
||||
await this.redis.del(key);
|
||||
return;
|
||||
} catch (error) {
|
||||
logger.warn("Redis delete fehlgeschlagen", { error, key });
|
||||
}
|
||||
}
|
||||
this.memoryCache.delete(key);
|
||||
}
|
||||
|
||||
async disconnect(): Promise<void> {
|
||||
if (this.redis) {
|
||||
await this.redis.quit();
|
||||
this.redis = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user