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).