HBase
 sql >> Datenbank >  >> NoSQL >> HBase

Apache HBase + Apache Hadoop + Xceiver

Einführung

Einige der in Apache Hadoop gefundenen Konfigurationseigenschaften wirken sich direkt auf Clients aus, z. B. Apache HBase. Eine dieser Eigenschaften heißt "dfs.datanode.max.xcievers" und gehört zum HDFS-Unterprojekt. Es definiert die Anzahl der serverseitigen Threads und – in gewissem Umfang – Sockets, die für Datenverbindungen verwendet werden. Wenn Sie diese Zahl zu niedrig einstellen, kann dies zu Problemen führen, wenn Sie Ihren Cluster vergrößern oder die Auslastung erhöhen. Dieser Beitrag hilft Ihnen zu verstehen, was zwischen dem Client und dem Server passiert und wie Sie eine angemessene Zahl für diese Eigenschaft bestimmen können.

Das Problem

Da HBase alles, was es benötigt, in HDFS speichert, kann die durch die Konfigurationseigenschaft „dfs.datanode.max.xcievers“ auferlegte harte Obergrenze dazu führen, dass HBase zu wenig Ressourcen zur Verfügung stehen, was sich als IOExceptions auf beiden Seiten der Verbindung manifestiert. Hier ein Beispiel aus der HBase-Mailingliste [1], wo auf der RegionServer-Seite zunächst folgende Meldungen protokolliert wurden:

2008-11-11 19:55:52,451 INFO org.apache.hadoop.dfs.DFSClient:Ausnahme in createBlockOutputStream java.io.IOException:Stream konnte nicht gelesen werden
2008-11-11 19:55:52,451 INFO org.apache.hadoop.dfs.DFSClient:Abandoning block blk_-5467014108758633036_595771
2008-11- 11 19:55:58,455 WARN org.apache.hadoop.dfs.DFSClient:DataStreamer-Ausnahme:java.io.IOException:Neuer Block kann nicht erstellt werden.
2008-11-11 19:55:58,455 WARN org.apache .hadoop.dfs.DFSClient:Error Recovery for block blk_-5467014108758633036_595771 bad datanode[0]
2008-11-11 19:55:58,482 FATAL org.apache.hadoop.hbase.regionserver.Flusher:Wiederholung von hlog erforderlich . Herunterfahren des Servers erzwingen

Eine Korrelation mit den Hadoop DataNode-Protokollen ergab den folgenden Eintrag:

ERROR org.apache.hadoop.dfs.DataNode: DatanodeRegistration(10.10.10.53:50010,storageID=DS-1570581820-10.10.10.53-50010-1224117842339,infoPort=50075, ipcPort=50020):DataXceiver:java.io.IOException: xceiverCount 258 überschreitet die Grenze gleichzeitiger xcievers 256

In diesem Beispiel führte der niedrige Wert von „dfs.datanode.max.xcievers“ für die DataNodes dazu, dass der gesamte RegionServer heruntergefahren wurde. Das ist eine wirklich schlimme Situation. Leider gibt es keine feste Regel, die erklärt, wie das erforderliche Limit berechnet wird. Es wird allgemein empfohlen, die Zahl vom Standardwert 256 auf etwa 4096 zu erhöhen (siehe [1], [2], [3], [4] und [5] als Referenz). Dies geschieht durch Hinzufügen dieser Eigenschaft zur Datei hdfs-site.xml aller DataNodes (beachten Sie, dass sie falsch geschrieben ist):

    dfs.datanode.max.xcievers
4096

Hinweis:Sie müssen Ihre DataNodes neu starten, nachdem Sie diese Änderung an der Konfigurationsdatei vorgenommen haben.

Dies sollte bei dem obigen Problem helfen, aber vielleicht möchten Sie trotzdem mehr darüber erfahren, wie das alles zusammenspielt und was HBase mit diesen Ressourcen macht. Wir werden dies im Rest dieses Beitrags besprechen. Aber bevor wir das tun, müssen wir uns darüber im Klaren sein, warum Sie diese Zahl nicht einfach sehr hoch setzen können, sagen wir 64K, und fertig.

Es gibt einen Grund für eine Obergrenze, und zwar zweierlei:Erstens benötigen Threads ihren eigenen Stack, was bedeutet, dass sie Speicher belegen. Für aktuelle Server bedeutet dies standardmäßig 1 MB pro Thread[6]. Mit anderen Worten, wenn Sie alle 4096 DataXceiver-Threads aufbrauchen, benötigen Sie etwa 4 GB Heap, um sie aufzunehmen. Dies schneidet in den Speicherplatz ein, den Sie Memstores und Block-Caches sowie allen anderen beweglichen Teilen der JVM zugewiesen haben. Im schlimmsten Fall könnten Sie auf eine OutOfMemoryException stoßen, und der RegionServer-Prozess wird getoastet. Sie sollten diese Eigenschaft auf einen angemessen hohen Wert setzen, aber auch nicht zu hoch.

