gRPC-之gRPC的高级应用

789 阅读11分钟

跟孙哥学java

孙哥主页

1. 拦截器  一元拦截器
2. Stream Tracer [监听流]  流拦截器
3. Retry Policy 客户端重试
4. NameResolover
5. 负载均衡  (pick-first ,轮询)
6. gRPC与微服务的整合
   序列化(protobuf) Dubbo
   grpc             Dubbo
   grpc             GateWay
   grpc             JWT
   grpc             Nacos 2.0
   grpc             OpenFeign

拦截器

gRPC(gRPC Remote Procedure Calls)是一个开源的远程过程调用(RPC)框架,它使用协议缓冲区(Protocol Buffers)作为接口描述语言。gRPC拦截器是一种机制,允许你在 gRPC 服务端和客户端上定义和使用拦截器函数。拦截器可以在 RPC 调用的不同阶段执行自定义的逻辑,例如在请求到达服务端之前或响应发送到客户端之前。 在 gRPC 中,拦截器用于执行预处理和后处理逻辑,类似于中间件。拦截器是通过实现 gRPC 提供的 Interceptor 接口来创建的。在 gRPC 中,有两种类型的拦截器:客户端拦截器和服务端拦截器。 作用: 鉴权,数据校验,限流..... 减少代码冗余,有利于维护

常见的拦截器
  web         Filter
  springmvc   Interceptor
  SS 					Filter
  Netty   		handler
gRPC的拦截器
1. 一元请求的  拦截器
   客户端 [请求,响应]
   服务端 [请求,响应]
3. 流式请求的  拦截器
   客户端 [请求,响应]
   服务端 [请求,响应]

一元拦截器

简单客户端拦截器的开发

//1 开发拦截器 
@Slf4j
public class CustomClientInterceptor implements ClientInterceptor {
    @Override
    public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
        log.debug("这是一个拦截启动的处理 ,统一的做了一些操作 ....");
        return next.newCall(method, callOptions);
    }
}


//2 在客户端进行设置 
 ManagedChannel managedChannel = ManagedChannelBuilder.forAddress("localhost", 9000)
                .usePlaintext()
                .intercept(new CustomClientInterceptor())
                .build();
细节:
  usePlaintext()
  intercept()
  调用的先后顺序没有必须关联,谁先谁后都行。
  • 简单客户端的的问题
1. 只能拦截请求,不能拦截响应。
2. 即使拦截请求操作,但是他的力度过于宽泛,不精准。
   1. 开始阶段 2. 设置消息数量 3. 发送数据阶段 4. 半连接阶段
  • 复杂客户端拦截器
1. 拦截请求,拦截响应
2. 分阶段的进行 请求 响应的拦截

渗透到  ClientCall[方法]
实际上是对ClientCall包装,  使用装饰器设计模式  典型的使用场景  
public class CustomClientInterceptor implements ClientInterceptor {
    @Override
    public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
        log.debug("这是一个拦截启动的处理 ,统一的做了一些操作 ....");
        /*
           如果我们需要用复杂客户端拦截器 ,就需要对原始的ClientCall进行包装
           那么这个时候,就不能反悔原始ClientCall对象,
           应该返回 包装的ClientCall ---> CustomForwardingClientClass
         */
        //return next.newCall(method, callOptions);
        return new CustomForwardingClientClass<>(next.newCall(method, callOptions));
    }
}

/*
   这个类型 适用于控制 拦截 请求发送各个环节
 */
@Slf4j
class CustomForwardingClientClass<ReqT, RespT> extends ClientInterceptors.CheckedForwardingClientCall<ReqT, RespT> {

    protected CustomForwardingClientClass(ClientCall<ReqT, RespT> delegate) {
        super(delegate);
    }

    @Override
    //开始调用
    // 目的 看一个这个RPC请求是不是可以被发起。
    protected void checkedStart(Listener<RespT> responseListener, Metadata headers) throws Exception {
        log.debug("发送请求数据之前的检查.....");
        //真正的去发起grpc的请求
        // 是否真正发送grpc的请求,取决这个start方法的调用
        //delegate().start(responseListener, headers);
        delegate().start(new CustomCallListener<>(responseListener), headers);
    }

