좋은 시스템 설계가 없는 경우 멀티스레딩을 사용하면 일반적으로 오른쪽에 표시된 결과가 나타납니다(세로 좌표 참조). 스레드 수를 처음 늘리면 시스템 처리 속도가 증가합니다. 스레드 수를 더 늘리면 시스템 처리 속도가 천천히 증가하거나 감소합니다.
주요 병목 현상은 다음과 같습니다. 일반적으로 시스템에는 여러 스레드가 동시에 액세스하는 공유 리소스가 있습니다. 공유 리소스의 정확성을 보장하려면 스레드 안전을 보장하는 추가 메커니즘이 필요합니다. 잠금으로 인해 추가 비용이 발생합니다.
예를 들어 가장 일반적으로 사용되는 List
유형을 예로 들어 Redis가 멀티 스레드 설계를 채택하고 LPUSHList
/code> 및 LPUSH
작업은 실행될 때마다 동일한 결과를 얻기 위해, 즉 [B 스레드는 A 스레드가 입력한 데이터를 가져옵니다] , 이 두 프로세스는 순차적으로 실행되어야 합니다. 이는 다중 스레드 프로그래밍 모델이 직면한 공유 리소스의 동시 액세스 제어 문제입니다. List
类型来举例吧,假设Redis采用多线程设计,有两个线程A和B分别对List
做LPUSH
和LPUSH
操作,为了使得每次执行都是相同的结果,即【B线程取出A线程放入的数据】就需要让这两个过程串行执行。这就是多线程编程模式面临的共享资源的并发访问控制问题。
并发访问控制一直是多线程开发中的一个难点问题:如果只是简单地采用一个互斥锁,就会出现即使增加了线程,大部分线程也在等待获取互斥锁,并行变串行,系统吞吐率并没有随着线程的增加而增加。
同时加入并发访问控制后也会降低系统代码的可读性和可维护性,所以Redis干脆直接采用了单线程模式。
之所以使用单线程是Redis设计者多方面衡量的结果。
Redis的大部分操作在内存上完成
采用了高效的数据结构,例如哈希表和跳表
采用了多路复用机制,使其在网络IO操作中能并发处理大量的客户端请求,实现高吞吐率
既然Redis使用单线程进行IO,如果线程被阻塞了就无法进行多路复用了,所以不难想象,Redis肯定还针对网络和IO操作的潜在阻塞点进行了设计。
在网络通信里,服务器为了处理一个Get请求,需要监听客户端请求(bind/listen
),和客户端建立连接(accept
),从socket中读取请求(recv
),解析客户端发送请求(parse
),最后给客户端返回结果(send
)。
最基本的一种单线程实现是依次执行上面的操作。
上面标红的accept和recv操作都是潜在的阻塞点:
当Redis监听到有连接请求,但却一直不能成功建立起连接时,就会阻塞在accept()
函数这里,其他客户端此时也无法和Redis建立连接
当Redis通过recv()
从一个客户端读取数据时,如果数据一直没有到达,也会一直阻塞
为了解决IO中的阻塞问题,Redis采用了Linux的IO多路复用机制,该机制允许内核中,同时存在多个监听套接字和已连接套接字(select/epoll
)。
内核会一直监听这些套接字上的连接或数据请求。Redis会处理到达的请求,从而实现了一个线程处理多个IO流的效果。
此时,Redis线程就不会阻塞在某一个特定的客户端请求处理上,所以它可以同时和多个客户端连接并处理请求。
select/epoll一旦监测到FD上有请求到达时,就会触发相应的事件被放进一个队列里,Redis线程对该事件队列不断进行处理,所以就实现了基于事件的回调。
例如,Redis会对Accept和Read事件注册accept
和get
回调函数。当Linux内核监听到有连接请求或读数据请求时,就会触发Accept事件和Read事件,此时,内核就会回调Redis相应的accept
和get
bind/listen
)을 수신하고 연결을 설정해야 합니다. 클라이언트(accept
), 소켓에서 요청을 읽고(recv
), 클라이언트가 보낸 요청을 구문 분석하고(parse
) 마지막으로 결과를 클라이언트에 반환합니다(send
). 🎜🎜가장 기본적인 단일 스레드 구현은 위 작업을 순서대로 수행하는 것입니다. 🎜🎜🎜🎜빨간색으로 표시 accept 및 recv 작업은 잠재적인 차단 지점입니다. 🎜accept에서 차단됩니다. ()
함수를 사용하면 현재 다른 클라이언트는 Redis와 연결을 설정할 수 없습니다🎜recv()
를 전달하면 끝이 데이터를 읽을 때, 데이터가 도착하지 않으면 항상 차단됩니다🎜select/epoll
). 🎜🎜커널은 항상 이러한 소켓에 대한 연결이나 데이터 요청을 수신합니다. Redis는 들어오는 요청을 처리하여 하나의 스레드가 여러 IO 스트림을 처리하는 효과를 얻습니다. 🎜🎜🎜🎜여기서 Redis 스레드는 특정 클라이언트 요청 처리를 차단하지 않으므로 동시에 여러 클라이언트에 연결하여 요청을 처리할 수 있습니다. 🎜🎜콜백 메커니즘🎜🎜select/epoll 요청이 FD에 도착하면 해당 이벤트를 트리거하고 이를 큐에 넣습니다. Redis 스레드는 이벤트 기반 콜백을 구현합니다. 🎜🎜예를 들어 Redis는 Accept 및 Read 이벤트에 대해 accept
및 get
콜백 함수를 등록합니다. Linux 커널은 연결 요청이나 데이터 읽기 요청을 모니터링할 때 Accept 이벤트 및 Read 이벤트를 트리거합니다. 이때 커널은 해당 accept
및 get을 콜백합니다. code>를 처리할 함수입니다. 🎜🎜Redis의 성능 병목 현상🎜🎜위 분석 후, 멀티플렉싱 메커니즘을 통해 여러 클라이언트 요청을 동시에 모니터링할 수 있지만 Redis에는 여전히 성능 병목 현상이 있으며 이는 일상적인 프로그래밍에서 피해야 하는 상황이기도 합니다. . 🎜<h4>1. 시간이 많이 걸리는 작업</h4>
<p>Redis에서 요청이 오래 걸리면 전체 서버 성능에 영향을 미칩니다. 후속 요청은 처리되기 전에 시간이 많이 걸리는 이전 요청이 처리될 때까지 기다려야 합니다. </p>
<p>비즈니스 시나리오를 설계할 때 이는 피해야 합니다. Redis의 <code>lazy-free
메커니즘은 또한 실행을 위해 비동기 스레드에서 메모리를 해제하는 데 시간이 많이 걸리는 작업을 수행합니다.
동시성이 매우 클 경우 단일 스레드로 클라이언트 IO 데이터를 읽고 쓰는 데 성능 병목 현상이 발생합니다. IO 다중화 메커니즘을 사용하더라도 여전히 클라이언트를 읽을 수만 있습니다. 여러 CPU 코어를 활용할 수 없는 단일 스레드로 데이터를 순차적으로 처리합니다.
6.0의 Redis는 CPU 멀티 코어 및 멀티 스레딩을 사용하여 클라이언트 데이터를 읽고 쓸 수 있지만 클라이언트에 대한 읽기 및 쓰기만 병렬이며 각 명령의 실제 작업은 여전히 단일 스레드입니다.
이 기회에 Redis와 관련된 몇 가지 흥미로운 질문을 던져보고 싶습니다.
Redis를 사용하는 이유는 메모리에 직접 접근하는 것도 나쁘지 않나요?
사실 이 글은 명확하게 정의되어 있지 않습니다. 자주 변경되지 않는 일부 데이터의 경우에는 Redis에 직접 배치할 필요가 없습니다. 데이터를 업데이트할 때 일관성 문제가 발생할 수 있습니다. 즉, 한 서버의 데이터만 수정될 수 있으므로 데이터는 로컬 메모리에만 존재합니다. Redis 서버에 액세스하면 Redis를 사용하여 일관성 문제를 해결할 수 있습니다.
메모리에 저장할 수 없는 데이터가 너무 많으면 어떻게 해야 하나요? 예를 들어, 100G의 데이터를 캐시하고 싶다면 어떻게 해야 합니까?
여기에도 광고가 있습니다. Tair는 Taobao의 오픈 소스 분산 KV 캐시 시스템입니다. 이론적으로 전체 데이터 양은 무제한입니다. 업그레이드, 관심있는 친구들이 알아보세요~
위 내용은 Redis가 단일 스레드를 사용하면 왜 이렇게 빠른가요?의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!