In der folgenden Spalte erfahren Sie, warum Redis 6.0 in der Spalte „Redis-Tutorial“ Multithreading einführt. , ich hoffe, es wird Freunden in Not helfen!
Über den Autor: Er arbeitete einst als technischer Direktor für Internetunternehmen wie Alibaba und Daily Youxian. 15 Jahre Erfahrung im E-Commerce-Internet.Vor hundert Tagen veröffentlichte der Redis-Autor Antirez auf seinem Blog (antirez.com) eine große Neuigkeit und Redis 6.0 wurde offiziell veröffentlicht. Eine der auffälligsten Änderungen ist die Einführung von Multithreading mit Redis 6.0.
Dieser Artikel ist hauptsächlich in zwei Teile gegliedert. Lassen Sie uns zunächst darüber sprechen, warum Redis vor 6.0 das Single-Threaded-Modell eingeführt hat. Anschließend erkläre ich das Multithreading von Redis6.0 im Detail.
Warum hat Redis vor 6.0 ein Single-Threaded-Modell eingeführt?
Genau genommen ist es nach Redis 4.0 kein Single-Threaded-Modell. Zusätzlich zum Hauptthread gibt es auch einige Hintergrundthreads, die einige langsamere Vorgänge verarbeiten, z. B. das Lösen nutzloser Verbindungen, das Löschen großer Schlüssel usw.
Der Autor von Redis hat von Beginn des Entwurfs an viele Aspekte berücksichtigt. Letztendlich entscheiden wir uns für die Verwendung eines Single-Threaded-Modells zur Verarbeitung von Befehlen. Es gibt mehrere wichtige Gründe, warum wir uns für das Single-Threaded-Modell entscheiden:
Redis-Operationen basieren auf dem Speicher, und der Leistungsengpass der meisten Operationen liegt nicht in der CPU
Das Single-Threaded-Modell vermeidet Leistungsprobleme verursacht durch das Umschalten zwischen Threads Overhead
Die Verwendung eines Single-Thread-Modells kann auch Clientanforderungen gleichzeitig verarbeiten (multiplexte E/A)
Die Verwendung eines Single-Thread-Modells bietet eine höhere Wartbarkeit und geringere Entwicklung, Debugging und Wartung Kosten.
Der dritte oben genannte Grund ist der entscheidende Faktor für die Einführung eines Single-Thread-Modells. Die anderen beiden Gründe sind zusätzliche Vorteile der Verwendung eines Single-Thread-Modells. Hier werden wir die oben genannten Gründe der Reihe nach vorstellen .
Warum heißt es, dass der Engpass von Redis nicht die CPU ist?
Erstens basieren die meisten Vorgänge von Redis auf dem Speicher und sind reine KV-Vorgänge (Schlüsselwert), sodass die Geschwindigkeit der Befehlsausführung sehr hoch ist. Wir können grob verstehen, dass die Daten in Redis in einer großen HashMap gespeichert werden. Der Vorteil von HashMap besteht darin, dass die zeitliche Komplexität des Suchens und Schreibens O(1) ist. Redis verwendet diese Struktur intern zum Speichern von Daten, was den Grundstein für die hohe Leistung von Redis legt. Laut der Beschreibung auf der offiziellen Website von Redis kann Redis unter idealen Umständen eine Million Anfragen pro Sekunde senden, und die für die Übermittlung jeder Anfrage erforderliche Zeit liegt in der Größenordnung von Nanosekunden. Da jede Redis-Operation so schnell ist und vollständig von einem einzelnen Thread abgewickelt werden kann, warum sollte man sich dann die Mühe machen, Multi-Threads zu verwenden?
Problem beim Thread-KontextwechselDarüber hinaus kommt es in Multithread-Szenarien zu Thread-Kontextwechseln. Threads werden von der CPU geplant. Ein Kern der CPU kann innerhalb einer Zeitscheibe nur einen Thread gleichzeitig ausführen. Eine Reihe von Vorgängen wird ausgeführt, wenn die CPU von Thread A zu Thread B wechselt. Der Hauptprozess umfasst das Speichern der Ausführung von Thread A. Szene, und laden Sie dann die Ausführungsszene von Thread B. Dieser Prozess ist „Thread-Kontextwechsel“. Dies beinhaltet das Speichern und Wiederherstellen von Thread-bezogenen Anweisungen.
Häufiger Thread-Kontextwechsel kann zu einem starken Leistungsabfall führen, was dazu führt, dass wir nicht nur die Geschwindigkeit der Verarbeitung von Anforderungen nicht verbessern, sondern auch die Leistung verringern. Dies ist einer der Gründe, warum Redis bei der Multithreading-Technologie vorsichtig ist .In Linux-Systemen können Sie den Befehl vmstat verwenden, um die Anzahl der Kontextwechsel zu überprüfen. Das Folgende ist ein Beispiel für die Verwendung von vmstat, um die Anzahl der Kontextwechsel zu überprüfen:
vmstat 1 bedeutet, einmal pro Sekunde zu zählen, wobei die cs Die Spalte bezieht sich auf die Anzahl der Kontextwechsel. Allgemeine Situation Unter dieser Bedingung liegen die Kontextwechsel des Leerlaufsystems unter 1500 pro Sekunde.Parallele Verarbeitung von Client-Anfragen (I/O-Multiplexing)
Bei Netzwerkengpässen verwendet Redis Multiplexing-Technologie im Netzwerk-I/O-Modell, um die Auswirkungen von Netzwerkengpässen zu reduzieren. Die Verwendung eines Single-Threaded-Modells in vielen Szenarien bedeutet nicht, dass das Programm Aufgaben nicht gleichzeitig verarbeiten kann. Obwohl Redis ein Single-Threaded-Modell zur Verarbeitung von Benutzeranforderungen verwendet, nutzt es die I/O-Multiplexing-Technologie, um mehrere Verbindungen vom Client „parallel“ zu verarbeiten und auf Anforderungen zu warten, die von mehreren Verbindungen gleichzeitig gesendet werden. Der Einsatz der E/A-Multiplexing-Technologie kann den Systemaufwand erheblich reduzieren. Das System muss nicht mehr für jede Verbindung einen eigenen Überwachungsthread erstellen, wodurch der enorme Leistungsaufwand vermieden wird, der durch die Erstellung einer großen Anzahl von Threads entsteht.
Lassen Sie uns das Multiplex-I/O-Modell im Detail erklären. Um es besser zu verstehen, verstehen wir zunächst einige grundlegende Konzepte.
Socket: Socket kann als Kommunikationsendpunkt in zwei Anwendungen verstanden werden, wenn zwei Anwendungen über das Netzwerk kommunizieren. Während der Kommunikation schreibt eine Anwendung Daten in den Socket und sendet die Daten dann über die Netzwerkkarte an den Socket einer anderen Anwendung. Die Fernkommunikation, die wir normalerweise als HTTP- und TCP-Protokolle bezeichnen, wird basierend auf Socket auf der untersten Ebene implementiert. Die fünf Netzwerk-IO-Modelle implementieren außerdem alle eine Netzwerkkommunikation basierend auf Socket.
Blockieren und Nichtblockieren: Das sogenannte Blockieren bedeutet, dass eine Anfrage nicht sofort zurückgegeben werden kann und eine Antwort nicht zurückgegeben werden kann, bis die gesamte Logik verarbeitet wurde. Im Gegensatz dazu sendet Non-Blocking eine Anfrage und gibt sofort eine Antwort zurück, ohne auf die Verarbeitung der gesamten Logik zu warten.
Kernelspace und Userspace: Unter Linux ist die Stabilität von Anwendungsprogrammen der von Betriebssystemprogrammen weit unterlegen. Um die Stabilität des Betriebssystems zu gewährleisten, unterscheidet Linux zwischen Kernelspace und Userspace. Es versteht sich, dass im Kernelbereich Betriebssystemprogramme und -treiber ausgeführt werden und im Benutzerbereich Anwendungen ausgeführt werden. Auf diese Weise isoliert Linux Betriebssystemprogramme und -anwendungen und verhindert so, dass Anwendungen die Stabilität des Betriebssystems selbst beeinträchtigen. Dies ist auch der Hauptgrund, warum Linux-Systeme superstabil sind. Alle Systemressourcenvorgänge werden im Kernelraum ausgeführt, z. B. das Lesen und Schreiben von Festplattendateien, die Speicherzuweisung und -wiederverwendung, Netzwerkschnittstellenaufrufe usw. Daher werden die Daten während eines Netzwerk-E/A-Lesevorgangs nicht direkt von der Netzwerkkarte in den Anwendungspuffer im Benutzerbereich gelesen, sondern zunächst von der Netzwerkkarte in den Kernel-Speicherplatzpuffer und dann vom Kernel zum Benutzer kopiert Speicherplatz. Für den Netzwerk-E/A-Schreibvorgang ist der Vorgang umgekehrt. Die Daten werden zuerst vom Anwendungspuffer im Benutzerbereich in den Kernelpuffer kopiert und dann werden die Daten vom Kernelpuffer über die Netzwerkkarte gesendet.
Das Multiplex-I/O-Modell basiert auf den Mehrkanal-Ereignistrennungsfunktionen Select, Poll und Epoll. Am Beispiel von Epoll, das von Redis verwendet wird: Bevor eine Leseanforderung initiiert wird, wird zunächst die Socket-Überwachungsliste von Epoll aktualisiert und dann auf die Rückkehr der Epoll-Funktion gewartet (dieser Prozess ist blockierend, daher ist Multiplex-E/A im Wesentlichen ein blockierendes E/A-Modell). . Wenn Daten von einem bestimmten Socket eintreffen, kehrt die Epoll-Funktion zurück. Zu diesem Zeitpunkt initiiert der Benutzerthread offiziell eine Leseanforderung zum Lesen und Verarbeiten der Daten. Dieser Modus verwendet einen dedizierten Überwachungsthread, um mehrere Sockets zu überprüfen. Wenn Daten in einem bestimmten Socket ankommen, werden sie zur Verarbeitung an den Worker-Thread übergeben. Da das Warten auf das Eintreffen von Socket-Daten sehr zeitaufwändig ist, löst diese Methode das Problem, dass eine Socket-Verbindung im blockierenden E/A-Modell einen Thread erfordert, und es gibt kein Problem mit CPU-Leistungsverlusten, die durch ausgelastete Abfragen im Nicht-IO-Modell verursacht werden -blockierendes IO-Modell. Es gibt viele praktische Anwendungsszenarien für das Multiplex-IO-Modell. Die bekannten Redis, Java NIO und Netty, das von Dubbo verwendete Kommunikationsframework, übernehmen alle dieses Modell.
Die folgende Abbildung zeigt den detaillierten Prozess der Socket-Programmierung basierend auf der Epoll-Funktion.
Wir wissen, dass Multithreading in Szenarien mit hoher Parallelität CPU-Verluste durch E/A-Wartezeiten reduzieren und eine gute Leistung bringen kann . Allerdings ist Multithreading ein zweischneidiges Schwert. Es bringt zwar Vorteile mit sich, bringt aber auch Schwierigkeiten bei der Codepflege, Schwierigkeiten beim Auffinden und Debuggen von Online-Problemen, Deadlocks und anderen Problemen mit sich. Der Ausführungsprozess von Code im Multithreading-Modell ist nicht mehr seriell, und gemeinsam genutzte Variablen, auf die mehrere Threads gleichzeitig zugreifen, verursachen bei unsachgemäßer Handhabung ebenfalls seltsame Probleme.
Schauen wir uns anhand eines Beispiels die seltsamen Phänomene an, die in Multithread-Szenarien auftreten. Schauen Sie sich den Code unten an: Wenn
class MemoryReordering { int num = 0; boolean flag = false; public void set() { num = 1; //语句1 flag = true; //语句2 } public int cal() { if( flag == true) { //语句3 return num + num; //语句4 } return -1; } }
flag wahr ist, was ist der Rückgabewert der Methode cal()? Viele Leute werden sagen: Muss man überhaupt fragen? Auf jeden Fall 2
结果可能会让你大吃一惊!上面的这段代码,由于语句1和语句2没有数据依赖性,可能会发生指令重排序,有可能编译器会把flag=true放到num=1的前面。此时set和cal方法分别在不同线程中执行,没有先后关系。cal方法,只要flag为true,就会进入if的代码块执行相加的操作。可能的顺序是:
语句1先于语句2执行,这时的执行顺序可能是:语句1->语句2->语句3->语句4。执行语句4前,num = 1,所以cal的返回值是2
语句2先于语句1执行,这时的执行顺序可能是:语句2->语句3->语句4->语句1。执行语句4前,num = 0,所以cal的返回值是0
我们可以看到,在多线程环境下如果发生了指令重排序,会对结果造成严重影响。
当然可以在第三行处,给flag加上关键字volatile来避免指令重排。即在flag处加上了内存栅栏,来阻隔flag(栅栏)前后的代码的重排序。当然多线程还会带来可见性问题,死锁问题以及共享资源安全等问题。
boolean volatile flag = false;
Redis6.0引入的多线程部分,实际上只是用来处理网络数据的读写和协议解析,执行命令仍然是单一工作线程。
从上图我们可以看到Redis在处理网络数据时,调用epoll的过程是阻塞的,也就是说这个过程会阻塞线程,如果并发量很高,达到几万的QPS,此处可能会成为瓶颈。一般我们遇到此类网络IO瓶颈的问题,可以增加线程数来解决。开启多线程除了可以减少由于网络I/O等待造成的影响,还可以充分利用CPU的多核优势。Redis6.0也不例外,在此处增加了多线程来处理网络数据,以此来提高Redis的吞吐量。当然相关的命令处理还是单线程运行,不存在多线程下并发访问带来的种种问题。
性能对比
压测配置:
Redis Server: 阿里云 Ubuntu 18.04,8 CPU 2.5 GHZ, 8G 内存,主机型号 ecs.ic5.2xlarge Redis Benchmark Client: 阿里云 Ubuntu 18.04,8 2.5 GHZ CPU, 8G 内存,主机型号 ecs.ic5.2xlarge
多线程版本Redis 6.0,单线程版本是 Redis 5.0.5。多线程版本需要新增以下配置:
io-threads 4 # 开启 4 个 IO 线程 io-threads-do-reads yes # 请求解析也是用 IO 线程
压测命令: redis-benchmark -h 192.168.0.49 -a foobared -t set,get -n 1000000 -r 100000000 --threads 4 -d ${datasize} -c 256
图片来源于网络
图片来源于网络
从上面可以看到 GET/SET 命令在多线程版本中性能相比单线程几乎翻了一倍。另外,这些数据只是为了简单验证多线程 I/O 是否真正带来性能优化,并没有针对具体的场景进行压测,数据仅供参考。本次性能测试基于 unstble 分支,不排除后续发布的正式版本的性能会更好。
最后
可见单线程有单线程的好处,多线程有多线程的优势,只有充分理解其中的本质原理,才能灵活运用于生产实践当中。
Das obige ist der detaillierte Inhalt vonWarum führt Redis6.0 Multithreading ein?. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!