Skip to main content
Glama

Open Search MCP

by flyanima
MIT License
2
  • Apple
  • Linux
advanced-cache.ts16.4 kB
/** * Advanced Caching System * 高级缓存优化系统 * * 功能: * - 智能缓存策略 * - 缓存预热 * - 缓存失效机制 * - 多层缓存架构 * - 缓存性能监控 */ import { Logger } from '../utils/logger.js'; import { SearchResult, EnhancedSearchResult } from '../types.js'; import * as fs from 'fs/promises'; import * as path from 'path'; import * as crypto from 'crypto'; export interface CacheConfig { // 缓存策略 strategy: 'lru' | 'lfu' | 'ttl' | 'adaptive'; // 容量配置 maxMemoryItems: number; // 内存缓存最大条目数 maxDiskSize: number; // 磁盘缓存最大大小 (MB) // 时间配置 defaultTTL: number; // 默认TTL (毫秒) maxTTL: number; // 最大TTL (毫秒) minTTL: number; // 最小TTL (毫秒) // 预热配置 preWarmEnabled: boolean; // 启用缓存预热 preWarmQueries: string[]; // 预热查询列表 // 性能配置 compressionEnabled: boolean; // 启用压缩 encryptionEnabled: boolean; // 启用加密 // 目录配置 cacheDir: string; // 缓存目录 } export interface CacheEntry { key: string; data: any; timestamp: number; ttl: number; accessCount: number; lastAccessed: number; size: number; compressed: boolean; encrypted: boolean; metadata: { query: string; source: string; resultCount: number; quality: number; }; } export interface CacheStats { // 基础统计 totalEntries: number; memoryEntries: number; diskEntries: number; // 大小统计 memorySize: number; diskSize: number; totalSize: number; // 性能统计 hitRate: number; missRate: number; totalRequests: number; totalHits: number; totalMisses: number; // 时间统计 averageAccessTime: number; averageWriteTime: number; // 策略统计 evictions: number; expirations: number; // 时间范围 since: Date; } export class AdvancedCache { private logger: Logger; private config: CacheConfig; // 内存缓存 private memoryCache = new Map<string, CacheEntry>(); // 访问统计 private accessStats = { requests: 0, hits: 0, misses: 0, accessTimes: [] as number[], writeTimes: [] as number[], evictions: 0, expirations: 0, startTime: new Date() }; // LRU 访问顺序 private accessOrder: string[] = []; // LFU 频率计数 private frequencyCount = new Map<string, number>(); constructor(config?: Partial<CacheConfig>) { this.logger = new Logger('AdvancedCache'); this.config = { strategy: 'adaptive', maxMemoryItems: 1000, maxDiskSize: 500, // 500MB defaultTTL: 60 * 60 * 1000, // 1小时 maxTTL: 24 * 60 * 60 * 1000, // 24小时 minTTL: 5 * 60 * 1000, // 5分钟 preWarmEnabled: true, preWarmQueries: [ 'artificial intelligence', 'machine learning', 'deep learning', 'neural networks', 'natural language processing' ], compressionEnabled: true, encryptionEnabled: false, cacheDir: './cache', ...config }; this.initializeCache(); } /** * 获取缓存数据 */ async get(key: string): Promise<any | null> { const startTime = performance.now(); this.accessStats.requests++; try { // 生成缓存键 const cacheKey = this.generateCacheKey(key); // 首先检查内存缓存 const memoryEntry = this.memoryCache.get(cacheKey); if (memoryEntry && this.isEntryValid(memoryEntry)) { this.updateAccessStats(memoryEntry); this.accessStats.hits++; const accessTime = performance.now() - startTime; this.accessStats.accessTimes.push(accessTime); this.logger.debug(`Cache hit (memory): ${key}`); return this.deserializeData(memoryEntry.data, memoryEntry); } // 检查磁盘缓存 const diskEntry = await this.getDiskEntry(cacheKey); if (diskEntry && this.isEntryValid(diskEntry)) { // 将热点数据提升到内存缓存 this.promoteToMemory(diskEntry); this.updateAccessStats(diskEntry); this.accessStats.hits++; const accessTime = performance.now() - startTime; this.accessStats.accessTimes.push(accessTime); this.logger.debug(`Cache hit (disk): ${key}`); return this.deserializeData(diskEntry.data, diskEntry); } // 缓存未命中 this.accessStats.misses++; this.logger.debug(`Cache miss: ${key}`); return null; } catch (error) { this.logger.error(`Cache get error for ${key}:`, error); this.accessStats.misses++; return null; } } /** * 设置缓存数据 */ async set( key: string, data: any, options?: { ttl?: number; metadata?: Partial<CacheEntry['metadata']>; priority?: 'low' | 'normal' | 'high'; } ): Promise<void> { const startTime = performance.now(); try { const cacheKey = this.generateCacheKey(key); const ttl = options?.ttl || this.calculateAdaptiveTTL(data, options?.metadata); const entry: CacheEntry = { key: cacheKey, data: await this.serializeData(data), timestamp: Date.now(), ttl, accessCount: 1, lastAccessed: Date.now(), size: this.calculateDataSize(data), compressed: this.config.compressionEnabled, encrypted: this.config.encryptionEnabled, metadata: { query: key, source: 'unknown', resultCount: Array.isArray(data) ? data.length : 1, quality: 0.8, ...options?.metadata } }; // 根据优先级决定存储策略 const priority = options?.priority || 'normal'; if (priority === 'high' || this.shouldStoreInMemory(entry)) { await this.setMemoryEntry(cacheKey, entry); } else { await this.setDiskEntry(cacheKey, entry); } const writeTime = performance.now() - startTime; this.accessStats.writeTimes.push(writeTime); this.logger.debug(`Cache set: ${key} (TTL: ${ttl}ms, Size: ${entry.size} bytes)`); } catch (error) { this.logger.error(`Cache set error for ${key}:`, error); } } /** * 删除缓存条目 */ async delete(key: string): Promise<boolean> { try { const cacheKey = this.generateCacheKey(key); // 从内存缓存删除 const memoryDeleted = this.memoryCache.delete(cacheKey); this.removeFromAccessOrder(cacheKey); this.frequencyCount.delete(cacheKey); // 从磁盘缓存删除 const diskDeleted = await this.deleteDiskEntry(cacheKey); this.logger.debug(`Cache delete: ${key}`); return memoryDeleted || diskDeleted; } catch (error) { this.logger.error(`Cache delete error for ${key}:`, error); return false; } } /** * 清空缓存 */ async clear(): Promise<void> { try { // 清空内存缓存 this.memoryCache.clear(); this.accessOrder = []; this.frequencyCount.clear(); // 清空磁盘缓存 await this.clearDiskCache(); // 重置统计 this.accessStats = { requests: 0, hits: 0, misses: 0, accessTimes: [], writeTimes: [], evictions: 0, expirations: 0, startTime: new Date() }; this.logger.info('Cache cleared'); } catch (error) { this.logger.error('Cache clear error:', error); } } /** * 获取缓存统计信息 */ getStats(): CacheStats { const memorySize = Array.from(this.memoryCache.values()) .reduce((total, entry) => total + entry.size, 0); return { totalEntries: this.memoryCache.size, memoryEntries: this.memoryCache.size, diskEntries: 0, // TODO: 实现磁盘条目计数 memorySize, diskSize: 0, // TODO: 实现磁盘大小计算 totalSize: memorySize, hitRate: this.accessStats.requests > 0 ? this.accessStats.hits / this.accessStats.requests : 0, missRate: this.accessStats.requests > 0 ? this.accessStats.misses / this.accessStats.requests : 0, totalRequests: this.accessStats.requests, totalHits: this.accessStats.hits, totalMisses: this.accessStats.misses, averageAccessTime: this.accessStats.accessTimes.length > 0 ? this.accessStats.accessTimes.reduce((a, b) => a + b, 0) / this.accessStats.accessTimes.length : 0, averageWriteTime: this.accessStats.writeTimes.length > 0 ? this.accessStats.writeTimes.reduce((a, b) => a + b, 0) / this.accessStats.writeTimes.length : 0, evictions: this.accessStats.evictions, expirations: this.accessStats.expirations, since: this.accessStats.startTime }; } /** * 缓存预热 */ async preWarm(): Promise<void> { if (!this.config.preWarmEnabled) return; this.logger.info('Starting cache pre-warming...'); for (const query of this.config.preWarmQueries) { try { // 这里应该调用实际的搜索逻辑来预热缓存 // 暂时跳过实际实现 this.logger.debug(`Pre-warming cache for query: ${query}`); } catch (error) { this.logger.warn(`Pre-warm failed for query ${query}:`, error); } } this.logger.info('Cache pre-warming completed'); } // 私有方法 private async initializeCache(): Promise<void> { try { // 创建缓存目录 await fs.mkdir(this.config.cacheDir, { recursive: true }); // 启动清理任务 this.startCleanupTask(); // 启动预热 if (this.config.preWarmEnabled) { setTimeout(() => this.preWarm(), 5000); // 5秒后开始预热 } this.logger.info('Advanced cache initialized'); } catch (error) { this.logger.error('Cache initialization error:', error); } } private generateCacheKey(key: string): string { return crypto.createHash('sha256').update(key).digest('hex'); } private isEntryValid(entry: CacheEntry): boolean { const now = Date.now(); return (now - entry.timestamp) < entry.ttl; } private updateAccessStats(entry: CacheEntry): void { entry.accessCount++; entry.lastAccessed = Date.now(); // 更新LRU顺序 this.updateAccessOrder(entry.key); // 更新LFU频率 this.frequencyCount.set(entry.key, (this.frequencyCount.get(entry.key) || 0) + 1); } private calculateAdaptiveTTL(data: any, metadata?: Partial<CacheEntry['metadata']>): number { let ttl = this.config.defaultTTL; // 根据数据质量调整TTL if (metadata?.quality) { if (metadata.quality > 0.9) { ttl = Math.min(ttl * 2, this.config.maxTTL); // 高质量数据缓存更久 } else if (metadata.quality < 0.5) { ttl = Math.max(ttl / 2, this.config.minTTL); // 低质量数据缓存较短 } } // 根据结果数量调整TTL if (metadata?.resultCount) { if (metadata.resultCount > 10) { ttl = Math.min(ttl * 1.5, this.config.maxTTL); // 丰富结果缓存更久 } } return ttl; } private shouldStoreInMemory(entry: CacheEntry): boolean { // 高质量、小尺寸的数据优先存储在内存 return entry.metadata.quality > 0.8 && entry.size < 10000; // 10KB } private async setMemoryEntry(key: string, entry: CacheEntry): Promise<void> { // 检查内存容量 if (this.memoryCache.size >= this.config.maxMemoryItems) { await this.evictMemoryEntry(); } this.memoryCache.set(key, entry); this.updateAccessOrder(key); } private async evictMemoryEntry(): Promise<void> { let keyToEvict: string | null = null; switch (this.config.strategy) { case 'lru': keyToEvict = this.accessOrder[0]; break; case 'lfu': keyToEvict = this.findLFUKey(); break; case 'ttl': keyToEvict = this.findExpiredKey(); break; case 'adaptive': keyToEvict = this.findAdaptiveEvictionKey(); break; } if (keyToEvict) { const entry = this.memoryCache.get(keyToEvict); if (entry) { // 将被驱逐的条目移到磁盘 await this.setDiskEntry(keyToEvict, entry); } this.memoryCache.delete(keyToEvict); this.removeFromAccessOrder(keyToEvict); this.accessStats.evictions++; } } private updateAccessOrder(key: string): void { // 移除旧位置 this.removeFromAccessOrder(key); // 添加到末尾(最近访问) this.accessOrder.push(key); } private removeFromAccessOrder(key: string): void { const index = this.accessOrder.indexOf(key); if (index > -1) { this.accessOrder.splice(index, 1); } } private findLFUKey(): string | null { let minFreq = Infinity; let keyToEvict: string | null = null; for (const [key, freq] of this.frequencyCount.entries()) { if (freq < minFreq) { minFreq = freq; keyToEvict = key; } } return keyToEvict; } private findExpiredKey(): string | null { const now = Date.now(); for (const [key, entry] of this.memoryCache.entries()) { if (!this.isEntryValid(entry)) { return key; } } return null; } private findAdaptiveEvictionKey(): string | null { // 综合考虑访问频率、时间和质量 let bestScore = -1; let keyToEvict: string | null = null; for (const [key, entry] of this.memoryCache.entries()) { const freq = this.frequencyCount.get(key) || 1; const age = Date.now() - entry.lastAccessed; const quality = entry.metadata.quality; // 计算驱逐分数(越高越应该被驱逐) const score = age / (freq * quality * 1000); if (score > bestScore) { bestScore = score; keyToEvict = key; } } return keyToEvict; } private async serializeData(data: any): Promise<string> { let serialized = JSON.stringify(data); if (this.config.compressionEnabled) { // TODO: 实现压缩 } if (this.config.encryptionEnabled) { // TODO: 实现加密 } return serialized; } private async deserializeData(data: string, entry: CacheEntry): Promise<any> { let deserialized = data; if (entry.encrypted) { // TODO: 实现解密 } if (entry.compressed) { // TODO: 实现解压缩 } return JSON.parse(deserialized); } private calculateDataSize(data: any): number { return JSON.stringify(data).length * 2; // 粗略估算(UTF-16) } private promoteToMemory(entry: CacheEntry): void { if (this.memoryCache.size < this.config.maxMemoryItems) { this.memoryCache.set(entry.key, entry); this.updateAccessOrder(entry.key); } } // 磁盘缓存方法(简化实现) private async getDiskEntry(key: string): Promise<CacheEntry | null> { // TODO: 实现磁盘缓存读取 return null; } private async setDiskEntry(key: string, entry: CacheEntry): Promise<void> { // TODO: 实现磁盘缓存写入 } private async deleteDiskEntry(key: string): Promise<boolean> { // TODO: 实现磁盘缓存删除 return false; } private async clearDiskCache(): Promise<void> { // TODO: 实现磁盘缓存清空 } private startCleanupTask(): void { // 每10分钟清理一次过期条目 setInterval(() => { this.cleanupExpiredEntries(); }, 10 * 60 * 1000); } private cleanupExpiredEntries(): void { const now = Date.now(); let cleanedCount = 0; for (const [key, entry] of this.memoryCache.entries()) { if (!this.isEntryValid(entry)) { this.memoryCache.delete(key); this.removeFromAccessOrder(key); this.frequencyCount.delete(key); cleanedCount++; this.accessStats.expirations++; } } if (cleanedCount > 0) { this.logger.debug(`Cleaned up ${cleanedCount} expired cache entries`); } } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/flyanima/open-search-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server