Zweitens, wenn diese vielen Threads aktiv sind, werden Sie auch sehen, dass Ihre CPU zunehmend belastet wird. Es werden viele Kontextwechsel stattfinden, um die gesamte gleichzeitige Arbeit zu bewältigen, was Ressourcen für die eigentliche Arbeit wegnimmt. Wie bei den Sorgen um den Speicher möchten Sie, dass die Anzahl der Threads nicht unbegrenzt wächst, sondern eine vernünftige Obergrenze bietet – und dafür ist „dfs.datanode.max.xcievers“ da.

Details zum Hadoop-Dateisystem

Auf der Clientseite stellt die HDFS-Bibliothek die Abstraktion namens Path bereit. Diese Klasse stellt eine Datei in einem von Hadoop unterstützten Dateisystem dar, das durch die FileSystem-Klasse dargestellt wird. Es gibt einige konkrete Implementierungen der abstrakten FileSystem-Klasse, von denen eine das DistributedFileSytem ist, das HDFS darstellt. Diese Klasse umschließt wiederum die eigentliche DFSClient-Klasse, die alle Interaktionen mit den Remote-Servern abwickelt, also den NameNode und die vielen DataNodes.

Wenn ein Client wie HBase eine Datei öffnet, tut er dies beispielsweise durch Aufrufen der open() oder create()-Methoden der FileSystem-Klasse, hier die einfachsten Inkarnationen

  public DFSInputStream open(String src) löst IOException aus
public FSDataOutputStream create(Path f) löst IOException aus

Die zurückgegebene Stream-Instanz benötigt einen serverseitigen Socket und Thread, die zum Lesen und Schreiben von Datenblöcken verwendet werden. Sie sind Bestandteil des Vertrages zum Datenaustausch zwischen Client und Server. Beachten Sie, dass es andere RPC-basierte Protokolle gibt, die zwischen den verschiedenen Maschinen verwendet werden, aber für den Zweck dieser Diskussion können sie ignoriert werden.

Die zurückgegebene Stream-Instanz ist eine spezialisierte DFSOutputStream- oder DFSInputStream-Klasse, die die gesamte Interaktion mit dem NameNode verarbeitet, um herauszufinden, wo sich die Kopien der Blöcke befinden, und die Datenkommunikation pro Block und DataNode.

Auf der Serverseite umschließt der DataNode eine Instanz von DataXceiverServer, der eigentlichen Klasse, die den obigen Konfigurationsschlüssel liest und auch die obige Ausnahme auslöst, wenn das Limit überschritten wird.

Wenn der DataNode startet, erstellt er eine Thread-Gruppe und startet die erwähnte DataXceiverServer-Instanz wie folgt:

  this.threadGroup =new ThreadGroup(“dataXceiverServer”);
this.dataXceiverServer =new Daemon( threadGroup,
new DataXceiverServer(ss, conf, this));
this.threadGroup.setDaemon(true); // automatisch zerstören, wenn leer

Beachten Sie, dass der DataXceiverServer-Thread bereits einen Platz in der Thread-Gruppe einnimmt. Der DataNode hat auch diese interne Klasse, um die Anzahl der derzeit aktiven Threads in dieser Gruppe abzurufen:

  /** Anzahl gleichzeitiger Xceiver pro Knoten. */
int getXceiverCount() {
return threadGroup ==null ? 0 :threadGroup.activeCount();
}

Das Lesen und Schreiben von Blöcken, wie vom Client initiiert, führt dazu, dass eine Verbindung hergestellt wird, die vom DataXceiverServer Thread in eine DataXceiver-Instanz verpackt wird. Während dieser Übergabe wird ein Thread erstellt und in der obigen Thread-Gruppe registriert. Für jeden aktiven Lese- und Schreibvorgang wird also serverseitig ein neuer Thread verfolgt. Wenn die Anzahl der Threads in der Gruppe das konfigurierte Maximum überschreitet, wird die besagte Ausnahme ausgelöst und in den Protokollen des DataNode aufgezeichnet:

  if (curXceiverCount> dataXceiverServer.maxXceiverCount) {
throw new IOException(“xceiverCount ” + curXceiverCount
+ ” überschreitet die Grenze gleichzeitiger xcievers ”
+ dataXceiverServer.maxXceiverCount);
}

Implikationen für Kunden

Nun stellt sich die Frage, wie sich das Lesen und Schreiben des Clients auf die serverseitigen Threads bezieht. Bevor wir jedoch auf die Details eingehen, verwenden wir die Debug-Informationen, die die DataXceiver-Klasse protokolliert, wenn sie erstellt und geschlossen wird

  LOG.debug(“Anzahl aktiver Verbindungen:” + datanode.getXceiverCount());

LOG.debug(datanode.dnRegistration + „:Anzahl der aktiven Verbindungen ist:„     + datanode.getXceiverCount());

und beim Start von HBase überwachen, was auf dem DataNode protokolliert wird. Der Einfachheit halber erfolgt dies in einem pseudoverteilten Setup mit einer einzigen DataNode- und RegionServer-Instanz. Das Folgende zeigt den oberen Rand der Statusseite des RegionServers.

