Spring rsocket: rsocket load balancing based on service registration discovery

Time:2021-4-14

Spring rsocket: rsocket load balancing based on service registration discovery

Author: Lei Juan
Source|Alibaba cloud official account

Rsocket distributed communication protocol is the core content of spring reactive. Since spring framework 5.2, rsocket has been a built-in function of spring. Spring boot 2.3 also adds spring boot starter rsocket, which simplifies the service writing and service calling of rsocket. The core architecture of rsocket communication includes two modes: broker agent mode and service direct connection mode.

The communication mode of broker is more flexible, such as Alibaba rsocket broker, which adopts event driven model architecture. At present, more architectures are service-oriented design, which is commonly referred to as service registration discovery and service direct connection communication mode. The most famous one is spring cloud technology stack, which involves configuration push, service registration discovery, service gateway, disconnection protection, etc. In the service-oriented distributed network communication, such as rest API, grpc and Alibaba Dubbo, they are well integrated with spring cloud. Users can complete a very stable distributed network communication architecture without caring about the underlying details of service registration discovery and client load balancing.

As a rising star of communication protocol, the core of rsocket is binary asynchronous message communication. Can it also be combined with spring cloud technology stack to realize service registration discovery and client load balancing, so as to realize service-oriented architecture more efficiently? In this article, we will discuss the combination of spring cloud and rsocket to realize service registration discovery and load balancing.

Service registration discovery

The principle of service registration discovery is very simple, mainly involving three roles: service provider, service consumer and service registry. The typical architecture is as follows:

Spring rsocket: rsocket load balancing based on service registration discovery

After the application is started, the service provider, such as rsocket server, will register the application related information with the service registry, such as application name, IP address, web server listening port number, etc. of course, it will also include some meta information, such as service group, service version, rsocket listening port number. If it is websocket communication, it also needs to provide WS Many developers will submit the service interface list of the service provider to the service registry as tags to facilitate subsequent service query and governance.

In this paper, we use consul as the service registry, mainly because consul is relatively simple and can be executed after downloadingconsul agent -devYou can start the corresponding service. Of course, you can use docker compose. The configuration is very simple, and thendocker-compose up -dYou can start the consul service.

When we register and query services with the service center, we need to have an application name corresponding to spring cloud, that is, the application name corresponding to spring bootspring.application.nameThe value of, here we call it the application name, that is, the subsequent service lookup is based on the application name. If you callReactiveDiscoveryClient.getInstances(String serviceId);When looking up the service instance list, the serviceid parameter is actually the application name of spring boot. Considering the coordination of service registration and subsequent rsocket service routing, and the convenience for you to understand, here we plan to design a simple naming specification.

Suppose you have a service application named calculator, which provides two services: math calculator service and exchange calculator service. How can we name the application and its corresponding service interface?

Here, we adopt the naming standard similar to java package, and use the way of domain name inversion. For example, the corresponding of calculator application iscom-example-calculatorStyle, why is the middle line, not the dot?.In DNS resolution, as a host name is illegal, it can only exist as a subdomain name, not as a host name. However, the current design of service registry follows DNS protocol, so we use the middle dash method to name the application. In this way, the combination of domain name inversion and application name can ensure that there will be no duplicate names between applications. In addition, it is convenient to convert the name of java package, that is, the-And.The mutual transformation between them.

How to name the service interface included in the application? The full name of service interface is composed of application name and interface name. The rules are as follows:

String serviceFullName = appName.replace("-", ".") + "." + serviceInterfaceName; 

For example, the following service naming is standard:

  • com.example.calculator.MathCalculatorService
  • com.example.calculator.ExchangeCalculatorService

andcom.example.calculator.math.MathCalculatorServiceIt is wrong because there is too much space between the application name and the interface namemath. Why use this naming convention? First, let’s look at how the service consumer invokes the remote service. Suppose the service consumer gets a service interface, such ascom.example.calculator.MathCalculatorServiceHow can he initiate a service call?

  • First, extract the app name according to the service, such ascom.example.calculator.MathCalculatorServiceThe appName corresponding to the service iscom-example-calculator. If there is no relationship between the application and the service interface, you may also need the application name to obtain the service provider information corresponding to the service interface, which will be relatively troublesome. If the interface name contains the corresponding application information, it will be much simpler. You can understand that the application is a part of the comprehensive service.
  • callReactiveDiscoveryClient.getInstances(appName)Get the service instance list (serviceinstance) corresponding to the application name. The serviceinstance object will contain other meta information such as IP address, web port number, rsocket listening port number, etc.
  • according toRSocketRequester.Builder.transports(servers)Build the rsocketrequester object with load balancing ability.
  • Use the full service name and specific function name as the route to call the rsocketrequester API. The sample code is as follows:

rsocketRequester .route("com.example.calculator.MathCalculatorService.square")  .data(number)  .retrieveMono(Integer.class)

