Abstract: the original address: zijiancode. Cn/archives/ri… Welcome to reprint, please keep the abstract when reprint, thank you!

The sample in this article uses the services in the Nacos article, which can be viewed either directly from the article or in conjunction with gitee code

Gitee: gitee.com/lzj960515/m…

What is the Ribbon

Ribbon is a client-side IPC(interprocess communication) library developed by Netflix that provides the following features

  • Load balancing
  • Fault tolerance
  • Supports multiple protocols (HTTP, TCP, and UDP)
  • Caching and batching

Github address: github.com/Netflix/rib…

What is client load balancing?

In inter-process communication, the service provider (server) has multiple instances, and the service consumer (client) autonomously selects an instance to initiate invocation through the load balancing algorithm, which is the load balancing of the client.

The counterpart is load balancing on the server side, as is common with Nginx

Introduction to Ribbon

1. Introduce Ribbon dependencies

<dependencies>
  <dependency>
    <groupId>com.netflix.ribbon</groupId>
    <artifactId>ribbon</artifactId>
    <version>2.3.0</version>
  </dependency>
  <dependency>
    <groupId>com.netflix.ribbon</groupId>
    <artifactId>ribbon-core</artifactId>
  </dependency>
  <dependency>
    <groupId>com.netflix.ribbon</groupId>
    <artifactId>ribbon-loadbalancer</artifactId>
  </dependency>
  <dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
  </dependency>
  <dependency>
    <groupId>io.reactivex</groupId>
    <artifactId>rxjava</artifactId>
  </dependency>
  <dependency>
    <groupId>io.reactivex</groupId>
    <artifactId>rxnetty</artifactId>
    <version>0.4.9</version>
  </dependency>
  <dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
  </dependency>
  <! -- used to send requests, you can also use JDK -->
  <dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-http</artifactId>
    <version>5.6.3</version>
  </dependency>
  <dependency>
    <groupId>commons-logging</groupId>
    <artifactId>commons-logging</artifactId>
    <version>1.2</version>
  </dependency>
</dependencies>
Copy the code

2. Write code

public class RibbonDemo {

    public static void main(String[] args) throws Exception {
        The first parameter is the number of times the same instance was called. The second parameter is the number of times other instances were tried again.
        final RetryHandler retryHandler = new DefaultLoadBalancerRetryHandler(0.1.true);
        List<Server> serverList = Lists.newArrayList(
                new Server("127.0.0.1".8083),
                new Server("127.0.0.1".8084));
        // Create a load balancer. The default load balancing algorithm is polling
        ILoadBalancer loadBalancer = LoadBalancerBuilder.newBuilder()
                .buildFixedServerListLoadBalancer(serverList);
        for (int i = 0; i < 6; i++) {
            String result = LoadBalancerCommand.<String>builder()
                    .withLoadBalancer(loadBalancer)
                    .withRetryHandler(retryHandler)
                    .build()
                    .submit(server -> {
                        String url = "http://" + server.getHost() + ":" + server.getPort() + "/integral/remain";
                        returnObservable.just(HttpUtil.get(url)); }).toBlocking().first(); System.out.println(result); }}}Copy the code

Here, I used the credits service of Nacos. Friends can choose a service by themselves.

3. The test

Create two instances, port 8083 and port 8084, test result:

Your current credits are: 31 Service port: 8084 Your current credits are: 78 Service port: 8083 Your current credits are: 37 Service port: 8084 Your current credits are: 31 Service port: 8083 Your current credits are: 59 Service port: 8084 54 Service port: 8083Copy the code

Stop the 8083 and test again

Your current credits are: 66 Service port: 8084 Your current credits are: 32 Service port: 8084 Your current credits are: 52 Service port: 8084 Your current credits are: 87 Service port: 8084 Your current credits are: 66 Service port: 8084 46 Service port: 8084Copy the code

It can be found that all six times are successful, and all the services are switched to port 8084

Test by changing the RetryHandler to the following configuration

RetryHandler retryHandler = new DefaultLoadBalancerRetryHandler(0, 0, true);
Copy the code
Your current integral is: 59 service port: 8084 Exception in the thread. "main" cn hutool. Core. IO. IORuntimeException: ConnectException: Connection refused (Connection refused)Copy the code

The service on port 8084 was called the first time, and an exception was thrown the second time

Spring Cloud integrates Ribbon

After understanding the basic usage of the Ribbon, the next step is to learn how to integrate it into the microservice system. Netflix also provides a Spring-cloud-starter-Netflix-Ribbon for quick integration into spring Cloud.

