gRPC를 배워보자 4일차 - gRPC Server-side streaming RPC

gRPC

목록 보기
4/6

Server-side streaming RPC

  1. client측 java code: https://github.com/grpc/grpc-java/blob/master/examples/src/main/java/io/grpc/examples/routeguide/RouteGuideUtil.java
  2. server 측 golang code: https://github.com/grpc/grpc-go/blob/master/examples/route_guide/server/server.go

먼저 protocol buffer의 spec을 보도록 하자.

service RouteGuide {
  // A simple RPC.
  //
  // Obtains the feature at a given position.
  //
  // A feature with an empty name is returned if there's no feature at the given
  // position.
  rpc GetFeature(Point) returns (Feature) {}

  // A server-to-client streaming RPC.
  //
  // Obtains the Features available within the given Rectangle.  Results are
  // streamed rather than returned at once (e.g. in a response message with a
  // repeated field), as the rectangle may cover a large area and contain a
  // huge number of features.
  rpc ListFeatures(Rectangle) returns (stream Feature) {}
  ...
}

우리가 사용했던 simple grpc인 GetFeature과 달리 ListFeatures의 return type 부분을 보면 stream이라는 지시어를 볼 수 있다.

이는 Feature 데이터를 다량의 stream으로 전달하겠다는 것이다. 즉, 서버 측에서 단일 요청인 Rectangle을 받고 다량의 Feature stream 데이터를 전달한다는 것이다.

Server-side streaming RPC golang 서버 구현

routeGuideServerListFeatures만 구현 추가해주면 된다.

  • go-routeguide/server/routeguide_server.go
func (s *routeGuideServer) ListFeatures(rect *pb.Rectangle, stream grpc.ServerStreamingServer[pb.Feature]) error {
	for _, feature := range s.savedFeatures {
		if inRange(feature.Location, rect) {
			if err := stream.Send(feature); err != nil {
				return err
			}
		}
	}
	return nil
}

func inRange(point *pb.Point, rect *pb.Rectangle) bool {
	left := math.Min(float64(rect.Lo.Longitude), float64(rect.Hi.Longitude))
	right := math.Max(float64(rect.Lo.Longitude), float64(rect.Hi.Longitude))
	top := math.Max(float64(rect.Lo.Latitude), float64(rect.Hi.Latitude))
	bottom := math.Min(float64(rect.Lo.Latitude), float64(rect.Hi.Latitude))

	if float64(point.Longitude) >= left &&
		float64(point.Longitude) <= right &&
		float64(point.Latitude) >= bottom &&
		float64(point.Latitude) <= top {
		return true
	}
	return false
}

ListFeatures 메서드의 스펙은 RouteGuideServer을 보고 똑같이 쓰면 된다. 단, 지금의 경우 pb.Feature 타입에 대한 generic을 사용하고 있지만 generic 사용이 불가능한 버전에서는 호환성을 위해서 다음의 메서드도 있을 것이다.

  • go-routeguide/gorouteguide/route_guide_grpc.pb.go
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type RouteGuide_ListFeaturesServer = grpc.ServerStreamingServer[Feature]

이 타입을 사용해도 똑같이 동작한다.

func (s *routeGuideServer) ListFeatures(rect *pb.Rectangle, stream RouteGuide_ListFeaturesServer) error {
	for _, feature := range s.savedFeatures {
		if inRange(feature.Location, rect) {
			if err := stream.Send(feature); err != nil {
				return err
			}
		}
	}
	return nil
}

함수 구현을 보면 client로 부터 rect 데이터를 받고 rect안에 있다면 stream.Send로 다량의 stream을 보내주는 방식이다.

stream 변수인 grpc.ServerStreamServer을 보면 다음과 같다.

type ServerStreamingServer[Res any] interface {
	// Send sends a response message to the client.  The server handler may
	// call Send multiple times to send multiple messages to the client.  An
	// error is returned if the stream was terminated unexpectedly, and the
	// handler method should return, as the stream is no longer usable.
	Send(*Res) error

	// ServerStream is embedded to provide Context, SetHeader, SendHeader, and
	// SetTrailer functionality.  No other methods in the ServerStream should
	// be called directly.
	ServerStream
}