Through the above naming specification, we can extract the application name from the full name of the service interface, and then interact with the service registry to find the corresponding instance list, then establish a connection with the service provider, and finally call the service based on the service name. The naming specification basically minimizes the dependency, and the developer is completely based on the service interface call, which is very simple.

Rsocket service writing

With the service naming specification and service registration, writing the rsocket service is still very simple, which is no different from writing a spring bean. introducespring-boot-starter-rsocketCreate a corresponding function name of mapannotation, and then add a mapannotation class as the basic code

@Controller @MessageMapping("com.example.calculator.MathCalculatorService") public class MathCalculatorController implements MathCalculatorService {     @MessageMapping("square")     public Mono<Integer> square(Integer input) {         System.out.println("received: " + input);         return Mono.just(input * input);     } }

The above code seems a bit strange. Since it is a service implementation, adding @ controller and @ message mapping seems a bit nondescript. Of course, these annotations reflect some technical details. You can also see that the service implementation of rsocket is based on spring message and is message oriented. Here, we just need to add a custom @ springrsocketservice annotation to solve this problem. The code is as follows:

@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Controller @MessageMapping() public @interface SpringRSocketService {     @AliasFor(annotation = MessageMapping.class)     String[] value() default {}; }

As like as two peas, we implemented the @SpringRSocketService annotation, which is exactly the same as the standard RPC service interface. In addition, @ springrsocketservice and @ rsockethandler are two annotations, which are also convenient for us to do some bean scanning and IDE plug-in assistance.

@SpringRSocketService("com.example.calculator.MathCalculatorService") public class MathCalculatorImpl implements MathCalculatorService {     @RSocketHandler("square")     public Mono<Integer> square(Integer input) {         System.out.println("received: " + input);         return Mono.just(input * input);     } }

Finally, let’s add the spring cloud starter consumer discovery dependency and set it bootstrap.properties , and then application.properties Set the port and meta information monitored by rsocket. We also send the list of service interfaces provided by the application to the service registry as tags. Of course, this is also convenient for our subsequent service management. Examples are as follows:

spring.application.name=com-example-calculator spring.cloud.consul.discovery.instance-id=com-example-calculator-${random.uuid} spring.cloud.consul.discovery.prefer-ip-address=true server.port=0 spring.rsocket.server.port=6565 spring.cloud.consul.discovery.metadata.rsocketPort=${spring.rsocket.server.port} spring.cloud.consul.discovery.tags=com.example.calculator.ExchangeCalculatorService,com.example.calculator.MathCalculatorService

After the rsocket service application is started, we can see the service registration information in the consult console. The screen capture is as follows:

Spring rsocket: rsocket load balancing based on service registration discovery

Rsocket client access

The client access is a little bit complicated, mainly based on the service interface. We need to do a series of related operations, but we already have the naming specification, so the problem is not big. The client application will also access the service registry, so that we can get theReactiveDiscoveryClientBean, and then according to the full name of the service interface, such ascom.example.calculator.ExchangeCalculatorServiceBuild a load balanced rsocketrequester.

The principle is also very simple. As mentioned earlier, the corresponding application name is obtained according to the full name of the service interface, and then called.ReactiveDiscoveryClient.getInstances(appName)Get the instance list corresponding to the service application, and then convert the service instance list to the loadalancetarget list of rsockt, which is actually POJO conversion. Finally, encapsulate the loadalancetarget list with flux (such as using sink interface) and pass it to the RSocketRequester.Builder Complete the rsocketrequester with load balancing capability Build, detailed code details, you can refer to the project’s code base.

Here, we should pay attention to how to perceive the changes in the server instance list, such as application up and down, service pause, etc. Here I use a timing task scheme to query the address list corresponding to the service. Of course, there are other mechanisms. If it is a standard spring cloud service discovery interface, client polling is required at present. Of course, it can also be combined with spring cloud bus or message middleware to monitor server list changes. If the client perceives the change of the service list, it only needs to call the sink interface of reactor to send a new list. After perceiving the change, rsocket load balance will automatically respond, such as closing the failing connection, creating a new connection, etc.

In the actual communication between applications, there will be some service providers not available, such as the service provider’s sudden downtime or its network is not available, which leads to some services in the service application list not available, so how will rsocket deal with this time? Don’t worry, rsocket load balance has a retrial mechanism. When a service call has connection and other exceptions, it will get a connection from the list again for communication, and the wrong connection will be marked as availability 0 and will not be used by subsequent requests. Service list push and fault-tolerant retrial mechanism during communication ensure the high availability of distributed communication.

Finally, let’s start the client app, and then launch a remote rsocket call from the client. The screen capture is as follows:

Spring rsocket: rsocket load balancing based on service registration discovery

Abovecom-example-calculatorThe service application includes three instances, and the service calls will be carried out alternately in these three instances (round robin strategy).

Some considerations of development experience

