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();
}
}
}