zoukankan      html  css  js  c++  java
  • gRPC CodeFirst

    前言:

    gRPC默认是ProtoFirst的,即先写 proto文件,再生成代码,需要人工维护proto,生成的代码也不友好,所以出现了gRPC CodeFirst,下面来说说我们是怎么实现gRPC CodeFirst

    目录:

    实现和WCF一样的CodeFirst

    (1). 实现gRPC CodeFirst,  简化WCF一定要抽取接口的问题

    (2). 通过代码生成proto和注释,给第三方语言使用

    (3). 实现gRPC DashBoard,用于Http远程调用和管理

    (4). 实现服务注册与发现

    (5). 实现分布式日志跟踪

    (6). 日志监控等等

    我们是怎么实现gRPC CodeFirst

     

    1.要实现CodeFirst先要了解ProtoFirst生成的代码,下面我截了部分生成代码

    (1).关键是这个BindService,用于把实现的gRPC方法绑定到ServerServiceDefinition

    复制代码
    public static partial class Greeter
    {
        static readonly string __ServiceName = "helloworld.Greeter";
    
        static readonly grpc::Marshaller<global::Helloworld.HelloRequest> __Marshaller_helloworld_HelloRequest = grpc::Marshallers.Create((arg) => global::Google.Protobuf.MessageExtensions.ToByteArray(arg), global::Helloworld.HelloRequest.Parser.ParseFrom);
        static readonly grpc::Marshaller<global::Helloworld.HelloReply> __Marshaller_helloworld_HelloReply = grpc::Marshallers.Create((arg) => global::Google.Protobuf.MessageExtensions.ToByteArray(arg), global::Helloworld.HelloReply.Parser.ParseFrom);
    
        static readonly grpc::Method<global::Helloworld.HelloRequest, global::Helloworld.HelloReply> __Method_SayHello = new grpc::Method<global::Helloworld.HelloRequest, global::Helloworld.HelloReply>(
            grpc::MethodType.Unary,
            __ServiceName,
            "SayHello",
            __Marshaller_helloworld_HelloRequest,
            __Marshaller_helloworld_HelloReply);
    
        static readonly grpc::Method<global::Helloworld.HelloRequest, global::Helloworld.HelloReply> __Method_SayHelloStream = new grpc::Method<global::Helloworld.HelloRequest, global::Helloworld.HelloReply>(
            grpc::MethodType.ClientStreaming,
            __ServiceName,
            "SayHelloStream",
            __Marshaller_helloworld_HelloRequest,
            __Marshaller_helloworld_HelloReply);
    
        /// <summary>Creates service definition that can be registered with a server</summary>
        /// <param name="serviceImpl">An object implementing the server-side handling logic.</param>
        public static grpc::ServerServiceDefinition BindService(GreeterBase serviceImpl)
        {
          return grpc::ServerServiceDefinition.CreateBuilder()
              .AddMethod(__Method_SayHello, serviceImpl.SayHello)
              .AddMethod(__Method_SayHelloStream, serviceImpl.SayHelloStream).Build();
        }
    
        /// <summary>Register service method with a service binder with or without implementation. Useful when customizing the  service binding logic.
        /// Note: this method is part of an experimental API that can change or be removed without any prior notice.</summary>
        /// <param name="serviceBinder">Service methods will be bound by calling <c>AddMethod</c> on this object.</param>
        /// <param name="serviceImpl">An object implementing the server-side handling logic.</param>
        public static void BindService(grpc::ServiceBinderBase serviceBinder, GreeterBase serviceImpl)
        {
          serviceBinder.AddMethod(__Method_SayHello, serviceImpl == null ? null : new grpc::UnaryServerMethod<global::Helloworld.HelloRequest, global::Helloworld.HelloReply>(serviceImpl.SayHello));
          serviceBinder.AddMethod(__Method_SayHelloStream, serviceImpl == null ? null : new grpc::ClientStreamingServerMethod<global::Helloworld.HelloRequest, global::Helloworld.HelloReply>(serviceImpl.SayHelloStream));
        }
    }
    复制代码

     (2).__Marshaller_helloworld_HelloRequest这个是实现protobuffer的序列化和反序列化的一个委托 

     服务的请求参数和返回参数,我们使用Protobuf-net来实现序列化和反序列化,和 WCF一样需要给类打上标签

    复制代码
        /// <summary>
        /// 加法请求参数
        /// </summary>
        [ProtoContract]
        public class AddRequest
        {
            /// <summary>
            /// 第一个数字
            /// </summary>
            [ProtoMember(1)]
            public int Num1 { get; set; }
    
            /// <summary>
            /// 第二个数字
            /// </summary>
            [ProtoMember(2)]
            public int Num2 { get; set; }
        }
    复制代码

    2.要实现CodeFirst需要实现这个BindService,把我们的Grpc方法添加到ServerServiceDefinition

    (1).我们让Grpc服务实现IGrpcService空接口,用于标识是GrpcService

    复制代码
        /// <summary>
        /// MathGrpc
        /// </summary>
        public class MathGrpc : IGrpcService
        {
            /// <summary>
            /// 加法
            /// </summary>
            /// <param name="request"></param>
            /// <param name="context"></param>
            /// <returns></returns>
            public Task<IntMessage> Add(AddRequest request, ServerCallContext context)
            {
                var result = new IntMessage();
                result.Value = request.Num1 + request.Num2;
                return Task.FromResult(result);
            }
       }
    复制代码

    (2).获取实现了IGrpcService接口的类,然后反射获取方法,再添加到ServerServiceDefinition即可

    这里调用了GrpcMethodHelper.AutoRegisterMethod()方法,这是通过反射自动注册GrpcMethod的方法

    复制代码
            /// <summary>
        /// 注入IGrpcService
        /// </summary>
        /// <param name="grpcServices"></param>
        /// <returns></returns>
        private ServerBuilder UseGrpcService(IEnumerable<IGrpcService> grpcServices)
        {
            var builder = ServerServiceDefinition.CreateBuilder();
            grpcServices.ToList().ForEach(grpc => GrpcMethodHelper.AutoRegisterMethod(grpc, builder));
            _serviceDefinitions.Add(builder.Build());
            return this;
        }
    复制代码

    未完,待续,欢迎评论拍砖

    这些功能早在2018年就已经实现并运行在生产,感兴趣的同学可以去 github上查看,你要的都有,欢迎提issue

    出处:https://www.cnblogs.com/rabbityi/p/12593922.html

    =========================================================================================

    前言:

    gRPC默认是ProtoFirst的,即先写 proto文件,再生成代码,需要人工维护proto,生成的代码也不友好,所以出现了gRPC CodeFirst,下面来说说我们是怎么实现gRPC CodeFirst

    目录:

    实现和WCF一样的CodeFirst

    (1). 实现gRPC CodeFirst,  简化WCF一定要抽取接口的问题

    (2). 通过代码生成proto和注释,给第三方语言使用

    (3). 实现gRPC DashBoard,用于Http远程调用和管理

    (4). 实现服务注册与发现

    (5). 实现分布式日志跟踪

    (6). 日志监控等等

    我们是怎么实现gRPC CodeFirst-生成proto

    1.怎么根据代码生成Proto,上文我们调用了GrpcMethodHelper.AutoRegisterMethod()方法,这是通过反射自动注册GrpcMethod的方法

    (1).这里面调用了一个BuildMethod方法,用于生成grpc的序列化和反序列化的委托

    (2).同时可以收集grpc方法和参数的信息,用于生成proto

    复制代码
        /// <summary>
        /// 生成Grpc方法(CodeFirst方式)
        /// </summary>
        /// <typeparam name="TRequest"></typeparam>
        /// <typeparam name="TResponse"></typeparam>
        /// <param name="srv"></param>
        /// <param name="methodName"></param>
        /// <param name="package"></param>
        /// <param name="srvName"></param>
        /// <param name="mType"></param>
        /// <returns></returns>
        public static Method<TRequest, TResponse> BuildMethod<TRequest, TResponse>(this IGrpcService srv,
            string methodName, string package = null, string srvName = null, MethodType mType = MethodType.Unary)
        {
            var serviceName = srvName ??
                              GrpcExtensionsOptions.Instance.GlobalService ??
                              srv.GetType().Name;
            var pkg = package ?? GrpcExtensionsOptions.Instance.GlobalPackage;
            if (!string.IsNullOrWhiteSpace(pkg))
            {
                serviceName = $"{pkg}.{serviceName}";
            }
            #region 为生成proto收集信息
            if (!(srv is IGrpcBaseService) || GrpcExtensionsOptions.Instance.GenBaseServiceProtoEnable)
            {
                ProtoInfo.Methods.Add(new ProtoMethodInfo
                {
                    ServiceName = serviceName,
                    MethodName = methodName,
                    RequestName = typeof(TRequest).Name,
                    ResponseName = typeof(TResponse).Name,
                    MethodType = mType
                });
                ProtoGenerator.AddProto<TRequest>(typeof(TRequest).Name);
                ProtoGenerator.AddProto<TResponse>(typeof(TResponse).Name);
            }
            #endregion
            var request = Marshallers.Create<TRequest>((arg) => ProtobufExtensions.Serialize<TRequest>(arg), data => ProtobufExtensions.Deserialize<TRequest>(data));
            var response = Marshallers.Create<TResponse>((arg) => ProtobufExtensions.Serialize<TResponse>(arg), data => ProtobufExtensions.Deserialize<TResponse>(data));
            return new Method<TRequest, TResponse>(mType, serviceName, methodName, request, response);
        }
    复制代码

    2.不重复造轮子,通过protobuf-net的Serializer.GetProto()来生成请求参数和返回参数的proto

    (1).这里简单过滤了重复的proto,但GetProto()会把依赖的类都生成proto,这样公用类就会生成多份,需要再次过滤重复即可

    (2).生成message非关键代码这里我就不列出来了,都是字符串拼接的活

    复制代码
        /// <summary>
        /// 添加proto
        /// </summary>
        public static void AddProto<TEntity>(string entityName)
        {
            if (!ProtoMethodInfo.Protos.ContainsKey(entityName))
            {
                var msg = Serializer.GetProto<TEntity>(ProtoBuf.Meta.ProtoSyntax.Proto3);
                ProtoMethodInfo.Protos.TryAdd(entityName, msg.FilterHead().AddMessageComment<TEntity>());
            }
        }
    复制代码

     3.服务方法的proto就更简单了,直接根据方法类型拼出来即可

    复制代码
        /// <summary>
        /// 生成grpc的service的proto内容
        /// </summary>
        private static string GenGrpcServiceProto(string msgProtoName, string pkgName, string srvName, List<ProtoMethodInfo> methodInfo, bool spiltProto)
        {
            var sb = new StringBuilder();
            sb.AppendLine("syntax = "proto3";");
            if (!string.IsNullOrWhiteSpace(GrpcExtensionsOptions.Instance.ProtoNameSpace))
            {
                sb.AppendLine("option csharp_namespace = "" + GrpcExtensionsOptions.Instance.ProtoNameSpace.Trim() + "";");
            }
            if (!string.IsNullOrWhiteSpace(pkgName))
            {
                sb.AppendLine($"package {pkgName.Trim()};");
            }
            if (spiltProto)
            {
                sb.AppendLine(string.Format("import "{0}";", msgProtoName));
            }
            sb.AppendLine(Environment.NewLine);
            sb.AppendLine("service " + srvName + " {");
    
            var template = @"   rpc {0}({1}) returns({2})";
            methodInfo.ForEach(q => {
                var requestName = q.RequestName;
                var responseName = q.ResponseName;
                switch (q.MethodType)
                {
                    case Core.MethodType.Unary:
                        break;
                    case Core.MethodType.ClientStreaming:
                        requestName = "stream " + requestName;
                        break;
                    case Core.MethodType.ServerStreaming:
                        responseName = "stream " + responseName;
                        break;
                    case Core.MethodType.DuplexStreaming:
                        requestName = "stream " + requestName;
                        responseName = "stream " + responseName;
                        break;
                }
                ProtoCommentGenerator.AddServiceComment(q,sb);
                sb.AppendLine(string.Format(template, q.MethodName, requestName, responseName) + ";" + Environment.NewLine);
            });
    
            sb.AppendLine("}");
            return sb.ToString();
        }
    复制代码

    4.生成 proto没有注释,第三方对接时就尴尬了,虽然命名规范,但注释还是要有的,减少沟通成本

    (1).我们通过在类和方法上加入注释,然后项目里设置生成xml注释文档

    (2).生成proto时通过扫描xml注释文档来给proto加入注释即可

    未完,待续,欢迎评论拍砖

    这些功能早在2018年就已经实现并运行在生产,感兴趣的同学可以去 github(grpc.extensions) 上查看,你要的都有,欢迎提issue

    出处:https://www.cnblogs.com/rabbityi/p/12605202.html

  • 相关阅读:
    11月12号实验课
    10月29日实验
    10.15实验课作业
    Idea的使用和设置
    常用的Linux命令
    KAFKA总结
    SpringBoot+Maven+MyBaties+Mysql快速搭建一个项目
    Spring 常见面试
    Windows下安装ZK kafka Mysql dubbo redis
    MySQL常见的面试题
  • 原文地址:https://www.cnblogs.com/mq0036/p/12613061.html
Copyright © 2011-2022 走看看