Einführung
Ich habe gRPC Java Tutorial ausprobiert und war neugierig, wie es funktioniert, also habe ich verschiedene Dinge untersucht.
--GRPC-Wie man Java benutzt, wird im Tutorial beschrieben, daher werde ich es nicht im Detail erklären.
――Es ist eine Zusammenfassung dessen, was ich über die Kommunikation verstanden habe und wie die detaillierte interne Implementierung aussieht.
Bestätigte Version
releases: gRPC-Java v1.16.1
- Die Quelle des Pakets routeguide wurde im Lernprogramm verwendet.
- Klicken Sie hier für Gradle-Einstellungen
compile 'io.grpc:grpc-netty-shaded:1.16.1'
compile 'io.grpc:grpc-protobuf:1.16.1'
compile 'io.grpc:grpc-stub:1.16.1'
Über gRPC
Zunächst werden zu gRPC die Punkte aus Official Document extrahiert.
- Verwenden Sie Protokollpuffer, um die zu sendenden und zu empfangenden Daten zu serialisieren und die Schnittstelle von RPC zu definieren.
- Die Definitionsdatei für Protokollpuffer definiert nur die Serialisierung von Daten, aber gRPC erweitert sie, um auch die RPC-Schnittstelle zu definieren.
- Der größte Teil des Codes für die zu serialisierenden Daten und Schnittstellen wird automatisch generiert, sodass Sie sich auf Ihre Geschäftslogik konzentrieren können.
--Kommunikation verwendet HTTP / 2
- HTTP / 2-Format zum Einrichten der Kommunikation und zum Senden / Empfangen von Headern und Daten
--Bidirektionale Streaming-Kommunikation ist mit einer TCP-Verbindung möglich, und die folgenden 4 Arten von RPC-Methoden können definiert werden.
type |
Überblick |
A simple RPC |
Gibt 1 Antwort für 1 Anfrage zurück |
A server-side streaming RPC |
Gibt mehrere Antworten für eine Anfrage zurück |
A client-side streaming RPC |
Gibt eine Antwort für mehrere Anforderungen zurück |
A bidirectional streaming RPC |
Tauschen Sie mehrere Anfragen und mehrere Antworten in beide Richtungen aus |
Über gRPC-Java
gRPC-Punkte der Java-Implementierung.
Über Netty
Netty, das in gRPC-Java verwendet wird, ist ein Framework, mit dem Sie nicht blockierende E / A-Anwendungen (NIO) in Java erstellen können. (Verwenden Sie kein Java-Servlet.)
Der in gRPC angezeigte Thread, der Ereignisname von NIO und die Verarbeitung von Netty sind wie folgt.
- Erkennt "OP_ACCEPT" im Boss-Thread und registriert das "OP_READ" -Ereignis
- Der Worker-Thread führt die Verarbeitung von "OP_READ" und "OP_WRITE" aus
- Der Worker-Thread liest den Anforderungsinhalt des Clients und erstellt einen ausführbaren Thread, um den Prozess auszuführen, wenn der Aufruf der gRPC-Methode erforderlich ist.
Charakter |
Rolle |
Boss-Thread (ServerBootstrap parentGroup) |
Ich von dem Netzwerk von Netty verwendet/Ein Thread, der O erkennt. Eine wird standardmäßig beim Start generiert. |
Arbeitsthread (ServerBootstrap childGroup) |
Ich habe von Netty benutzt/O Ein Thread, der jedes Ereignis verarbeitet. Der Standardwert istAnzahl der Prozessoren x 2。 |
Executor-Thread |
Ein Thread, der die in gRPC definierten Methoden ausführt. Wird für jeden Methodenaufruf generiert. |
I/O Ereignisname |
Überblick |
OP_ACCEPT |
Verbindung vom Client |
OP_READ |
Netzwerk I./Lesen Sie O. |
OP_WRITE |
Netzwerk I./Exportieren Sie O. |
Ergänzung
Für Netty ist "JJUG CCC 2018 Spring - I-7 (I) First Netty" sehr hilfreich. Wurde.
Über HTTP / 2
Aus Googles Einführung in HTTP / 2 werden die Punkte zum Verständnis von gRPC entnommen.
In HTTP / 1.x wird durch Zeilenumbrüche getrennter Klartext als eine Anforderung (oder Antwort) betrachtet, in HTTP / 2 wird dies jedoch als eine Nachricht ausgedrückt und die Nachricht wird weiter in die folgenden Rahmeneinheiten unterteilt.
--HEADERS-Frame (HTTP / 1.x-Header)
--DATA-Frame (HTTP / 1.x-Body)
Darüber hinaus können Frames mit einer einzigen TCP-Verbindung parallel und in beide Richtungen ausgetauscht werden.
Mit anderen Worten, es ist nicht erforderlich, für jede Anforderung eine TCP-Verbindung herzustellen, und für eine Anforderung können mehrere Antworten und Server-Pushs durchgeführt werden.
Übersicht über die gRPC-Java-Verarbeitung
Von diesem Punkt an wird es so etwas wie "welcher Teil des Codes und welcher Prozess ~" sein, also werde ich vorher zusammenfassen, was ich über die Reihe von Prozessen interpretiert habe.
- Der gRPC-Server erzeugt einen Boss-Thread, der von Netty beim Start verwendet wird
- Der gRPC-Client sendet eine Anfrage an den gRPC-Server
- Wenn der Boss-Thread das "OP_ACCEPT" des Netzwerk-E / A-Ereignisses erkennt, erstellt er einen Netty-Worker-Thread (oder ruft ihn aus dem Pool ab) und delegiert die Verarbeitung von "OP_READ" / "OP_WRITE".
- Der Worker-Thread wird jedes Mal aufgerufen, wenn ein E / A-Ereignis wie der Aufbau einer TCP-Verbindung (3-Wege-Handshake usw.), ein HTTP / 2-HEADERS-Frame, ein DATA-Frame usw. auftritt.
- Nach dem Empfang der für den gRPC-Aufruf erforderlichen HTTP / 2-Nachricht (mehrere Frames) vom Client erzeugt der Worker-Thread einen ausführbaren Thread zum Ausführen der gRPC-Methode.
- Netty ist ein Ereignisschleifenmechanismus, der nur für den Boss-Thread und den Worker-Thread ausgeführt wird. Von dort wird jedoch für jeden gRPC-Methodenaufruf ein Thread erstellt.
- Daher scheinen Netty-Threads nicht blockiert zu sein, selbst wenn die Blockierungsverarbeitung in der gRPC-Methode ausgeführt wird.
- Der Executor-Thread führt die gRPC-Methode aus und registriert die Antwort "OP_WRITE"
- Der Worker-Thread erkennt "OP_WRITE" und gibt eine Antwort an den Client zurück.
- Wenn der Client weiterhin eine Verbindung herstellt, muss keine TCP-Verbindung wiederhergestellt werden, sodass die Prozesse "5. ~ 7." viele Male parallel ausgeführt werden können.
- Trennen Sie TCP, wenn der Client die Verbindung trennt
Code lesen auf der Serverseite
TODO Ich schreibe es für mein eigenes Memo, also werde ich es später hinzufügen und organisieren. (Es war schwierig zu verfolgen, wie verschiedene Threads erstellt und neue Threads mit Rückrufen erstellt wurden. Ich würde mich freuen, wenn Sie mir mitteilen könnten, ob ich einen Fehler gemacht habe.)
Anlaufen
- Führen Sie RouteGuideServer # main aus
- Holen Sie sich eine Instanz von ServerBuilder mit ServerBuilder # forPort
- Überprüfen Sie in ServiceProviders # loadAll ClassLoader, um festzustellen, ob es sich um eine Android-App handelt oder nicht.
- Verwenden Sie für Nicht-Android-Benutzer den Lademechanismus ServiceLoader # im Paket java.util für META-INF / services / io.grpc.ServerProvider. Von .16.1 / netty / src / main / resources / META-INF / services / io.grpc.ServerProvider) bis NettyServerProvider.java Sie erhalten eine Instanz von netty / src / main / java / io / grpc / netty / NettyServerProvider.java)
- Übergeben Sie in NettyServerBuilder # addService die Klasse, die die automatisch generierte xxxGrpc.xxxImplBase erbt
- Hier ist die in Protokollpuffer definierte Methode gebunden.
- AbstractServerImplBuilder.java # L146 Weil der automatisch generierte Code bindService aufgerufen wird
- NettyServerBuilder#build
--Erstellen Sie eine ServerImpl-Instanz und geben Sie sie zurück
- Zu diesem Zeitpunkt erstellt AbstractServerImplBuilder # buildTransportServer eine Instanz von NettyServer, übergibt sie als Argument und wechselt in das Feld InternalServer transportServer von ServerImpl.
- Führen Sie NettyServer # start in ServerImpl # start aus, der von Builder erhalten wurde
--Erstellen Sie NioEventLoopGroup-Instanzen von Eltern (Chef) und Kind (Arbeiter), die für Netty mit NettyServer # allocateSharedGroups erforderlich sind
- Neuer Server-Bootstrap zum Starten des Netty-Servers
- Implementieren Sie ChannelInitializer und übergeben Sie es an ServerBootstrap # childHandler
- io.netty.bootstrap.AbstractBootstrap#bind
--Bounch Boss Thread mit initAndRegister
- Starten Sie das Abhören des Ereignisses "OP_ACCEPT" in io.netty.channel.nio.NioEventLoop.run
--Pass ThreadPoolExecutor mit ThreadFactory (ThreadFactoryBuilder $ ThreadFactory), um einen Thread mit dem Namen
grpc-default-executor-% d
an executor zu erstellen
- Die gRPC-Methode erstellt und führt von hier aus bei jedem Aufruf einen Thread aus.
- Wenn Sie NettyServer starten, verwendet der Hauptthread Runtime.getRuntime (). AddShutdownHook (), um den Prozess am Ende zu definieren.
- Außerdem ruft der Hauptthread RouteGuideServer.blockUntilShutdown auf, um in den Status WAIT zu wechseln.
Anfrage erhalten
- Wenn der Worker-Thread die Aufrufanforderung für die gRPC-Methode akzeptiert, NettyServerHandler ist ServerImpl $ ServerTransportListenerImpl # streamCreated /io/grpc/netty/NettyServerHandler.java#L437)
--ServerImpl $ ServerTransportListenerImpl # streamCreated [Übergeben Sie eine Instanz von StreamCreated, die ContextRunnable erbt, an SerializingExecutor, der ThreadPoolExecutor umschließt](https://github.com/grpc/grpc-java/blob/v1.16.1/core/src/ main / java / io / grpc / internal / ServerImpl.java # L495)
--Diese Ausführung führt die folgende ausführbare Aufgabe mit dem Threadnamen "grpc-default-executor-% d" in ThreadFactoryBuilder $ ThreadFactory aus.
--StreamCreated # run-> StreamCreated # runInContext wird ausgeführt
--Und ServerImpl # JumpToApplicationThreadServerStreamListener $ MessagesAvailable # runInContext wird aufgerufen
- Außerdem wird die Methode ServerImpl \ $ JumpToApplicationThreadServerStreamListener $ 1HalfClosed # runInContextHalfClosedgRPC ausgeführt.
--Wenn das in gRPC definierte
getFeature
aufgerufen wird, sieht es so aus
getFeature:130, RouteGuideServer$RouteGuideService (grpc.routeguide)
invoke:462, RouteGuideGrpc$MethodHandlers (grpc.routeguide)
onHalfClose:171, ServerCalls$UnaryServerCallHandler$UnaryServerCallListener (io.grpc.stub)
halfClosed:283, ServerCallImpl$ServerStreamListenerImpl (io.grpc.internal)
runInContext:710, ServerImpl$JumpToApplicationThreadServerStreamListener$1HalfClosed (io.grpc.internal)
run:37, ContextRunnable (io.grpc.internal)
run:123, SerializingExecutor (io.grpc.internal)
runWorker:1149, ThreadPoolExecutor (java.util.concurrent)
run:624, ThreadPoolExecutor$Worker (java.util.concurrent)
run:748, Thread (java.lang)