Der wichtige Teil befindet sich im Abschnitt „Metriken“, wo „storefiles=22“ steht. Unter der Annahme, dass HBase mindestens so viele Dateien zu verarbeiten hat, plus einige zusätzliche Dateien für das Write-Ahead-Protokoll, sollten wir den obigen Protokollmeldungsstatus sehen, dass wir mindestens 22 „aktive Verbindungen“ haben. Lassen Sie uns HBase starten und die DataNode- und RegionServer-Protokolldateien überprüfen:

Befehlszeile:

$ bin/start-hbase.sh

DataNode-Protokoll:

2012-03-05 13:01:35,309 DEBUG org.apache.hadoop.hdfs.server.datanode. DataNode:Anzahl der aktiven Verbindungen ist:1
2012-03-05 13:01:35,315 DEBUG org.apache.hadoop.hdfs.server.datanode.DataNode:DatanodeRegistration(127.0.0.1:50010, storageID=DS- 1423642448-10.0.0.64-50010-1321352233772, infoPort=50075, ipcPort=50020):Anzahl der aktiven Verbindungen ist:2
12/03/05 13:01:35 INFO regionserver.MemStoreFlusher:globalMemStoreLimit=396.7m, globalMemStoreLimitLowMark=347.1m, maxHeap=991.7m
03.12.05 13:01:39 INFO http.HttpServer:Der von webServer.getConnectors()[0].getLocalPort() zurückgegebene Port vor open() ist -1 . Öffnen des Listeners auf 60030
2012-03-05 13:01:40,003 DEBUG org.apache.hadoop.hdfs.server.datanode.DataNode:Anzahl der aktiven Verbindungen ist:1
12/03/05 13:01:40 INFO regionserver.HRegionServer:Anforderung zum Öffnen der Region:-ROOT-,,0.70236052
2012-03-05 13:01:40,882 DEBUG org.apache.hadoop.hdfs.server.datanode.DataNode :Anzahl der aktiven Verbindungen ist:3
2012-03-05 13:01:40,884 DEBUG org.apache.hadoop.hdfs.server.datanode.DataNode:DatanodeRegistration(127.0.0.1:50010, storageID=DS-1423642448 -10.0.0.64-50010-1321352233772, infoPort=50075, ipcPort=50020):Anzahl der aktiven Verbindungen ist:4
2012-03-05 13:01:40,888 DEBUG org.apache.hadoop.hdfs.server. datanode.DataNode:Anzahl der aktiven Verbindungen ist:3

12/03/05 13:01:40 INFO regionserver.HRegion:Onlined -ROOT-,,0.70236052; next sequenceid=63083
2012-03-05 13:01:40,982 DEBUG org.apache.hadoop.hdfs.server.datanode.DataNode:Anzahl der aktiven Verbindungen ist:3
2012-03-05 13 :01:40,983 DEBUG org.apache.hadoop.hdfs.server.datanode.DataNode:DatanodeRegistration(127.0.0.1:50010, storageID=DS-1423642448-10.0.0.64-50010-1321352233772, infoPort=50075, ipcPort=50020):Anzahl aktiver Verbindungen:4

12/03/05 13:01:41 INFO regionserver.HRegionServer:Empfangene Anfrage zum Öffnen der Region:.META.,,1.1028785192
2012-03 -05 13:01:41,026 DEBUG org.apache.hadoop.hdfs.server.datanode.DataNode:Anzahl der aktiven Verbindungen ist:3
2012-03-05 13:01:41,027 DEBUG org.apache.hadoop. hdfs.server.datanode.DataNode:DatanodeRegistration(127.0.0.1:50010, storageID=DS-1423642448-10.0.0.64-50010-1321352233772, infoPort=50075, ipcPort=50020):Anzahl der aktiven Verbindungen ist:4

12/03/05 13:01:41 INFO regionserver.HRegion:Onlined .META.,,1.1028785192; next sequenceid=63082
2012-03-05 13:01:41,109 DEBUG org.apache.hadoop.hdfs.server.datanode.DataNode:Anzahl der aktiven Verbindungen ist:3
2012-03-05 13 :01:41,114 DEBUG org.apache.hadoop.hdfs.server.datanode.DataNode:Anzahl der aktiven Verbindungen ist:4
2012-03-05 13:01:41,117 DEBUG org.apache.hadoop.hdfs.server .datanode.DataNode:Anzahl aktiver Verbindungen:5
03.12.05 13:01:41 INFO regionserver.HRegionServer:Anforderung zum Öffnen von 16 Region(en) erhalten
03.12.05 13 :01:41 INFO regionserver.HRegionServer:Anfrage zum Öffnen der Region erhalten:Benutzertabelle,,1330944810191.62a312d67981c86c42b6bc02e6ec7e3f.
12/03/05 13:01:41 INFO regionserver.HRegionServer:Anfrage zum Öffnen der Region erhalten:Benutzertabelle,Benutzer1120311784, 1330944810191.90d287473fe223f0ddc137020efda25d.

2012-03-05 13:01:41,246 DEBUG org.apache.hadoop.hdfs.server.datanode. DataNode:Anzahl aktiver Verbindungen:6
2012-03-05 13:01:41,248 DEBUG org.apache.hadoop.hdfs.server.datanode.DataNode:Anzahl aktiver Verbindungen:7