Github address: github.com/spring-clou…

Note: Select the 2.x tag. The 3.x branch has been removed from the Ribbon

The basic use

1. Introduce dependencies in the my-Order service

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
Copy the code

Nacos dependency has already been introduced when Nacos dependency is introduced

2. Configuration RestTemplate

@LoadBalanced
@Bean
public RestTemplate restTemplate(a){
  return new RestTemplate();
}
Copy the code

3. Initiate the call

restTemplate.getForObject("http://my-goods/goods/get", String.class);
Copy the code

That’s gone? Well, that’s gone

RestTemplate Interceptor

After viewing the basic usage, you must be surprised to see that you have added a @loadBalanced annotation.

Let’s talk a little bit about how it works.

In RestTemplate, the interceptor mechanism is implemented, for example

public class RestTemplateDemo {

    public static void main(String[] args) {
        RestTemplate restTemplate = new RestTemplate();
        List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
        interceptors.add(new RestTemplateInterceptor());
        restTemplate.setInterceptors(interceptors);
        System.out.println(restTemplate.getForObject("http://127.0.0.1:8083/integral/remain", String.class));
    }

    static class RestTemplateInterceptor implements ClientHttpRequestInterceptor {

        @Override
        public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
            URI uri = request.getURI();
            System.out.println(uri.getRawPath());
            returnexecution.execute(request, body); }}}Copy the code

A custom RestTemplateInterceptor is added before the call, which will enter the Intercept method when called

The @loadBalanced annotation adds an interceptor, LoadBalancerInterceptor, to the RestTemplate at service startup

Test effect:

12:43:33. 585. [the main] the DEBUG org. Springframework. Web. Client. The RestTemplate - HTTP GET http://127.0.0.1:8083/integral/remain 12:43:33. 603. [the main] the DEBUG org. Springframework. Web. Client. The RestTemplate - Accept = [text/plain, application/json, application/*+json, * / *] / integral/remain 12:43:33) 773 [main] the DEBUG org. Springframework. Web. Client. The RestTemplate - 200 OK Response 12:43:33. 775. [the main] the DEBUG org. Springframework. Web. Client. RestTemplate - Reading to [Java. Lang. String] as "text/plain; Charset = utF-8 "Your current score is: 83 Service port: 8083Copy the code

Now let’s simulate the Ribbon by replacing the server with a real IP address

public class RestTemplateDemo {

    public static void main(String[] args) {
        RestTemplate restTemplate = new RestTemplate();
        List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
        interceptors.add(new RestTemplateInterceptor());
        restTemplate.setInterceptors(interceptors);
        System.out.println(restTemplate.getForObject("http://my-goods/goods/get", String.class));
    }

    static class RestTemplateInterceptor implements ClientHttpRequestInterceptor {

        @SneakyThrows
        @Override
        public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
            return execution.execute(newMyRequestWrapper(request), body); }}static class MyRequestWrapper extends HttpRequestWrapper {
        /** * Emulated registry storage service information */
        private final Map<String, String> serverMap = Maps.newHashMap("my-goods"."127.0.0.1:8081");


        public MyRequestWrapper(HttpRequest request) {
            super(request);
        }

        @SneakyThrows
        @Override
        public URI getURI(a) {
            URI uri = super.getRequest().getURI();
            String server = uri.getHost();
            // Simulate fetching the real IP from the registry
            String host = serverMap.get(server);
            / / replace URI
            return new URI(uri.getScheme() + ": / /"+ host + uri.getRawPath()); }}}Copy the code

We define MyRequestWrapper and override the getURI method to replace the URI. This mimicked the Ribbon’s process of replacing service names with real IP addresses.

The logic to get service information from the serverMap can be further encapsulated to fetch multiple service information, using a load balancing policy to fetch one of them.

Load Balancing Policy

There are many load balancing policies implemented in the Ribbon, all of which are implemented in an IRule interface

RetryRule: A retry policy that contains an internal subRule. It uses subRule(polling by default) to select services. If a service is not available, the system tries again within the configured time to find available services or times out.

RoundRobinRule: indicates a polling policy.

WeightedResponseTimeRule: weighted according to the response time, the longer the response time, the smaller the weight, the lower the possibility of being chosen.

ZoneAvoidanceRule: The default load-balancing policy that gets a list of services based on their area and availability and then polls them. No region is equivalent to polling.

AvailabilityFilteringRule: using polling choose services, services for state judgment of choice, the filter has been connection service, until you find the normal service available.