Send가 있고 ServerStream 구조체를 임베딩하고 있는 데, 주석에 볼 수 있듯이 서버 측에서는 Context, SetHeader, SendHeader, SetTrailer 기능 이외에는 사용하지 말도록 하자. 내부를 보면 Recv도 있는데 이런 건 호출하면 안된다는 것이다.

클라이언트 측으로 가기 전에 서버측의 데이터를 좀 더 추가해주도록 하자.

var exampleData = []byte(`[{
    "location": {
        "latitude": 407838351,
        "longitude": -746143763
    },
    "name": "Patriots Path, Mendham, NJ 07945, USA"
}, {
    "location": {
        "latitude": 408122808,
        "longitude": -743999179
    },
    "name": "101 New Jersey 10, Whippany, NJ 07981, USA"
}, {
    "location": {
        "latitude": 413628156,
        "longitude": -749015468
    },
    "name": "U.S. 6, Shohola, PA 18458, USA"
}, {
    "location": {
        "latitude": 419999544,
        "longitude": -740371136
    },
    "name": "5 Conners Road, Kingston, NY 12401, USA"
}, {
    "location": {
        "latitude": 414008389,
        "longitude": -743951297
    },
    "name": "Mid Hudson Psychiatric Center, New Hampton, NY 10958, USA"
}, {
    "location": {
        "latitude": 419611318,
        "longitude": -746524769
    },
    "name": "287 Flugertown Road, Livingston Manor, NY 12758, USA"
}, {
    "location": {
        "latitude": 406109563,
        "longitude": -742186778
    },
    "name": "4001 Tremley Point Road, Linden, NJ 07036, USA"
}, {
    "location": {
        "latitude": 416802456,
        "longitude": -742370183
    },
    "name": "352 South Mountain Road, Wallkill, NY 12589, USA"
}, {
    "location": {
        "latitude": 412950425,
        "longitude": -741077389
    },
    "name": "Bailey Turn Road, Harriman, NY 10926, USA"
}, {
    "location": {
        "latitude": 412144655,
        "longitude": -743949739
    },
    "name": "193-199 Wawayanda Road, Hewitt, NJ 07421, USA"
}, {
    "location": {
        "latitude": 415736605,
        "longitude": -742847522
    },
    "name": "406-496 Ward Avenue, Pine Bush, NY 12566, USA"
}, {
    "location": {
        "latitude": 413843930,
        "longitude": -740501726
    },
    "name": "162 Merrill Road, Highland Mills, NY 10930, USA"
}, {
    "location": {
        "latitude": 410873075,
        "longitude": -744459023
    },
    "name": "Clinton Road, West Milford, NJ 07480, USA"
}, {
    "location": {
        "latitude": 412346009,
        "longitude": -744026814
    },
    "name": "16 Old Brook Lane, Warwick, NY 10990, USA"
}, {
    "location": {
        "latitude": 402948455,
        "longitude": -747903913
    },
    "name": "3 Drake Lane, Pennington, NJ 08534, USA"
}, {
    "location": {
        "latitude": 406337092,
        "longitude": -740122226
    },
    "name": "6324 8th Avenue, Brooklyn, NY 11220, USA"
}, {
    "location": {
        "latitude": 406421967,
        "longitude": -747727624
    },
    "name": "1 Merck Access Road, Whitehouse Station, NJ 08889, USA"
}, {
    "location": {
        "latitude": 416318082,
        "longitude": -749677716
    },
    "name": "78-98 Schalck Road, Narrowsburg, NY 12764, USA"
}, {
    "location": {
        "latitude": 415301720,
        "longitude": -748416257
    },
    "name": "282 Lakeview Drive Road, Highland Lake, NY 12743, USA"
}, {
    "location": {
        "latitude": 402647019,
        "longitude": -747071791
    },
    "name": "330 Evelyn Avenue, Hamilton Township, NJ 08619, USA"
}, {
    "location": {
        "latitude": 412567807,
        "longitude": -741058078
    },
    "name": "New York State Reference Route 987E, Southfields, NY 10975, USA"
}, {
    "location": {
        "latitude": 416855156,
        "longitude": -744420597
    },
    "name": "103-271 Tempaloni Road, Ellenville, NY 12428, USA"
}, {
    "location": {
        "latitude": 404663628,
        "longitude": -744820157
    },
    "name": "1300 Airport Road, North Brunswick Township, NJ 08902, USA"
}, {
    "location": {
        "latitude": 407113723,
        "longitude": -749746483
    },
    "name": ""
}, {
    "location": {
        "latitude": 402133926,
        "longitude": -743613249
    },
    "name": ""
}, {
    "location": {
        "latitude": 400273442,
        "longitude": -741220915
    },
    "name": ""
}, {
    "location": {
        "latitude": 411236786,
        "longitude": -744070769
    },
    "name": ""
}, {
    "location": {
        "latitude": 411633782,
        "longitude": -746784970
    },
    "name": "211-225 Plains Road, Augusta, NJ 07822, USA"
}, {
    "location": {
        "latitude": 415830701,
        "longitude": -742952812
    },
    "name": ""
}, {
    "location": {
        "latitude": 413447164,
        "longitude": -748712898
    },
    "name": "165 Pedersen Ridge Road, Milford, PA 18337, USA"
}, {
    "location": {
        "latitude": 405047245,
        "longitude": -749800722
    },
    "name": "100-122 Locktown Road, Frenchtown, NJ 08825, USA"
}, {
    "location": {
        "latitude": 418858923,
        "longitude": -746156790
    },
    "name": ""
}, {
    "location": {
        "latitude": 417951888,
        "longitude": -748484944
    },
    "name": "650-652 Willi Hill Road, Swan Lake, NY 12783, USA"
}, {
    "location": {
        "latitude": 407033786,
        "longitude": -743977337
    },
    "name": "26 East 3rd Street, New Providence, NJ 07974, USA"
}, {
    "location": {
        "latitude": 417548014,
        "longitude": -740075041
    },
    "name": ""
}, {
    "location": {
        "latitude": 410395868,
        "longitude": -744972325
    },
    "name": ""
}, {
    "location": {
        "latitude": 404615353,
        "longitude": -745129803
    },
    "name": ""
}, {
    "location": {
        "latitude": 406589790,
        "longitude": -743560121
    },
    "name": "611 Lawrence Avenue, Westfield, NJ 07090, USA"
}, {
    "location": {
        "latitude": 414653148,
        "longitude": -740477477
    },
    "name": "18 Lannis Avenue, New Windsor, NY 12553, USA"
}, {
    "location": {
        "latitude": 405957808,
        "longitude": -743255336
    },
    "name": "82-104 Amherst Avenue, Colonia, NJ 07067, USA"
}, {
    "location": {
        "latitude": 411733589,
        "longitude": -741648093
    },
    "name": "170 Seven Lakes Drive, Sloatsburg, NY 10974, USA"
}, {
    "location": {
        "latitude": 412676291,
        "longitude": -742606606
    },
    "name": "1270 Lakes Road, Monroe, NY 10950, USA"
}, {
    "location": {
        "latitude": 409224445,
        "longitude": -748286738
    },
    "name": "509-535 Alphano Road, Great Meadows, NJ 07838, USA"
}, {
    "location": {
        "latitude": 406523420,
        "longitude": -742135517
    },
    "name": "652 Garden Street, Elizabeth, NJ 07202, USA"
}, {
    "location": {
        "latitude": 401827388,
        "longitude": -740294537
    },
    "name": "349 Sea Spray Court, Neptune City, NJ 07753, USA"
}, {
    "location": {
        "latitude": 410564152,
        "longitude": -743685054
    },
    "name": "13-17 Stanley Street, West Milford, NJ 07480, USA"
}, {
    "location": {
        "latitude": 408472324,
        "longitude": -740726046
    },
    "name": "47 Industrial Avenue, Teterboro, NJ 07608, USA"
}, {
    "location": {
        "latitude": 412452168,
        "longitude": -740214052
    },
    "name": "5 White Oak Lane, Stony Point, NY 10980, USA"
}, {
    "location": {
        "latitude": 409146138,
        "longitude": -746188906
    },
    "name": "Berkshire Valley Management Area Trail, Jefferson, NJ, USA"
}, {
    "location": {
        "latitude": 404701380,
        "longitude": -744781745
    },
    "name": "1007 Jersey Avenue, New Brunswick, NJ 08901, USA"
}, {
    "location": {
        "latitude": 409642566,
        "longitude": -746017679
    },
    "name": "6 East Emerald Isle Drive, Lake Hopatcong, NJ 07849, USA"
}, {
    "location": {
        "latitude": 408031728,
        "longitude": -748645385
    },
    "name": "1358-1474 New Jersey 57, Port Murray, NJ 07865, USA"
}, {
    "location": {
        "latitude": 413700272,
        "longitude": -742135189
    },
    "name": "367 Prospect Road, Chester, NY 10918, USA"
}, {
    "location": {
        "latitude": 404310607,
        "longitude": -740282632
    },
    "name": "10 Simon Lake Drive, Atlantic Highlands, NJ 07716, USA"
}, {
    "location": {
        "latitude": 409319800,
        "longitude": -746201391
    },
    "name": "11 Ward Street, Mount Arlington, NJ 07856, USA"
}, {
    "location": {
        "latitude": 406685311,
        "longitude": -742108603
    },
    "name": "300-398 Jefferson Avenue, Elizabeth, NJ 07201, USA"
}, {
    "location": {
        "latitude": 419018117,
        "longitude": -749142781
    },
    "name": "43 Dreher Road, Roscoe, NY 12776, USA"
}, {
    "location": {
        "latitude": 412856162,
        "longitude": -745148837
    },
    "name": "Swan Street, Pine Island, NY 10969, USA"
}, {
    "location": {
        "latitude": 416560744,
        "longitude": -746721964
    },
    "name": "66 Pleasantview Avenue, Monticello, NY 12701, USA"
}, {
    "location": {
        "latitude": 405314270,
        "longitude": -749836354
    },
    "name": ""
}, {
    "location": {
        "latitude": 414219548,
        "longitude": -743327440
    },
    "name": ""
}, {
    "location": {
        "latitude": 415534177,
        "longitude": -742900616
    },
    "name": "565 Winding Hills Road, Montgomery, NY 12549, USA"
}, {
    "location": {
        "latitude": 406898530,
        "longitude": -749127080
    },
    "name": "231 Rocky Run Road, Glen Gardner, NJ 08826, USA"
}, {
    "location": {
        "latitude": 407586880,
        "longitude": -741670168
    },
    "name": "100 Mount Pleasant Avenue, Newark, NJ 07104, USA"
}, {
    "location": {
        "latitude": 400106455,
        "longitude": -742870190
    },
    "name": "517-521 Huntington Drive, Manchester Township, NJ 08759, USA"
}, {
    "location": {
        "latitude": 400066188,
        "longitude": -746793294
    },
    "name": ""
}, {
    "location": {
        "latitude": 418803880,
        "longitude": -744102673
    },
    "name": "40 Mountain Road, Napanoch, NY 12458, USA"
}, {
    "location": {
        "latitude": 414204288,
        "longitude": -747895140
    },
    "name": ""
}, {
    "location": {
        "latitude": 414777405,
        "longitude": -740615601
    },
    "name": ""
}, {
    "location": {
        "latitude": 415464475,
        "longitude": -747175374
    },
    "name": "48 North Road, Forestburgh, NY 12777, USA"
}, {
    "location": {
        "latitude": 404062378,
        "longitude": -746376177
    },
    "name": ""
}, {
    "location": {
        "latitude": 405688272,
        "longitude": -749285130
    },
    "name": ""
}, {
    "location": {
        "latitude": 400342070,
        "longitude": -748788996
    },
    "name": ""
}, {
    "location": {
        "latitude": 401809022,
        "longitude": -744157964
    },
    "name": ""
}, {
    "location": {
        "latitude": 404226644,
        "longitude": -740517141
    },
    "name": "9 Thompson Avenue, Leonardo, NJ 07737, USA"
}, {
    "location": {
        "latitude": 410322033,
        "longitude": -747871659
    },
    "name": ""
}, {
    "location": {
        "latitude": 407100674,
        "longitude": -747742727
    },
    "name": ""
}, {
    "location": {
        "latitude": 418811433,
        "longitude": -741718005
    },
    "name": "213 Bush Road, Stone Ridge, NY 12484, USA"
}, {
    "location": {
        "latitude": 415034302,
        "longitude": -743850945
    },
    "name": ""
}, {
    "location": {
        "latitude": 411349992,
        "longitude": -743694161
    },
    "name": ""
}, {
    "location": {
        "latitude": 404839914,
        "longitude": -744759616
    },
    "name": "1-17 Bergen Court, New Brunswick, NJ 08901, USA"
}, {
    "location": {
        "latitude": 414638017,
        "longitude": -745957854
    },
    "name": "35 Oakland Valley Road, Cuddebackville, NY 12729, USA"
}, {
    "location": {
        "latitude": 412127800,
        "longitude": -740173578
    },
    "name": ""
}, {
    "location": {
        "latitude": 401263460,
        "longitude": -747964303
    },
    "name": ""
}, {
    "location": {
        "latitude": 412843391,
        "longitude": -749086026
    },
    "name": ""
}, {
    "location": {
        "latitude": 418512773,
        "longitude": -743067823
    },
    "name": ""
}, {
    "location": {
        "latitude": 404318328,
        "longitude": -740835638
    },
    "name": "42-102 Main Street, Belford, NJ 07718, USA"
}, {
    "location": {
        "latitude": 419020746,
        "longitude": -741172328
    },
    "name": ""
}, {
    "location": {
        "latitude": 404080723,
        "longitude": -746119569
    },
    "name": ""
}, {
    "location": {
        "latitude": 401012643,
        "longitude": -744035134
    },
    "name": ""
}, {
    "location": {
        "latitude": 404306372,
        "longitude": -741079661
    },
    "name": ""
}, {
    "location": {
        "latitude": 403966326,
        "longitude": -748519297
    },
    "name": ""
}, {
    "location": {
        "latitude": 405002031,
        "longitude": -748407866
    },
    "name": ""
}, {
    "location": {
        "latitude": 409532885,
        "longitude": -742200683
    },
    "name": ""
}, {
    "location": {
        "latitude": 416851321,
        "longitude": -742674555
    },
    "name": ""
}, {
    "location": {
        "latitude": 406411633,
        "longitude": -741722051
    },
    "name": "3387 Richmond Terrace, Staten Island, NY 10303, USA"
}, {
    "location": {
        "latitude": 413069058,
        "longitude": -744597778
    },
    "name": "261 Van Sickle Road, Goshen, NY 10924, USA"
}, {
    "location": {
        "latitude": 418465462,
        "longitude": -746859398
    },
    "name": ""
}, {
    "location": {
        "latitude": 411733222,
        "longitude": -744228360
    },
    "name": ""
}, {
    "location": {
        "latitude": 410248224,
        "longitude": -747127767
    },
    "name": "3 Hasta Way, Newton, NJ 07860, USA"
}]`)

이제 서버측 구현은 완료되었고 클라이언트 측 구현을 해주도록 하자.

Server-side streaming RPC java 클라이언트 구현

java의 client 측 구현은 더욱 간단하다.

public class RouteGuideClient {
    private final RouteGuideGrpc.RouteGuideBlockingStub blockingStub;
    private final RouteGuideGrpc.RouteGuideStub asyncStub;

    private Random random = new Random();
    private TestHelper testHelper;

    ...

    public void listFeatures(int lowLat, int lowLon, int hiLat, int hiLon) {
        System.out.printf("*** ListFeatures: lowLat=%d lowLon=%d hiLat=%d hiLon={%d", lowLat, lowLon, hiLat, hiLon);

        Rectangle request =
                Rectangle.newBuilder()
                        .setLo(Point.newBuilder().setLatitude(lowLat).setLongitude(lowLon).build())
                        .setHi(Point.newBuilder().setLatitude(hiLat).setLongitude(hiLon).build()).build();
        Iterator<Feature> features;
        try {
            features = blockingStub.listFeatures(request);
            for (int i = 1; features.hasNext(); i++) {
                Feature feature = features.next();
                System.out.println("Result #" + i + ":" + feature);
                if (testHelper != null) {
                    testHelper.onMessage(feature);
                }
            }
        } catch (StatusRuntimeException e) {
            System.out.println("RPC failed: " + e.getStatus());
            if (testHelper != null) {
                testHelper.onRpcError(e);
            }
        }
    }
}