2012-03-05 13:01:41,257 DEBUG org.apache.hadoop.hdfs.server.datanode.DataNode:DatanodeRegistration(127.0.0.1:50010, storageID=DS-1423642448-10.0.0.64-50010-1321352233772 , infoPort=50075, ipcPort=50020):Anzahl der aktiven Verbindungen ist:10
2012-03-05 13:01:41,257 DEBUG org.apache.hadoop.hdfs.server.datanode.DataNode:DatanodeRegistration(127.0. 0.1:50010, storageID=DS-1423642448-10.0.0.64-50010-1321352233772, infoPort=50075, ipcPort=50020):Anzahl der aktiven Verbindungen ist:9

12/03/05 13:01:41 INFO regionserver.HRegion:Onlined usertable,user1120311784,1330944810191.90d287473fe223f0ddc137020efda25d.; next sequenceid=62917
12/03/05 13:01:41 INFO regionserver.HRegion:Onlined usertable,,1330944810191.62a312d67981c86c42b6bc02e6ec7e3f.; next sequenceid=62916

12/03/05 13:01:41 INFO regionserver.HRegion:Onlined usertable,user1361265841,1330944811370.80663fcf291e3ce00080599964f406ba.; next sequenceid=62919
2012-03-05 13:01:41,474 DEBUG org.apache.hadoop.hdfs.server.datanode.DataNode:Anzahl der aktiven Verbindungen ist:6
2012-03-05 13 :01:41,491 DEBUG org.apache.hadoop.hdfs.server.datanode.DataNode:Anzahl der aktiven Verbindungen ist:7
2012-03-05 13:01:41,495 DEBUG org.apache.hadoop.hdfs.server .datanode.DataNode:DatanodeRegistration(127.0.0.1:50010, storageID=DS-1423642448-10.0.0.64-50010-1321352233772, infoPort=50075, ipcPort=50020):Anzahl aktiver Verbindungen:8
2012-03 -05 13:01:41,508 DEBUG org.apache.hadoop.hdfs.server.datanode.DataNode:Anzahl der aktiven Verbindungen ist:7

12/03/05 13:01:41 INFO regionserver .HRegion:Onlined usertable,user1964968041,1330944848231.dd89596e9129e1caa7e07f8a491c9734.; next sequenceid=62920
2012-03-05 13:01:41,618 DEBUG org.apache.hadoop.hdfs.server.datanode.DataNode:Anzahl der aktiven Verbindungen ist:6
2012-03-05 13 :01:41,621 DEBUG org.apache.hadoop.hdfs.server.datanode.DataNode:DatanodeRegistration(127.0.0.1:50010, storageID=DS-1423642448-10.0.0.64-50010-1321352233772, infoPort=50075, ipcPort=50020):Anzahl aktiver Verbindungen:7

2012-03-05 13:01:41,829 DEBUG org.apache.hadoop.hdfs.server.datanode.DataNode:DatanodeRegistration(127.0.0.1:50010, storageID =DS-1423642448-10.0.0.64-50010-1321352233772, infoPort=50075, ipcPort=50020):Anzahl der aktiven Verbindungen ist:7
12/03/05 13:01:41 INFO regionserver.HRegion:Onlined usertable ,Benutzer515290649,1330944849739.d23924dc9e9d5891f332c337977af83d.; next sequenceid=62926
2012-03-05 13:01:41,832 DEBUG org.apache.hadoop.hdfs.server.datanode.DataNode:Anzahl der aktiven Verbindungen ist:6
2012-03-05 13 :01:41,838 DEBUG org.apache.hadoop.hdfs.server.datanode.DataNode:DatanodeRegistration(127.0.0.1:50010, storageID=DS-1423642448-10.0.0.64-50010-1321352233772, infoPort=50075, ipcPort=50020):Anzahl aktiver Verbindungen:7
12/03/05 13:01:41 INFO regionserver.HRegion:Onlined usertable,user757669512,1330944850808.cd0d6f16d8ae9cf0c9277f5d6c6c6b9f.; next sequenceid=62929

2012-03-05 14:01:39,711 DEBUG org.apache.hadoop.hdfs.server.datanode.DataNode:Anzahl der aktiven Verbindungen ist:4
2012 -03-05 22:48:41,945 DEBUG org.apache.hadoop.hdfs.server.datanode.DataNode:DatanodeRegistration(127.0.0.1:50010, storageID=DS-1423642448-10.0.0.64-50010-1321352233772, infoPort=50075, ipcPort=50020):Anzahl der aktiven Verbindungen ist:4
12/03/05 22:48:41 INFO regionserver.HRegion:Onlined usertable,user757669512,1330944850808.cd0d6f16d8ae9cf0c9277f5d6c6c6b9f.; next sequenceid=62929
2012-03-05 22:48:41,963 DEBUG org.apache.hadoop.hdfs.server.datanode.DataNode:DatanodeRegistration(127.0.0.1:50010, storageID=DS-1423642448-10.0.0.64 -50010-1321352233772, infoPort=50075, ipcPort=50020):Anzahl der aktiven Verbindungen ist:4

