trpc.group/trpc-go/trpc-go@v1.0.3/stream/README.md (about)

     1  English | [中文](README.zh_CN.md)
     2  
     3  # Building Stream Services with tRPC-Go
     4  
     5  ## Introduction
     6  
     7  What is Stream:
     8  
     9  In a regular RPC, the client sends a request to the server, waits for the server to process the request, and returns a response to the client.
    10  
    11  In contrast, with stream RPC, the client and server can establish a continuous connection to send and receive data continuously, allowing the server to provide continuous responses.
    12  
    13  tRPC streaming is divided into three types:
    14  
    15  - Server-side streaming RPC
    16  - Client-side streaming RPC
    17  - Bidirectional streaming RPC
    18  
    19  Why do we need streaming? Are there any issues with Simple RPC? When using Simple RPC, the following issues may arise:
    20  
    21  - Instantaneous pressure caused by large data packets.
    22  - When receiving data packets, all packets must be received correctly before the response is received and business processing can take place (it is not possible to receive and process data on the client and server simultaneously).
    23  
    24  Why use Streaming RPC:
    25  
    26  - With Simple RPC, for large data packets such as a large file that needs to be transmitted, the packets must be manually divided and reassembled, and any issues with packets arriving out of order must be resolved. In contrast, with streaming, the client can read the file and transmit it directly without the need to split the file into packets or worry about packet order.
    27  - n real-time scenarios such as multi-person chat rooms, the server must push real-time messages to multiple clients upon receiving a message.
    28  
    29  ## Principle
    30  
    31  See [here](https://github.com/trpc-group/trpc/blob/main/docs/cn/trpc_protocol_design.md) for the tRPC streaming design principle.
    32  
    33  ## Example
    34  
    35  ### Client-side streaming
    36  
    37  #### Define the protocol file
    38  
    39  ```protobuf
    40  syntax = "proto3";
    41  
    42  package trpc.test.helloworld;
    43  option go_package="github.com/some-repo/examples/helloworld";
    44  
    45  // The greeting service definition.
    46  service Greeter {
    47    // Sends a greeting
    48    rpc SayHello (stream HelloRequest) returns (HelloReply);
    49  }
    50  // The request message containing the user's name.
    51  message HelloRequest {
    52    string name = 1;
    53  }
    54  // The response message containing the greetings
    55  message HelloReply {
    56    string message = 1;
    57  }
    58  ```
    59  
    60  #### Generate service code
    61  
    62  First install [trpc-cmdline](https://github.com/trpc-group/trpc-cmdline).
    63  
    64  Then generate the streaming service stub code
    65  
    66  ```shell
    67  trpc create -p helloworld.proto
    68  ```
    69  
    70  #### Server code
    71  
    72  ```go
    73  package main
    74  
    75  import (
    76      "fmt"
    77      "io"
    78      "strings"
    79    
    80      "trpc.group/trpc-go/trpc-go/log"
    81      trpc "trpc.group/trpc-go/trpc-go"
    82      _ "trpc.group/trpc-go/trpc-go/stream"
    83      pb "github.com/some-repo/examples/helloworld"
    84  )
    85  
    86  type greeterServerImpl struct{}
    87  
    88  // SayHello Client streaming, SayHello passes pb.Greeter_SayHelloServer as a parameter, returns error
    89  // pb.Greeter_SayHelloServer provides interfaces such as Recv() and SendAndClose() for streaming interaction.
    90  func (s *greeterServerImpl) SayHello(gs pb.Greeter_SayHelloServer) error {
    91      var names []string
    92      for {
    93          // The server uses a for loop to recv and receive data from the client
    94          in, err := gs.Recv()
    95          if err == nil {
    96              log.Infof("receive hi, %s\n", in.Name)
    97          }
    98          // If EOF is returned, it means that the client stream has ended and the client has sent all the data
    99          if err == io.EOF {
   100              log.Infof("recveive error io eof %v\n", err)
   101              // SendAndClose send and close the stream
   102              gs.SendAndClose(&pb.HelloReply{Message: "hello " + strings.Join(names, ",")})
   103              return nil
   104          }
   105          // Indicates that an exception occurred in the stream and needs to be returned
   106          if err != nil {
   107              log.Errorf("receive from %v\n", err)
   108              return err
   109          }
   110          names = append(names, in.Name)
   111      }
   112  }
   113  
   114  func main() {
   115      // Create a service object, the bottom layer will automatically read the service configuration and initialize the plug-in, which must be placed in the first line of the main function, and the business initialization logic must be placed after NewServer.
   116      s := trpc.NewServer()
   117      // Register the current implementation into the service object.
   118      pb.RegisterGreeterService(s, &greeterServerImpl{})
   119      // Start the service and block here.
   120      if err := s.Serve(); err != nil {
   121          panic(err)
   122      }
   123  }
   124  ```
   125  
   126  #### Client code
   127  
   128  ```go
   129  package main
   130  
   131  import (
   132      "context"
   133      "flag"
   134      "fmt"
   135      "strconv"
   136    
   137      "trpc.group/trpc-go/trpc-go/client"
   138      "trpc.group/trpc-go/trpc-go/log"
   139      pb "github.com/some-repo/examples/helloworld"
   140  )
   141  
   142  func main() {
   143    
   144      target := flag.String("ipPort", "", "ip port")
   145      serviceName := flag.String("serviceName", "", "serviceName")
   146    
   147      flag.Parse()
   148    
   149      var ctx = context.Background()
   150      opts := []client.Option{
   151          client.WithNamespace("Development"),
   152          client.WithServiceName("trpc.test.helloworld.Greeter"),
   153          client.WithTarget(*target),
   154      }
   155      log.Debugf("client: %s,%s", *serviceName, *target)
   156      proxy := pb.NewGreeterClientProxy(opts...)
   157      // Different from a single RPC, calling SayHello does not need to pass in a request, and returns cstream for send and recv
   158      cstream, err := proxy.SayHello(ctx, opts...)
   159      if err != nil {
   160          log.Error("Error in stream sayHello")
   161          return
   162      }
   163      for i := 0; i < 10; i++ {
   164          // Call Send to continuously send data
   165          err = cstream.Send(&pb.HelloRequest{Name: "trpc-go" + strconv.Itoa(i)})
   166          if err != nil {
   167              log.Errorf("Send error %v\n", err)
   168              return err
   169          }
   170      }
   171      // The server only returns once, so call CloseAndRecv to receive
   172      reply, err := cstream.CloseAndRecv()
   173      if err == nil && reply != nil {
   174          log.Infof("reply is %s\n", reply.Message)
   175      }
   176      if err != nil {
   177          log.Errorf("receive error from server :%v", err)
   178      }
   179  }
   180  ```
   181  
   182  ### Server-side streaming
   183  
   184  #### Define the protocol file
   185  
   186  ```protobuf
   187  service Greeter {
   188    // Add stream in front of HelloReply.
   189    rpc SayHello (HelloRequest) returns (stream HelloReply) {}
   190  }
   191  ```
   192  
   193  #### Server code
   194  
   195  ```go
   196  // SayHello Server-side streaming, SayHello passes in a request and pb.Greeter_SayHelloServer as parameters, and returns an error
   197  // b.Greeter_SayHelloServer provides Send() interface for streaming interaction
   198  func (s *greeterServerImpl) SayHello(in *pb.HelloRequest, gs pb.Greeter_SayHelloServer) error {
   199      name := in.Name
   200      for i := 0; i < 100; i++ {
   201          // Continuously call Send to send the response
   202          gs.Send(&pb.HelloReply{Message: "hello " + name + strconv.Itoa(i)})
   203      }
   204      return nil
   205  }
   206  ```
   207  
   208  #### Client code
   209  
   210  ```go
   211  func main() {
   212      proxy := pb.NewGreeterClientProxy(opts...)
   213      // The client directly fills in the parameters, and the returned cstream can be used to continuously receive the response from the server
   214      cstream, err := proxy.SayHello(ctx, &pb.HelloRequest{Name: "trpc-go"}, opts...)
   215      if err != nil {
   216          log.Error("Error in stream sayHello")
   217          return
   218      }
   219      for {
   220          reply, err := cstream.Recv()
   221          // Note that errors.Is(err, io.EOF) cannot be used here to determine the end of the stream
   222          if err == io.EOF {
   223              break
   224          }
   225          if err != nil {
   226              log.Infof("failed to recv: %v\n", err)
   227          }
   228          log.Infof("Greeting:%s \n", reply.Message)
   229      }
   230  }
   231  ```
   232  
   233  ### Bidirectional streaming
   234  
   235  #### Define the protocol file
   236  
   237  ```protobuf
   238  service Greeter {
   239    rpc SayHello (stream HelloRequest) returns (stream HelloReply) {}
   240  }
   241  ```
   242  
   243  #### Server code
   244  
   245  ```go
   246  // SayHello Bidirectional streaming,SayHello passes pb.Greeter_SayHelloServer as a parameter, returns error
   247  // pb.Greeter_SayHelloServer provides interfaces such as Recv() and SendAndClose() for streaming interaction 
   248  func (s *greeterServerImpl) SayHello(gs pb.Greeter_SayHelloServer) error {
   249      var names []string
   250      for {
   251          // Call Recv in a loop
   252          in, err := gs.Recv()
   253          if err == nil {
   254              log.Infof("receive hi, %s\n", in.Name)
   255          }
   256        
   257          if err == io.EOF {
   258              log.Infof("recveive error io eof %v\n", err)
   259              // EOF means that the client stream message has been sent
   260              gs.Send(&pb.HelloReply{Message: "hello " + strings.Join(names, ",")})
   261              return nil
   262          }
   263          if err != nil {
   264              log.Errorf("receive from %v\n", err)
   265              return err
   266          }
   267          names = append(names, in.Name)
   268      }
   269  }
   270  ```
   271  
   272  #### Client code
   273  
   274  ```go
   275  func main() {
   276      proxy := pb.NewGreeterClientProxy(opts...)
   277      cstream, err := proxy.SayHello(ctx, opts...)
   278      if err != nil {
   279          log.Error("Error in stream sayHello %v", err)
   280          return
   281      }
   282      for i := 0; i < 10; i++ {
   283          // Keep sending messages.
   284          cstream.Send(&pb.HelloRequest{Name: "jesse" + strconv.Itoa(i)})
   285      }
   286      // Call CloseSend to indicate that the stream has ended.
   287      err = cstream.CloseSend()
   288      if err != nil {
   289          log.Infof("error is %v \n", err)
   290          return
   291      }
   292      for {
   293          // Continuously call Recv to receive server response.
   294          reply, err := cstream.Recv()
   295          if err == nil && reply != nil {
   296              log.Infof("reply is %s\n", reply.Message)
   297          }
   298          // Note that errors.Is(err, io.EOF) cannot be used here to determine the end of the stream.
   299          if err == io.EOF {
   300              log.Infof("recvice EOF: %v\n", err)
   301              break
   302          }
   303          if err != nil {
   304              log.Errorf("receive error from server :%v", err)
   305          }
   306      }
   307      if err != nil {
   308          log.Fatal(err)
   309      }
   310  }
   311  ```
   312  
   313  ## Flow control
   314  
   315  What happens if the sender's transmission speed is too fast for the receiver to handle? This can lead to receiver overload, memory overflow, and other issues.
   316  
   317  To solve this problem, tRPC implements a flow control feature similar to http2.0.
   318  
   319  - RPC flow control is based on a single stream, not overall connection flow control.
   320  - Similar to HTTP2.0, the entire flow control is based on trust in the sender.
   321  - The tRPC sender can set the initial window size (for a single stream). During tRPC stream initialization, the window size is sent to the receiver.
   322  - After receiving the initial window size, the receiver records it locally. For each DATA frame sent by the sender, the sender subtracts the size of the payload (excluding the frame header) from the current window size.
   323  - If the available window size becomes less than 0 during this process, the sender cannot send the frame without splitting it (unlike HTTP2.0) and the upper layer API becomes blocked.
   324  - After consuming 1/4 of the initial window size, the receiver sends feedback in the form of a feedback frame, carrying an incremental window size. After receiving the incremental window size, the sender adds it to the current available window size.
   325  - For frame priority, feedback frames are given higher priority than data frames to prevent blocking due to priority issues.
   326  
   327  Flow control is enabled by default, with a default window size of 65535. If the sender continuously sends data larger than 65535 (after serialization and compression), and the receiver does not call Recv, the sender will block. To set the maximum window size for the client to receive, use the client option `WithMaxWindowSize`.
   328  
   329  ```go
   330  opts := []client.Option{
   331      client.WithNamespace("Development"),
   332      client.WithMaxWindowSize(1 * 1024 * 1024),
   333      client.WithServiceName("trpc.test.helloworld.Greeter"),
   334          client.WithTarget(*target),
   335  }
   336  proxy := pb.NewGreeterClientProxy(opts...)
   337  ...
   338  ```
   339  
   340  If you want to set the server receiving window size, use server option `WithMaxWindowSize`
   341  
   342  ```go
   343  s := trpc.NewServer(server.WithMaxWindowSize(1 * 1024 * 1024))
   344  pb.RegisterGreeterService(s, &greeterServiceImpl{})
   345  if err := s.Serve(); err != nil {
   346      log.Fatal(err)
   347  }
   348  ```
   349  
   350  ## Warning
   351  
   352  ### Streaming services only support synchronous mode
   353  
   354  When a pb file defines both ordinary RPC methods and stream methods for the same service, setting the asynchronous mode will not take effect. Only synchronous mode can be used. This is because streams only support synchronous mode. Therefore, if you want to use asynchronous mode, you must define a service with only ordinary RPC methods.
   355  
   356  ### The streaming client must use `err == io.EOF` to determine the end of the stream
   357  
   358  It is recommended to use `err == io.EOF` to determine the end of a stream instead of `errors.Is(err, io.EOF)`. This is because the underlying connection may return `io.EOF` after disconnection, which will be encapsulated by the framework and returned to the business layer. If the business layer uses `errors.Is(err, io.EOF)` and receives a true value, it may mistakenly believe that the stream has been closed properly, when in fact the underlying connection has been disconnected and the stream has ended abnormally.
   359  
   360  ## Filter
   361  
   362  Stream filter refers to [trpc-go/filter](/filter).