redis 의 부적 절 한 사용 으로 인 한 카드 사 bug 의 과정 분석
처음에 환경 응답 이 느리다 고 불평 할 때 우 리 는 응용 프로그램 을 다시 시작 하고 응용 프로그램 이 정상 으로 돌아 와 서 처리 하지 않 았 다.그러나 그 후에 문제 가 발생 하 는 빈도 가 점점 잦 아 지면 서 동료 들 이 불평 하기 시 작 했 고 코드 에 문제 가 있 을 것 같 아서 검 사 를 시작 했다.
먼저 개 발 된 로 컬 id 에 문제 가 발견 되 지 않 았 습 니 다.카드 가 죽 었 을 때 데이터 베 이 스 를 사용 하고 redis 가 정상 적 이 며 특별한 오류 로그 가 없습니다.샌 드 박스 환경 기계 문제 라 고 의심 하기 시작 했다.(테스트 환경 자체 가 바삭바삭 하 다!!)
그래서 ssh 는 서버 에 올 라 다음 명령 을 수행 합 니 다.
top
이때 기계 가 그런대로 정상 인 것 을 발 견 했 지만,마음 은 여전히😖,그래서 jvm 스 택 정 보 를 보 려 고 합 니 다.
먼저 문 제 를 보고 자원 을 비교적 소모 하 는 스 레 드 를 응용 하 다.
집행
상대 적 으로 자원 을 소모 하 는 세 개의 스 레 드 를 찾 습 니 다.
jstack 메모리 보기
jstack 12798|grep 12799 의 16 진법 31ff
아무 문제 도 알 아 채 지 못 하고 상하 10 줄 도 보고 집행 하 다.
일부 스 레 드 가 lock 상태 에 있 는 것 을 보 았 습 니 다.그러나 업무 관련 코드 가 나타 나 지 않 아 무시 했다.이 럴 때 는 두서 가 없다.한 번 생각 하 다.이번에 렉 걸 린 기 계 를 포기 하기 로 했 어 요.
사고 현장 을 보호 하기 위해 먼저 문제 프로 세 스 의 모든 메모 리 를 덤 프 한 다음 debug 모드 에서 테스트 환경 응용 을 다시 시작 합 니 다.문 제 를 다시 표시 할 때 원 격 debug 문제 기 계 를 직접 사용 합 니 다.
다음 날 문제 가 재현 되자 운영 nginx 리 트 윗 에 이 문제 응용 프로그램 을 제거 하고 원 격 debug tomcat 를 사용 하 라 고 알 렸 습 니 다.
스스로 인 터 페 이 스 를 찾 았 습 니 다.인터페이스 입구 에 정지점 이 있 었 습 니 다.비극 이 시작 되 었 고 아무 일 도 일어나 지 않 았 습 니 다!API 가 서비스 응답 을 기다 리 고 정지점 에 들 어가 지 않 았 습 니 다.이때 약간 어리둥절 하고 냉정 해 졌 습 니 다.입구 앞 에 있 는 op 에서 정지점 을 내 렸 고 debug 를 한 번 더 했 습 니 다.이번 에는 정지점 에 들 어 갔 습 니 다.f8 N 번 후에 redis 명령 을 수행 할 때 카드 주 를 발 견 했 습 니 다.계속 따라 가세 요.마지막 으로 제 디 스 의 한 곳 에서 문 제 를 발 견 했 습 니 다.
/**
* Returns a Jedis instance to be used as a Redis connection. The instance can be newly created or retrieved from a
* pool.
*
* @return Jedis instance ready for wrapping into a {@link RedisConnection}.
*/
protected Jedis fetchJedisConnector() {
try {
if (usePool && pool != null) {
return pool.getResource();
}
Jedis jedis = new Jedis(getShardInfo());
// force initialization (see Jedis issue #82)
jedis.connect();
return jedis;
} catch (Exception ex) {
throw new RedisConnectionFailureException("Cannot get Jedis connection", ex);
}
}
위 pool.getResource()후 스 레 드 시작 wait
public T getResource() {
try {
return internalPool.borrowObject();
} catch (Exception e) {
throw new JedisConnectionException("Could not get a resource from the pool", e);
}
}
return internalPool.borrowObject(); 이 코드 는 임대 코드 일 거 예요.이어서...
public T borrowObject(long borrowMaxWaitMillis) throws Exception {
this.assertOpen();
AbandonedConfig ac = this.abandonedConfig;
if (ac != null && ac.getRemoveAbandonedOnBorrow() && this.getNumIdle() < 2 && this.getNumActive() > this.getMaxTotal() - 3) {
this.removeAbandoned(ac);
}
PooledObject<T> p = null;
boolean blockWhenExhausted = this.getBlockWhenExhausted();
long waitTime = 0L;
while(p == null) {
boolean create = false;
if (blockWhenExhausted) {
p = (PooledObject)this.idleObjects.pollFirst();
if (p == null) {
create = true;
p = this.create();
}
if (p == null) {
if (borrowMaxWaitMillis < 0L) {
p = (PooledObject)this.idleObjects.takeFirst();
} else {
waitTime = System.currentTimeMillis();
p = (PooledObject)this.idleObjects.pollFirst(borrowMaxWaitMillis, TimeUnit.MILLISECONDS);
waitTime = System.currentTimeMillis() - waitTime;
}
}
if (p == null) {
throw new NoSuchElementException("Timeout waiting for idle object");
}
그 중 에 코드 가 있어 요.
if (p == null) {
if (borrowMaxWaitMillis < 0L) {
p = (PooledObject)this.idleObjects.takeFirst();
} else {
waitTime = System.currentTimeMillis();
p = (PooledObject)this.idleObjects.pollFirst(borrowMaxWaitMillis, TimeUnit.MILLISECONDS);
waitTime = System.currentTimeMillis() - waitTime;
}
}
borrow MaxWait Millis<0 은 계속 실 행 됩 니 다.그리고 계속 순환 합 니 다. 이 값 이 설정 되 어 있 지 않 은 지 의심 하기 시작 합 니 다.redis pool 설정 을 찾 았 습 니 다.MaxWait Millis 가 설정 되 어 있 지 않 은 것 을 발 견 했 습 니 다.설정 후 else 코드 도 Exception 으로 문 제 를 해결 할 수 없습니다.
계속 F8
public E takeFirst() throws InterruptedException {
this.lock.lock();
Object var2;
try {
Object x;
while((x = this.unlinkFirst()) == null) {
this.notEmpty.await();
}
var2 = x;
} finally {
this.lock.unlock();
}
return var2;
}
여기에 와 서 lock 글 자 를 발견 하고 모든 요청 api 가 차단 되 었 다 고 의심 하기 시작 했다.그래서 다시 ssh 서버 설치 arthas ,(Arthas 알 리 바 바 가 오픈 한 자바 진단 도구 입 니 다)
thread 명령 실행
http-nio 의 스 레 드 waiting 상 태 를 많이 발 견 했 습 니 다.http-nio-8083-exec-이 스 레 드 는 http 에서 요청 한 tomcat 스 레 드 입 니 다.
임의의 스 레 드 를 찾 아 메모리 더 미 를 봅 니 다.
thread -428
이것 은 api 가 계속 빙빙 돌 고 있 는 문제 임 을 확인 할 수 있 습 니 다.바로 이 redis 가 연결 코드 를 가 져 와 서 생 긴 것 입 니 다.
이 메모리 코드 해독 모든 스 레 드 가 기다 리 고 있 습 니 다. @53e 5504 e 이 대상 은 자 물 쇠 를 방출 합 니 다.그래서 jstack 는 전체 국면 에서 53e 5504 e 를 찾 았 지만 이 대상 이 있 는 스 레 드 를 찾 지 못 했다.
이로부터.문제 의 원인 은 redis 연결 이 가 져 온 문제 임 이 확실 합 니 다.그런데 어떤 이유 로 연결 이 안 되 는 지 아직 확실 하지 않 아 요.
arthas 의 thread-b(thread-b 를 다시 실행 하여 현재 다른 스 레 드 를 막 고 있 는 스 레 드 를 찾 습 니 다)
결과 가 없다.여 기 는 생각 과 달리 차단 스 레 드 를 찾 을 수 있 을 것 입 니 다.그래서 이 명령 을 내 린 문 서 를 보 니 다음 말 이 있 습 니 다.
좋아,우 리 는 마침 후자 야...
다시 한 번 생각 을 정리 하 다.이번 redis pool 설정 을 수정 하면 연결 시간 초과 설정 을 2s 로 설정 한 다음 문제 가 다시 복 원 될 때 응용 프로그램 이 마지막 으로 정상 적 인 시간 에 무엇 을 했 는 지 관찰 합 니 다.
설정 추가
JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory();
.......
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxWaitMillis(2000);
.......
jedisConnectionFactory.afterPropertiesSet();
서비스 재 개,대기...또 하루 지나 다시
ssh 서버,tomcat accesslog 검사,대량의 api 요청 500,
org.springframework.data.redis.RedisConnectionFailureException: Cannot get Jedis connection; nested exception is redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource fr
om the pool
at org.springframework.data.redis.connection.jedis.JedisConnectionFactory.fetchJedisConnector(JedisConnectionFactory.java:140)
at org.springframework.data.redis.connection.jedis.JedisConnectionFactory.getConnection(JedisConnectionFactory.java:229)
at org.springframework.data.redis.connection.jedis.JedisConnectionFactory.getConnection(JedisConnectionFactory.java:57)
at org.springframework.data.redis.core.RedisConnectionUtils.doGetConnection(RedisConnectionUtils.java:128)
at org.springframework.data.redis.core.RedisConnectionUtils.getConnection(RedisConnectionUtils.java:91)
at org.springframework.data.redis.core.RedisConnectionUtils.getConnection(RedisConnectionUtils.java:78)
at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:177)
at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:152)
at org.springframework.data.redis.core.AbstractOperations.execute(AbstractOperations.java:85)
at org.springframework.data.redis.core.DefaultHashOperations.get(DefaultHashOperations.java:48)
원천 을 찾 아 처음으로 500 곳 이 나 타 났 다.
다음 코드 발견
.......
Cursor c = stringRedisTemplate.getConnectionFactory().getConnection().scan(options);
while (c.hasNext()) {
.....,,
}
이 코드 를 분석 하면 string RedisTemplate.getConnection Factory().getConnection()은 pool 의 redisConnection 을 가 져 온 후 후속 작업 을 하지 않 았 습 니 다.즉,이때 redis 연결 풀 에 있 는 링크 가 임대 되 었 을 때 풀 리 거나 링크 풀 에 반환 되 지 않 았 습 니 다.비록 업무 가 처리 되 었 지만 redisConnection 은 이미 비어 있 습 니 다.하지만 pool 의 redisConnection 상 태 는 아직 idle 상태 로 돌아 가지 않 았 습 니 다.정상
이 문 제 는 이미 찾 았 다.
총화:spring stringRedisTemplate 는 redis 의 일반적인 작업 을 패키지 로 했 지만 Scan SetNx 와 같은 명령 은 지원 되 지 않 습 니 다.이 때 는 jedis Connection 을 받 아 특별한 Commands 를 진행 해 야 합 니 다.
쓰다 string RedisTemplate.getConnection Factory().getConnection()은 추천 되 지 않 습 니 다.
우 리 는 사용 할 수 있다.
stringRedisTemplate.execute(new RedisCallback<Cursor>() {
@Override
public Cursor doInRedis(RedisConnection connection) throws DataAccessException {
return connection.scan(options);
}
});
수행 하 러 왔 습 니 다.또는 connection 을 사용 한 후 사용 합 니 다.
RedisConnectionUtils.releaseConnection(conn, factory);
연결 을 해제 합 니 다.또한,redis 에서 도 keys 명령 을 사용 하 는 것 을 권장 하지 않 습 니 다.redis pool 의 설정 은 합 리 적 으로 맞 춰 야 합 니 다.그렇지 않 으 면 문제 가 발생 하지 않 고 오류 로그 가 없 으 며 오류 가 없 으 며 위치 추적 이 상당히 어렵 습 니 다.
여기 서 redis 의 부적 절 한 사용 으로 인해 카드 가 죽 는 bug 를 분석 하 는 과정 에 관 한 글 을 소개 합 니 다.더 많은 관련 redis 로 인해 카드 가 죽 는 내용 은 우리 의 이전 글 을 검색 하거나 아래 의 관련 글 을 계속 조회 하 시기 바 랍 니 다.앞으로 많은 응원 바 랍 니 다!
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
Redis 해시에 대한 완벽한 가이드변경 가능하므로 필요에 따라 쉽게 변경하고 업데이트할 수 있습니다. Redis 해시는 구조가 평평하므로 JSON에서와 같이 여러 수준을 가질 수 없습니다. redis 해시의 명명 규칙은 hash:key 로 입력되므로...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.