blockingStub을 사용했기 때문에 모든 응답이 올 때까지 기다린다는 특징이 있다.

listFeatures를 호출하고 Iterator<Feature>로 받아내는 것을 볼 수 있다. grpc stub이 만들어낸 listFeatures 구현을 보면 다음과 같다.

...
public java.util.Iterator<com.grpc.javarouteguide.grpc.routeguide.Feature> listFeatures(
        com.grpc.javarouteguide.grpc.routeguide.Rectangle request) {
      return io.grpc.stub.ClientCalls.blockingServerStreamingCall(
          getChannel(), getListFeaturesMethod(), getCallOptions(), request);
    }
  }
...

blockingServerStreamingCall static 메서드를 호출하는 것을 볼 수 있다. 따라가면 다음의 메서드 형식이 나온다.

...
public static <ReqT, RespT> Iterator<RespT> blockingServerStreamingCall(Channel channel, MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, ReqT req) {
        ClientCall<ReqT, RespT> call = channel.newCall(method, callOptions.withOption(STUB_TYPE_OPTION, ClientCalls.StubType.BLOCKING));
        BlockingResponseStream<RespT> result = new BlockingResponseStream<RespT>(call);
        asyncUnaryRequestCall(call, req, result.listener());
        return result;
    }
...

응답으로 Iterator<RespT> 을 반환하는 것을 볼 수 있다. 제네릭으로 <ReqT, RespT>를 받는데, ReqTRectangle이고 RespTFeature이다.

