Wenn es kein gutes Systemdesign gibt, führt die Verwendung von Multi-Threading normalerweise zu den rechts gezeigten Ergebnissen (beachten Sie die Ordinate). Wenn Sie die Anzahl der Threads zum ersten Mal erhöhen, erhöht sich die Systemdurchsatzrate. Wenn Sie die Anzahl der Threads weiter erhöhen, steigt die Systemdurchsatzrate langsam an oder nimmt sogar ab.
Der entscheidende Engpass ist: Es gibt in der Regel gemeinsam genutzte Ressourcen im System, auf die mehrere Threads gleichzeitig zugreifen. Um die Korrektheit der gemeinsam genutzten Ressourcen sicherzustellen, sind zusätzliche Mechanismen erforderlich, um die Thread-Sicherheit zu gewährleisten, wie z B. eine Verriegelung, die mit zusätzlichen Kosten verbunden ist.
Nehmen Sie als Beispiel den am häufigsten verwendeten List
-Typ. Angenommen, Redis verwendet ein Multi-Thread-Design und es gibt zwei Threads A und B, die LPUSHList bzw. /code>- und <code>LPUSH
-Operationen, um bei jeder Ausführung das gleiche Ergebnis zu erzielen, d. h. [B-Thread entnimmt die von A-Thread eingegebenen Daten], diese beiden Prozesse müssen seriell ausgeführt werden. Dies ist das Problem der gleichzeitigen Zugriffskontrolle gemeinsam genutzter Ressourcen, mit dem das Multithread-Programmiermodell konfrontiert ist. 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
) und eine Verbindung mit dieser herstellen den Client (accept
), lesen Sie die Anfrage vom Socket (recv
), analysieren Sie die vom Client gesendete Anfrage (parse
) und schließlich Geben Sie das Ergebnis an den Client zurück (send
). 🎜🎜Die grundlegendste Single-Thread-Implementierung besteht darin, die oben genannten Vorgänge nacheinander auszuführen. 🎜🎜🎜🎜rot markiert Die Vorgänge „accept“ und „rev“ sind potenzielle Blockierungspunkte: 🎜accept( )
Funktion, andere Clients können zu diesem Zeitpunkt keine Verbindung mit Redis herstellen🎜recv()
von einem Client übergibt Wenn das Ende Daten liest, wenn Wenn die Daten nicht angekommen sind, werden sie immer blockiert Listening-Sockets und verbundene Sockets müssen gleichzeitig im Kernel vorhanden sein (select/epoll
). 🎜🎜Der Kernel wartet immer auf Verbindungen oder Datenanfragen auf diesen Sockets. Redis verarbeitet eingehende Anforderungen und erzielt so den Effekt, dass ein Thread mehrere E/A-Streams verarbeitet. 🎜🎜🎜🎜An dieser Stelle Gleichzeitig blockiert der Redis-Thread die Verarbeitung einer bestimmten Client-Anforderung nicht, sodass er gleichzeitig eine Verbindung zu mehreren Clients herstellen und Anforderungen verarbeiten kann. 🎜🎜Rückrufmechanismus🎜🎜Sobald select/epoll erkennt, dass eine Anfrage auf FD eintrifft, löst es das entsprechende Ereignis aus und stellt es in eine Warteschlange, sodass ereignisbasierte Rückrufe implementiert werden. 🎜🎜Zum Beispiel registriert Redis die Rückruffunktionen accept
und get
für Accept- und Read-Ereignisse. Wenn der Linux-Kernel eine Verbindungsanforderung oder eine Datenleseanforderung überwacht, löst er das Accept-Ereignis und das Read-Ereignis aus. Zu diesem Zeitpunkt ruft der Kernel die entsprechenden accept
und get auf. Code> der zu verarbeitenden Redis-Funktion. 🎜🎜Leistungsengpässe von Redis🎜🎜Nach der obigen Analyse weist Redis immer noch einige Leistungsengpässe auf, obwohl mehrere Clientanforderungen gleichzeitig über den Multiplexing-Mechanismus überwacht werden können. Dies ist auch eine Situation, die wir in unserer täglichen Programmierung vermeiden müssen . 🎜<h4>1. Zeitaufwändige Vorgänge</h4>
<p>Wenn eine Anfrage in Redis lange dauert, wirkt sich dies auf die Leistung des gesamten Servers aus. Nachfolgende Anfragen müssen auf die Verarbeitung der vorherigen zeitaufwändigen Anfrage warten, bevor sie verarbeitet werden können. </p>
<p>Dies muss beim Entwerfen von Geschäftsszenarien vermieden werden. Der <code>lazy-free
-Mechanismus von Redis erfordert auch die zeitaufwändige Freigabe von Speicher in einem asynchronen Thread zur Ausführung.
Wenn die Parallelität sehr groß ist, gibt es einen Leistungsengpass beim Lesen und Schreiben von Client-E/A-Daten mit einem einzelnen Thread. Obwohl der E/A-Multiplexmechanismus verwendet wird, kann er immer noch nur den Client lesen Daten sequentiell mit einem einzelnen Thread, der nicht die Vorteile mehrerer CPU-Kerne nutzen kann.
Redis in 6.0 kann CPU-Multi-Core und Multi-Threading zum Lesen und Schreiben von Client-Daten verwenden, aber nur das Lesen und Schreiben für den Client erfolgt parallel und die eigentliche Ausführung jedes Befehls erfolgt weiterhin Single-Threaded.
Ich möchte diese Gelegenheit nutzen, um einige interessante Fragen zu Redis zu stellen.
Warum Redis verwenden, ist es nicht schlecht, direkt auf den Speicher zuzugreifen?
Tatsächlich ist dieser Artikel nicht klar definiert. Für einige Daten, die sich nicht häufig ändern, kann er nicht direkt im Speicher abgelegt werden. Beim Aktualisieren von Daten kann es zu Konsistenzproblemen kommen, d. h. die Daten werden möglicherweise nur auf einem Server geändert, sodass die Daten nur im lokalen Speicher vorhanden sind. Durch den Zugriff auf den Redis-Server kann das Konsistenzproblem mithilfe von Redis gelöst werden.
Was soll ich tun, wenn zu viele Daten vorhanden sind, die nicht im Speicher gespeichert werden können? Was soll ich beispielsweise tun, wenn ich 100 GB Daten zwischenspeichern möchte?
Hier gibt es auch eine Werbung für das verteilte Open-Source-KV-Cache-System von Taobao. Theoretisch ist das Gesamtdatenvolumen unbegrenzt und es wurde auch auf Verfügbarkeit, Skalierbarkeit und Zuverlässigkeit optimiert . Upgrade, interessierte Freunde können es herausfinden~
Das obige ist der detaillierte Inhalt vonWarum ist Redis mit Single-Thread so schnell?. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!