Microservice architecture foundation

Keywords: Java Back-end architecture Microservices

Microservice Architecture Foundation (II)

To be continued

Continue the previous basic project for expansion

Load balancing and service invocation

  • Ribbon

Ribbon local load balancing client & nginx server load balancing difference
Nginx is server load balancing. All client requests will be handed over to nginx, and then nginx will forward the requests. That is, load balancing is realized by the server. It belongs to in-process load balancing.
Ribbon local load balancing, when calling the micro service interface, will obtain the registration information list in the registry and cache it to the JVM local, so as to realize the RPC remote service call technology locally. It belongs to centralized load balancing.

As long as the dependency is added, the Ribbon can be automatically integrated, which is why the previous project can use load balancing:

<!-- Service registration -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

You can find:

The difference between getforebject() & getforentity()

The return object of getForObject()/postForObject() is the object converted from the data in the response body, which can be basically understood as Json
The return object of getForEntity()/postForEntity() is the ResponseEntity object, which contains some important information in the response body, such as response header, response status code, response body, etc.

Simple test:

Add the following test code to the control layer of port 80

 @GetMapping("/test/book/id/{id}")
 public Object findBookById_1(@PathVariable("id") String id){
     ResponseEntity responseEntity = restTemplate.getForEntity(URL1+"/book/id/"+id,String.class);
     if(responseEntity.getStatusCode().is2xxSuccessful()){ // Judge status code information
         // Success code
         // return responseEntity.getBody();  Return response body data
         return responseEntity;
     }else{
         return responseEntity.getStatusCode();
     }
 }

Ribbon load balancing strategy

  • Polling (roundrobin rule)
  • Random rule
  • First obtain the service according to the roundrobin rule policy. If the service acquisition fails, retry within the specified time to obtain the available service (RetryRule)
  • For the extension of roundrobin rule, the faster the response speed, the greater the weight of the instance selection, and the easier it is to be selected (WeightResponseTimeRule)
  • First filter out the services in the circuit breaker tripping state due to multiple faults, and then select a service with the least concurrency (BestAvailableRelu)
  • First filter out the failed instances, and then select the instance with less concurrency (availability filtering rule)
  • The default rule is to composite judge the new capabilities in the region where the server is located and the availability of the server, and select the server (zoneavoidance rule)

In order to prevent conflicts with the @ Component annotation (because @ springbootapplication contains this annotation), you should at least use the policy above the parent directory of the directory where the annotation is located

First, create a policy class:


Policy class:

package cn.rule;

import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MySelfRule {
    @Bean // Inject IoC management
    public IRule myRule(){
        return new RandomRule(); // Select random strategy
    }
}

Modify the main startup class (add comments, fill in the name of load balancing target application and rule class settings):

package cn.wu;

import cn.rule.MySelfRule;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.ribbon.RibbonClient;

@SpringBootApplication
@EnableEurekaClient
@RibbonClient(name = "micro-service-1",configuration = MySelfRule.class)
public class MainApplication {
    public static void main(String[] args) {
        SpringApplication.run(MainApplication.class,args);
    }
}

Done, final test

  • OpenFeign

What is OpenFeign?
OpenFeign is a declarative Web service client, which makes it easier to write the We service client. With OpenFeign, you just need to create an interface and use annotations. Using OpenFeign in the Spring Cloud can use HTTP requests to access remote services, just like calling local methods. Developers are completely unaware that they are calling remote methods, let alone accessing HTTP requests.

What can OpenFeign do?
OpenFeign is designed to make it easier to write Java Http clients
When Ribbon+RestjTemplate is used earlier, the encapsulation of http requests by RestTemplate forms a set of template calling methods. However, in the actual development, because there may be more than one invocation of service dependencies, and often an interface will be invoked multiple times, the invocation of these dependent services is usually packaged for some client classes encapsulated by each microservice. Therefore, OpenFeign further encapsulates on this basis to help us define and implement the definition of dependent service interfaces. Under the implementation of OpenFeign, we only need to create an interface and configure it by annotation to complete the interface binding to the service provider, which simplifies the opening of automatically encapsulating the service call client when using the Spring Cloud Ribbon. At the same time, OpenFeign integrates Ribbon, which can define the service binding interface through OpenFeign, and implement service invocation gracefully and simply in a declarative way.

New service consumer module (main-service-8081):

Dependent configuration:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>Project</artifactId>
        <groupId>cn.wu</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>main-service-8081</artifactId>

    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-boot.version>2.3.7.RELEASE</spring-boot.version>
        <spring-cloud.version>Hoxton.SR9</spring-cloud.version>
    </properties>
    <dependencies>
        <!-- Entity class -->
        <dependency>
            <groupId>cn.wu</groupId>
            <artifactId>api-commons</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <!-- Service registration -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!-- openfeign rely on  -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.3.7.RELEASE</version>
                <configuration>
                    <mainClass>cn.wu.MainApplication8081</mainClass>
                </configuration>
                <executions>
                    <execution>
                        <id>repackage</id>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

application.yml:

server:
  port: 8081