Sie können sehen, wie die Regionen nacheinander geöffnet werden, aber was Sie vielleicht auch bemerken, ist, dass die Anzahl der aktiven Verbindungen nie auf 22 steigt – sie erreicht kaum 10. Warum ist das so? Um dies besser zu verstehen, müssen wir sehen, wie Dateien in HDFS der Instanz des serverseitigen DataXceivers zugeordnet sind – und die tatsächlichen Threads, die sie darstellen.

Hadoop Deep Dive

Die oben erwähnten DFSInputStream und DFSOutputStream sind wirklich Fassaden um die üblichen Stream-Konzepte herum. Sie verpacken die Client-Server-Kommunikation in diese Standard-Java-Schnittstellen, während sie den Datenverkehr intern an einen ausgewählten DataNode weiterleiten – das ist derjenige, der eine Kopie des aktuellen Blocks enthält. Es hat die Freiheit, diese Verbindung nach Bedarf zu öffnen und zu schließen. Wenn ein Client eine Datei in HDFS liest, wechseln die Client-Bibliotheksklassen transparent von Block zu Block und damit von DataNode zu DataNode, sodass Verbindungen nach Bedarf geöffnet und geschlossen werden müssen.

Der DFSInputStream hat eine Instanz einer DFSClient.BlockReader Klasse, die die Verbindung zum DataNode öffnet. Die Stream-Instanz ruft blockSeekTo() für jeden Aufruf von read() auf, was sich um das Öffnen der Verbindung kümmert, falls noch keine vorhanden ist. Sobald ein Block vollständig gelesen ist, wird die Verbindung geschlossen. Das Schließen des Streams hat natürlich den gleichen Effekt.

Der DFSOutputStream hat eine ähnliche Hilfsklasse, den DataStreamer. Es verfolgt die Verbindung zum Server, die von der nextBlockOutputStream()-Methode initiiert wird. Es hat weitere interne Klassen, die beim Ausschreiben der Blockdaten helfen, die wir hier der Kürze halber weglassen.

Sowohl das Schreiben als auch das Lesen von Blöcken erfordert einen Thread, um die Socket- und Zwischendaten auf der Serverseite zu halten, die in die DataXceiver-Instanz eingeschlossen sind. Je nachdem, was Ihr Client tut, werden Sie feststellen, dass die Anzahl der Verbindungen um die Anzahl der aktuell aufgerufenen Dateien in HDFS herum schwankt.

Zurück zum obigen HBase-Rätsel:Der Grund, warum Sie beim Start nicht bis zu 22 (und mehr) Verbindungen sehen, ist, dass, während die Regionen geöffnet sind, die einzigen erforderlichen Daten der Infoblock von HFile sind. Dieser Block wird gelesen, um wichtige Details zu jeder Datei zu erhalten, aber dann wieder geschlossen. Das bedeutet, dass die serverseitige Ressource in schneller Folge freigegeben wird. Die verbleibenden vier Verbindungen sind schwieriger zu bestimmen. Sie können JStack verwenden, um alle Threads auf dem DataNode auszugeben, was in diesem Beispiel diesen Eintrag zeigt:

"DataXceiver for client /127.0.0.1:64281 [sending block blk_5532741233443227208_4201]" Daemon prio=5 tid=7fb96481d000 nid=0x1178b4000 ausführbar [1178b3000]
java.lang.Thread.State:AUSFÜHRBAR

“DataXceiver für Client /127.0.0.1:64172 [Empfangsblock blk_-2005512129579433420_4199 client=DFSClient_hb_rs_10.0.0.29 ,60020,1330984111693_1330984118810]” Daemon prio=5 tid=7fb966109000 nid=0x1169cb000 lauffähig [1169ca000]
java.lang.Thread.State:RUNNABLE

Dies sind die einzigen DataXceiver-Einträge (in diesem Beispiel), daher ist die Zählung in der Thread-Gruppe etwas irreführend. Denken Sie daran, dass der DataXceiverServer-Daemon-Thread bereits einen zusätzlichen Eintrag berücksichtigt, der zusammen mit den beiden oben genannten Konten für die drei aktiven Verbindungen steht – was tatsächlich drei aktive Threads bedeutet. Der Grund, warum das Protokoll stattdessen vier anzeigt, ist, dass es die Anzahl von einem aktiven Thread protokolliert, der kurz vor dem Ende steht. Also, kurz nachdem die Zählung von vier protokolliert wurde, ist es tatsächlich eine weniger, d. h. drei, und entspricht daher unserer Kopfzahl aktiver Threads.

Beachten Sie auch, dass die internen Hilfsklassen wie der PacketResponder einen anderen Thread in der Gruppe belegen, während sie aktiv sind. Die JStack-Ausgabe weist darauf hin und listet den Thread als solchen auf:

„PacketResponder 0 for Block blk_-2005512129579433420_4199“ Daemon prio=5 tid=7fb96384d000 nid=0x116ace000 in Object.wait () [116acd000]
java.lang.Thread.State:TIMED_WAITING (auf Objektmonitor)
bei java.lang.Object.wait (native Methode)
bei org.apache.hadoop. hdfs.server.datanode.BlockReceiver$PacketResponder \
.lastDataNodeRun(BlockReceiver.java:779)
– gesperrt (ein org.apache.hadoop.hdfs.server.datanode.BlockReceiver$PacketResponder)
unter org.apache.hadoop.hdfs.server.datanode.BlockReceiver$PacketResponder.run(BlockReceiver.java:870)
unter java.lang.Thread.run(Thread.java:680)

