了解grpc/protobuf

gRPC是一个高性能、通用的开源RPC框架,其由Google主要面向移动应用开发并基于HTTP/2协议标准而设计,基于ProtoBuf(Protocol Buffers)序列化协议开发,且支持众多开发语言。
gRPC提供了一种简单的方法来精确地定义服务和为iOS、Android和后台支持服务自动生成可靠性很强的客户端功能库。客户端充分利用高级流和链接功能,从而有助于节省带宽、降低的TCP链接次数、节省CPU使用、和电池寿命。

Protobuf(Protocol Buffers),是 Google 开发的一种跨语言、跨平台的可扩展机制,用于序列化结构化数据。
grpc与传统的 REST 架构相比,REST架构通过 http 传输 JSON 或者 XML ,会带来了一个问题:服务 A 把原始数据编码成 JSON/XML 格式,发送一长串字符给服务 B,B 通过解码还原成原始数据,通信的总体数据量很大。
但在两个微服务的通信间,我们不需要字符串中的所有数据,所以我们采用难理解但更加轻量的二进制数据进行交互。gRPC 采用的是支持二进制数据的 HTTP 2.0 规范,而protobuf负责处理二进制数据, 它更小、更快、更便捷。
protobuf 目前支持 C++、Java、Python、Objective-C,如果使用 proto3,还支持 C#、Ruby、Go、PHP、JavaScript 等语言。

官网地址:https://developers.google.cn/protocol-buffers/
GitHub 地址:https://github.com/protocolbuffers/protobuf

Grpc中文文档:http://doc.oschina.net/grpc?t=60133

优点:

  • 性能好
  • 跨语言

缺点:

  • 二进制格式可读性差:为了提高性能,protobuf 采用了二进制格式进行编码,这直接导致了可读性差。
  • 缺乏自描述:XML 是自描述的,而 protobuf 不是,不配合定义的结构体是看不出来什么作用的。

环境配置

第一步:安装protobuf:

  1. 先下载protoc:https://github.com/protocolbuffers/protobuf/releases/
  2. 把这么文件里面的bin里面的protoc.exe 复制到GOPATH/bin下,GOPATH/bin加入环境变量。也可以放到别的目录,把那个目录 配置到环境变量。反正最后的效果就是 在cmd 输入 protoc 不会报错。
  3. 在cmd 中运行 go get -u github.com/golang/protobuf/protoc-gen-go 获取protobuf的编译器插件 protoc-gen-go。
  4. 到这里 protobuf 就配的差不多了,protoc --go_out=plugins=grpc:. xxxx.proto 这条命令就是 编译 协议的,具体怎么使用我们以后说。

第二步:安装grpc:

  1. 第一种方法:官方的安装方法是 go get -u google.golang.org/grpc ,但是需要翻墙。
  2. 第二种方法:从git上 克隆 grpc 的各种 依赖库 ,然后 移到我们 的 GOPATH 目录下面。(网上找的代码,亲测有效)。
    1. git clone https://github.com/grpc/grpc-go.git $GOPATH/src/google.golang.org/grpc
    2. git clone https://github.com/golang/net.git $GOPATH/src/golang.org/x/net
    3. git clone https://github.com/golang/text.git $GOPATH/src/golang.org/x/text
    4. git clone https://github.com/google/go-genproto.git $GOPATH/src/google.golang.org/genproto
    5. cd $GOPATH/src/

第三步:安装gRPC运行时接口编解码支持库

1
go get -u github.com/golang/protobuf/proto 

注意:上面这个文件也需要移到src目录下。

调用过程

1、客户端(gRPC Stub)调用 A 方法,发起 RPC 调用。

2、对请求信息使用 Protobuf 进行对象序列化压缩(IDL)。

3、服务端(gRPC Server)接收到请求后,解码请求体,进行业务逻辑处理并返回。

4、对响应结果使用 Protobuf 进行对象序列化压缩(IDL)。

5、客户端接受到服务端响应,解码请求体。回调被调用的 A 方法,唤醒正在等待响应(阻塞)的客户端调用并返回响应结果。

调用方式

一、Unary RPC:一元 RPC

Server

  1. type SearchService struct{}
  2.  
  3. func (s *SearchService) Search(ctx context.Context, r *pb.SearchRequest) (*pb.SearchResponse, error) {
  4. return &pb.SearchResponse{Response: r.GetRequest() + " Server"}, nil
  5. }
  6.  
  7. const PORT = "9001"
  8.  
  9. func main() {
  10. server := grpc.NewServer()
  11. pb.RegisterSearchServiceServer(server, &SearchService{})
  12.  
  13. lis, err := net.Listen("tcp", ":"+PORT)
  14. ...
  15.  
  16. server.Serve(lis)
  17. }
  • 创建 gRPC Server 对象,你可以理解为它是 Server 端的抽象对象。

  • 将 SearchService(其包含需要被调用的服务端接口)注册到 gRPC Server。的内部注册中心。这样可以在接受到请求时,通过内部的 “服务发现”,发现该服务端接口并转接进行逻辑处理。

  • 创建 Listen,监听 TCP 端口。

  • gRPC Server 开始 lis.Accept,直到 Stop 或 GracefulStop。

Client

  1. func main() {
  2. conn, err := grpc.Dial(":"+PORT, grpc.WithInsecure())
  3. ...
  4. defer conn.Close()
  5.  
  6. client := pb.NewSearchServiceClient(conn)
  7. resp, err := client.Search(context.Background(), &pb.SearchRequest{
  8. Request: "gRPC",
  9. })
  10. ...
  11. }
  • 创建与给定目标(服务端)的连接句柄。

  • 创建 SearchService 的客户端对象。

  • 发送 RPC 请求,等待同步响应,得到回调后返回响应结果。

二、Server-side streaming RPC:服务端流式 RPC

Server

  1. func (s *StreamService) List(r *pb.StreamRequest, stream pb.StreamService_ListServer) error {
  2. for n := 0; n <= 6; n++ {
  3. stream.Send(&pb.StreamResponse{
  4. Pt: &pb.StreamPoint{
  5. ...
  6. },
  7. })
  8. }
  9.  
  10. return nil
  11. }

Client

  1. func printLists(client pb.StreamServiceClient, r *pb.StreamRequest) error {
  2. stream, err := client.List(context.Background(), r)
  3. ...
  4.  
  5. for {
  6. resp, err := stream.Recv()
  7. if err == io.EOF {
  8. break
  9. }
  10. ...
  11. }
  12.  
  13. return nil
  14. }

三、Client-side streaming RPC:客户端流式 RPC

Server

  1. func (s *StreamService) Record(stream pb.StreamService_RecordServer) error {
  2. for {
  3. r, err := stream.Recv()
  4. if err == io.EOF {
  5. return stream.SendAndClose(&pb.StreamResponse{Pt: &pb.StreamPoint{...}})
  6. }
  7. ...
  8.  
  9. }
  10.  
  11. return nil
  12. }

Client

  1. func printRecord(client pb.StreamServiceClient, r *pb.StreamRequest) error {
  2. stream, err := client.Record(context.Background())
  3. ...
  4.  
  5. for n := 0; n < 6; n++ {
  6. stream.Send(r)
  7. }
  8.  
  9. resp, err := stream.CloseAndRecv()
  10. ...
  11.  
  12. return nil
  13. }

四、Bidirectional streaming RPC:双向流式 RPC

Server

  1. func (s *StreamService) Route(stream pb.StreamService_RouteServer) error {
  2. for {
  3. stream.Send(&pb.StreamResponse{...})
  4. r, err := stream.Recv()
  5. if err == io.EOF {
  6. return nil
  7. }
  8. ...
  9. }
  10.  
  11. return nil
  12. }

Client

  1. func printRoute(client pb.StreamServiceClient, r *pb.StreamRequest) error {
  2. stream, err := client.Route(context.Background())
  3. ...
  4.  
  5. for n := 0; n <= 6; n++ {
  6. stream.Send(r)
  7. resp, err := stream.Recv()
  8. if err == io.EOF {
  9. break
  10. }
  11. ...
  12. }
  13.  
  14. stream.CloseSend()
  15.  
  16. return nil
  17. }

浅谈理解