eureka:
  client:
    register-with-eureka: true # Register yourself
    fetch-registry: true # To obtain the registration information, you need to retrieve the service
    service-url:
      # For the service port address of the cluster version, you can directly fill in all the server addresses
      defaultZone: http://eureka7001.cn:7001/eureka,http://eureka7002.cn:7002/eureka
spring:
  application:
    name: main-service-8081

Modify the main startup class:

package cn.wu;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringBootApplication
@EnableFeignClients // Open Feign
public class MainApplication8081 {
    public static void main(String[] args) {
        SpringApplication.run(MainApplication8081.class,args);
    }
}

Create a new Fegin service interface:

package cn.wu.service.feign;

import cn.wu.entity.Book;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;

// Pit: the @ Component annotation has been included here, and the Bean is named as the default name
// value represents the name of the micro service application
@FeignClient(value = "micro-service-1") // Using Feign
public interface BookService {
    @PostMapping("/book/add")
    boolean addBook(Book book);
    @GetMapping("/book/id/{id}")
    String findBookById(@PathVariable("id") String id);
}

New control layer:

package cn.wu.controller;

import cn.wu.entity.Book;
import cn.wu.service.fegin.BookService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;


@RestController
public class BookController {
    @Resource // injection
    private BookService bookService;

    @GetMapping("/book/id/{id}")
    public String findBookById(@PathVariable("id") String  id){
        return bookService.findBookById(id);
    }
    @PostMapping("/book/add")
    public boolean addBook(Book book){
        return bookService.addBook(book);
    }
}

Operation results:

Timeout control

Scenario Description: if the service provider's processing time is greater than the maximum waiting time of the service consumer, a 500 timeout error will appear
Therefore, it is necessary to improve the waiting time of service consumers

Simulation timeout

Add the following codes to the two service provider control layers:

// Test timeout control
@GetMapping("/test/timeout")
public String testTimeOut(){
    try {
        Thread.sleep(4000); // Time required to simulate business
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return "test Timeout";
}

Add the following code to the service consumer interface:

// Test timeout control
@GetMapping("/test/timeout")
String testTimeOut();

The service consumer adds the following code in the control layer:

@GetMapping("/test/timeout")
 public String testTimeOut(){
     return bookService.testTimeOut();
 }

By default, the result is timeout:

Add the application.yml configuration file, and finally restart:

feign:
  client:
    config:
      default:
        ConnectionTimeOut: 5000 # Maximum waiting time to connect to the network
        ReadTimeOut: 5000 # The maximum time available to read resources after a connection is established

Visit again and wait for a while. At this time, the result is:

Log printing

In short, it is to monitor and output the call of Fegin interface

Log level
NONE: by default, no logs are displayed
BASIC: only record the request method, URL, response status code and execution time
HEADERS: in addition to the information defined in BASIC, there are also header information of request and response
FULL: in addition to the information defined in HEAERS, there are also the body and metadata of the request and response

Add configuration class:

package cn.wu.config;

import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FeignConfig {
    @Bean // Injection IoC container management
    Logger.Level feignLoggerLevel(){
        return Logger.Level.FULL; // The return log level is detailed log
    }
}

application.yml add log configuration:

logging:
  level:
    cn.wu.service.feign.BookService: debug

After accessing the URL, the console outputs details:

2021-11-06 16:08:39.536  INFO 18720 --- [erListUpdater-0] c.netflix.config.ChainedDynamicProperty  : Flipping property: micro-service-1.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647
2021-11-06 16:08:42.641 DEBUG 18720 --- [nio-8081-exec-1] cn.wu.service.feign.BookService          : [BookService#testTimeOut] <--- HTTP/1.1 200 (4324ms)
2021-11-06 16:08:42.641 DEBUG 18720 --- [nio-8081-exec-1] cn.wu.service.feign.BookService          : [BookService#testTimeOut] connection: keep-alive
2021-11-06 16:08:42.641 DEBUG 18720 --- [nio-8081-exec-1] cn.wu.service.feign.BookService          : [BookService#testTimeOut] content-length: 13
2021-11-06 16:08:42.641 DEBUG 18720 --- [nio-8081-exec-1] cn.wu.service.feign.BookService          : [BookService#testTimeOut] content-type: text/plain;charset=UTF-8
2021-11-06 16:08:42.641 DEBUG 18720 --- [nio-8081-exec-1] cn.wu.service.feign.BookService          : [BookService#testTimeOut] date: Sat, 06 Nov 2021 08:08:42 GMT
2021-11-06 16:08:42.641 DEBUG 18720 --- [nio-8081-exec-1] cn.wu.service.feign.BookService          : [BookService#testTimeOut] keep-alive: timeout=60
2021-11-06 16:08:42.641 DEBUG 18720 --- [nio-8081-exec-1] cn.wu.service.feign.BookService          : [BookService#testTimeOut] 
2021-11-06 16:08:42.643 DEBUG 18720 --- [nio-8081-exec-1] cn.wu.service.feign.BookService          : [BookService#testTimeOut] test Timeout
2021-11-06 16:08:42.643 DEBUG 18720 --- [nio-8081-exec-1] cn.wu.service.feign.BookService          : [BookService#testTimeOut] <--- END HTTP (13-byte body)

Posted by cassius on Sat, 06 Nov 2021 15:58:08 -0700