HTTP 和 RPC
在微服務體系結構中,獨立部署在各個機器或容器上的服務之間,如何進行有效的通信,是一個很重要的問題,現在常用的主要是 RESTful HTTP 和 RPC
HTTP 的優點
- 通用性強,基本上所有框架,所有語言都支持 HTTP
- 可讀性高,URL 對資源的定義,Action 對操作的定義,Payload 的定義都比較清晰易懂
- 可以通過各種防火牆、網關
HTTP 的缺點
- HTTP 協議的效率比較低(畢竟是網絡第 7 層協議)
- HTTP 協議的有效信息占比小
- 客戶度編寫調用 HTTP 請求的代碼並不易用
RPC(Remote Procedure Call,遠程過程調用)使客戶端向服務端發請求就像調用本地函數一樣
比如客戶端調用
String message = service.sayHi("dubbo");
服務端會有相應的函數被執行並返回結果給客戶端
public String sayHi(String name) {
return "hi, " + name;
}
RPC 的優點
- 使用 TCP 或 HTTP2.0 協議,通信效率高,有效信息占比大
- 客戶端調用 RPC 請求就像調用本地函數一樣,代碼比較簡單,容易使用
RPC 的缺點
- 可讀性沒有 HTTP 強
- 缺少通用性,和 HTTP 是統一的標准不同,RPC 框架的實現各不相同,有的僅支持單語言,有的支持跨語言,有的支持多種序列化協議,有的僅有一種固定的序列化協議,有的支持管理中心、服務發現、負載均衡、熔斷降級等功能,有的不支持
- 需要和特定的框架綁定使用,比較緊耦合
- 不同的網關對 RPC 的支持可能會不夠(對 TCP、HTTP2.0 的支持)
可以看到,HTTP 和 RPC 各有千秋,通常暴露給外部用戶的都是 HTTP,而當系統內部的微服務特別多,微服務之間的通信量特別大的時候,為了提高性能簡化代碼可以使用 RPC,但是如果微服務沒有拆的很細,或是對性能要求不是很高,內部通信也可以使用 HTTP
RPC 技術
通常包含以下技術實現
- 動態代理
客戶端實際上只定義了接口,具體的實現在服務端,所以需要有動態代理,當客戶端調用函數的時候,將信息傳遞到服務端,在服務端調用真正的實現,接受它的返回給客戶端
- 序列化放序列化
客戶端需要將數據序列化,在服務端要做反序列化
- 通信協議
需要高效的通信協議以提高性能
- 異常處理
出現網絡故障、服務端錯誤等各種異常的時候要怎么處理
RPC 常用框架
- dubbo
阿里巴巴開發的 RPC 框架,支持 Java,2012 年開源,2014 年停止維護,2017 年又重新維護,后來捐給了 Apache
dubbo 架構如下圖
https://dubbo.apache.org/zh/docs/v2.7/user/preface/architecture/
Provider 向 Registry 注冊服務,Consumer 通過 Registry 獲取 Provider 服務的信息
服務注冊中心,服務提供者,服務消費者三者之間均為長連接
Consumer 向 Provider (可以配多個 Provider 實例)發請求時,可以自動實現負載均衡等算法
監控中心負責統計各服務調用次數,調用時間等,統計先在內存匯總后每分鍾一次發送到監控中心服務器,並以報表展示
注冊中心和監控中心全部宕機,不影響已運行的提供者和消費者,消費者在本地緩存了提供者列表
注冊中心和監控中心都是可選的,服務消費者可以直連服務提供者
健壯性和伸縮性
底層協議可以是:dubbo、rest、http、hessian、redis、thrift、gRPC、memcached、rmi、webservice,默認是 dubbo(采用單一長連接和 NIO 異步通訊,適合於小數據量大並發的服務調用,不適合傳送大數據量的服務)
https://dubbo.apache.org/zh/docs/v2.7/user/references/xml/dubbo-registry/
https://dubbo.apache.org/zh/docs/v2.7/user/references/protocol/
https://dubbo.apache.org/zh/docs/v2.7/user/perf-test/
注冊中心可以是:dubbo, nacos, multicast, zookeeper, redis, consul, sofa, etcd,推薦使用 zookeeper
https://dubbo.apache.org/zh/docs/v2.7/user/references/xml/dubbo-registry/
2.7.5 引入了基於 AK/SK 機制的認證鑒權機制,並且引入了鑒權服務中心,主要原理是消費端在請求需要鑒權的服務時,會通過 SK、請求元數據、時間戳、參數等信息來生成對應的請求簽名,通過 Dubbo 的 Attahcment 機制攜帶到對端進行驗簽,驗簽通過才進行業務邏輯處理
dubbo 可以直接和 SpringBoot 集成
各種用法示例
https://dubbo.apache.org/zh/docs/v2.7/user/examples/
- gRPC
Google 開發的 RPC 框架
支持多種語言,並且 Consumer 和 Provider 之間可以跨語言
使用 proto3 定義接口后可以生成不同語言的源文件
沒有注冊服務中心、負載均衡、熔斷降級等功能
默認使用 protocol buffers 作為序列化反序列化機制,protocol buffers 的壓縮速度快,壓縮率高
支持 SSL/TLS、OAuth 2.0 等認證授權協議
使用 HTTP2.0 作為通信協議,HTTP2.0 采用新的數據格式、新的 Header 壓縮算法、服務端推送、鏈接共享等機制,性能大幅度提升,但現在支持的組件可能不多,比如如果要通過 Nginx 它可能不認 HTTP2.0 只把它當做 TCP 消息對待
- Motan
微博開發的 RPC 框架,用 Java 實現,可以和 SpringBoot 集成
https://github.com/weibocom/motan
無需多少額外代碼即可實現
支持 Consul、Zookeeper 作為服務發現中心
支持負載均衡
為高負載場景做了優化
支持同步調用和異步調用
支持 Java、Go、PHP 等多語言
- rpcx
微博開發的,用 Go 語言開發,參考了阿里巴巴的 dubbo 和微博的 Motan
https://github.com/smallnest/rpcx
https://blog.rpcx.io/posts/why-did-i-develop-rpcx/
https://doc.rpcx.io/
性能很好,貌似比 dubbo、grpc、motan 都要好
支持原生 Go 函數,不需要定義 proto 文件
支持 TCP、HTTP、KCP、QUIC 等傳輸協議
支持 JSON、Protobuf、MessagePack 數據編碼協議
服務發現,支持 P2P、zookeeper、etcd、consul、mDNS 等
容錯(Fault tolerance):支持 Failover(切換)、Failfast(快速失敗)、Failtry(重試)
支持負載均衡
支持認證授權
支持心跳檢測
貌似還支持跨語言
rpcx uses a binary protocol and platform-independent, which means you can develop services in other languages such as Java, python, nodejs, and you can use other prorgramming languages to invoke services developed in Go.
- Thrift
由 Facebook 開發的 RPC 框架,后來捐給了 Apache
https://github.com/apache/thrift
是一個輕量級的、跨語言的、點到點的 RPC 框架
定義好接口后,代碼生成器可以生成不同語言的代碼
從上圖可以看到,Thrift 支持很多種語言、傳輸協議、數據協議、應用模式
Thrift 不支持服務發現、負載均衡、熔斷降級等功能
dubbo 例子
定義有 3 個模塊的項目
<groupId>com.example</groupId>
<artifactId>dubbo</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>dubbo-example-interface</module>
<module>dubbo-example-consumer</module>
<module>dubbo-example-provider</module>
</modules>
interface 只定義了一個接口
package com.example.dubbo.interfaces;
public interface GreetingService {
String sayHi(String name);
}
maven install 編譯安裝 interface
provider 實現了接口
<parent>
<groupId>com.example</groupId>
<artifactId>dubbo</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>dubbo-example-provider</artifactId>
<packaging>jar</packaging>
<description>Demo project for dubbo</description>
<dependencies>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>2.7.8</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-dependencies-zookeeper</artifactId>
<version>2.7.8</version>
<type>pom</type>
</dependency>
<dependency>
<groupId>com.example</groupId>
<artifactId>dubbo-example-interface</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
public class GreetingServiceImpl implements GreetingService {
@Override
public String sayHi(String name) {
System.out.println("receive msg " + name);
return "hi, " + name;
}
}
public class Provider {
private static String zookeeperHost = System.getProperty("zookeeper.address", "localhost");
public static void main(String[] args) throws Exception {
ServiceConfig<GreetingService> service = new ServiceConfig<>();
service.setApplication(new ApplicationConfig("first-dubbo-provider"));
service.setRegistry(new RegistryConfig("zookeeper://" + zookeeperHost + ":2181"));
service.setInterface(GreetingService.class);
service.setRef(new GreetingServiceImpl());
service.export();
System.out.println("dubbo service started");
new CountDownLatch(1).await();
}
}
consumer 調用接口
<parent>
<groupId>com.example</groupId>
<artifactId>dubbo</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>dubbo-example-consumer</artifactId>
<packaging>jar</packaging>
<description>Demo project for dubbo</description>
<dependencies>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>2.7.8</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-dependencies-zookeeper</artifactId>
<version>2.7.8</version>
<type>pom</type>
</dependency>
<dependency>
<groupId>com.example</groupId>
<artifactId>dubbo-example-interface</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
public class Consumer {
private static String zookeeperHost = System.getProperty("zookeeper.address", "localhost");
public static void main(String[] args) {
ReferenceConfig<GreetingService> reference = new ReferenceConfig<>();
reference.setApplication(new ApplicationConfig("first-dubbo-consumer"));
reference.setRegistry(new RegistryConfig("zookeeper://" + zookeeperHost + ":2181"));
reference.setInterface(GreetingService.class);
GreetingService service = reference.get();
String message = service.sayHi("dubbo");
System.out.println(message);
}
}
運行后看到 consumer 打出 hi, dubbo
gRPC 例子
定義有 4 個模塊的項目
<modules>
<module>grpc-example-proto</module>
<module>grpc-example-interfaces</module>
<module>grpc-example-client</module>
<module>grpc-example-server</module>
</modules>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
<version>1.35.0</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>1.35.0</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>1.35.0</version>
</dependency>
<dependency> <!-- necessary for Java 9+ -->
<groupId>org.apache.tomcat</groupId>
<artifactId>annotations-api</artifactId>
<version>6.0.53</version>
<scope>provided</scope>
</dependency>
</dependencies>
</dependencyManagement>
proto 定義接口並用於生產代碼
<parent>
<groupId>com.example</groupId>
<artifactId>grpc</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>grpc-example-proto</artifactId>
<packaging>jar</packaging>
<description>Demo project for grpc</description>
<dependencies>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
</dependency>
</dependencies>
<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.6.2</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.6.1</version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:3.12.0:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.35.0:exe:${os.detected.classifier}</pluginArtifact>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
在 src/main/proto 下創建 GreetingService.proto 文件,定義接口
syntax = "proto3";
option java_multiple_files = true;
option java_package = "com.example.grpc.interfaces";
option java_outer_classname = "GreetingServiceProto";
option objc_class_prefix = "HLW";
package GreetingService;
service GreetingService {
rpc SayHi (GreetingRequest) returns (GreetingReply) {}
}
message GreetingRequest {
string name = 1;
}
message GreetingReply {
string message = 1;
}
然后執行 maven install 會生產源代碼
將 target/generated-sources/protobuf 下面的源文件都考到 interface 項目下面
interface 的 pom 要引用 grpc
<parent>
<groupId>com.example</groupId>
<artifactId>grpc</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>grpc-example-interfaces</artifactId>
<packaging>jar</packaging>
<description>Demo project for grpc</description>
<dependencies>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
maven install 編譯安裝 interface
再編寫 server 實現接口
<parent>
<groupId>com.example</groupId>
<artifactId>grpc</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>grpc-example-server</artifactId>
<packaging>jar</packaging>
<description>Demo project for grpc</description>
<dependencies>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
</dependency>
<dependency>
<groupId>com.example</groupId>
<artifactId>grpc-example-interfaces</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
public class GreetingServiceImpl extends GreetingServiceGrpc.GreetingServiceImplBase {
@Override
public void sayHi(GreetingRequest req, StreamObserver<GreetingReply> responseObserver){
GreetingReply reply = GreetingReply.newBuilder().setMessage(("Hi " + req.getName())).build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
}
}
public class GrpcServer {
private static final Logger logger = Logger.getLogger(GrpcServer.class.getName());
private int port = 50051;
private Server server;
private void start() throws IOException {
server = ServerBuilder.forPort(port)
.addService(new GreetingServiceImpl())
.build()
.start();
logger.info("Server started, listening on "+ port);
Runtime.getRuntime().addShutdownHook(new Thread(){
@Override
public void run(){
System.err.println("*** shutting down gRPC server since JVM is shutting down");
GrpcServer.this.stop();
System.err.println("*** server shut down");
}
});
}
private void stop() {
if (server != null){
server.shutdown();
}
}
private void blockUntilShutdown() throws InterruptedException {
if (server != null){
server.awaitTermination();
}
}
public static void main(String[] args) throws IOException, InterruptedException {
final GrpcServer server = new GrpcServer();
server.start();
server.blockUntilShutdown();
}
}
再編寫 client 調用接口
<parent>
<groupId>com.example</groupId>
<artifactId>grpc</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>grpc-example-client</artifactId>
<packaging>jar</packaging>
<description>Demo project for grpc</description>
<dependencies>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
</dependency>
<dependency>
<groupId>com.example</groupId>
<artifactId>grpc-example-interfaces</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
public class GrpcClient {
private final ManagedChannel channel;
private final GreetingServiceGrpc.GreetingServiceBlockingStub blockingStub;
private static final Logger logger = Logger.getLogger(GrpcClient.class.getName());
public GrpcClient(String host, int port){
channel = ManagedChannelBuilder.forAddress(host, port)
.usePlaintext()
.build();
blockingStub = GreetingServiceGrpc.newBlockingStub(channel);
}
public void shutdown() throws InterruptedException {
channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
}
public void greet(String name){
GreetingRequest request = GreetingRequest.newBuilder().setName(name).build();
GreetingReply response;
try{
response = blockingStub.sayHi(request);
} catch (StatusRuntimeException e) {
logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus());
return;
}
logger.info("Greeting: " + response.getMessage());
}
public static void main(String[] args) throws InterruptedException {
GrpcClient client = new GrpcClient("localhost", 50051);
try{
String user = "GRPC";
if (args.length > 0){
user = args[0];
}
client.greet(user);
}finally {
client.shutdown();
}
}
}