Dieser Thread befindet sich derzeit im Status TIMED_WAITING und wird nicht als aktiv betrachtet. Aus diesem Grund enthält die von den DataXceiver-Protokollanweisungen ausgegebene Anzahl diese Art von Threads nicht. Wenn sie aktiv werden, weil der Client Sendedaten sendet, steigt die Anzahl aktiver Threads wieder an. Beachten Sie auch, dass dieser Thread keine separate Verbindung oder Socket zwischen dem Client und dem Server benötigt. Der PacketResponder ist nur ein Thread auf der Serverseite, um Blockdaten zu empfangen und an den nächsten DataNode in der Schreibpipeline zu streamen.

Der Hadoop-Befehl fsck hat auch eine Option, um zu melden, welche Dateien derzeit zum Schreiben geöffnet sind:

$ hadoop fsck /hbase -openforwrite
FSCK gestartet von larsgeorge ab /10.0.0.29 für Pfad / hbase am Mo. Mär 05 22:59:47 CET 2012
……/hbase/.logs/10.0.0.29,60020,1330984111693/10.0.0.29%3A60020.1330984118842 0 Bytes, 1 Block(s), OPENFORWRITE:………………………………..Status:HEALTHY
Gesamtgröße:     2088783626 B
Gesamtverzeichnisse:     54
Gesamtdateien:   45

Dies bezieht sich nicht unmittelbar auf einen belegten serverseitigen Thread, da diese per Block-ID zugeteilt werden. Aber Sie können daraus entnehmen, dass es einen offenen Block zum Schreiben gibt. Der Hadoop-Befehl hat zusätzliche Optionen, um die eigentlichen Dateien und Block-IDs, aus denen sie bestehen, auszudrucken:

$ hadoop fsck /hbase -files -blocks
FSCK gestartet von larsgeorge ab /10.0.0.29 für path /hbase am Di. März 06 10:39:50 CET 2012


/hbase/.META./1028785192/.tmp


/hbase/.META./1028785192/info
/hbase/.META./1028785192/info/4027596949915293355 36517 Bytes, 1 Block(s):  OK
0. blk_5532741233443227208_4201 len=36517 repl=1


Status:HEALTHY
Gesamtgröße:     2088788703 B
Gesamtverzeichnisse :     54
Gesamtzahl Dateien:     45 (aktuell geschriebene Dateien:1)
Gesamtzahl Blöcke (validiert):     64 (durchschnittliche Blockgröße 32637323 B) (Gesamtzahl offener Dateiblöcke (nicht validiert):1)
Minimal replizierte Blöcke:     64 (100,0 %)

Das gibt Ihnen zwei Dinge. Erstens gibt die Zusammenfassung an, dass zum Zeitpunkt der Ausführung des Befehls ein offener Dateiblock vorhanden war – der mit der Anzahl übereinstimmt, die von der obigen Option „-openforwrite“ gemeldet wird. Zweitens ermöglicht Ihnen die Liste der Blöcke neben jeder Datei, den Thread-Namen mit der Datei abzugleichen, die den Block enthält, auf den zugegriffen wird. In diesem Beispiel wird der Block mit der ID „blk_5532741233443227208_4201“ vom Server an den Client, hier einen RegionServer, gesendet. Dieser Block gehört zur HBase .META. Tabelle, wie die Ausgabe des Hadoop-Befehls fsck zeigt. Die Kombination aus JStack und fsck kann als Ersatz für lsof dienen (ein Tool auf der Linux-Befehlszeile zum „Auflisten offener Dateien“).

Der JStack meldet auch, dass es einen DataXceiver-Thread mit einem begleitenden PacketResponder für die Block-ID „blk_-2005512129579433420_4199“ gibt, aber diese ID fehlt in der Liste der von fsck gemeldeten Blöcke. Dies liegt daran, dass der Block noch nicht fertig ist und daher den Lesern nicht zur Verfügung steht. Mit anderen Worten, Hadoop fsck meldet nur vollständige (oder synchronisierte[7][8] für Hadoop-Versionen, die diese Funktion unterstützen) Blöcke.

Zurück zu HBase

Das Öffnen aller Regionen benötigt nicht so viele Ressourcen auf dem Server, wie Sie erwartet hätten. Wenn Sie jedoch die gesamte HBase-Tabelle scannen, zwingen Sie HBase, alle Blöcke in allen HFiles zu lesen:

HBase-Shell:

hbase(main):003:0> scannen 'usertable'

1000000 Zeile(n) in 1460,3120 Sekunden

DataNode-Protokoll:

2012-03-05 14:42:20,580 DEBUG org.apache.hadoop.hdfs.server.datanode. DataNode:Anzahl aktiver Verbindungen:6
2012-03-05 14:43:23,293 DEBUG org.apache.hadoop.hdfs.server.datanode.DataNode:Anzahl aktiver Verbindungen:7
2012 -03-05 14:43:23,299 DEBUG org.apache.hadoop.hdfs.server.datanode.DataNode:DatanodeRegistration(127.0.0.1:50010, storageID=DS-1423642448-10.0.0.64-50010-1321352233772, infoPort=50075, ipcPort=50020):Anzahl der aktiven Verbindungen ist:8

