fixes
This commit is contained in:
parent
2c6c2f8e44
commit
830b9e94a1
5 changed files with 184 additions and 52 deletions
2
.env
2
.env
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
# Core Application Settings
|
# Core Application Settings
|
||||||
NODE_ENV=development
|
NODE_ENV=development
|
||||||
LOG_LEVEL=error
|
LOG_LEVEL=info
|
||||||
|
|
||||||
# Data Service Configuration
|
# Data Service Configuration
|
||||||
DATA_SERVICE_PORT=2001
|
DATA_SERVICE_PORT=2001
|
||||||
|
|
|
||||||
54
libs/cache/src/index.ts
vendored
54
libs/cache/src/index.ts
vendored
|
|
@ -1,5 +1,5 @@
|
||||||
import { RedisCache } from './redis-cache';
|
import { RedisCache } from './redis-cache';
|
||||||
import type { CacheOptions, CacheProvider, RedisConfig } from './types';
|
import type { CacheOptions, CacheProvider } from './types';
|
||||||
|
|
||||||
// Cache instances registry to prevent multiple instances with same prefix
|
// Cache instances registry to prevent multiple instances with same prefix
|
||||||
const cacheInstances = new Map<string, CacheProvider>();
|
const cacheInstances = new Map<string, CacheProvider>();
|
||||||
|
|
@ -37,62 +37,14 @@ export function createCache(options: CacheOptions): CacheProvider {
|
||||||
return new RedisCache(defaultOptions);
|
return new RedisCache(defaultOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a cache instance for trading data
|
|
||||||
*/
|
|
||||||
export function createTradingCache(redisConfig: RedisConfig, options?: Partial<Omit<CacheOptions, 'redisConfig'>>): CacheProvider {
|
|
||||||
return createCache({
|
|
||||||
keyPrefix: 'trading:',
|
|
||||||
ttl: 3600, // 1 hour default
|
|
||||||
enableMetrics: true,
|
|
||||||
shared: true,
|
|
||||||
...options,
|
|
||||||
redisConfig,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a cache for market data with shorter TTL
|
|
||||||
*/
|
|
||||||
export function createMarketDataCache(redisConfig: RedisConfig, options?: Partial<Omit<CacheOptions, 'redisConfig'>>): CacheProvider {
|
|
||||||
return createCache({
|
|
||||||
keyPrefix: 'market:',
|
|
||||||
ttl: 300, // 5 minutes for market data
|
|
||||||
enableMetrics: true,
|
|
||||||
shared: true,
|
|
||||||
...options,
|
|
||||||
redisConfig,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a cache for indicators with longer TTL
|
|
||||||
*/
|
|
||||||
export function createIndicatorCache(redisConfig: RedisConfig, options?: Partial<Omit<CacheOptions, 'redisConfig'>>): CacheProvider {
|
|
||||||
return createCache({
|
|
||||||
keyPrefix: 'indicators:',
|
|
||||||
ttl: 1800, // 30 minutes for indicators
|
|
||||||
enableMetrics: true,
|
|
||||||
shared: true,
|
|
||||||
...options,
|
|
||||||
redisConfig,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Export types and classes
|
// Export types and classes
|
||||||
export type {
|
export type {
|
||||||
CacheProvider,
|
CacheConfig, CacheKey, CacheOptions, CacheProvider, CacheStats, RedisConfig, SerializationOptions
|
||||||
CacheOptions,
|
|
||||||
CacheConfig,
|
|
||||||
CacheStats,
|
|
||||||
CacheKey,
|
|
||||||
SerializationOptions,
|
|
||||||
RedisConfig,
|
|
||||||
} from './types';
|
} from './types';
|
||||||
|
|
||||||
export { RedisCache } from './redis-cache';
|
|
||||||
export { RedisConnectionManager } from './connection-manager';
|
export { RedisConnectionManager } from './connection-manager';
|
||||||
export { CacheKeyGenerator } from './key-generator';
|
export { CacheKeyGenerator } from './key-generator';
|
||||||
|
export { RedisCache } from './redis-cache';
|
||||||
|
|
||||||
// Default export for convenience
|
// Default export for convenience
|
||||||
export default createCache;
|
export default createCache;
|
||||||
|
|
|
||||||
|
|
@ -9,4 +9,13 @@ export {
|
||||||
getWorkingProxies,
|
getWorkingProxies,
|
||||||
updateProxies
|
updateProxies
|
||||||
} from './proxy-manager';
|
} from './proxy-manager';
|
||||||
|
|
||||||
|
export {
|
||||||
|
ProxySyncService,
|
||||||
|
getProxySyncService,
|
||||||
|
startProxySync,
|
||||||
|
stopProxySync,
|
||||||
|
syncProxiesOnce
|
||||||
|
} from './proxy-sync';
|
||||||
|
|
||||||
export type { ProxyInfo } from '@stock-bot/http'; // Re-export for convenience
|
export type { ProxyInfo } from '@stock-bot/http'; // Re-export for convenience
|
||||||
|
|
@ -35,6 +35,11 @@ export class ProxyManager {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
logger.info('Initializing proxy manager...');
|
logger.info('Initializing proxy manager...');
|
||||||
|
|
||||||
|
// Wait for cache to be ready
|
||||||
|
await this.cache.waitForReady(10000); // Wait up to 10 seconds
|
||||||
|
logger.debug('Cache is ready');
|
||||||
|
|
||||||
await this.loadFromCache();
|
await this.loadFromCache();
|
||||||
this.isInitialized = true;
|
this.isInitialized = true;
|
||||||
logger.info('Proxy manager initialized', {
|
logger.info('Proxy manager initialized', {
|
||||||
|
|
@ -231,6 +236,15 @@ export class ProxyManager {
|
||||||
if (!ProxyManager.instance) {
|
if (!ProxyManager.instance) {
|
||||||
ProxyManager.instance = new ProxyManager();
|
ProxyManager.instance = new ProxyManager();
|
||||||
await ProxyManager.instance.initializeInternal();
|
await ProxyManager.instance.initializeInternal();
|
||||||
|
|
||||||
|
// Perform initial sync with proxy:active:* storage
|
||||||
|
try {
|
||||||
|
const { syncProxiesOnce } = await import('./proxy-sync');
|
||||||
|
await syncProxiesOnce();
|
||||||
|
logger.info('Initial proxy sync completed');
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Failed to perform initial proxy sync', { error });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
157
libs/utils/src/proxy/proxy-sync.ts
Normal file
157
libs/utils/src/proxy/proxy-sync.ts
Normal file
|
|
@ -0,0 +1,157 @@
|
||||||
|
/**
|
||||||
|
* Proxy Storage Synchronization Service
|
||||||
|
*
|
||||||
|
* This service bridges the gap between two proxy storage systems:
|
||||||
|
* 1. proxy:active:* keys (used by proxy tasks for individual proxy storage)
|
||||||
|
* 2. proxies:active-proxies (used by ProxyManager for centralized storage)
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { createCache, type CacheProvider } from '@stock-bot/cache';
|
||||||
|
import { getDatabaseConfig } from '@stock-bot/config';
|
||||||
|
import { getLogger } from '@stock-bot/logger';
|
||||||
|
import type { ProxyInfo } from '@stock-bot/http';
|
||||||
|
import { ProxyManager } from './proxy-manager';
|
||||||
|
|
||||||
|
const logger = getLogger('proxy-sync');
|
||||||
|
|
||||||
|
export class ProxySyncService {
|
||||||
|
private cache: CacheProvider;
|
||||||
|
private syncInterval: Timer | null = null;
|
||||||
|
private isRunning = false;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
const databaseConfig = getDatabaseConfig();
|
||||||
|
this.cache = createCache({
|
||||||
|
redisConfig: databaseConfig.dragonfly,
|
||||||
|
keyPrefix: '', // No prefix to access all keys
|
||||||
|
ttl: 86400,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start the synchronization service
|
||||||
|
* @param intervalMs - Sync interval in milliseconds (default: 5 minutes)
|
||||||
|
*/
|
||||||
|
async start(intervalMs: number = 300000): Promise<void> {
|
||||||
|
if (this.isRunning) {
|
||||||
|
logger.warn('Proxy sync service is already running');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isRunning = true;
|
||||||
|
logger.info('Starting proxy sync service', { intervalMs });
|
||||||
|
|
||||||
|
// Wait for cache to be ready before initial sync
|
||||||
|
await this.cache.waitForReady(10000);
|
||||||
|
|
||||||
|
// Initial sync
|
||||||
|
await this.syncProxies();
|
||||||
|
|
||||||
|
// Set up periodic sync
|
||||||
|
this.syncInterval = setInterval(async () => {
|
||||||
|
try {
|
||||||
|
await this.syncProxies();
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Error during periodic sync', { error });
|
||||||
|
}
|
||||||
|
}, intervalMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop the synchronization service
|
||||||
|
*/
|
||||||
|
stop(): void {
|
||||||
|
if (this.syncInterval) {
|
||||||
|
clearInterval(this.syncInterval);
|
||||||
|
this.syncInterval = null;
|
||||||
|
}
|
||||||
|
this.isRunning = false;
|
||||||
|
logger.info('Stopped proxy sync service');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform a one-time synchronization
|
||||||
|
*/
|
||||||
|
async syncProxies(): Promise<void> {
|
||||||
|
try {
|
||||||
|
logger.debug('Starting proxy synchronization');
|
||||||
|
|
||||||
|
// Wait for cache to be ready
|
||||||
|
await this.cache.waitForReady(5000);
|
||||||
|
|
||||||
|
// Collect all proxies from proxy:active:* storage
|
||||||
|
const proxyKeys = await this.cache.keys('proxy:active:*');
|
||||||
|
|
||||||
|
if (proxyKeys.length === 0) {
|
||||||
|
logger.debug('No proxies found in proxy:active:* storage');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const allProxies: ProxyInfo[] = [];
|
||||||
|
|
||||||
|
// Fetch all proxies in parallel for better performance
|
||||||
|
const proxyPromises = proxyKeys.map(key => this.cache.get<ProxyInfo>(key));
|
||||||
|
const proxyResults = await Promise.all(proxyPromises);
|
||||||
|
|
||||||
|
for (const proxy of proxyResults) {
|
||||||
|
if (proxy) {
|
||||||
|
allProxies.push(proxy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const workingCount = allProxies.filter(p => p.isWorking).length;
|
||||||
|
|
||||||
|
logger.info('Collected proxies from storage', {
|
||||||
|
total: allProxies.length,
|
||||||
|
working: workingCount,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update ProxyManager with all proxies
|
||||||
|
const manager = ProxyManager.getInstance();
|
||||||
|
await manager.updateProxies(allProxies);
|
||||||
|
|
||||||
|
logger.info('Proxy synchronization completed', {
|
||||||
|
synchronized: allProxies.length,
|
||||||
|
working: workingCount,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Failed to sync proxies', { error });
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get synchronization status
|
||||||
|
*/
|
||||||
|
getStatus(): { isRunning: boolean; lastSync?: Date } {
|
||||||
|
return {
|
||||||
|
isRunning: this.isRunning,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export singleton instance
|
||||||
|
let syncServiceInstance: ProxySyncService | null = null;
|
||||||
|
|
||||||
|
export function getProxySyncService(): ProxySyncService {
|
||||||
|
if (!syncServiceInstance) {
|
||||||
|
syncServiceInstance = new ProxySyncService();
|
||||||
|
}
|
||||||
|
return syncServiceInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convenience functions
|
||||||
|
export async function startProxySync(intervalMs?: number): Promise<void> {
|
||||||
|
const service = getProxySyncService();
|
||||||
|
await service.start(intervalMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function stopProxySync(): void {
|
||||||
|
const service = getProxySyncService();
|
||||||
|
service.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function syncProxiesOnce(): Promise<void> {
|
||||||
|
const service = getProxySyncService();
|
||||||
|
await service.syncProxies();
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue