我在 ElasticCache 上使用ioredis作为适配器我有 2 个节点,一个主节点和一个副本,
有了这个配置
- 数据分层:禁用
- 引擎版本兼容性:5.0.6
- 多可用区:启用
- 自动故障转移:启用
在上个月,我们开始遇到很多 ECONNRESET 和 ETIMEDOUT 错误,通常是随着新实例的扩展而出现的,其中一些连接了这些错误,而另一些则因这些错误而下降
Error: read ECONNRESET
at TCP.onStreamRead (internal/stream_base_commons.js:209:20)
at TCP.callbackTrampoline (internal/async_hooks.js:131:14)
和
Error: read ETIMEDOUT
at TCP.onStreamRead (internal/stream_base_commons.js:209:20)
at TCP.callbackTrampoline (internal/async_hooks.js:131:14)
这是我们的包装代码
import IORedis, { KeyType, Ok, ValueType } from 'ioredis';
import { logger, monitor } from '@utils/logging';
const { host, port, maxRetriesPerRequest, connectTimeout } = config.get('redis');
class RedisUtil {
private redis: IORedis.Redis;
private static handleError(type: string, key: string, error?: any): void {
let errorMessage = '';
switch (type) {
case 'increment':
errorMessage = `Error thrown when trying to increment ${key}`;
break;
case 'ttl':
errorMessage = `Error thrown when trying to get ttl for ${key}`;
break;
case 'decrement':
errorMessage = `Error thrown when trying to decrement ${key}`;
break;
case 'reset':
errorMessage = `Error thrown when trying to reset ${key}`;
break;
}
logger.error(error, errorMessage);
throw new Error(errorMessage);
}
constructor() {
this.initRedis();
}
public async set(key: KeyType, value: ValueType, ttl?: number | string): Promise<Ok> {
try {
this.notifyDbSet(key, value);
if (ttl) {
return await this.redis.set(key, value, 'EX', ttl);
}
return await this.redis.set(key, value);
} catch (error) {
logger.error(error, `Redis failed setting key ${key}`);
}
}
public async get(key: KeyType): Promise<any> {
try {
this.notifyDbGet(key);
return await this.redis.get(key);
} catch (error) {
logger.error(error, `Redis failed getting value for key ${key}`);
}
}
public async expire(key, expiry): Promise<void> {
await this.redis.pexpire(key, expiry);
}
public increment(key, cb): void {
this.redis
.multi()
.incr(key)
.pttl(key)
.exec(async (err, replies) => {
if (!Array.isArray(replies)) throw err;
const [[incrError, counter], [ttlError, ttl]] = replies;
if (incrError) RedisUtil.handleError('increment', key, incrError);
if (ttlError) RedisUtil.handleError('ttl', key, ttlError);
cb({ counter, ttl });
});
}
public decrement(key, cb): void {
this.redis
.multi()
.decr(key)
.pttl(key)
.exec(async (err, replies) => {
if (!Array.isArray(replies)) throw err;
const [[decrError, _], [ttlError, ttl]] = replies;
if (decrError) RedisUtil.handleError('decrement', key, decrError);
if (ttlError) RedisUtil.handleError('ttl', key, ttlError);
cb({ ttl });
});
}
public async resetKey(key): Promise<void> {
this.redis.del(key).catch(resetError => {
if (resetError) RedisUtil.handleError('reset', key, resetError);
});
}
private initRedis(): void {
this.redis = new IORedis({
host,
port,
enableReadyCheck: true,
showFriendlyErrorStack: true,
maxRetriesPerRequest: maxRetriesPerRequest | 3,
connectTimeout: connectTimeout | 2000
});
this.setEventListeners();
}
private setEventListeners(): void {
this.redis.on('connect', () => {
logger.info(`Redis connection is established. Host: ${host}`);
});
this.redis.on('ready', () => {
logger.info(`Redis is ready`);
});
this.redis.on(
'error',
(error => {
logger.error(error, 'Redis error occurred while trying to connect Redis server');
monitor.errorEvent(
'Redis error occurred while trying to connect Redis server',
`Error: ${error}. Host: ${host}`,
['redis_connection_failure'],
null
);
this.redis.disconnect();
}).bind(this)
);
this.redis.on('close', () => {
logger.info(`Redis server connection has closed`);
});
this.redis.on('end', ms => {
logger.info(`Redis connection end - connection is failed to establish`);
});
}
private notifyDbGet(key: KeyType): void {
try {
monitor.increment('redis_db.get', 1, [`db_host:${host}`]);
} catch (error) {
logger.error(error, `failed to notify datadog on redis DB get for key: ${key}`);
}
}
private notifyDbSet(key: KeyType, value: ValueType): void {
try {
monitor.increment('redis_db.set', 1, [`db_host:${host}`]);
} catch (error) {
logger.error(error, `failed to notify datadog on redis DB set for key: ${key}, value: ${value}`);
}
}
}
export const redis = new RedisUtil();
this.redis.on('error',(error => {
logger.error(error, 'Redis error occurred while trying to connect Redis server');
this.redis.disconnect();
}).bind(this)
);
- 有人遇到过这个问题吗?
- 错误处理的最佳实践是什么,在这种情况下,我应该在 [ECONNRESET,ETIMEDOUT] 错误状态代码返回时调用断开连接吗?