    @Override
    //指定发送消息的数量
    public void request(int numMessages) {
        //添加一些功能
        log.debug("request 方法被调用 ....");
        super.request(numMessages);
    }

    @Override
    //发送消息 缓冲区
    public void sendMessage(ReqT message) {
        log.debug("sendMessage 方法被调用... {} ", message);
        super.sendMessage(message);
    }

    @Override
    //开启半连接 请求消息无法发送,但是可以接受响应的消息
    public void halfClose() {
        log.debug("halfClose 方法被调用... 开启了半连接");
        super.halfClose();
    }
}

/*
   用于监听响应,并对响应进行拦截
 */
@Slf4j
class CustomCallListener<RespT> extends ForwardingClientCallListener.SimpleForwardingClientCallListener<RespT> {
    protected CustomCallListener(ClientCall.Listener<RespT> delegate) {
        super(delegate);
    }

    @Override
    public void onHeaders(Metadata headers) {
        log.info("响应头信息 回来了......");
        super.onHeaders(headers);
    }

    @Override
    public void onMessage(RespT message) {
        log.info("响应的数据 回来了.....{} ", message);
        super.onMessage(message);
    }
}


onHeaders这个方法 会在onNext调用后 监听到数据
onMessage这个方法 会在onCompleted调用后 监听到数据
  • 简单服务端拦截器
@Slf4j
public class CustomServerInterceptor implements ServerInterceptor {
    @Override
    public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> call, Metadata headers, ServerCallHandler<ReqT, RespT> next) {
        //在服务器端 拦截请求操作的功能 写在这个方法中
        log.debug("服务器端拦截器生效.....");
        return next.startCall(call, headers);
    }
}


public class GrpcServer {
    public static void main(String[] args) throws InterruptedException, IOException {
        ServerBuilder<?> serverBuilder = ServerBuilder.forPort(9000);
        serverBuilder.addService(new HelloServiceImpl());
        serverBuilder.intercept(new CustomServerInterceptor());
        Server server = serverBuilder.build();

        server.start();
        server.awaitTermination();
    }
}
  • 简单服务端拦截器问题
1. 拦截请求发送过来的数据,无法处理响应的数据
2. 拦截力度过于宽泛
  • 复杂服务端拦截器
  1. 拦截请求的数据,拦截响应的数据
  2. 粒度细致
@Slf4j
public class CustomServerInterceptor implements ServerInterceptor {
    @Override
    public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> call, Metadata headers, ServerCallHandler<ReqT, RespT> next) {
        //在服务器端 拦截请求操作的功能 写在这个方法中
        log.debug("服务器端拦截器生效.....");
        //默认返回的ServerCall.Listener仅仅能够完成请求数据的监听,单没有拦截功能
        //所以要做扩展,采用包装器设计模式。
        //return next.startCall(call, headers);
        return new CustomServerCallListener<>(next.startCall(call, headers));
    }
}

@Slf4j
class CustomServerCallListener<ReqT> extends ForwardingServerCallListener.SimpleForwardingServerCallListener<ReqT> {
    protected CustomServerCallListener(ServerCall.Listener<ReqT> delegate) {
        super(delegate);
    }

    @Override
    //准备接受请求数据
    public void onReady() {
        log.debug("onRead Method Invoke....");
        super.onReady();
    }

    @Override
    public void onMessage(ReqT message) {
        log.debug("接受到了 请求提交的数据  {} ", message);
        super.onMessage(message);
    }

    @Override
    public void onHalfClose() {
        log.debug("监听到了 半连接...");
        super.onHalfClose();
    }

    @Override
    public void onComplete() {
        log.debug("服务端 onCompleted()...");
        super.onComplete();
    }

    @Override
    public void onCancel() {
        log.debug("出现异常后 会调用这个方法... 关闭资源的操作");
        super.onCancel();
    }
}


目的:通过自定义的ServerCall 包装原始的ServerCall 增加对于响应拦截的功能
@Slf4j
class CustomServerCall<ReqT, RespT> extends ForwardingServerCall.SimpleForwardingServerCall<ReqT, RespT> {

