Skip to main content
Glama

Open Search MCP

by flyanima
MIT License
2
  • Apple
  • Linux
intelligent-cache.ts6.48 kB
/** * 智能缓存系统 - 多层缓存架构 */ import NodeCache from 'node-cache'; import { SearchResult } from '../types.js'; import { Logger } from '../utils/logger.js'; export interface CacheConfig { enabled: boolean; ttl: number; maxSize: number; local?: { enabled: boolean; maxKeys: number; ttl: number; }; redis?: { enabled: boolean; host: string; port: number; ttl: number; }; } export interface CacheStats { hits: number; misses: number; hitRate: number; totalKeys: number; memoryUsage: number; } export class IntelligentCache { private config: CacheConfig; private logger: Logger; private localCache: NodeCache; private stats: CacheStats; constructor(config: CacheConfig) { this.config = config; this.logger = new Logger('IntelligentCache'); // 初始化本地缓存 this.localCache = new NodeCache({ stdTTL: config.local?.ttl || config.ttl, maxKeys: config.local?.maxKeys || config.maxSize, checkperiod: 120, // 每2分钟检查过期键 useClones: false // 提高性能 }); // 初始化统计信息 this.stats = { hits: 0, misses: 0, hitRate: 0, totalKeys: 0, memoryUsage: 0 }; this.setupCacheEvents(); this.logger.info('Intelligent cache initialized'); } /** * 获取缓存数据 */ async get(key: string): Promise<SearchResult[] | null> { if (!this.config.enabled) return null; try { // 1. 尝试本地缓存 const localResult = this.localCache.get<SearchResult[]>(key); if (localResult) { this.recordHit(); this.logger.debug(`Local cache hit for key: ${key}`); return localResult; } // 2. 如果启用了Redis,尝试Redis缓存 if (this.config.redis?.enabled) { const redisResult = await this.getFromRedis(key); if (redisResult) { // 回填本地缓存 this.localCache.set(key, redisResult); this.recordHit(); this.logger.debug(`Redis cache hit for key: ${key}`); return redisResult; } } this.recordMiss(); return null; } catch (error) { this.logger.error('Cache get error:', error); this.recordMiss(); return null; } } /** * 设置缓存数据 */ async set(key: string, value: SearchResult[]): Promise<void> { if (!this.config.enabled || !value || value.length === 0) return; try { // 计算TTL const ttl = this.calculateTTL(key, value); // 1. 设置本地缓存 this.localCache.set(key, value, ttl.local); // 2. 如果启用了Redis,设置Redis缓存 if (this.config.redis?.enabled) { await this.setToRedis(key, value, ttl.redis); } this.updateStats(); this.logger.debug(`Cached ${value.length} results for key: ${key}`); } catch (error) { this.logger.error('Cache set error:', error); } } /** * 删除缓存 */ async delete(key: string): Promise<void> { try { this.localCache.del(key); if (this.config.redis?.enabled) { await this.deleteFromRedis(key); } this.logger.debug(`Deleted cache for key: ${key}`); } catch (error) { this.logger.error('Cache delete error:', error); } } /** * 清空缓存 */ async clear(): Promise<void> { try { this.localCache.flushAll(); if (this.config.redis?.enabled) { await this.clearRedis(); } this.resetStats(); this.logger.info('Cache cleared'); } catch (error) { this.logger.error('Cache clear error:', error); } } /** * 计算TTL */ private calculateTTL(key: string, value: SearchResult[]): { local: number; redis: number } { let baseTTL = this.config.ttl; // 根据内容类型调整TTL const hasNews = value.some(result => result.source?.includes('news') || result.url?.includes('news') ); const hasAcademic = value.some(result => result.source?.includes('arxiv') || result.source?.includes('pubmed') ); if (hasNews) { baseTTL = Math.min(baseTTL, 1800); // 新闻内容30分钟 } else if (hasAcademic) { baseTTL = Math.max(baseTTL, 7200); // 学术内容2小时 } return { local: baseTTL, redis: baseTTL * 2 // Redis缓存时间更长 }; } /** * Redis操作方法(占位符) */ private async getFromRedis(key: string): Promise<SearchResult[] | null> { // TODO: 实现Redis获取逻辑 return null; } private async setToRedis(key: string, value: SearchResult[], ttl: number): Promise<void> { // TODO: 实现Redis设置逻辑 } private async deleteFromRedis(key: string): Promise<void> { // TODO: 实现Redis删除逻辑 } private async clearRedis(): Promise<void> { // TODO: 实现Redis清空逻辑 } /** * 设置缓存事件监听 */ private setupCacheEvents(): void { this.localCache.on('expired', (key, value) => { this.logger.debug(`Cache key expired: ${key}`); }); this.localCache.on('del', (key, value) => { this.logger.debug(`Cache key deleted: ${key}`); }); } /** * 记录缓存命中 */ private recordHit(): void { this.stats.hits++; this.updateHitRate(); } /** * 记录缓存未命中 */ private recordMiss(): void { this.stats.misses++; this.updateHitRate(); } /** * 更新命中率 */ private updateHitRate(): void { const total = this.stats.hits + this.stats.misses; this.stats.hitRate = total > 0 ? this.stats.hits / total : 0; } /** * 更新统计信息 */ private updateStats(): void { this.stats.totalKeys = this.localCache.keys().length; this.stats.memoryUsage = process.memoryUsage().heapUsed; } /** * 重置统计信息 */ private resetStats(): void { this.stats = { hits: 0, misses: 0, hitRate: 0, totalKeys: 0, memoryUsage: 0 }; } /** * 获取缓存统计信息 */ getStats(): CacheStats { this.updateStats(); return { ...this.stats }; } /** * 获取缓存键列表 */ getKeys(): string[] { return this.localCache.keys(); } /** * 关闭缓存 */ async close(): Promise<void> { this.localCache.close(); // TODO: 关闭Redis连接 this.logger.info('Cache closed'); } }

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