2012-03-05 14:49:24,332 DEBUG org.apache.hadoop.hdfs.server.datanode.DataNode:DatanodeRegistration(127.0. 0.1:50010, storageID=DS-1423642448-10.0.0.64-50010-1321352233772, infoPort=50075, ipcPort=50020):Anzahl der aktiven Verbindungen ist:11
2012-03-05 14:49:24,332 DEBUG org .apache.hadoop.hdfs.server.datanode.DataNode:Anzahl der aktiven Verbindungen ist:10
2012-03-05 14:49:59,987 DEBUG org.apache.hadoop.hdfs.server.datanod e.DataNode:Anzahl der aktiven Verbindungen ist:11
2012-03-05 14:51:12,603 ​​DEBUG org.apache.hadoop.hdfs.server.datanode.DataNode:DatanodeRegistration(127.0.0.1:50010, storageID=DS-1423642448-10.0.0.64-50010-1321352233772, infoPort=50075, ipcPort=50020):Anzahl der aktiven Verbindungen ist:12
2012-03-05 14:51:12,605 DEBUG org.apache.hadoop.hdfs .server.datanode.DataNode:Anzahl aktiver Verbindungen:11
2012-03-05 14:51:46,473 DEBUG org.apache.hadoop.hdfs.server.datanode.DataNode:Anzahl aktiver Verbindungen:12

2012-03-05 14:56:59,420 DEBUG org.apache.hadoop.hdfs.server.datanode.DataNode:Anzahl aktiver Verbindungen:15
2012-03-05 14:57:31,722 DEBUG org.apache.hadoop.hdfs.server.datanode.DataNode:Anzahl der aktiven Verbindungen ist:16
2012-03-05 14:58:24,909 DEBUG org.apache.hadoop.hdfs. server.datanode.DataNode:DatanodeRegistration(127.0.0.1:50010, storageID=DS-1423642448-10.0.0.64-50010-1321352233772, infoPort=50075, ipcPort=50020):Nummer der Akt ive Verbindungen ist:17
2012-03-05 14:58:24,910 DEBUG org.apache.hadoop.hdfs.server.datanode.DataNode:Anzahl der aktiven Verbindungen ist:16

2012-03-05 15:04:17,688 DEBUG org.apache.hadoop.hdfs.server.datanode.DataNode:Anzahl der aktiven Verbindungen ist:21
2012-03-05 15:04:17,689 DEBUG org.apache .hadoop.hdfs.server.datanode.DataNode:DatanodeRegistration(127.0.0.1:50010, storageID=DS-1423642448-10.0.0.64-50010-1321352233772, infoPort=50075, ipcPort=50020):Anzahl der aktiven Verbindungen ist:22
2012-03-05 15:04:54,545 DEBUG org.apache.hadoop.hdfs.server.datanode.DataNode:Anzahl der aktiven Verbindungen ist:21
2012-03-05 15:05:55,901 DEBUG org.apache.hadoop.hdfs.server.datanode.DataNode:DatanodeRegistration(127.0.0.1:50010, storageID=DS-1423642448-10.0.0.64-50010-1321352233772, infoPort=50075, ipcPort=50020):Anzahl der aktiven Verbindungen ist :22
2012-03-05 15:05:55,901 DEBUG org.apache.hadoop.hdfs.server.datanode.DataNode:Anzahl der aktiven Verbindungen ist:21

Die Zahl der aktiven Verbindungen erreicht jetzt die schwer fassbaren 22. Beachten Sie, dass diese Zählung bereits den Server-Thread enthält, sodass wir immer noch etwas unter dem liegen, was wir als theoretisches Maximum betrachten könnten – basierend auf der Anzahl der Dateien, die HBase verarbeiten muss.

Was bedeutet das alles?

Also, wie viele „xcievers (sic)“ brauchen Sie? Da Sie nur HBase verwenden, könnten Sie einfach die obige „Storefiles“-Metrik (die Sie auch über Ganglia oder JMX erhalten) überwachen und ein paar Prozent für Zwischen- und Write-Ahead-Protokolldateien hinzufügen. Dies sollte für Systeme in Bewegung funktionieren. Wenn Sie diese Zahl jedoch auf einem inaktiven, vollständig komprimierten System ermitteln und davon ausgehen, dass es sich um das Maximum handelt, könnten Sie feststellen, dass diese Zahl zu niedrig ist, sobald Sie während regelmäßiger Memstore-Flushes weitere Speicherdateien hinzufügen, d. h. sobald Sie damit beginnen Daten zu den HBase-Tabellen hinzufügen. Oder wenn Sie MapReduce auch auf demselben Cluster verwenden, Flume-Protokollaggregation usw. Sie müssen diese zusätzlichen Dateien berücksichtigen und, was noch wichtiger ist, Blöcke zum Lesen und Schreiben öffnen.

