What is the Ribbon

After looking at how to use native Feign, today we’ll look at Ribbon, another library developed by the Netflix team. The Ribbon and Feign have a lot in common. First, they are both HTTP clients at heart, and second, they both have retry, integrated circuit breakers, and more. The biggest difference is that the Ribbon has a built-in load balancer, which Feign does not.

This article will show you how to use the native Ribbon, which is native, not wrapped in layers of Spring.

Why use the Ribbon

Here we need to answer two questions:

  1. Why use an HTTP Client?
  2. Why build load balancer into HTTP Client?

The first of these is already covered in how to use native Feign, so let’s move on to the second without further ado.

We know that the Apache HTTP Client and Feign do not have a built-in load balancer. That is, the HTTP client does not have to have a built-in load balancer. Why does the Ribbon need to be special?

In fact, if you think about it, the Ribbon is mostly used for internal invocation, and this scenario has one big feature — the target service is clustered deployment. Typically, when a target service is invoked, we want requests to be distributed to each instance as evenly as possible. With the built-in load balancer, the Ribbon meets this requirement well, whereas the Apache HTTP Client and Feign do not.

Therefore, the load balancer is built into the HTTP client to provide load balancing support when the target service is deployed in a cluster.

Some people might say, you can just deploy a load balancer, why bother? Sure, you can do that. But it’s important to consider that mid-Tier services are much more demanding than Edge services, so you need a very high performance load balancer. In this sense, the Ribbon solution saves you the cost of deploying a load balancer independently.

How to use Ribbon

Project I use RxNettty wrote a simple HTTP interface (see cn. ZZS. Ribbon. RxUserServer) for the back of the example calls, this interface operation in 8080, 8081, 8082, the interface of the machine, used to simulate three different instances. So, if you want to test the examples in your project, start these three instances first.

http://127.0.0.1:8080/user/getUserById?userId= {username} request: userId = 1 response: the User (id = 1, name = zzs001, age = 18]Copy the code

As a reminder, the Ribbon API uses a lot of RxJava code. If you haven’t seen it before, you should know about it.

Project environment

OS: win 10

The JDK: 1.8.0 comes with _231

Maven: 3.6.3

IDE: Spring Tool Suite 4.6.1.release

Ribbon: 2.7.17

Used as an HTTP client

Like Feign, the Ribbon supports HTTP interface definitions using annotations. In addition, the Ribbon supports HTTP interface definitions using HttpRequestTemplate, HttpClientRequest, and other methods. Interested can go to the project source code.

The list of service instances is set through the ConfigurationManager. Now, when you look at ConfigurationManager, does it look familiar? We’ve covered this in detail in Eureka: Exploring Eureka’s powerful configuration system. Yes, the Ribbon uses the same configuration system. Note that the configuration system developed by the Netflix team provides dynamic configuration support (if you know how to use it, of course). For example, if you change the listOfServers through the ConfigurationManager during the run, The Ribbon senses this change and uses the latest listOfServers for load balancing, with only a slight delay.

// Use annotations to define the HTTP API
@ClientProperties(properties = { @Property(name="ReadTimeout", value="2000"), @Property(name="ConnectTimeout", value="1000"), @Property(name="MaxAutoRetries", value="1"), @Property(name="MaxAutoRetriesNextServer", value="2") }, exportToArchaius = true)
interface UserService {
    @TemplateName("getUserById")
    @Http( method = HttpMethod.GET, uri = "/user/getUserById? userId={userId}", headers = { @Header(name = "X-Platform-Version", value = "xyz"), @Header(name = "X-Auth-Token", value = "abc") })
    RibbonRequest<ByteBuf> getUserById(@Var("userId") String userId);
}

public class RxUserProxyTest {
    @Test
    public void testBase(a) throws InterruptedException {
        // Specify the address of the service instance
        / / key: + service ". Ribbon. "+ name of the configuration items (see com.netflix.client.config.Com monClientConfigKey)
        ConfigurationManager.getConfigInstance().setProperty(
                "UserService.ribbon.listOfServers"."127.0.0.1:8080,127.0.0.1:8081,127.0.0.1:8082");
        
        UserService userService = Ribbon.from(UserService.class);
        
        userService.getUserById("1")
            .toObservable()
            .subscribe(new Subscriber<Object>() {
                @Override
                public void onCompleted(a) {
                    LOG.info("onCompleted");
                }
                
                @Override
                public void onError(Throwable e) {
                    e.printStackTrace();
                }
                
                @Override
                public void onNext(Object t) {
                    LOG.info("onNext:{}", t);
                    if(t ! =null && t instanceofByteBuf) { LOG.info(ByteBuf.class.cast(t).toString(Charset.defaultCharset())); }}});// Since the request HTTP interface is asynchronous, let the test main thread sleep for a while
        Thread.sleep(10000); }}Copy the code