    protected CustomServerCall(ServerCall<ReqT, RespT> delegate) {
        super(delegate);
    }

    @Override
    //指定发送消息的数量 【响应消息】
    public void request(int numMessages) {
        log.debug("response 指定消息的数量 【request】");
        super.request(numMessages);
    }

    @Override
    //设置响应头
    public void sendHeaders(Metadata headers) {
        log.debug("response 设置响应头 【sendHeaders】");
        super.sendHeaders(headers);
    }

    @Override
    //响应数据
    public void sendMessage(RespT message) {
        log.debug("response 响应数据  【send Message 】 {} ", message);
        super.sendMessage(message);
    }

    @Override
    //关闭连接
    public void close(Status status, Metadata trailers) {
        log.debug("respnse 关闭连接 【close】");
        super.close(status, trailers);
    }
}

目的:就是把自定义的ServerCall与gRpc服务端进行整合
@Slf4j
public class CustomServerInterceptor implements ServerInterceptor {
    @Override
    public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> call, Metadata headers, ServerCallHandler<ReqT, RespT> next) {
        //在服务器端 拦截请求操作的功能 写在这个方法中
        log.debug("服务器端拦截器生效.....");
      
        //1. 包装ServerCall 处理服务端响应拦截
        CustomServerCall<ReqT,RespT> reqTRespTCustomServerCall = new CustomServerCall<>(call);
        //2. 包装Listener   处理服务端请求拦截
        CustomServerCallListener<ReqT> reqTCustomServerCallListener = new CustomServerCallListener<>(next.startCall(reqTRespTCustomServerCall, headers));
        return reqTCustomServerCallListener;
    }
}

流式拦截器

主要应用于 gRPC除了一元RPC之外的其他形式RPC操作的拦截工作

  1. 服务端流式RPC
  2. 客户端流式RPC
  3. 双向流RPC
一元通信方式和流式通信方式区别:
1. 对比一元通信方式,流式通信方式的特点是消息多,而不是一次通信,是分批的。这个时候一元拦截器就无法
   细粒度的拦截特定信息
  • 客户端流式拦截器的开发
1. 拦截请求  拦截响应
2. 开发思路
   ClientStreamTracer [拦截请求  拦截响应]
   ClientStreamTracerFactory
      目的:就是创建ClientStreamTracer
@Slf4j
public class CustomerClientInterceptor implements ClientInterceptor {
    @Override
    public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
        log.debug("执行客户端拦截器...");


        //把自己开发的ClientStreamTracerFactory融入到gRPC体系
        callOptions = callOptions.withStreamTracerFactory(new CustomClientStreamTracerFactory<>());
        return next.newCall(method, callOptions);
    }
}

class CustomClientStreamTracerFactory<ReqT, RespT> extends ClientStreamTracer.Factory {
    @Override
    public ClientStreamTracer newClientStreamTracer(ClientStreamTracer.StreamInfo info, Metadata headers) {
        return new CustomClientStreamTracer<>();
    }
}


/*
 作用: 用于客户端流式拦截:拦截请求 拦截响应
 */
@Slf4j
class CustomClientStreamTracer<ReqT, RespT> extends ClientStreamTracer {
    //outbound 对于请求相关操作的拦截

    @Override
    //用于输出响应头
    public void outboundHeaders() {
        log.debug("client: 用于输出请求头.....");
        super.outboundHeaders();
    }

    @Override
    //设置消息编号
    public void outboundMessage(int seqNo) {
        log.debug("client: 设置流消息的编号 {} ", seqNo);
        super.outboundMessage(seqNo);
    }

    @Override
    public void outboundUncompressedSize(long bytes) {
        log.debug("client: 获得未压缩消息的大小 {} ", bytes);
        super.outboundUncompressedSize(bytes);
    }

    @Override
    //用于获得 输出消息的大小
    public void outboundWireSize(long bytes) {
        log.debug("client: 用于获得 输出消息的大小 {} ", bytes);
        super.outboundWireSize(bytes);
    }

    @Override
    //拦截消息发送
    public void outboundMessageSent(int seqNo, long optionalWireSize, long optionalUncompressedSize) {
        log.debug("client: 监控请求操作 outboundMessageSent {} ", seqNo);
        super.outboundMessageSent(seqNo, optionalWireSize, optionalUncompressedSize);
    }