iterator를 통해서 순회하면서 데이터를 읽는 코드인 것이다. 이제 코드를 실행시켜서 요청을 보내보도록 하자.

@SpringBootApplication
public class JavaRouteguideApplication {
    public static void main(String[] args) throws InterruptedException {
        SpringApplication.run(JavaRouteguideApplication.class, args);

        String target = "localhost:50051";
        List<Feature> features;

        ManagedChannel channel = Grpc.newChannelBuilder(target, InsecureChannelCredentials.create()).build();
        try {
            RouteGuideClient client = new RouteGuideClient(channel);
            client.listFeatures(400000000, -750000000, 420000000, -730000000);
        } finally {
            channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS);
        }
    }
}

실행시켜보면 다음과 같은 결과가 나올 것이다.

*** ListFeatures: lowLat=400000000 lowLon=-750000000 hiLat=420000000 hiLon={-730000000Result #1:name: "Patriots Path, Mendham, NJ 07945, USA"
location {
  latitude: 407838351
  longitude: -746143763
}

...

Result #100:name: "3 Hasta Way, Newton, NJ 07860, USA"
location {
  latitude: 410248224
  longitude: -747127767
}

클라이언트 측 java에서 gRPC로 위도 경도에 대한 데이터를 잘 보냈고, 서버는 이에 대해서 해당하는 stream 데이터 100개를 전달해준 것이다.

server-side streaming 예제를 보았으니 이번에는 client-side streaming을 보도록 하자.

0개의 댓글