服务端

为什么四行代码,就能够起一个 gRPC Server,内部做了什么逻辑。你有想过吗?接下来我们一步步剖析,看看里面到底是何方神圣。

一、初始化

  1. // grpc.NewServer()
  2. func NewServer(opt ...ServerOption) *Server {
  3. opts := defaultServerOptions
  4. for _, o := range opt {
  5. o(&opts)
  6. }
  7. s := &Server{
  8. lis: make(map[net.Listener]bool),
  9. opts: opts,
  10. conns: make(map[io.Closer]bool),
  11. m: make(map[string]*service),
  12. quit: make(chan struct{}),
  13. done: make(chan struct{}),
  14. czData: new(channelzData),
  15. }
  16. s.cv = sync.NewCond(&s.mu)
  17. ...
  18.  
  19. return s
  20. }

这块比较简单,主要是实例 grpc.Server 并进行初始化动作。涉及如下:

  • lis:监听地址列表。

  • opts:服务选项,这块包含 Credentials、Interceptor 以及一些基础配置。

  • conns:客户端连接句柄列表。

  • m:服务信息映射。

  • quit:退出信号。

  • done:完成信号。

  • czData:用于存储 ClientConn,addrConn 和 Server 的channelz 相关数据。

  • cv:当优雅退出时,会等待这个信号量,直到所有 RPC 请求都处理并断开才会继续处理。

二、注册

  1. pb.RegisterSearchServiceServer(server, &SearchService{})

步骤一:Service API interface

  1. // search.pb.go
  2. type SearchServiceServer interface {
  3. Search(context.Context, *SearchRequest) (*SearchResponse, error)
  4. }
  5.  
  6. func RegisterSearchServiceServer(s *grpc.Server, srv SearchServiceServer) {
  7. s.RegisterService(&_SearchService_serviceDesc, srv)
  8. }

还记得我们平时编写的 Protobuf 吗?在生成出来的.pb.go文件中,会定义出 Service APIs interface 的具体实现约束。而我们在 gRPC Server 进行注册时,会传入应用 Service 的功能接口实现,此时生成的RegisterServer方法就会保证两者之间的一致性。

步骤二:Service API IDL

你想乱传糊弄一下?不可能的,请乖乖定义与 Protobuf 一致的接口方法。但是那个&_SearchService_serviceDesc又有什么作用呢?代码如下:

  1. // search.pb.go
  2. var _SearchService_serviceDesc = grpc.ServiceDesc{
  3. ServiceName: "proto.SearchService",
  4. HandlerType: (*SearchServiceServer)(nil),
  5. Methods: []grpc.MethodDesc{
  6. {
  7. MethodName: "Search",
  8. Handler: _SearchService_Search_Handler,
  9. },
  10. },
  11. Streams: []grpc.StreamDesc{},
  12. Metadata: "search.proto",
  13. }

这看上去像服务的描述代码,用来向内部表述 “我” 都有什么。涉及如下:

  • ServiceName:服务名称

  • HandlerType:服务接口,用于检查用户提供的实现是否满足接口要求

  • Methods:一元方法集,注意结构内的Handler方法,其对应最终的 RPC 处理方法,在执行 RPC 方法的阶段会使用。

  • Streams:流式方法集

  • Metadata:元数据,是一个描述数据属性的东西。在这里主要是描述SearchServiceServer服务

步骤三:Register Service

  1. func (s *Server) register(sd *ServiceDesc, ss interface{}) {
  2. ...
  3. srv := &service{
  4. server: ss,
  5. md: make(map[string]*MethodDesc),
  6. sd: make(map[string]*StreamDesc),
  7. mdata: sd.Metadata,
  8. }
  9. for i := range sd.Methods {
  10. d := &sd.Methods[i]
  11. srv.md[d.MethodName] = d
  12. }
  13. for i := range sd.Streams {
  14. ...
  15. }
  16. s.m[sd.ServiceName] = srv
  17. }

在最后一步中,我们会将先前的服务接口信息、服务描述信息给注册到内部service去,以便于后续实际调用的使用。涉及如下:

  • server:服务的接口信息

  • md:一元服务的 RPC 方法集

  • sd:流式服务的 RPC 方法集

  • mdata:metadata,元数据