BestAvaliableRule: Selects the service with the least concurrent requests.

RandomRule: a RandomRule strategy.

NacosRule: policy provided by Nacos, same cluster first policy.

use

Use @bean annotations

@Configuration
public class RuleConfiguration {

    @Bean
    public IRule rule(a){
        return newNacosRule(); }}Copy the code

The above methods take effect globally

If you want to set a different load policy for each service, do as follows:

Configuration mode

my-goods:
	ribbon:
  	NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule
Copy the code

Note that this way must with a service name, used alone ribbon. NFLoadBalancerRuleClassName will not take effect

User-defined load balancing policies

In addition to using the built-in load balancing policies in the framework, we can also implement our own policies, such as same-version-first call

@Slf4j
public class VersionRule extends AbstractLoadBalancerRule {

    @Autowired
    private NacosDiscoveryProperties nacosDiscoveryProperties;

    @Autowired
    private NacosServiceManager nacosServiceManager;

    @Override
    public Server choose(Object key) {
        try {
            // Get metadata information
            Map<String, String> metadata = this.nacosDiscoveryProperties.getMetadata();
            // Retrieve the version information
            String version = metadata.get("version");

            // Get the list of invoked services
            String group = this.nacosDiscoveryProperties.getGroup();
            DynamicServerListLoadBalancer loadBalancer = (DynamicServerListLoadBalancer) getLoadBalancer();
            String name = loadBalancer.getName();

            NamingService namingService = nacosServiceManager
                    .getNamingService(nacosDiscoveryProperties.getNacosProperties());
            List<Instance> instances = namingService.selectInstances(name, group, true);
            if (CollectionUtils.isEmpty(instances)) {
                log.warn("no instance in service {}", name);
                return null;
            }

            List<Instance> instancesToChoose = instances;
            if (StringUtils.isNotBlank(version)) {
                // Select services of the same version
                List<Instance> sameClusterInstances = instances.stream()
                        .filter(instance -> Objects.equals(version,
                                instance.getMetadata().get("version")))
                        .collect(Collectors.toList());
                if(! CollectionUtils.isEmpty(sameClusterInstances)) { instancesToChoose = sameClusterInstances; }else {
                    log.warn(
                            "A cross-cluster call occurs, name = {}, version = {}, instance = {}",
                            name, version, instances);
                }
            }

            Instance instance = ExtendBalancer.getHostByRandomWeight2(instancesToChoose);

            return new NacosServer(instance);
        }
        catch (Exception e) {
            log.warn("NacosRule error", e);
            return null; }}@Override
    public void initWithNiwsConfig(IClientConfig iClientConfig) {}}Copy the code

Add version configuration:

spring:
  application:
    name: my-order
  main:
    allow-bean-definition-overriding: true
  cloud:
    nacos:
      discovery:
        server-addr: 192.1681.11.: 8850
        namespace: public
        username: nacos
        password: nacos
        metadata:
          version: 1.0
Copy the code

Metadata is a map structure with customizable key-value pairs

Service fault tolerance

Sometimes the request fails due to network jitter or other problems. In this case, you can try again or invoke another service. You can add the following configuration

ribbon:
  Number of retries for the same service
  MaxAutoRetries: 1
  # Number of attempts to call other instances again
  MaxAutoRetriesNextServer: 1
  By default, only GET requests are retried
  OkToRetryOnAllOperations: true
  Service connection timeout
  ConnectTimeout: 1000
  Service response timeout
  ReadTimeout: 2000
  Otherwise, the configuration is invalid
  restclient:
    enabled: true
Copy the code

Complete configuration

ribbon:
  # hungry load, create client at startup
  eager-load:
    enabled: true
    clients:
      - my-goods
  Number of retries for the same service
  MaxAutoRetries: 1
  # Number of attempts to call other instances again
  MaxAutoRetriesNextServer: 1
  By default, only GET requests are retried
  OkToRetryOnAllOperations: false
  Service connection timeout
  ConnectTimeout: 1000
  Service response timeout
  ReadTimeout: 2000
  restclient:
    enabled: true

my-goods:
  ribbon:
    NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule
Copy the code

For configuration information about the Ribbon, see the CommonClientConfigKey class

summary

This article introduces the Ribbon, what it is, how to use it, how to integrate it into Spring Cloud, and how to customize load balancing policies.

If you want to know more exciting content, welcome to pay attention to the public account: programmer AH Jian, AH Jian in the public account welcome your arrival ~