    //inbound  对于相应相关操作的拦截
    @Override
    public void inboundHeaders() {
        log.debug("用于获得响应头....");
        super.inboundHeaders();
    }

    @Override
    public void inboundMessage(int seqNo) {
        log.debug("获得响应消息的编号...{} ",seqNo);
        super.inboundMessage(seqNo);
    }

    @Override
    public void inboundWireSize(long bytes) {
        log.debug("获得响应消息的大小...{} ",bytes);
        super.inboundWireSize(bytes);
    }

    @Override
    public void inboundMessageRead(int seqNo, long optionalWireSize, long optionalUncompressedSize) {
        log.debug("集中获得消息的编号 ,大小 ,未压缩大小 {} {} {}", seqNo, optionalWireSize, optionalUncompressedSize);
        super.inboundMessageRead(seqNo, optionalWireSize, optionalUncompressedSize);
    }

    @Override
    public void inboundUncompressedSize(long bytes) {
        log.debug("获得响应消息未压缩大小 {} ",bytes);
        super.inboundUncompressedSize(bytes);
    }

    @Override
    public void inboundTrailers(Metadata trailers) {
        log.debug("响应结束..");
        super.inboundTrailers(trailers);
    }
}
  • 服务端流式拦截器的开发
1. 拦截请求  拦截响应
2. 开发思路
   ServerStreamTracer [拦截请求  拦截响应]
   ServerStreamTracerFactory
      目的:就是创建ClientStreamTracer
public class CustomServerStreamFactory extends ServerStreamTracer.Factory {
    @Override
    public ServerStreamTracer newServerStreamTracer(String fullMethodName, Metadata headers) {
        return new CustomServerStreamTracer();
    }
}

@Slf4j
class CustomServerStreamTracer extends ServerStreamTracer {
    //inbound 拦截请求
    @Override
    public void inboundMessage(int seqNo) {
        super.inboundMessage(seqNo);
    }

    @Override
    public void inboundWireSize(long bytes) {
        super.inboundWireSize(bytes);
    }

    @Override
    public void inboundMessageRead(int seqNo, long optionalWireSize, long optionalUncompressedSize) {
        log.debug("server: 获得client发送的请求消息 ...{} {} {}", seqNo, optionalWireSize, optionalUncompressedSize);
        super.inboundMessageRead(seqNo, optionalWireSize, optionalUncompressedSize);
    }

    @Override
    public void inboundUncompressedSize(long bytes) {
        super.inboundUncompressedSize(bytes);
    }

    //outbound 拦截请求

    @Override
    public void outboundMessage(int seqNo) {
        super.outboundMessage(seqNo);
    }


    @Override
    public void outboundMessageSent(int seqNo, long optionalWireSize, long optionalUncompressedSize) {
        log.debug("server: 响应数据的拦截 ...{} {} {}", seqNo, optionalWireSize, optionalUncompressedSize);
        super.outboundMessageSent(seqNo, optionalWireSize, optionalUncompressedSize);
    }

    @Override
    public void outboundWireSize(long bytes) {
        super.outboundWireSize(bytes);
    }

    @Override
    public void outboundUncompressedSize(long bytes) {
        super.outboundUncompressedSize(bytes);
    }
}

serverBuilder.addStreamTracerFactory(new CustomServerStreamFactory());

客户端重试

客户端重试保证了系统可靠性。
在RGPC调用过程中看,因为网络延迟 或者抖动,可能操作客户端暂时链接不上服务端的情况,
如果出现了此类情况的话。那么可以让client重试连接。
尽可能的避免,因为网络问题,影响通信。从而保证了系统的可靠性。

