Golden Nine과 Silver Ten이 곧 공개됩니다. 20가지 Redis전형적인 인터뷰 질문이 여러분에게 도움이 되기를 바랍니다.
Redis, 정식 영어명은 Remote Dictionary Server(Remote Dictionary Service)로 ANSI C 언어로 작성된 오픈소스 로그형 Key-Value 데이터베이스로, 네트워크를 지원하고 메모리 기반으로 구성이 가능하며 지속되며 여러 언어로 API를 제공합니다. [관련 권장사항: Redis 동영상 튜토리얼]
MySQL 데이터베이스와 달리 Redis 데이터는 메모리에 저장됩니다. 읽기 및 쓰기 속도는 매우 빠르며 초당 100,000회 이상의 읽기 및 쓰기 작업을 처리할 수 있습니다. 따라서 Redis는 캐싱에도 널리 사용됩니다. 또한 Redis는 분산 잠금에도 자주 사용됩니다. 또한 Redis는 트랜잭션, 지속성, LUA 스크립트, LRU 기반 이벤트 및 다양한 클러스터 솔루션을 지원합니다.
2 Redis의 기본 데이터 구조 유형에 대해 이야기해 보겠습니다.문자열( string)
소개: 문자열은 Redis의 가장 기본적인 데이터 구조 유형이며 바이너리 안전하며 저장되는 최대 값은 512M입니다.키 값 설정 code>, <code>키 가져오기
등
set key value
、get key
等int(8字节长整型)/embstr(小于等于39字节字符串)/raw(大于39个字节字符串)
C语言的字符串是char[]
实现的,而Redis使用SDS(simple dynamic string) 封装,sds源码如下:
struct sdshdr{ unsigned int len; // 标记buf的长度 unsigned int free; //标记buf中未使用的元素个数 char buf[]; // 存放元素的坑 }
SDS 结构图如下:
Redis为什么选择SDS结构,而C语言原生的 char[]
不香吗?
举例其中一点,SDS中,O(1)时间复杂度,就可以获取字符串长度;而C 字符串,需要遍历整个字符串,时间复杂度为O(n)
hset key field value
、hget key field
ziplist(压缩列表)
、hashtable(哈希表)
字符串和哈希类型对比如下图:
lpush key value [value ...]
、lrange key start end
int(8바이트 긴 정수)/embstr(39바이트 문자열 이하)/raw(39바이트 문자열 초과)
3가지 유형이 있습니다char[]
로 구현되며 Redis는 zadd user:ranking:2021-03-03 Jay 3SDS 구조 다이어그램은 다음과 같습니다.
왜 Redis가
SDS구조를 선택한 걸까요? 그리고 C 언어의 기본char[]
가 좋지 않나요?내부 인코딩:
- 예를 들어 SDS에서는 문자열 길이를 O(1) 시간 복잡도로 얻을 수 있지만 C 문자열의 경우 전체 문자열을 탐색해야 하며 시간 복잡도는 O(n)입니다. h4 data- id="heading-5">해시(Hash)
- 소개: Redis에서 해시 유형은 v(값) 자체를 참조하며 이는 키-값 쌍(k-v) 구조이기도 합니다
- 간단한 사용 예 :
hset 키 필드 값
,hget 키 필드
ziplist(압축 목록)
,hashtable(해시 테이블)
Note
: hgetall을 개발에 사용하고 해시 요소가 많으면 Redis가 차단될 수 있습니다. hscan을 사용할 수 있습니다. 일부 필드만 가져오려면 hmget을 사용하는 것이 좋습니다. 🎜🎜🎜문자열과 해시 유형의 비교는 다음과 같습니다. 🎜🎜🎜 lpush 키 값 [값 ...]
, lrange 키 시작 끝
🎜🎜내부 인코딩: ziplist(압축 목록), linkedlist(연결 목록) )🎜 🎜응용 시나리오: 메시지 큐, 기사 목록, 🎜🎜🎜목록 유형의 삽입 및 팝업을 이해하기 위한 한 장의 그림: 🎜🎜🎜🎜🎜list 응용 시나리오는 다음을 참조하십시오. 🎜🎜🎜🎜lpush+lpop= 스택(스택)🎜🎜lpush +rpop=큐(큐)🎜🎜lpsh+ltrim=Capped Collection(제한된 컬렉션)🎜🎜lpush+brpop=Message Queue(메시지 큐)🎜🎜🎜🎜Set(세트)🎜🎜🎜🎜sadd key element [element ...]
, smembers key
sadd key element [element ...]
、smembers key
intset(整数集合)
、hashtable(哈希表)
zadd key score member [score member ...]
,zrank key member
ziplist(压缩列表)
、skiplist(跳跃表)
intset(정수 집합)
, hashtable(해시 테이블)
zadd 핵심 점수 멤버 [ 점수 멤버 ...]
, zrank 키 멤버
ziplist(압축 목록)
, skiplist(건너뛰기 목록)
code>응용 시나리오: 순위, 사회적 요구(예: 사용자 좋아요). Geo: Redis3.2에서 출시된 지리적 위치 위치 확인은 지리적 위치 정보를 저장하고 저장된 정보에 대해 작동하는 데 사용됩니다.
비트맵: 요소의 상태를 매핑하려면 1비트를 사용하세요. Redis에서는 하위 레이어가 문자열 유형을 기반으로 합니다. 비트맵을 단위로 사용하여 배열로 변환할 수 있습니다.
3. 왜 그렇게 빠른가요?
3.1 메모리 저장 기반 구현
우리 모두는 디스크에 데이터를 저장하는 MySQL 데이터베이스에 비해 메모리 읽기 및 쓰기가 디스크보다 훨씬 빠르다는 것을 알고 있습니다. 아 소비.
- 3.2 효율적인 데이터 구조
- Mysql 인덱스는 효율성을 높이기 위해 B+ 트리 데이터 구조를 선택한다는 것을 알고 있습니다. 실제로, 합리적인 데이터 구조는 애플리케이션/프로그램을 더 빠르게 만들 수 있습니다. 먼저 Redis의 데이터 구조 및 내부 인코딩 다이어그램을 살펴보겠습니다.
- SDS 단순 동적 문자열
공간 사전 할당: 문자열을 자주 수정할수록 메모리 할당이 더 자주 발생합니다. 성능을 소모하며, SDS 수정 및 공간 확장은 성능 손실을 줄이기 위해 사용되지 않는 공간을 추가로 할당해야 합니다.
Lazy 공간 해제: SDS가 단축되면 초과된 메모리 공간을 재활용하는 대신 Free가 초과된 공간을 기록합니다. 이후 변경 사항이 있으면 Free로 기록된 공간을 직접 사용하여 할당을 줄입니다.바이너리 안전성: Redis는 일부 바이너리 데이터, C 언어에서 발견되는 문자열을 저장할 수 있습니다.
3.4 합리적인 스레딩 모델
- 문자열: 숫자가 저장되면 int 유형 인코딩이 사용됩니다. 숫자가 아닌 값이 저장되면 39바이트보다 작거나 같은 문자열은 embstr입니다. 39바이트보다 크면 원시 인코딩이 사용됩니다.
- List: 목록의 요소 수가 512개 미만이고 목록의 각 요소 값이 64바이트(기본값) 미만인 경우 ziplist 인코딩을 사용하고, 그렇지 않으면 linkedlist 인코딩을 사용합니다. 해시 유형 요소가 512 미만이고, 모든 값이 64바이트 미만이면 ziplist 인코딩을 사용하고, 그렇지 않으면 해시테이블 인코딩을 사용합니다.
- Set: 세트의 요소가 모두 정수이고 요소 수가 512개 미만인 경우 intset 인코딩을 사용하고, 그렇지 않으면 해시테이블 인코딩을 사용합니다.
- Zset: 순서 집합의 요소 수가 128개 미만이고 각 요소의 값이 64바이트 미만인 경우 ziplist 인코딩을 사용하고, 그렇지 않으면 Skiplist(건너뛰기 목록) 인코딩을 사용합니다.
I/O 멀티플렉싱
다중 I/O 멀티플렉싱 기술을 사용하면 단일 스레드가 여러 연결 요청을 효율적으로 처리할 수 있으며 Redis는 I/O 멀티플렉싱 기술로 epoll을 사용합니다. 또한 Redis의 자체 이벤트 처리 모델은 네트워크 I/O에 너무 많은 시간을 낭비하지 않고 epoll의 연결, 읽기, 쓰기 및 종료를 이벤트로 변환합니다.
I/O 멀티플렉싱이란 무엇입니까?I/O: 네트워크 I/O단일 스레드 모델
- Multiple: 여러 네트워크 연결
- Multiplexing: 동일한 스레드를 재사용합니다.
- IO 멀티플렉싱은 실제로 여러 파일 핸들을 모니터링할 수 있는 스레드를 구현하는 동기식 IO 모델입니다. 파일 핸들이 준비되면 파일 핸들 없이 해당 읽기 및 쓰기 작업을 수행하도록 애플리케이션에 알릴 수 있습니다. 애플리케이션이 차단되고 CPU가 넘겨집니다.
Redis는 단일 스레드 모델이며, 단일 스레딩은 불필요한 CPU 컨텍스트 전환 및 경쟁 잠금 소비를 방지합니다. 정확하게는 단일 스레드이기 때문에 특정 명령을 너무 오랫동안 실행하면(예: hgetall 명령) 차단이 발생합니다. Redis는 빠른 실행 시나리오를 위한 데이터베이스입니다. 이므로 smembers, lrange, hgetall 등과 같은 명령은 주의해서 사용해야 합니다.
- Redis 6.0은 속도를 높이기 위해 멀티스레딩을 도입했으며, 명령 실행 및 메모리 작업은 여전히 단일 스레드입니다.
- 3.5 가상 메모리 메커니즘
Redis는 VM 메커니즘을 자체적으로 직접 구축하므로 일반 시스템처럼 시스템 기능을 호출하지 않으므로 이동 및 요청에 일정량의 시간이 낭비됩니다.
Redis의 가상 메모리 메커니즘은 무엇인가요?가상 메모리 메커니즘은 자주 액세스하지 않는 데이터(콜드 데이터)를 메모리에서 디스크로 일시적으로 교환하여 액세스해야 하는 다른 데이터(핫 데이터)를 위한 귀중한 메모리 공간을 확보합니다. VM 기능은 핫 데이터와 콜드 데이터의 분리를 실현할 수 있으므로 핫 데이터는 여전히 메모리에 있고 콜드 데이터는 디스크에 저장됩니다. 이렇게 하면 메모리 부족으로 인해 액세스 속도가 느려지는 문제를 피할 수 있습니다.
4. 캐시 고장, 캐시 침투, 캐시 눈사태란 무엇인가요?4.1 캐시 침투 문제
먼저 캐시를 사용하는 일반적인 방법을 살펴보겠습니다. 읽기 요청이 오면 먼저 캐시를 확인하고, 캐시에 적중이 있으면 바로 반환합니다. , 데이터베이스를 확인한 다음 데이터베이스를 넣습니다. 값은 캐시에 업데이트된 후 반환됩니다.
캐시 침투: 확실히 존재하지 않는 데이터를 쿼리하는 것을 의미합니다. 캐시에 도달하지 않기 때문에 데이터를 찾을 수 없으면 데이터베이스에서 쿼리해야 합니다. 캐시로 인해 존재하지 않는 데이터가 발생하게 됩니다. 각 요청은 데이터베이스에서 쿼리되어야 하므로 데이터베이스에 부담을 줍니다.
간단히 말하면, 읽기 요청에 접근할 때 캐시나 데이터베이스 모두 특정 값을 갖고 있지 않으며, 이로 인해 이 값에 대한 모든 쿼리 요청이 데이터베이스에 침투하게 됩니다.캐시 침투는 일반적으로 다음과 같은 상황으로 인해 발생합니다.비합리적인 비즈니스 설계
캐시 침투를 피하는 방법은 무엇입니까?- 예를 들어 대부분의 사용자는 가드를 활성화하지 않았지만 모든 요청은 캐시로 이동하여 특정 사용자 ID를 쿼리합니다. 보호 장치가 있는지 확인하십시오.
캐시 및 데이터베이스 데이터가 실수로 삭제되는 등의 업무/운영 및 유지/개발 오류- .
해커에 의한 불법 요청 공격- 예를 들어, 해커는 존재하지 않는 비즈니스 데이터를 읽기 위해 의도적으로 수많은 불법 요청을 조작합니다.
일반적으로 세 가지 방법이 있습니다.
- 1. 불법 요청인 경우 API 입구에서 매개변수를 확인하고 불법 값을 필터링합니다.
- 2. 쿼리 데이터베이스가 비어 있으면 캐시에 null 값이나 기본값을 설정할 수 있습니다. 그러나 쓰기 요청이 들어오면 캐시 일관성을 보장하기 위해 캐시를 업데이트해야 하며 동시에 캐시에 대한 적절한 만료 시간이 설정됩니다. (비즈니스에서 자주 사용되며 간단하고 효과적입니다.)
- 3. Bloom 필터를 사용하면 데이터 존재 여부를 빠르게 확인할 수 있습니다. 즉, 쿼리 요청이 들어오면 Bloom 필터를 통해 해당 값이 존재하는지 먼저 판단한 후 계속해서 존재하는지 확인하는 것입니다.
Bloom 필터 원리: 초기 값이 0인 비트맵 배열과 N개의 해시 함수로 구성됩니다. 키에 대해 N개의 해시 알고리즘을 수행하여 N개의 값을 비트 배열에서 해시하고 1로 설정합니다. 그런 다음 확인 시 이러한 특정 위치가 모두 1이면 블룸 필터링을 통해 서버는 키가 존재한다고 판단합니다. .
4.2 캐시 스노우런 문제
캐시 스노우런: 캐시에 있는 대용량 데이터의 만료 시간을 말하며, 쿼리 데이터가 방대하고 요청이 데이터베이스에 직접 액세스하여 데이터베이스에 과도한 부담을 줍니다. 심지어 다운타임도 발생합니다.
- 캐시 폭설은 일반적으로 동시에 많은 양의 데이터가 만료될 때 발생합니다. 따라서 만료 시간을 균등하게 설정하는 것, 즉 만료 시간을 상대적으로 이산적으로 설정하면 해결할 수 있습니다. 더 큰 고정 값 + 더 작은 임의 값을 사용하는 경우 5시간 + 0 ~ 1800초입니다.
- Redis 오류로 인해 캐시 폭설이 발생할 수도 있습니다. 이를 위해서는 Redis 고가용성 클러스터를 구축해야 합니다.
4.3 캐시 고장 문제
캐시 고장: 특정 시점에 핫스팟 키가 만료되는 경우를 말하며, 이 시점에서 이 키에 대한 동시 요청이 많이 발생하게 되므로 많은 수의 요청이 db에 도달했습니다.
캐시 분해는 약간 비슷해 보이지만 실제로는 캐시 충돌이 발생하면 데이터베이스가 과도한 압력을 받거나 심지어는 다운된다는 의미가 됩니다. 고장은 캐시 스노우런의 하위 집합이라고 간주할 수 있습니다. 일부 기사에서는 둘 사이의 차이점이 분석이 특정 단축키 캐시를 목표로 하는 반면 Xuebeng은 많은 키를 목표로 한다는 점이라고 생각합니다.
두 가지 해결책이 있습니다:
- 1 뮤텍스 잠금 방식을 사용하세요. 캐시가 실패하면 db 데이터를 즉시 로드하는 대신 먼저 (Redis의 setnx)와 같은 성공적인 반환과 함께 일부 원자성 작업 명령을 사용하여 작업합니다. 성공하면 db 데이터베이스 데이터를 로드하고 캐시를 설정합니다. 그렇지 않으면 캐시를 다시 가져오십시오.
- 2. "만료되지 않음"은 만료 시간이 설정되지 않았지만 핫스팟 데이터가 만료되려고 할 때 비동기 스레드가 업데이트되고 만료 시간을 설정한다는 의미입니다.
5. 단축키 문제란 무엇이며 단축키 문제를 해결하는 방법
단축키란 무엇입니까? Redis에서는 액세스 빈도가 높은 키를 단축키로 호출합니다.
특정 핫스팟 키에 대한 요청이 서버 호스트로 전송되는 경우 요청량이 특히 많아 호스트 리소스가 부족하거나 다운타임이 발생하여 정상적인 서비스에 영향을 줄 수 있습니다.
그리고 핫스팟 키는 어떻게 생성되나요? 두 가지 주요 이유가 있습니다:
- 플래시 세일, 핫 뉴스 및 읽기가 많고 쓰기가 적은 기타 시나리오와 같이 사용자가 소비하는 데이터는 생성된 데이터보다 훨씬 큽니다.
- 요청 샤딩이 집중되어 단일 Redi 서버의 성능을 초과합니다. 예를 들어 고정 이름 키와 해시가 동일한 서버에 속하고 즉각적인 액세스 양이 많아 시스템 병목 현상을 초과하고 핫키 문제가 발생합니다.
그렇다면 일상적인 개발에서 단축키를 식별하는 방법은 무엇일까요?
- 경험을 바탕으로 어떤 단축키를 결정합니다.
- 클라이언트 통계 보고
- 서비스 에이전트 계층에 보고
단축키 문제를 해결하는 방법은 무엇입니까?
- Redis 클러스터 확장: 샤드 복사본을 추가하여 읽기 트래픽 균형을 유지합니다.
- 핫 키를 다른 서버에 배포합니다.
- 2차 수준 캐시, 즉 JVM 로컬 캐시를 사용하여 Redis 읽기 요청을 줄입니다.
6. Redis 만료 전략 및 메모리 제거 전략
6.1 Redis 만료 전략
여기 있습니다
set key
的时候,可以给它设置一个过期时间,比如expire key 60
. 이 키가 60초 후에 만료되도록 지정합니다. Redis는 60초 후에 이를 어떻게 처리합니까? 먼저 몇 가지 만료 전략을 소개하겠습니다.Timed 만료
만료 시간이 있는 각 키는 타이머를 생성해야 하며, 만료 시간에 도달하면 키가 즉시 삭제됩니다. 이 전략은 만료된 데이터를 즉시 지울 수 있고 메모리 친화적이지만 만료된 데이터를 처리하는 데 많은 양의 CPU 리소스를 차지하므로 캐시 응답 시간과 처리량에 영향을 미칩니다.
지연 만료
키에 액세스할 때만 키가 만료되었는지 판단하고, 만료되면 삭제됩니다. 이 전략은 CPU 자원을 최대한 절약할 수 있지만 메모리에는 매우 비우호적입니다. 극단적인 경우에는 만료된 많은 수의 키에 다시 액세스할 수 없으므로 삭제되지 않고 많은 양의 메모리를 차지할 수 있습니다.
주기적인 만료
특정 시간마다 특정 수의 데이터베이스 만료 사전에 있는 특정 수의 키가 스캔되고 만료된 키가 지워집니다. 이 전략은 처음 두 가지의 절충안입니다. 예약된 스캔의 시간 간격과 각 스캔의 제한된 시간 소비를 조정함으로써 다양한 상황에서 CPU와 메모리 리소스 간의 최적의 균형을 달성할 수 있습니다.
만료 사전은 만료 시간이 설정된 모든 키의 만료 시간 데이터를 저장합니다. 여기서 키는 키 공간의 키에 대한 포인터이고 값은 밀리초 정밀도로 키의 UNIX 타임스탬프로 표시되는 만료 시간입니다. 키 공간은 Redis 클러스터에 저장된 모든 키를 나타냅니다.
Redis는 지연 만료와 주기적 만료 두 가지 만료 전략을 모두 사용합니다.
- Redis가 현재 300,000개의 키를 저장하고 있고 모두 만료 시간이 설정되어 있다고 가정해 보겠습니다. 모든 키를 100ms마다 확인하면 CPU 부하가 극도로 높아져 결국 중단될 수 있습니다.
- 따라서 redis는 정기적인 만료를 사용하고 특정 수의 키를 무작위로 선택하여 100ms마다 확인하고 삭제합니다.
- 하지만 결국에는 삭제되지 않은 만료된 키가 많이 있을 수 있습니다. 이때 redis는 지연 삭제를 사용합니다. 키를 받으면 redis가 이를 확인합니다. 키에 만료 시간이 설정되어 있고 만료된 경우 이때 삭제됩니다.
그러나 일반 삭제에서 만료된 키가 많이 누락되면 지연 삭제가 사용되지 않습니다. 메모리에 만료된 키가 많이 축적되어 메모리 폭발의 직접적인 원인이 됩니다. 또는 비즈니스 볼륨이 증가할 때 Redis 키를 많이 사용하고 메모리가 부족하여 운영 및 유지 관리 담당자가 메모리를 늘리는 것을 잊어버리는 경우도 있습니다. Redis가 이렇게 끊을 수 있을까요? 아니요! Redis는 8가지 메모리 제거 전략으로 자신을 보호합니다~
6.2 Redis 메모리 제거 전략
캐시7 Redis의 일반적인 애플리케이션 시나리오에 대해 이야기해 보겠습니다.
- 휘발성-lru: 새로 작성된 데이터를 수용할 만큼 메모리가 부족할 경우 만료 시간이 있는 키에서 LRU(최근 사용)를 사용합니다. set ) 제거 알고리즘;
- allkeys-lru: 새로 작성된 데이터를 수용할 만큼 메모리가 부족한 경우 LRU(최근 사용) 알고리즘을 사용하여 모든 키를 제거합니다.
- 휘발성-lfu: 버전 4.0에 새로 추가되었으며, 새로 작성된 데이터를 수용할 만큼 메모리가 충분하지 않은 경우 LFU 알고리즘을 사용하여 만료된 키 중에서 키를 삭제합니다.
- allkeys-lfu: 버전 4.0의 새로운 기능으로, 새로 작성된 데이터를 수용할 만큼 메모리가 충분하지 않은 경우 LFU 알고리즘을 사용하여 모든 키에서 제거합니다. 새로 작성된 데이터는 만료 시간이 설정된 키에서 데이터가 무작위로 제거됩니다.
- allkeys-random: 메모리가 새로 작성된 데이터를 수용하기에 충분하지 않으면 모든 키에서 데이터가 무작위로 제거됩니다.
- 휘발성-ttl: 새로 작성된 데이터를 수용할 만큼 메모리가 충분하지 않은 경우 만료 시간이 설정된 키가 만료 시간에 따라 제거되고, 이전에 만료된 키가 먼저 제거됩니다.
- noeviction: The 기본 전략, 메모리가 부족한 경우 새로 작성된 데이터를 수용하기 위해 새 쓰기 작업은 오류를 보고합니다.
- 순위 목록
- 카운터 애플리케이션
- 공유 세션
- 분산 잠금
- 소셜 네트워크
- 메시지 대기열
- 비트 연산
- 7.1 Caching
redis를 언급하면 우리는 자연스럽게 캐시를 떠올립니다. 국내외 중대형 웹사이트는 캐시와 떼려야 뗄 수 없는 관계입니다. 핫스팟 데이터 캐싱과 같은 캐시의 합리적인 사용은 웹 사이트의 접속 속도를 향상시킬 뿐만 아니라 데이터베이스 DB에 대한 부담을 줄일 수 있습니다. 또한 Memcached에 비해 Redis는 풍부한 데이터 구조를 제공하고 가장 강력한 RDB 및 AOF와 같은 지속성 메커니즘을 제공합니다.
7.2 순위
현재 인터넷 애플리케이션에는 전자상거래 사이트의 월간 매출 순위, 소셜 APP의 선물 순위, 미니 프로그램의 투표 순위 등 다양한 순위가 있습니다. Redis에서 제공하는
데이터 유형은 이러한 복잡한 순위를 구현할 수 있습니다.
zset
예를 들어 사용자가 매일 동영상을 업로드하는 경우 좋아요 순위 목록은 다음과 같이 설계될 수 있습니다.
- 1.用户Jay上传一个视频,获得6个赞,可以酱紫:
zadd user:ranking:2021-03-03 Jay 3
- 过了一段时间,再获得一个赞,可以这样:
zincrby user:ranking:2021-03-03 Jay 1
- 如果某个用户John作弊,需要删除该用户:
zrem user:ranking:2021-03-03 John
- 展示获取赞数最多的3个用户
zrevrangebyrank user:ranking:2021-03-03 0 27.3 计数器应用
各大网站、APP应用经常需要计数器的功能,如短视频的播放数、电商网站的浏览数。这些播放数、浏览数一般要求实时的,每一次播放和浏览都要做加1的操作,如果并发量很大对于传统关系型数据的性能是一种挑战。Redis天然支持计数功能而且计数的性能也非常好,可以说是计数器系统的重要选择。
7.4 共享Session
如果一个分布式Web服务将用户的Session信息保存在各自服务器,用户刷新一次可能就需要重新登录了,这样显然有问题。实际上,可以使用Redis将用户的Session进行集中管理,每次用户更新或者查询登录信息都直接从Redis中集中获取。
7.5 分布式锁
几乎每个互联网公司中都使用了分布式部署,分布式服务下,就会遇到对同一个资源的并发访问的技术难题,如秒杀、下单减库存等场景。
- 用synchronize或者reentrantlock本地锁肯定是不行的。
- 如果是并发量不大话,使用数据库的悲观锁、乐观锁来实现没啥问题。
- 但是在并发量高的场合中,利用数据库锁来控制资源的并发访问,会影响数据库的性能。
- 实际上,可以用Redis的setnx来实现分布式的锁。
7.6 社交网络
赞/踩、粉丝、共同好友/喜好、推送、下拉刷新等是社交网站的必备功能,由于社交网站访问量通常比较大,而且传统的关系型数据不太适保存 这种类型的数据,Redis提供的数据结构可以相对比较容易地实现这些功能。
7.7 消息队列
消息队列是大型网站必用中间件,如ActiveMQ、RabbitMQ、Kafka等流行的消息队列中间件,主要用于业务解耦、流量削峰及异步处理实时性低的业务。Redis提供了发布/订阅及阻塞队列功能,能实现一个简单的消息队列系统。另外,这个不能和专业的消息中间件相比。
7.8 位操作
用于数据量上亿的场景下,例如几亿用户系统的签到,去重登录次数统计,某用户是否在线状态等等。腾讯10亿用户,要几个毫秒内查询到某个用户是否在线,能怎么做?千万别说给每个用户建立一个key,然后挨个记(你可以算一下需要的内存会很恐怖,而且这种类似的需求很多。这里要用到位操作——使用setbit、getbit、bitcount命令。原理是:redis内构建一个足够长的数组,每个数组元素只能是0和1两个值,然后这个数组的下标index用来表示用户id(必须是数字哈),那么很显然,这个几亿长的大数组就能通过下标和元素值(0和1)来构建一个记忆系统。
8. Redis 的持久化机制有哪些?优缺点说说
Redis是基于内存的非关系型K-V数据库,既然它是基于内存的,如果Redis服务器挂了,数据就会丢失。为了避免数据丢失了,Redis提供了持久化,即把数据保存到磁盘。
Redis提供了RDB和AOF两种持久化机制,它持久化文件加载流程如下:
8.1 RDB
RDB,就是把内存数据以快照的形式保存到磁盘上。
什么是快照?可以这样理解,给当前时刻的数据,拍一张照片,然后保存下来。
RDB持久化,是指在指定的时间间隔内,执行指定次数的写操作,将内存中的数据集快照写入磁盘中,它是Redis默认的持久化方式。执行完操作后,在指定目录下会生成一个
dump.rdb
文件,Redis 重启的时候,通过加载dump.rdb
文件来恢复数据。RDB触发机制主要有以下几种:RDB 的优点
- 适合大规模的数据恢复场景,如备份,全量复制等
RDB缺点
- 실시간 지속성/2차 지속성을 달성할 수 있는 방법은 없습니다.
- 이전 버전과 새 버전에는 RDB 형식 호환성 문제가 있습니다
AOF
AOF(파일만 추가) 지속성, 로그 형식을 사용하여 각 쓰기 작업을 기록하고, 파일에 추가하고, 다음과 같은 경우 AOF 파일을 다시 실행합니다. 데이터를 복구하기 위해 명령을 다시 시작합니다. 주로 데이터 지속성의 실시간 문제를 해결합니다. 기본값은 활성화되어 있지 않습니다.
AOF의 작업 흐름은 다음과 같습니다.
AOF의 장점
- 더 높은 데이터 일관성 및 무결성
AOF의 단점
- 더 많은 콘텐츠 AOF 기록 , 파일이 좋을수록 대용량 데이터 복구 속도가 느려집니다.
9. Redis의 고가용성을 달성하는 방법은 무엇입니까?
우리는 프로젝트에서 Redis를 사용하며 Redis 서비스를 단일 지점에 배포하지 않을 것입니다. 단일 지점 배포가 중단되면 더 이상 사용할 수 없기 때문입니다. 고가용성을 달성하기 위해 일반적인 방법은 데이터베이스의 여러 복사본을 복사하여 다른 서버에 배포하는 것입니다. 그 중 하나가 실패하면 계속해서 서비스를 제공할 수 있습니다. 고가용성을 달성하기 위한 Redis 배포 모드에는 마스터-슬레이브 모드, 센티넬 모드, 클러스터 모드라는 세 가지 배포 모드가 있습니다.
9.1 마스터-슬레이브 모드
마스터-슬레이브 모드에서 Redis는 읽기 및 쓰기 작업을 담당하는 마스터 노드와 읽기 작업만 담당하는 슬레이브 노드로 구성된 여러 머신을 배포합니다. 슬레이브 노드의 데이터는 마스터 노드에서 옵니다. 구현 원칙은 마스터-슬레이브 복제 메커니즘
마스터-슬레이브 복제에는 전체 복제와 증분 복제가 포함됩니다. 일반적으로 슬레이브가 마스터에 처음 연결을 시작하거나 처음 연결한 것으로 간주할 때 full copy를 사용합니다. 전체 복사 과정은 다음과 같습니다.
- 1 .슬레이브는 마스터에게 동기화 명령을 보냅니다.
- 2. SYNC 명령을 받은 후 마스터는 bgsave 명령을 실행하여 전체 RDB 파일을 생성합니다.
- 3. 마스터는 버퍼를 사용하여 RDB 스냅샷 생성 중 모든 쓰기 명령을 기록합니다.
- 4. 마스터는 bgsave를 실행한 후 RDB 스냅샷 파일을 모든 슬레이브에 보냅니다.
- 5. RDB 스냅샷 파일을 수신한 후 슬레이브는 수신된 스냅샷을 로드하고 구문 분석합니다.
- 6. 마스터는 버퍼를 사용하여 RDB 동기화 중에 생성된 모든 작성된 명령을 기록합니다.
- 7. 마스터 스냅샷이 전송된 후 버퍼의 쓰기 명령을 슬레이브로 보내기 시작합니다.
- 8.salve는 명령 요청을 수락하고 마스터 버퍼에서 쓰기 명령을 실행합니다.
redis2.8 이후 버전에서는 sync 명령이 시스템 리소스를 소비하고 psync가 더 효율적이기 때문에 sync를 대체하기 위해 psync가 사용되었습니다.
슬레이브가 마스터와 완전히 동기화된 후 마스터의 데이터가 다시 업데이트되면 증분 복제가 실행됩니다.
마스터 노드에서 데이터가 증가하거나 감소하면
replicationFeedSalves()
函数,接下来在 Master节点上调用的每一个命令会使用replicationFeedSlaves()
가 트리거되어 슬레이브 노드에 동기화됩니다. 이 기능을 실행하기 전에 마스터 노드는 사용자가 실행한 명령에 데이터 업데이트가 있는지 확인하고, 데이터 업데이트가 있고 슬레이브 노드가 비어 있지 않은 경우 이 기능을 실행합니다. 이 기능의 기능은 다음과 같습니다. 사용자가 실행한 명령을 모든 슬레이브 노드에 전송하고 슬레이브 노드가 이를 실행하도록 합니다. 프로세스는 다음과 같습니다.9.2 Sentinel 모드
마스터-슬레이브 모드에서는 마스터 노드가 장애로 인해 서비스를 제공할 수 없게 되면 슬레이브 노드를 마스터 노드로 수동으로 승격시켜야 하며, 동시에 마스터 노드 주소를 업데이트하도록 애플리케이션에 알립니다. 분명히 이 오류 처리 방법은 대부분의 비즈니스 시나리오에서 허용되지 않습니다. Redis는 이 문제를 해결하기 위해 2.8부터 Redis Sentinel 아키텍처를 공식적으로 제공했습니다.
하나 이상의 Sentinel 인스턴스로 구성된 Sentinel 시스템인 Sentinel 모드는 모든 Redis 마스터 노드와 슬레이브 노드를 모니터링할 수 있으며, 모니터링되는 마스터 노드가 오프라인 상태가 되면 아래에 있는 오프라인 마스터 A 슬레이브 노드를 자동으로 제거합니다. 서버가 새로운 마스터 노드로 업그레이드되었습니다. 그러나 Sentinel 프로세스가 Redis 노드를 모니터링하는 경우 문제가 발생할 수 있습니다(Single Point Problem). 따라서 여러 Sentinel을 사용하여 Redis 노드를 모니터링할 수 있으며 각 Sentinel도 서로 모니터링합니다.
간단히 말하면 센티넬 모드에는 세 가지 기능이 있습니다.
장애 조치 과정은 어떻게 되나요?
- 명령을 보내고 Redis 서버(마스터 서버 및 슬레이브 서버 포함)가 실행 상태를 모니터링하기 위해 돌아올 때까지 기다립니다. 마스터 노드가 다운되면 자동으로 슬레이브 노드를 마스터 노드로 전환한 다음 게시 및 구독 모드를 통해 다른 슬레이브 노드에 알리고 구성 파일을 수정하여 호스트를 전환하게 합니다.
- 센티널도 각 노드를 모니터링합니다. 다른 하나는 고가용성을 달성하기 위한 것입니다.
메인 서버가 다운되고 Sentinel 1이 이 결과를 먼저 감지한다고 가정하면 시스템은 즉시 장애 조치 프로세스를 수행하지 않습니다. 이는 단지 Sentinel 1이 메인 서버를 사용할 수 없다고 주관적으로 믿는 것일 뿐입니다. 후속 센티널도 주 서버를 사용할 수 없음을 감지하고 숫자가 특정 값에 도달하면 센티널 간에 투표가 진행됩니다. 투표 결과는 하나의 센티널에 의해 시작되어 장애 조치 작업을 수행합니다. 전환이 성공한 후 각 센티널은 게시-구독 모드를 사용하여 모니터링하는 슬레이브 서버를 호스트로 전환합니다. 이 프로세스를 목표 오프라인이라고 합니다. 이렇게 하면 모든 것이 클라이언트에게 투명해집니다.
Sentinel의 작동 모드는 다음과 같습니다.
각 Sentinel은 마스터, 슬레이브 및 자신이 알고 있는 기타 Sentinel 인스턴스에 초당 한 번씩 PING 명령을 보냅니다.
PING 명령에 대한 마지막 유효한 응답 이후의 시간이 down-after-milliseconds 옵션에 지정된 값을 초과하는 경우 인스턴스는 Sentinel에 의해 주관적으로 오프라인으로 표시됩니다.
마스터가 주관적 오프라인 상태로 표시되면 이 마스터를 모니터링하는 모든 Sentinel은 마스터가 실제로 주관적 오프라인 상태에 진입했는지 1초에 한 번씩 확인해야 합니다.
충분한 수의 센티널(구성 파일에 지정된 값 이상)이 마스터가 지정된 시간 범위 내에 실제로 주관적인 오프라인 상태에 진입했음을 확인하면 마스터는 객관적으로 오프라인으로 표시됩니다.
일반적인 상황에서 각 Sentinel은 10초마다 한 번씩 자신이 알고 있는 모든 마스터와 슬레이브에 INFO 명령을 보냅니다.
마스터가 Sentinel에 의해 객관적으로 오프라인으로 표시되면 Sentinel이 오프라인 마스터의 모든 슬레이브에 INFO 명령을 보내는 빈도가 10초에 한 번에서 1초에 한 번으로 변경됩니다.
충분하지 않은 경우 동의하는 센티넬 마스터가 오프라인인 경우 마스터의 객관적인 오프라인 상태가 제거됩니다. 마스터가 Sentinel의 PING 명령에 대해 유효한 응답을 다시 반환하면 마스터의 주관적인 오프라인 상태가 제거됩니다.
9.3 클러스터 클러스터 모드
Sentinel 모드는 마스터-슬레이브 모드를 기반으로 하며 읽기 및 쓰기 분리를 구현하며 자동으로 전환할 수도 있으며 시스템 가용성이 더 높습니다. 하지만 각 노드에 저장되는 데이터가 동일하기 때문에 메모리가 낭비되고 온라인 확장이 쉽지 않습니다. 그리하여 클러스터 클러스터가 탄생하게 되었습니다. Redis 3.0에 추가되어 Redis의 분산 스토리지를 구현한 것입니다. 데이터를 분할합니다. 즉, 각 Redis 노드에 서로 다른 콘텐츠를 저장하여 온라인 확장 문제를 해결합니다. 또한 복제 및 장애 조치 기능도 제공합니다.
클러스터 클러스터 노드의 통신
Redis 클러스터는 여러 노드로 구성됩니다. 각 노드는 어떻게 서로 통신합니까? 가십 프로토콜을 통해!
Redis Cluster 클러스터는 Gossip 프로토콜을 통해 통신합니다. 교환되는 정보에는 노드 실패, 새 노드 가입, 마스터-슬레이브 노드 변경 정보 등이 포함됩니다. 일반적으로 사용되는 가십 메시지는 핑(Ping), 퐁(Pong), 만남(Meet), 실패(Fail)의 네 가지 유형으로 구분됩니다.
- 미트 메시지: 새로운 노드에 참여하도록 알립니다. 메시지 발신자는 수신자에게 현재 클러스터에 합류하도록 알립니다. Meet 메시지 통신이 정상적으로 완료된 후 수신 노드는 클러스터에 합류하고 주기적으로 핑 및 퐁 메시지 교환을 수행합니다.
- Ping 메시지: 클러스터에서 가장 자주 교환되는 메시지입니다. 클러스터의 각 노드는 1초마다 여러 다른 노드에 ping 메시지를 보내며, 이를 통해 노드가 온라인인지 여부를 감지하고 서로 상태 정보를 교환합니다.
- 퐁 메시지: 핑이나 미팅 메시지를 받으면 메시지의 정상적인 통신을 확인하기 위해 보낸 사람에게 응답 메시지로 회신합니다. Pong 메시지는 자체 상태 데이터를 내부적으로 캡슐화합니다. 노드는 자체 퐁 메시지를 클러스터에 브로드캐스트하여 전체 클러스터에 상태를 업데이트하도록 알릴 수도 있습니다.
- 실패 메시지: 노드가 클러스터의 다른 노드가 오프라인임을 확인하면 실패 메시지를 수신한 후 다른 노드가 해당 노드를 오프라인 상태로 업데이트합니다.
특히 각 노드는 클러스터 버스를 통해 다른 노드와 통신합니다. 통신 시에는 특수한 포트 번호, 즉 외부 서비스 포트 번호에 10000을 더한 번호를 사용하세요. 예를 들어, 노드의 포트 번호가 6379인 경우 다른 노드와 통신하는 데 사용되는 포트 번호는 16379입니다. 노드 간 통신은 특수 바이너리 프로토콜을 사용합니다.
해시 슬롯 알고리즘
분산 저장소이기 때문에 클러스터 클러스터에서 사용하는 분산 알고리즘은 일관된 해시? 아니요, 해시 슬롯 알고리즘입니다.
슬롯 알고리즘 전체 데이터베이스는 16384개의 슬롯(슬롯)으로 나누어집니다. Redis에 들어오는 각 키-값 쌍은 키에 따라 해시되어 이 16384개의 슬롯 중 하나에 할당됩니다. 사용된 해시 맵도 비교적 간단합니다. CRC16 알고리즘을 사용하여 16비트 값을 계산한 다음 모듈로 16384를 계산합니다. 데이터베이스의 각 키는 이러한 16384개 슬롯 중 하나에 속하며 클러스터의 각 노드는 이러한 16384개 슬롯을 처리할 수 있습니다.
클러스터의 각 노드는 해시 슬롯의 일부를 담당합니다. 예를 들어 현재 클러스터에는 노드 A, B, C가 있고 각 노드의 해시 슬롯 수는 16384/3이므로 다음과 같습니다.
- 노드 A가 해시 슬롯 0~5460을 담당
- 노드 B가 해시 슬롯 5461~10922를 담당
- 노드 C가 해시 슬롯 10923~16383을 담당
Redis 클러스터 클러스터
Redis 클러스터 클러스터에서, 16384개의 슬롯이 일치하는지 확인해야 합니다. 모든 노드가 정상적으로 작동하고 있습니다. 노드에 장애가 발생하면 해당 노드가 담당하는 슬롯도 무효화되고 전체 클러스터가 작동하지 않게 됩니다.
따라서 고가용성을 보장하기 위해 클러스터 클러스터는 마스터-슬레이브 복제를 도입하고 하나의 마스터 노드는 하나 이상의 슬레이브 노드에 해당합니다. 다른 마스터 노드가 마스터 노드 A를 ping할 때 마스터 노드의 절반 이상이 A와 통신하는 시간이 초과되면 마스터 노드 A가 다운된 것으로 간주됩니다. 마스터 노드가 다운되면 슬레이브 노드가 활성화됩니다.
Redis의 각 노드에는 두 가지가 있습니다. 하나는 슬롯이고 값 범위는 016383입니다. 다른 하나는 클러스터 관리 플러그인으로 이해될 수 있는 클러스터입니다. 우리가 액세스하는 키가 도착하면 Redis는 CRC16 알고리즘을 기반으로 16비트 값을 얻은 다음 모듈로 16384 결과를 가져옵니다. Jiangzi의 각 키는 016383 사이의 해시 슬롯에 해당합니다. 이 값을 사용하여 해당 슬롯에 해당하는 노드를 찾은 다음 자동으로 해당 노드로 점프하여 액세스 작업을 수행합니다.
데이터는 서로 다른 노드에 별도로 저장되지만 클라이언트에게는 전체 클러스터가 전체적으로 보입니다. 클라이언트는 모든 노드에 연결되며 Redis의 단일 인스턴스를 운영하는 것과 동일하게 보입니다. 클라이언트가 조작하는 키가 올바른 노드에 할당되지 않은 경우 Redis는 리디렉션 명령을 반환하고 최종적으로 올바른 노드를 가리킵니다. 이는 브라우저 페이지의 302 리디렉션 점프와 약간 비슷합니다.
Failover
Redis 클러스터는 고가용성을 달성합니다. 클러스터의 노드에 장애가 발생하면 failover를 사용하여 클러스터가 정상적인 외부 서비스를 제공할 수 있도록 합니다.
redis 클러스터는 ping/pong 메시지를 통해 오류 검색을 실현합니다. 이 환경에는 주관적 오프라인과 객관적인 오프라인이 포함됩니다.
주관적인 오프라인: 한 노드는 다른 노드가 접속 불가능하다고 생각합니다. 즉, 오프라인 상태입니다. 이 상태는 한 노드의 의견만을 대변할 수 있으며 오판이 있을 수 있습니다.
목표 오프라인: 노드가 실제로 오프라인임을 나타냅니다. 클러스터의 여러 노드는 노드를 사용할 수 없다고 믿고 합의에 도달합니다. 슬롯을 보유한 마스터 노드에 장애가 발생하면 해당 노드에 대해 장애 조치를 수행해야 합니다.
- 노드 A가 노드 B를 주관적으로 오프라인으로 표시한다고 가정해 보겠습니다. 노드 A는 메시지를 통해 노드 B의 상태를 다른 노드에 보냅니다. 노드 C가 메시지를 수신하고 메시지 본문을 구문 분석하면 노드 B가 발견됩니다. pfail 상태일 때 목표 오프라인 프로세스가 시작됩니다.
- 마스터 노드가 오프라인일 때 Redis 클러스터 클러스터는 오프라인 보고서가 절반에 도달했을 때 투표 수가 절반에 도달했는지 확인하기 위해 통계 슬롯을 보유한 마스터 노드에 투표합니다. 통계가 절반보다 크며 객관적인 오프라인 상태로 표시됩니다.
프로세스는 다음과 같습니다.
실패 복구: 결함이 발견된 후 오프라인 노드가 마스터 노드인 경우 슬레이브 노드 중 하나를 선택하여 교체해야 합니다. 클러스터의 가용성. 과정은 다음과 같습니다.
- Quality check: 슬레이브 노드가 장애가 발생한 마스터 노드를 교체할 수 있는 조건을 갖추고 있는지 확인합니다.
- 선거 시간 준비: 자격 확인을 통과한 후 트리거 오류 선택 시간을 업데이트합니다.
- 선거 개시: 잘못된 선거 시간이 되면 선거를 실시합니다.
- 선거 투표: 슬롯을 보유하고 있는 마스터 노드만 투표권을 가집니다. 슬레이브 노드가 충분한 표(절반 이상)를 모으면 마스터 노드 교체 작업이 시작됩니다
10. Redis 분산 잠금? 주의할 점은 무엇인가요?
분산 잠금은 공유 리소스에 공동으로 액세스하기 위해 분산 시스템의 다양한 프로세스를 제어하는 잠금을 구현한 것입니다. 깜짝 판매 주문, 빨간 봉투 집기 등과 같은 비즈니스 시나리오에서는 모두 분산 잠금을 사용해야 합니다. Redis는 프로젝트에서 분산 잠금으로 사용되는 경우가 많습니다.
选了Redis分布式锁的几种实现方法,大家来讨论下,看有没有啥问题哈。
- 命令setnx + expire分开写
- setnx + value值是过期时间
- set的扩展命令(set ex px nx)
- set ex px nx + 校验唯一随机值,再删除
10.1 命令setnx + expire分开写
if(jedis.setnx(key,lock_value) == 1){ //加锁 expire(key,100); //设置过期时间 try { do something //业务请求 }catch(){ } finally { jedis.del(key); //释放锁 } }如果执行完
setnx
加锁,正要执行expire设置过期时间时,进程crash掉或者要重启维护了,那这个锁就“长生不老”了,别的线程永远获取不到锁啦,所以分布式锁不能这么实现。10.2 setnx + value值是过期时间
long expires = System.currentTimeMillis() + expireTime; //系统时间+设置的过期时间 String expiresStr = String.valueOf(expires); // 如果当前锁不存在,返回加锁成功 if (jedis.setnx(key, expiresStr) == 1) { return true; } // 如果锁已经存在,获取锁的过期时间 String currentValueStr = jedis.get(key); // 如果获取到的过期时间,小于系统当前时间,表示已经过期 if (currentValueStr != null && Long.parseLong(currentValueStr) < System.currentTimeMillis()) { // 锁已过期,获取上一个锁的过期时间,并设置现在锁的过期时间(不了解redis的getSet命令的小伙伴,可以去官网看下哈) String oldValueStr = jedis.getSet(key_resource_id, expiresStr); if (oldValueStr != null && oldValueStr.equals(currentValueStr)) { // 考虑多线程并发的情况,只有一个线程的设置值和当前值相同,它才可以加锁 return true; } } //其他情况,均返回加锁失败 return false; }笔者看过有开发小伙伴是这么实现分布式锁的,但是这种方案也有这些缺点:
- 过期时间是客户端自己生成的,分布式环境下,每个客户端的时间必须同步。
- 没有保存持有者的唯一标识,可能被别的客户端释放/解锁。
- 锁过期的时候,并发多个客户端同时请求过来,都执行了
jedis.getSet()
,最终只能有一个客户端加锁成功,但是该客户端锁的过期时间,可能被别的客户端覆盖。10.3: set的扩展命令(set ex px nx)(注意可能存在的问题)
if(jedis.set(key, lock_value, "NX", "EX", 100s) == 1){ //加锁 try { do something //业务处理 }catch(){ } finally { jedis.del(key); //释放锁 } }这个方案可能存在这样的问题:
- 锁过期释放了,业务还没执行完。
- 锁被别的线程误删。
10.4 set ex px nx + 校验唯一随机值,再删除
if(jedis.set(key, uni_request_id, "NX", "EX", 100s) == 1){ //加锁 try { do something //业务处理 }catch(){ } finally { //判断是不是当前线程加的锁,是才释放 if (uni_request_id.equals(jedis.get(key))) { jedis.del(key); //释放锁 } } }在这里,判断当前线程加的锁和释放锁是不是一个原子操作。如果调用jedis.del()释放锁的时候,可能这把锁已经不属于当前客户端,会解除他人加的锁。
一般也是用lua脚本代替。lua脚本如下:
if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end;这种方式比较不错了,一般情况下,已经可以使用这种实现方式。但是存在锁过期释放了,业务还没执行完的问题(实际上,估算个业务处理的时间,一般没啥问题了)。
11. 使用过Redisson嘛?说说它的原理
分布式锁可能存在锁过期释放,业务没执行完的问题。有些小伙伴认为,稍微把锁过期时间设置长一些就可以啦。其实我们设想一下,是否可以给获得锁的线程,开启一个定时守护线程,每隔一段时间检查锁是否还存在,存在则对锁的过期时间延长,防止锁过期提前释放。
当前开源框架Redisson就解决了这个分布式锁问题。我们一起来看下Redisson底层原理是怎样的吧:
只要线程一加锁成功,就会启动一个
watch dog
看门狗,它是一个后台线程,会每隔10秒检查一下,如果线程1还持有锁,那么就会不断的延长锁key的生存时间。因此,Redisson就是使用Redisson解决了锁过期释放,业务没执行完问题。12. 什么是Redlock算法
Redis一般都是集群部署的,假设数据在主从同步过程,主节点挂了,Redis分布式锁可能会有哪些问题呢?一起来看些这个流程图:
如果线程一在Redis的master节点上拿到了锁,但是加锁的key还没同步到slave节点。恰好这时,master节点发生故障,一个slave节点就会升级为master节点。线程二就可以获取同个key的锁啦,但线程一也已经拿到锁了,锁的安全性就没了。
为了解决这个问题,Redis作者 antirez提出一种高级的分布式锁算法:Redlock。Redlock核心思想是这样的:
搞多个Redis master部署,以保证它们不会同时宕掉。并且这些master节点是完全相互独立的,相互之间不存在数据同步。同时,需要确保在这多个master实例上,是与在Redis单实例,使用相同方法来获取和释放锁。
我们假设当前有5个Redis master节点,在5台服务器上面运行这些Redis实例。
RedLock的实现步骤:如下
- 1. 현재 시간을 밀리초 단위로 가져옵니다.
- 2. 5개의 마스터 노드에 순서대로 잠금을 요청합니다. 클라이언트는 네트워크 연결 및 응답 시간 초과를 설정하며 시간 초과는 잠금 만료 시간보다 작아야 합니다. (자동 잠금 만료 시간을 10초라고 가정할 때, 타임아웃 시간은 일반적으로 5~50밀리초 사이입니다. 타임아웃 시간은 50ms라고 가정합니다.) 시간이 초과되면 마스터 노드를 건너뛰고 최대한 빨리 다음 마스터 노드를 시도하세요.
- 3. 클라이언트는 현재 시간에서 잠금 획득 시작 시간(즉, 1단계에서 기록된 시간)을 뺀 시간을 사용하여 잠금 획득에 사용된 시간을 가져옵니다. Redis 마스터 노드의 절반 이상(N/2+1, 여기서는 5/2+1=3 노드)이 잠금을 획득하고 사용 시간이 잠금 만료 시간보다 짧은 경우에만 잠금이 성공적으로 획득됩니다. . (위 그림과 같이 10s>30ms+40ms+50ms+4m0s+50ms)
- 잠금을 획득하면 키의 실제 유효 시간이 바뀌고, 잠금을 획득하는데 소요된 시간을 빼야 합니다.
- 잠금 획득에 실패하는 경우(최소 N/2+1 마스터 인스턴스에서 잠금이 획득되지 않았거나 잠금 획득 시간이 유효 시간을 초과한 경우) 클라이언트는 모든 마스터 노드에서 잠금을 해제해야 합니다(일부 마스터 노드의 경우에도 마찬가지). 자물쇠를 전혀 획득하지 마십시오.) 자물쇠가 성공적이지 않더라도 일부 물고기가 그물을 통해 미끄러지는 것을 방지하기 위해 잠금을 해제해야 합니다.
간단한 단계는 다음과 같습니다.
- 5개의 마스터 노드에 순서대로 잠금을 요청합니다.
- 설정된 제한 시간에 따라 마스터 노드를 건너뛸지 여부를 결정합니다.
- 3개 이상의 노드가 잠금에 성공하고, 사용 시간이 잠금 유효 기간보다 짧으면 잠금에 성공한 것으로 간주할 수 있습니다.
- 잠금 획득에 실패하면 잠금을 해제하세요!
13. Redis의 스킵 테이블
- 스킵 테이블은 순서 집합 zset의 기본 구현 중 하나입니다.
- 스킵 테이블은 평균 O(logN) 및 최악의 경우 O(N)을 지원합니다. ) 복잡성 순차 작업을 통해 노드를 찾고 일괄 처리합니다.
- 스킵 목록 구현은 zskiplist 및 zskiplistNode 두 가지 구조로 구성됩니다. 여기서 zskiplist는 스킵 테이블 정보(예: 헤더 노드, 테일 노드, 길이)를 저장하는 데 사용되고 zskiplistNode는 스킵 목록 노드를 나타내는 데 사용됩니다.
- 스킵 리스트는 연결 리스트를 기반으로 하며, 검색 효율성을 높이기 위해 다단계 인덱스를 추가합니다.
14. MySQL과 Redis는 어떻게 이중 쓰기 일관성을 보장합니까?
- 캐시 지연 이중 삭제
- 캐시 재시도 메커니즘 삭제
- biglog 읽기 및 캐시 비동기 삭제
14.1 지연된 이중 삭제?
지연 이중 삭제란 무엇인가요? 순서도는 다음과 같습니다.
먼저 캐시를 삭제하고
데이터베이스를 업데이트하고
잠시(예: 1초) 잠자기 상태에서 캐시를 다시 삭제합니다.
잠깐 잠드는 데 보통 얼마나 걸리나요? 모두 1초인가요?
이 대기 시간 = 비즈니스 로직 데이터를 읽는 데 걸리는 시간 + 수백 밀리초. 읽기 요청이 종료되었는지 확인하기 위해 쓰기 요청은 읽기 요청으로 인해 발생할 수 있는 캐시된 더티 데이터를 삭제할 수 있습니다.
이 솔루션도 나쁘지 않습니다. 수면 시간(예: 단 1초)에만 더티 데이터가 있을 수 있으며, 일반 기업에서는 이를 허용합니다. 하지만 캐시 삭제에 두 번째 실패하면 어떻게 될까요? 캐시와 데이터베이스 데이터가 여전히 일치하지 않을 수 있습니다. 그렇죠? 키에 자연 만료 만료 시간을 설정하고 자동으로 만료되도록 하는 것은 어떻습니까? 기업은 만료 기간 내에 데이터 불일치를 수용해야 합니까? 아니면 다른 더 나은 해결책이 있습니까?
14.2 캐시 삭제 재시도 메커니즘
지연된 이중 삭제로 인해 캐시 삭제의 두 번째 단계가 실패하여 데이터 불일치가 발생할 수 있습니다. 이 솔루션을 사용하여 최적화할 수 있습니다. 삭제에 실패하면 몇 번 더 삭제하여 캐시 삭제가 성공했는지 확인하세요. 따라서 삭제 캐시 재시도 메커니즘
쓰기 요청을 도입하여 데이터베이스를 업데이트할 수 있습니다.
캐시는 어떤 이유로 인해 삭제에 실패했습니다
삭제에 실패한 키를 메시지 큐에 넣습니다
메시지 큐에서 메시지를 소비하고 키를 가져옵니다. 삭제됩니다
삭제 캐시 작업을 다시 시도하세요
14.3 biglog 읽기 비동기 캐시 삭제
삭제 캐시 재시도 메커니즘은 괜찮지만 비즈니스 코드 침입이 많이 발생합니다. 실제로 데이터베이스의 binlog를 통해 키를 비동기적으로 제거하는 방식으로 최적화할 수도 있습니다.
mysql을 예로 들어보세요
- Alibaba의 운하를 사용하여 binlog 로그를 수집하고 MQ 대기열로 보낼 수 있습니다
- 그런 다음 ACK 메커니즘을 통해 업데이트 메시지를 확인 및 처리하고, 캐시를 삭제하고, 데이터 캐시 일관성을 보장합니다
15 Redis가 변경된 이유는 무엇입니까? 6.0 이후에는 멀티스레딩이 가능합니까?
- Redis 6.0 이전에는 Redis가 소켓 읽기, 구문 분석, 실행, 소켓 쓰기 등을 포함한 클라이언트 요청을 처리할 때 모두 순차 및 직렬 메인 스레드에 의해 처리되었습니다. 이것이 소위 "단일 스레드"입니다.
- Redis6.0 이전에는 왜 멀티스레딩을 사용하지 않았나요? Redis를 사용하면 CPU가 병목 현상을 일으키는 경우가 거의 없습니다. Redis는 주로 메모리와 네트워크의 제한을 받습니다. 예를 들어 일반적인 Linux 시스템에서 Redis는 파이프라이닝을 사용하여 초당 100만 개의 요청을 처리할 수 있으므로 애플리케이션이 주로 O(N) 또는 O(log(N)) 명령을 사용하는 경우 CPU를 거의 차지하지 않습니다.
Redis의 멀티 스레딩 사용이 단일 스레딩을 완전히 포기한다는 의미는 아닙니다. Redis는 여전히 단일 스레드 모델을 사용하여 데이터 읽기 및 쓰기와 프로토콜 분석을 처리합니다. 여전히 단일 스레드를 사용하여 명령을 실행합니다.
이의 목적은 Redis의 성능 병목 현상이 CPU가 아닌 네트워크 IO에 있기 때문입니다. 멀티 스레딩을 사용하면 IO 읽기 및 쓰기 효율성이 향상되어 Redis의 전반적인 성능이 향상됩니다.
16. Redis 트랜잭션 메커니즘에 대해 이야기해 보겠습니다.
Redis는 MULTI, EXEC, WATCH와 같은 일련의 명령을 통해 트랜잭션 메커니즘을 구현합니다. 트랜잭션은 한 번에 여러 명령 실행을 지원하며 트랜잭션의 모든 명령은 직렬화됩니다. 트랜잭션 실행 과정에서 큐에 있는 명령은 순서대로 순차적으로 실행되며, 다른 클라이언트가 제출한 명령 요청은 트랜잭션 실행 명령 시퀀스에 삽입되지 않습니다.
간단히 말하면 Redis 트랜잭션은 큐에 있는 일련의 명령을 순차적으로, 일회적으로, 배타적으로 실행하는 것입니다.
Redis에서 트랜잭션을 실행하는 과정은 다음과 같습니다.트랜잭션 시작(MULTI)
- 큐에 넣기 명령
- 트랜잭션 실행(EXEC), 트랜잭션 취소(DISCARD)
명령 설명 EXEC 트랜잭션 블록 내 모든 명령 실행 DISCARD 트랜잭션을 취소하고 트랜잭션 블록 내 모든 명령 실행을 중단합니다 MULTI 트랜잭션 블록 시작 표시 UNWATCH Cancel WATCH 명령은 모든 키를 모니터링합니다. WATCH 모니터 키. 트랜잭션이 실행되기 전에 다른 명령에 의해 키가 변경되면 트랜잭션이 중단됩니다. 17. Redis에서 해시 충돌을 처리하는 방법
Redis는 전역 해시를 사용하여 모든 키-값 쌍을 저장하는 K-V 인메모리 데이터베이스입니다. 이 해시 테이블은 여러 개의 해시 버킷으로 구성됩니다. 해시 버킷의 항목 요소는 key 및 value 포인터를 저장합니다. 여기서 *key는 실제 키를 가리키고 *value는 실제 값을 가리킵니다.
해시 테이블 조회 속도는 매우 빠르며, Java의 HashMap과 다소 비슷합니다. 이를 통해 O(1) 시간 복잡도에서 키-값 쌍을 빠르게 찾을 수 있습니다. 먼저 키를 통해 해시 값을 계산하고 해당 해시 버킷 위치를 찾은 다음 항목을 찾고 항목에서 해당 데이터를 찾습니다.
해시 충돌이란 무엇인가요?
해시 충돌: 동일한 해시 값이 서로 다른 키를 통해 계산되어 동일한 해시 버킷이 생성됩니다.
해시 충돌을 해결하기 위해 Redis는 Chain Hash를 사용합니다. 체인 해싱은 동일한 해시 버킷의 여러 요소가 연결 목록에 저장되고 포인터를 사용하여 차례로 연결되는 것을 의미합니다.
일부 독자에게는 여전히 질문이 있을 수 있습니다. 해시 충돌 체인의 요소는 포인터를 통해 하나씩만 검색한 다음 작동할 수 있습니다. 해시 테이블에 많은 양의 데이터가 삽입되면 충돌이 많아지고 충돌 연결 리스트가 길어져 쿼리 효율성이 떨어지게 됩니다.
효율성을 유지하기 위해 Redis는 해시 테이블에서 rehash 작업을 수행합니다. 이는 해시 버킷을 추가하고 충돌을 줄이는 것을 의미합니다. 재해시를 더욱 효율적으로 만들기 위해 Redis는 기본적으로 두 개의 전역 해시 테이블을 사용합니다. 하나는 현재 사용하는 기본 해시 테이블이고 다른 하나는 확장용인 백업 해시 테이블입니다. 18. RDB 생성 중에 Redis가 쓰기 요청을 동시에 처리할 수 있나요?
예Redis는 RDB를 생성하는 두 가지 지침, 즉 save 및 bgsave를 제공합니다.
저장 명령어인 경우 메인 스레드에서 실행되기 때문에 차단됩니다.
- bgsave 명령인 경우 RDB 파일을 작성하기 위해 하위 프로세스를 포크합니다. 스냅샷 지속성은 하위 프로세스에 의해 완전히 처리되며 상위 프로세스는 클라이언트 요청을 계속 처리할 수 있습니다.
- 19. Redis 하단에는 어떤 프로토콜이 사용되나요?
RESP, 정식 영어 이름은 Redis Serialization Protocol입니다. 이는 Redis용으로 특별히 설계된 직렬화 프로토콜 집합입니다. 이 프로토콜은 실제로 Redis 버전 1.2에 등장했습니다. 그러나 redis2.0이 되어서야 마침내 redis 통신 프로토콜의 표준이 되었습니다.
RESP는 주로 구현이 간단하고 구문 분석 속도가 빠르며 가독성이 좋다는 장점이 있습니다
.20. Bloom 필터
캐시 침투
문제를 해결하기 위해Bloom 필터를 사용할 수 있습니다. 블룸 필터란 무엇입니까? 블룸 필터는 공간을 거의 차지하지 않는 데이터 구조입니다. 긴 바이너리 벡터와 해시 매핑 함수 집합으로 구성되어 있으며 요소가 집합에 있는지 여부, 공간 효율성 및 쿼리 시간을 검색하는 데 사용됩니다. 단점은 오인식률이 높고 삭제가 어렵다는 점입니다.
블룸 필터의 원리는 무엇인가요?
세트 A가 있고 A에 n개의 요소가 있다고 가정합니다.k 해싱 함수를 사용하여 A의 각 요소 는 길이가 비트인 배열 B의 다른 위치에 매핑되고 이 위치의 이진수는 모두 1로 설정됩니다. 검사할 요소가 이러한 k 해시 함수에 의해 매핑되고 해당 k 위치의 이진수 가 모두 1인 것으로 확인되면 이 요소는 집합 A에 속할 가능성이 높습니다. 반대로 는 집합에 속해서는 안 됩니다. A를 설정하세요. . 세트 A에 3개의 요소, 즉 {d1, d2, d3
}이 있다고 가정해 보겠습니다. 해시 함수는Hash1 1개입니다. 이제 A의 각 요소를 길이가 16비트인 배열 B에 매핑합니다.
이제 d1을 매핑합니다. Hash1(d1) = 2라고 가정하고 다음과 같이 배열 B의 첨자 2가 있는 그리드를 1로 변경합니다.
이제 Hash1을 가정하여 매핑합니다. (d2) = 5이면 배열 B의 첨자 5가 있는 그리드를 다음과 같이 1로 변경합니다.
그런 다음 Hash1(d3)도 2와 같다고 가정하고
d3도 매핑합니다. 아래 첨자 2가 1인 그리드:
따라서 dn 요소가 세트 A에 있는지 확인해야 합니다. Hash1(dn)에서 얻은 인덱스 첨자만 계산하면 됩니다. 0이면 이 요소 가 세트 A에 없음을 의미합니다. . 인덱스 첨자가 1이면 어떻게 되나요? 그러면 요소는 A의 요소가 될 수 있습니다. 보시다시피 d1과 d3에서 얻은 아래 첨자 값은 둘 다 1일 수도 있고 다른 숫자로 매핑될 수도 있습니다. Bloom 필터에는 다음과 같은 단점이 있습니다. 해시 충돌으로 인해 거짓 긍정이 발생합니다. 판단 오류다.
이 오류를 어떻게 줄이나요?
- 더 많은 해시 함수 매핑을 구축하여 해시 충돌 확률을 줄입니다
- 동시에 B 배열의 비트 길이를 늘리면 해시 함수에서 생성되는 데이터 범위가 늘어나고 해시 충돌 확률도 줄일 수 있습니다
또 다른 Hash2해시 맵함수를 추가합니다. Hash2(d1)=6, Hash2(d3)=8이라고 가정합니다. 다음과 같이 충돌하지 않습니다.
오류가 있어도 찾을 수 있습니다. , Bloom 필터는 전체 데이터를 저장하지 않고 일련의 해시 맵 함수를 사용하여 위치를 계산한 다음 이진 벡터를 채웁니다. 숫자가 큰인 경우 Bloom 필터는 매우 작은 오류율을 통해 많은 저장 공간을 절약할 수 있어 비용면에서 매우 효율적입니다. 현재 Bloom 필터에는
Google의 Guava 클래스 라이브러리및 Twitter의 Algebird 클래스 라이브러리와 같이 해당 구현을 구현하는 오픈 소스 클래스 라이브러리가 이미 있습니다. 이를 쉽게 얻을 수도 있고 제공되는 비트맵을 기반으로 자신만의 디자인을 구현할 수도 있습니다. 레디스와 함께. 더 많은 프로그래밍 관련 지식을 보려면
프로그래밍 비디오
위 내용은 Redis에 관한 20가지 전형적인 인터뷰 질문 요약 및 공유(답변 분석 포함)의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!