Default load balancing rules

To see how multiple requests are distributed across three instances, let’s now change the code and try to make six requests.

    @Test
    public void test01(a) throws InterruptedException {
        ConfigurationManager.getConfigInstance().setProperty(
                "UserService.ribbon.listOfServers"."127.0.0.1:8080,127.0.0.1:8081,127.0.0.1:8082");
            
        UserService userService = Ribbon.from(UserService.class);
        // Initiate multiple requests
        Observable<ByteBuf>[] requestList = new Observable[]{
                userService.getUserById("1").toObservable(),
                userService.getUserById("2").toObservable(),
                userService.getUserById("3").toObservable(),
                userService.getUserById("4").toObservable(),
                userService.getUserById("5").toObservable(),
                userService.getUserById("6").toObservable()
        };
        Observable.concat(Observable.from(requestList))
                .subscribe(subscriber);
        Thread.sleep(10000);
    }
Copy the code

Running the test, you can see that the six requests are evenly allocated to three instances.

In the log, you can see the default load balancing rules.

As can be seen from the source code, the default rule is essentially a RoundRobinRule. In addition, the Ribbon defines rules such as RandomRule and RetryRule for you to choose from.

public class AvailabilityFilteringRule {
    RoundRobinRule roundRobinRule = new RoundRobinRule();
}
Copy the code

User-defined load balancing rules

Custom load balancing rules need to be inherited com.net flix. Loadbalancer. AbstractLoadBalancerRule, and realize the method to choose. The rule I’ve defined here is that no matter how many instances there are, the default access is to the first one.

public class MyLoadBalancerRule extends AbstractLoadBalancerRule {
    @Override
    public Server choose(Object key) {
        
        ILoadBalancer lb = getLoadBalancer();
        
        List<Server> allServers = lb.getAllServers();
        
        return allServers.stream().findFirst().orElse(null); }}Copy the code

Then you just need to configure the custom rules through the ConfigurationManager.

    @Test
    public void test01(a) throws InterruptedException {
        ConfigurationManager.getConfigInstance().setProperty(
                "UserService.ribbon.listOfServers"."127.0.0.1:8080,127.0.0.1:8081,127.0.0.1:8082");
        // Configure a custom rule
        ConfigurationManager.getConfigInstance().setProperty(
                "UserService.ribbon.NFLoadBalancerRuleClassName"."cn.zzs.ribbon.MyLoadBalancerRule");
            
        UserService userService = Ribbon.from(UserService.class);
        
        Observable<ByteBuf>[] requestList = new Observable[]{
                userService.getUserById("1").toObservable(),
                userService.getUserById("2").toObservable(),
                userService.getUserById("3").toObservable(),
                userService.getUserById("1").toObservable(),
                userService.getUserById("2").toObservable(),
                userService.getUserById("3").toObservable()
        };
        Observable.concat(Observable.from(requestList))
                .subscribe(subscriber);
        Thread.sleep(10000);
    }
Copy the code

Running the test, you can see that all requests are assigned to the first instance. The user-defined load balancing rule takes effect.

Refresh the listOfServers dynamically

In the previous example, our service instance address, listOfServers, was written dead. However, in a real project, the number of instances and address of the target service would change, so we would need to update the listOfServers dynamically instead of writing them dead.

As mentioned above, the configuration system developed by Netflix supports dynamic configuration, so the simplest solution I can think of is to start a task that periodically pulls the latest list of instances from the registry, etc., and then stuff the new list into the ConfigurationManager.

For eureka Server as the registry of the project, the official provided an implementation scheme. Compared to the scheme I said above, this scheme logic is simple, but the implementation of more complex, source code readability is very high, interested friends can see.

    @Test
    public void testEureka(a) throws InterruptedException {
        // Specify the instance list to be obtained from Eureka
        ConfigurationManager.getConfigInstance().setProperty(
                "UserService.ribbon.NIWSServerListClassName"."com.netflix.niws.loadbalancer.DiscoveryEnabledNIWSServerList");
        ConfigurationManager.getConfigInstance().setProperty(
                "UserService.ribbon.DeploymentContextBasedVipAddresses"."UserService");
        
        // Initialize EurekaClient
        DiscoveryManager.getInstance().initComponent(new MyDataCenterInstanceConfig(), new DefaultEurekaClientConfig());
        
        UserService userService = Ribbon.from(UserService.class);
        userService.getUserById("1")
            .toObservable()
            .subscribe(subscriber);
        
        Thread.sleep(10000); }}Copy the code

conclusion

The Ribbon has other extendable features, such as circuit breakers and retries. If you’re interested, you can do your own analysis.

Finally, thanks for reading.

The resources

ribbon github

Related source code please move: github.com/ZhangZiShen…

This article original articles, reproduced please attach the original source link: www.cnblogs.com/ZhangZiShen…