Although there are still some problems in the client-side development, load balancing and service use experience, we haven’t done any better.

1. Communication based on service interface

Most RPC communication is based on interface, such as Apache Dubbo, grpc and so on. So can rsocket do it? The answer is that it can. On the server side, we have implemented the rsocket service based on the service interface. Next, we only need to implement the call based on the interface on the client side. For Java developers, this is not a big problem. We only need to build it based on Java proxy mechanism, and the invocationhandler corresponding to proxy will use rsocketrequester to implement the function call of invoke(). Please refer to theRSocketRemoteServiceBuilder.javaIn addition, the client app module also contains the bean implementation based on interface call.

2. Single parameter problem of service interface function

When using rsocketrequester to call the remote interface, the corresponding processing function can only accept a single parameter, which is similar to the design of grpc. Of course, the support of different object serialization frameworks is also considered. However, considering the actual use experience, it may involve multi parameter functions, so that the caller can have a better development experience. How to deal with it at this time? In fact, after Java 1.8, the default function is allowed to be added to the interface. We can add some more user-friendly default functions without affecting the service communication interface. The example is as follows:

public interface ExchangeCalculatorService {     double exchange(ExchangeRequest request);     default double rmbToDollar(double amount) {         return exchange(new ExchangeRequest(amount, "CNY", "USD"));     } }

Through the default method of the interface, we can provide convenient functions for callers. For example, in the network transmission, byte array is used. However, in the default function, we can add file object support to facilitate callers to use. The function API in the interface is responsible for the service communication protocol, and the default function improves the user’s experience. The combination of the two can easily solve the problem of multi parameter function. Of course, the default function can also be used as the outpost of data verification to a certain extent.

3. Rsocket broker support

As we mentioned earlier, rsocket also has a broker architecture, that is, the service provider is hidden behind the broker, and the request is mainly accepted by the broker, and then forwarded to the service provider for processing. An example of the architecture is as follows:

Spring rsocket: rsocket load balancing based on service registration discovery

So can the load balancing mechanism based on service discovery be mixed with rsocket Broker Mode? For example, some long tail or complex network applications can be registered with rsocket broker, and then the broker processes the request call and forwarding. In fact, this is not complicated. Previously, we talked about application and service interface naming specification. Here, we only need to add an application name prefix to solve this problem. Suppose we have an rsocker broker cluster, which is temporarily called broker 0 cluster. Of course, all instances of the broker cluster are registered in the service registry (such as consul). When calling the service on the rsocket broker, the service name is adjusted tobroker0:com.example.calculator.MathCalculatorService, that is, before the service nameappName:Such a prefix is actually another canonical form of URI. We can extract the application name before the colon, and then go to the service registry to query for the corresponding instance list of the application.  

Back to the scenario of broker interworking, we will query the service list corresponding to broker 0 to the service registry, and then create a connection with the instance list of broker 0 cluster, so that the subsequent service calls based on this interface will be sent to broker for processing, that is, the mixed use mode of service register discovery and broker mode is completed.

With the help of the association between the specified service interface and the application, it is also convenient for us to do some beta testscom.example.calculator.MathCalculatorServiceYou can use thecom-example-calculator-beta1:com.example.calculator.MathCalculatorServiceCall the service in this way, so that the traffic corresponding to the service call will be forwarded to the servercom-example-calculator-beta1The corresponding examples play the role of beta testing.

Back to the specification mentioned above, if you can’t achieve the binding relationship between the application name and the service interface, you can use this method to implement service invocation, such ascalculator-server:com.example.calculator.math.MathCalculatorServiceIt’s just that you need more complete documentation. Of course, this way can also solve the problem that the previous system is connected to the current architecture, and the cost of application migration is relatively small. If your previous service-oriented architecture design is also based on interface communication, there is no problem in migrating to rsocket through this way, and the client code adjustment is minimal.

summary

By integrating service registration discovery and combining with an actual naming specification, the elegant cooperation between service registration discovery and rsocket routing is completed. Of course, load balancing is also included. Compared with other RPC solutions, you don’t need to introduce RPC’s own service registry. You can reuse spring cloud’s service registry, such as Alibaba, Nacos, consult, Eureka and zookeeper, without unnecessary overhead and maintenance costs. If you want to know more about rsocket RPC, you can refer to spring’s official blog《Easy RPC with RSocket》

Welcome to the Alibaba rsocket broker group:

Spring rsocket: rsocket load balancing based on service registration discovery

For more detailed code details, you canClick the link to view the corresponding code base of the article!

Recommended Today

Review of SQL Sever basic command

catalogue preface Installation of virtual machine Commands and operations Basic command syntax Case sensitive SQL keyword and function name Column and Index Names alias Too long to see? Space Database connection Connection of SSMS Connection of command line Database operation establish delete constraint integrity constraint Common constraints NOT NULL UNIQUE PRIMARY KEY FOREIGN KEY DEFAULT […]