小结

在这一章节中,主要介绍的是 gRPC Server 在启动前的整理和注册行为,看上去很简单,但其实一切都是为了后续的实际运行的预先准备。因此我们整理一下思路,将其串联起来看看,如下:

三、监听

接下来到了整个流程中,最重要也是大家最关注的监听/处理阶段,核心代码如下:

  1. func (s *Server) Serve(lis net.Listener) error {
  2. ...
  3. var tempDelay time.Duration
  4. for {
  5. rawConn, err := lis.Accept()
  6. if err != nil {
  7. if ne, ok := err.(interface {
  8. Temporary() bool
  9. }); ok && ne.Temporary() {
  10. if tempDelay == 0 {
  11. tempDelay = 5 * time.Millisecond
  12. } else {
  13. tempDelay *= 2
  14. }
  15. if max := 1 * time.Second; tempDelay > max {
  16. tempDelay = max
  17. }
  18. ...
  19. timer := time.NewTimer(tempDelay)
  20. select {
  21. case <-timer.C:
  22. case <-s.quit:
  23. timer.Stop()
  24. return nil
  25. }
  26. continue
  27. }
  28. ...
  29. return err
  30. }
  31. tempDelay = 0
  32.  
  33. s.serveWG.Add(1)
  34. go func() {
  35. s.handleRawConn(rawConn)
  36. s.serveWG.Done()
  37. }()
  38. }
  39. }

Serve 会根据外部传入的 Listener 不同而调用不同的监听模式,这也是net.Listener的魅力,灵活性和扩展性会比较高。而在 gRPC Server 中最常用的就是TCPConn,基于 TCP Listener 去做。接下来我们一起看看具体的处理逻辑,如下:

  • 循环处理连接,通过lis.Accept取出连接,如果队列中没有需处理的连接时,会形成阻塞等待。

  • lis.Accept失败,则触发休眠机制,若为第一次失败那么休眠 5ms,否则翻倍,再次失败则不断翻倍直至上限休眠时间 1s,而休眠完毕后就会尝试去取下一个 “它”。

  • lis.Accept成功,则重置休眠的时间计数和启动一个新的 goroutine 调用handleRawConn方法去执行/处理新的请求,也就是大家很喜欢说的 “每一个请求都是不同的 goroutine 在处理”。

  • 在循环过程中,包含了 “退出” 服务的场景,主要是硬关闭和优雅重启服务两种情况。

客户端

一、创建拨号连接

  1. // grpc.Dial(":"+PORT, grpc.WithInsecure())
  2. func DialContext(ctx context.Context, target string, opts ...DialOption) (conn *ClientConn, err error) {
  3. cc := &ClientConn{
  4. target: target,
  5. csMgr: &connectivityStateManager{},
  6. conns: make(map[*addrConn]struct{}),
  7. dopts: defaultDialOptions(),
  8. blockingpicker: newPickerWrapper(),
  9. czData: new(channelzData),
  10. firstResolveEvent: grpcsync.NewEvent(),
  11. }
  12. ...
  13. chainUnaryClientInterceptors(cc)
  14. chainStreamClientInterceptors(cc)
  15.  
  16. ...
  17. }

grpc.Dial方法实际上是对于grpc.DialContext的封装,区别在于ctx是直接传入context.Background。其主要功能是创建与给定目标的客户端连接,其承担了以下职责:

  • 初始化 ClientConn

  • 初始化(基于进程 LB)负载均衡配置

  • 初始化 channelz

  • 初始化重试规则和客户端一元/流式拦截器

  • 初始化协议栈上的基础信息

  • 相关 context 的超时控制

  • 初始化并解析地址信息

  • 创建与服务端之间的连接

连没连

之前听到有的人说调用grpc.Dial后客户端就已经与服务端建立起了连接,但这对不对呢?我们先鸟瞰全貌,看看正在跑的 goroutine。如下:

我们可以有几个核心方法一直在等待/处理信号,通过分析底层源码可得知。涉及如下:

  1. func (ac *addrConn) connect()
    func (ac *addrConn) resetTransport()
    func (ac *addrConn) createTransport(addr resolver.Address, copts transport.ConnectOptions, connectDeadline time.Time)
    func (ac *addrConn) getReadyTransport()

在这里主要分析 goroutine 提示的resetTransport方法,看看都做了啥。核心代码如下:

  1. func (ac *addrConn) resetTransport() {
  2. for i := 0; ; i++ {
  3. if ac.state == connectivity.Shutdown {
  4. return
  5. }
  6. ...
  7. connectDeadline := time.Now().Add(dialDuration)
  8. ac.updateConnectivityState(connectivity.Connecting)
  9. newTr, addr, reconnect, err := ac.tryAllAddrs(addrs, connectDeadline)
  10. if err != nil {
  11. if ac.state == connectivity.Shutdown {
  12. return
  13. }
  14. ac.updateConnectivityState(connectivity.TransientFailure)
  15. timer := time.NewTimer(backoffFor)
  16. select {
  17. case <-timer.C:
  18. ...
  19. }
  20. continue
  21. }
  22.  
  23. if ac.state == connectivity.Shutdown {
  24. newTr.Close()
  25. return
  26. }
  27. ...
  28. if !healthcheckManagingState {
  29. ac.updateConnectivityState(connectivity.Ready)
  30. }
  31. ...
  32.  
  33. if ac.state == connectivity.Shutdown {
  34. return
  35. }
  36. ac.updateConnectivityState(connectivity.TransientFailure)
  37. }
  38. }

在该方法中会不断地去尝试创建连接,若成功则结束。否则不断地根据Backoff算法的重试机制去尝试创建连接,直到成功为止。从结论上来讲,单纯调用DialContext是异步建立连接的,也就是并不是马上生效,处于Connecting状态,而正式下要到达Ready状态才可用。

二、实例化 Service API

  1. type SearchServiceClient interface {
  2. Search(ctx context.Context, in *SearchRequest, opts ...grpc.CallOption) (*SearchResponse, error)
  3. }
  4.  
  5. type searchServiceClient struct {
  6. cc *grpc.ClientConn
  7. }
  8.  
  9. func NewSearchServiceClient(cc *grpc.ClientConn) SearchServiceClient {
  10. return &searchServiceClient{cc}
  11. }

这块就是实例 Service API interface,比较简单。

三、调用

  1. // search.pb.go
  2. func (c *searchServiceClient) Search(ctx context.Context, in *SearchRequest, opts ...grpc.CallOption) (*SearchResponse, error) {
  3. out := new(SearchResponse)
  4. err := c.cc.Invoke(ctx, "/proto.SearchService/Search", in, out, opts...)
  5. if err != nil {
  6. return nil, err
  7. }
  8. return out, nil
  9. }

proto 生成的 RPC 方法更像是一个包装盒,把需要的东西放进去,而实际上调用的还是grpc.invoke方法。如下:

  1. func invoke(ctx context.Context, method string, req, reply interface{}, cc *ClientConn, opts ...CallOption) error {
  2. cs, err := newClientStream(ctx, unaryStreamDesc, cc, method, opts...)
  3. if err != nil {
  4. return err
  5. }
  6. if err := cs.SendMsg(req); err != nil {
  7. return err
  8. }
  9. return cs.RecvMsg(reply)
  10. }

通过概览,可以关注到三块调用。如下:

  • newClientStream:获取传输层 Trasport 并组合封装到 ClientStream 中返回,在这块会涉及负载均衡、超时控制、 Encoding、 Stream 的动作,与服务端基本一致的行为。

  • cs.SendMsg:发送 RPC 请求出去,但其并不承担等待响应的功能。

  • cs.RecvMsg:阻塞等待接受到的 RPC 方法响应结果。

连接

  1. // clientconn.go
  2. func (cc *ClientConn) getTransport(ctx context.Context, failfast bool, method string) (transport.ClientTransport, func(balancer.DoneInfo), error) {
  3. t, done, err := cc.blockingpicker.pick(ctx, failfast, balancer.PickOptions{
  4. FullMethodName: method,
  5. })
  6. if err != nil {
  7. return nil, nil, toRPCErr(err)
  8. }
  9. return t, done, nil
  10. }