Beachten Sie erneut, dass die Beispiele in diesem Beitrag einen einzelnen DataNode verwenden, etwas, das Sie in einem echten Cluster nicht haben werden. Zu diesem Zweck müssen Sie die Gesamtzahl der gespeicherten Dateien (gemäß der HBase-Metrik) durch die Anzahl der vorhandenen DataNodes dividieren. Wenn Sie zum Beispiel eine Speicherdateianzahl von 1000 haben und Ihr Cluster 10 DataNodes hat, dann sollten Sie mit dem Standardwert von 256 Xceiver-Threads pro DataNode zufrieden sein.

Der Worst Case wäre die Anzahl aller aktiven Reader und Writer, also derjenigen, die gerade Daten senden oder empfangen. Da dies jedoch im Voraus schwer zu bestimmen ist, sollten Sie möglicherweise eine anständige Reserve anlegen. Da der Schreibprozess einen zusätzlichen – wenn auch kurzlebigeren – Thread (für den PacketResponder) benötigt, müssen Sie dies ebenfalls berücksichtigen. Eine vernünftige, aber recht vereinfachte Formel könnte also lauten:

Diese Formel berücksichtigt, dass Sie etwa zwei Threads für einen aktiven Autor und einen weiteren für einen aktiven Leser benötigen. Diese wird dann aufsummiert und durch die Anzahl der DataNodes dividiert, da man pro DataNode die „dfs.datanode.max.xcievers“ angeben muss.

Wenn Sie zum obigen HBase RegionServer-Screenshot zurückkehren, haben Sie gesehen, dass es 22 Store-Dateien gab. These are immutable and will only be read, or in other words occupy one thread only. For all memstores that are flushed to disk you need two threads – but only until they are fully written. The files are finalized and closed for good, cleaning up any thread in the process. So these come and go based on your flush frequency. Same goes for compactions, they will read N files and write them into a single new one, then finalize the new file. As for the write-ahead logs, these will occupy a thread once you have started to add data to any table. There is a log file per server, meaning that you can only have twice as many active threads for these files as you have RegionServers.

For a pure HBase setup (HBase plus its own HDFS, with no other user), we can estimate the number of needed DataXceiver’s with the following formula:

Since you will be hard pressed to determine the active number of store files, flushes, and so on, it might be better to estimate the theoretical maximum instead. This maximum value takes into account that you can only have a single flush and compaction active per region at any time. The maximum number of logs you can have active matches the number of RegionServers, leading us to this formula:

Obviously, the number of store files will increase over time, and the number of regions typically as well. Same for the numbers of servers, so keep in mind to adjust this number over time. In practice, you can add a buffer of, for example, 20%, as shown in the formula below – in an attempt to not force you to change the value too often.

On the other hand, if you keep the number of regions fixed per server[9], and rather split them manually, while adding new servers as you grow, you should be able to keep this configuration property stable for each server.

Final Advice &TL;DR

Here is the final formula you want to use:

It computes the maximum number of threads needed, based on your current HBase vitals (no. of store files, regions, and region servers). It also adds a fudge factor of 20% to give you room for growth. Keep an eye on the numbers on a regular basis and adjust the value as needed. You might want to use Nagios with appropriate checks to warn you when any of the vitals goes over a certain percentage of change.

Note:Please make sure you also adjust the number of file handles your process is allowed to use accordingly[10]. This affects the number of sockets you can use, and if that number is too low (default is often 1024), you will get connection issues first.

Finally, the engineering devil on one of your shoulders should already have started to snicker about how horribly non-Erlang-y this is, and how you should use an event driven approach, possibly using Akka with Scala[11] – if you want to stay within the JVM world. Bear in mind though that the clever developers in the community share the same thoughts and have already started to discuss various approaches[12][13].

Links:

  • [1] http://old.nabble.com/Re%3A-xceiverCount-257-exceeds-the-limit-of-concurrent-xcievers-256-p20469958.html
  • [2] http://ccgtech.blogspot.com/2010/02/hadoop-hdfs-deceived-by-xciever.html
  • [3] https://issues.apache.org/jira/browse/HDFS-1861 “Rename dfs.datanode.max.xcievers and bump its default value”
  • [4] https://issues.apache.org/jira/browse/HDFS-1866 “Document dfs.datanode.max.transfer.threads in hdfs-default.xml”
  • [5] http://hbase.apache.org/book.html#dfs.datanode.max.xcievers
  • [6] http://www.oracle.com/technetwork/java/hotspotfaq-138619.html#threads_oom
  • [7] https://issues.apache.org/jira/browse/HDFS-200 “In HDFS, sync() not yet guarantees data available to the new readers”
  • [8] https://issues.apache.org/jira/browse/HDFS-265 “Revisit append”
  • [9] http://search-hadoop.com/m/CBBoV3z24H1 “HBase, mail # user – region size/count per regionserver”
  • [10] http://hbase.apache.org/book.html#ulimit “ulimit and nproc”
  • [11] http://akka.io/ “Akka”
  • [12] https://issues.apache.org/jira/browse/HDFS-223 “Asynchronous IO Handling in Hadoop and HDFS”
  • [13] https://issues.apache.org/jira/browse/HDFS-918 “Use single Selector and small thread pool to replace many instances of BlockSender for reads”