模拟异常操作: 在服务端,模拟一个网络问题

   @Override
   public void hello(HelloProto.HelloRequest request, StreamObserver<HelloProto.HelloResponse> responseObserver) {
      Random random=new Random();
      if(random.nextInt(100)>30){
         log.debug("接收到client的请求,返回UNAVALIABLE");
         responseObserver.onError(Status.UNAVAILABLE.withDescription("for retry").asRuntimeException());
      }else {
         System.out.println("接收到客户端的请求="+request.getName());
         responseObserver.onNext(HelloProto.HelloResponse.newBuilder().setResult("ok").build());
         responseObserver.onCompleted();
      }

在客户端,编写重试配置文件

   {
  "methodConfig": [
    {
      "name": [
        {
          "service": "com.suns.HelloService", //服务的名字 包名 package
          "method": "hello"  //服务方法名字 
        }
      ],
      "retryPolicy": {
        "maxAttempts": "3",         //重试的次数 
        "initialBackoff": "0.5s", //初始重试的延迟时间
        "maxBackof": "30s",       //最大重试的延迟时间
        "backoffMultiplier": "2",   //退避指数 每一次重试的时间间隔,不是不固定,0.5 2.5 4.5 6.5...
        "retryableStatusCodes": [ //只有当接受到了这个数组中描述的异常,才会发起重试
          "UNAVAILABLE"
        ]
      }
    }
  ]
}

注意 方法的service的包名是 package com.suns;

命名解析 NameResover

  1. 什么是命名解析??

    本质上就是我们在SOA或者微服务中,叫做注册中心

  2. 为什么需要注册中心?

rpc集群的环境下,通过注册中心,统一管理rpc集群[负载均衡,监控监测]

  1. 目前开发中,常见的注册中心

Dubbo zookeeper SpringCloud eureka nacos consul etcd ...

  1. gRPC 名字解析 注册中心

    模式gRPC是通过DNS 做注册中心。 用户也可以自定义注册中心,用户扩展。

  2. gRPC注册中心的扩展

Zookeeper、Consul和Etcd都是分布式协调服务,用于维护配置信息、管理服务和提供分布式同步等。它们之间存在一些共同点和区别。 相同点:

  1. 都是分布式协调服务:Zookeeper、Consul和Etcd都是为分布式系统提供协调服务,帮助开发者构建更复杂的分布式系统。
  2. 维护配置信息:它们都用于存储和管理配置信息,让开发者可以方便地管理和修改这些配置,而无需修改代码。
  3. 管理服务:它们都可以用于管理服务的发现和注册,使得分布式系统中的服务可以互相发现和通信。
  4. 提供分布式同步:它们都提供了分布式同步功能,使得开发者可以在分布式系统中实现一致性的操作。

区别:

  1. 数据模型:Zookeeper和Consul使用键值对模型来存储数据,而Etcd使用基于Raft协议的 kv 存储。
  2. 算法:zk 使用Paxos ,etcd和consul使用Raft(主流)
  3. 多数据中心:consul支持
  4. web管理: zk和consul支持,etcd不支持
  5. 性能:在性能方面,Etcd通常比Zookeeper和Consul更高。这是因为Etcd使用了更高效的数据存储和查询算法。
  6. 用途:Zookeeper主要用于分布式系统中,而Consul更适合于服务发现和注册。Etcd则更多地被用于云平台中,提供分布式锁等重要功能。

Consul

:::info Consul是HashiCorp公司推出的开源工具,用于实现分布式系统的服务发现与配置。Consul是分布式的、高可用的、可横向扩展的。它具备以下特性:

  1. 服务发现:Consul通过DNS或者HTTP接口使服务注册和服务发现变的很容易,一些外部服务,例如saas提供的也可以一样注册。
  2. 健康检查:健康检测使Consul可以快速的告警在集群中的操作。和服务发现的集成,可以防止服务转发到故障的服务上面。
  3. 键/值存储:一个用来存储动态配置的系统。提供简单的HTTP接口,可以在任何地方操作。
  4. 多数据中心:无需复杂的配置,即可支持任意数量的区域。
  5. Consul使用Go语言编写,因此具有天然可移植性(支持Linux、windows和Mac OS X);安装包仅包含一个可执行文件,方便部署,与Docker等轻量级容器可无缝配合。 ::: Consul 作用: 1.注册中心2.配置中心 好处:1.安装简单--->自带web管理界面 2. 使用方便,比如 健康检查 3. 一致性算法Raft

Consul 安装 Consul 安装 终端【cmd】consul agent -dev

image.png