newClientStream方法中,我们通过getTransport方法获取了 Transport 层中抽象出来的 ClientTransport 和 ServerTransport,实际上就是获取一个连接给后续 RPC 调用传输使用。

四、关闭连接

  1. // conn.Close()
  2. func (cc *ClientConn) Close() error {
  3. defer cc.cancel()
  4. ...
  5. cc.csMgr.updateState(connectivity.Shutdown)
  6. ...
  7. cc.blockingpicker.close()
  8. if rWrapper != nil {
  9. rWrapper.close()
  10. }
  11. if bWrapper != nil {
  12. bWrapper.close()
  13. }
  14.  
  15. for ac := range conns {
  16. ac.tearDown(ErrClientConnClosing)
  17. }
  18. if channelz.IsOn() {
  19. ...
  20. channelz.AddTraceEvent(cc.channelzID, ted)
  21. channelz.RemoveEntry(cc.channelzID)
  22. }
  23. return nil
  24. }

该方法会取消 ClientConn 上下文,同时关闭所有底层传输。涉及如下:

  • Context Cancel

  • 清空并关闭客户端连接

  • 清空并关闭解析器连接

  • 清空并关闭负载均衡连接

  • 添加跟踪引用

  • 移除当前通道信息

总结

  • gRPC 基于 HTTP/2 + Protobuf。

  • gRPC 有四种调用方式,分别是一元、服务端/客户端流式、双向流式。

  • gRPC 的附加信息都会体现在 HEADERS 帧,数据在 DATA 帧上。

  • Client 请求若使用 grpc.Dial 默认是异步建立连接,当时状态为 Connecting。

  • Client 请求若需要同步则调用 WithBlock(),完成状态为 Ready。

  • Server 监听是循环等待连接,若没有则休眠,最大休眠时间 1s;若接收到新请求则起一个新的 goroutine 去处理。

  • grpc.ClientConn 不关闭连接,会导致 goroutine 和 Memory 等泄露。

  • 任何内/外调用如果不加超时控制,会出现泄漏和客户端不断重试。

  • 特定场景下,如果不对 grpc.ClientConn 加以调控,会影响调用。

  • 拦截器如果不用 go-grpc-middleware 链式处理,会覆盖。

  • 在选择 gRPC 的负载均衡模式时,需要谨慎。

参考

  • http://doc.oschina.net/grpc

  • https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md

  • https://juejin.im/post/5b88a4f56fb9a01a0b31a67e

  • https://www.ibm.com/developerworks/cn/web/wa-http2-under-the-hood/index.html

  • https://github.com/grpc/grpc-go/issues/1953

  • https://www.zhihu.com/question/52670041

可以拷贝的代码见:

https://github.com/EDDYCJY/blog/blob/master/golang/gRPC/2019-06-28-talking-grpc.md

原文链接:https://mp.weixin.qq.com/s/qet7FX26HGnXgLIG-lOSyw

Go微服务 grpc/protobuf的更多相关文章

  1. .net core微服务——gRPC(下)

    序 上一篇博客把grpc的概念说了个大概,介绍了proto的数据类型,基本语法,也写了个小demo,是不是没那么难? 今天要从理论到实际,写两个微服务,并利用grpc完成两者之间的通信.只是作为dem ...

  2. Go微服务 grpc的简单使用

    作者:薇文文链接:https://www.jianshu.com/p/20ed82218163来源:简书 准备工作 先安装Protobuf 编译器 protoc,下载地址:https://github ...

  3. Grpc微服务从零入门

    快速入门 安装 JDK 毫无疑问,要想玩Java,就必须得先装Java JDK,目前公司主要使用的是Oracle JDK 8,安装完成后要配置环境才能正常使用,真蠢,不过也就那么一下下,认了吧.配置方 ...

  4. 带你十天轻松搞定 Go 微服务之大结局(分布式事务)

    序言 我们通过一个系列文章跟大家详细展示一个 go-zero 微服务示例,整个系列分十篇文章,目录结构如下: 环境搭建 服务拆分 用户服务 产品服务 订单服务 支付服务 RPC 服务 Auth 验证 ...

  5. python3和grpc的微服务探索实践

    对于微服务的实践,一般都是基于Java和Golang的,博主最近研究了下基于Python的微服务实践,现在通过一个简单的服务来分析Python技术栈的微服务实践 技术栈:Python3 + grpc ...

  6. .NET Core微服务之路:基于gRPC服务发现与服务治理的方案

    重温最少化集群搭建,我相信很多朋友都已经搭建出来,基于Watch机制也实现了出来,相信也有很多朋友有了自己的实现思路,但是,很多朋友有个疑问,我API和服务分离好了,怎么通过服务中心进行发现呢,这个过 ...

  7. grpc 实现微服务生态笔记

    微服务的发展可谓是一波三折,一代一代经历和N多技术成果,grpc只是其中一个,因为其东家是google,明显比较稳定.加上其强大的文档和技术支持和跨平台的支持,在企业级应用上有很大的可信任感,所以也有 ...

  8. 《gPRC使用protobuf构建微服务》阅读笔记

    首先我需要去了解一些概念,根据百度百科了解到: l  微服务架构:微服务架构是一项在云中部署应用和服务的新技术.微服务可以在“自己的程序”中运行,并通过“轻量级设备与HTTP型API进行沟通”. l  ...

  9. 微服务通信方式——gRPC

    微服务设计的原则是单一职责.轻量级通信.服务粒度适当,而说到服务通信,我们熟知的有MQ通信,还有REST.Dubbo和Thrift等,这次我来说说gRPC, 谷歌开发的一种数据交换格式,说不定哪天就需 ...

随机推荐

  1. win10安装RabbitMQ

    简单来说,两步走:先装erlang,再装rabbitmq.rabbitmq是用erlang开发的,既然erlang是一种语言,你可以把它当做Jdk来安装. 1.下载erlang安装包(官网下载地址ht ...

  2. 算法习题---4-4骰子涂色(UVa253)

    一:题目 分别对两个骰子的六个面涂色r-红 b-蓝 g-绿,通过转动骰子,看两个骰子是不是一样的涂色方法 (一)题目详解 题目规定了正方体的六个面的序号:从1-,按照这个需要提供涂色序列 (二)案例展 ...

  3. hive-1.1.0-cdh5.11.1-src compile

    1. download cdh hive src  http://archive.cloudera.com/cdh5/cdh/5/hive-1.1.0-cdh5.11.1-src.tar.gz 2. ...

  4. PAT 甲级 1054 The Dominant Color (20 分)(简单题)

    1054 The Dominant Color (20 分)   Behind the scenes in the computer's memory, color is always talked ...

  5. LeetCode_141. Linked List Cycle

    141. Linked List Cycle Easy Given a linked list, determine if it has a cycle in it. To represent a c ...

  6. Spring Aop(十六)——编程式的自定义Advisor

    转发:https://www.iteye.com/blog/elim-2399437 https://www.iteye.com/blogs/subjects/springaop 编程式的自定义Adv ...

  7. VMware VSAN 设计规则

    1.集群节点数量:3-64台主机(生产环境最少4节点起,5.5版本支持32节点,6.0版本支持64节点),配置万兆网卡,主机规格应满足VSAN兼容性要求. 2.每台主机需配置磁盘组,每台主机的磁盘组数 ...

  8. 最新 锐之旗java校招面经 (含整理过的面试题大全)

    从6月到10月,经过4个月努力和坚持,自己有幸拿到了网易雷火.京东.去哪儿.锐之旗等10家互联网公司的校招Offer,因为某些自身原因最终选择了锐之旗.6.7月主要是做系统复习.项目复盘.LeetCo ...

  9. 迷惑性很强的crontab

      提到定时任务,我们通常会想起linux的crontab,可以说服务器端大部分定时任务都是由它完成的.这东西固然耗用,但是坑也不少.   这不,昨天我在部署一个备份任务的时候,就不幸踩坑了.差点酿成 ...

  10. Java 应用程序的运行机制

    计算机高级语言: 编译型 (C)                             解释性(JS)Java 使两种类型的结合 java     编译器    class   \|/jvm     ...