Microservice framework: overview of microservices

Microservice framework (2): overview of microservices

Learning Objectives

  • Understanding the evolution of system architecture
  • Understanding the difference between RPC and Http
  • Master the simple use of HttpClient
  • Know what SpringCloud is
  • Set up Eureka Registration Center independently
  • Configure Robbin load balancing independently

1. System Architecture Evolution

With the development of the Internet, the scale of website application is expanding.The surge in demand is bringing technological pressure.As a result, the system architecture continues to evolve, upgrade and iterate.From single applications, to vertical splitting, to distributed services, to SOA, to today's hot micro-service architecture, to the surging Service Mesh led by Google.Should we travel farther in a microservice boat or just get by?

In fact, life is not only about the present, but also about poems and distant places.So today we look back at the history to see the evolution of the system architecture; grasp the present, learn the hottest technology architecture; look forward to the future, and strive to become a good Java engineer.

1.1. Centralized Architecture

When the site traffic is small, you only need one application to deploy all the features together to reduce deployment nodes and costs.At this time, the data access framework (ORM) used to simplify the add-delete inspection workload is the key to affect project development.

Problems:

  • Code coupling, development and maintenance difficulties
  • Unable to optimize for different modules
  • Cannot expand horizontally
  • Low single point error tolerance and poor concurrency

1.2. Vertical Split

As access increases and a single application fails to meet demand, in response to higher concurrency and business needs, we split the system based on business functions:

Advantage:

  • System split achieves traffic sharing and solves concurrency problem
  • Optimize for different modules
  • Facilitate horizontal scaling, load balancing, improved fault tolerance

Disadvantages:

  • The systems are independent of each other, there will be a lot of redevelopment, which will affect the efficiency of development

1.3. Distributed Services

As more and more vertical applications are applied, the interaction between applications is unavoidable. The core business is extracted as an independent service, which gradually forms a stable service center, enabling front-end applications to respond faster to changing market demands.Distributed calls to improve business reuse and integration are key at this point.

Advantage:

  • Extracting basic services and calling each other between systems improves code reuse and development efficiency

Disadvantages:

  • Higher coupling between systems, complex call relationships, and difficult to maintain

1.4. Service Governance (SOA)

As more and more services are available, capacity evaluation and waste of small service resources become more and more obvious, a dispatch center should be added to manage cluster capacity in real time based on access pressure to improve cluster utilization.The Resource Scheduling and Governance Center (SOA) for improving machine utilization is critical at this point

What problems have occurred before?

  • There are more and more services, and you need to manage the addresses of each service
  • Invocation relationships are complex, making it difficult to clarify dependencies
  • Too many services to manage service status dynamically

What does service governance do?

  • Service Registry for automatic service registration and discovery without having to record service addresses artificially
  • Service auto-subscription, service list auto-push, service call transparency, no dependency
  • Dynamically monitor service status monitoring reports and control service status artificially

Disadvantages:

  • There will be dependencies between services, and if something goes wrong, it will have a greater impact
  • Complex service relationship, difficult operation, maintenance, test deployment, does not conform to DevOps idea

1.5. Microservices

SOA, as mentioned earlier, is translated in English as service-oriented.Micro services, which seem to be services, are all about splitting up systems.So they are very easy to confuse, but there are some differences between them:

Features of Micro Services:

  • Single Responsibility: Each service in a microservice has a unique business capability to perform a single responsibility
  • Micro: The granularity of service splitting for micro services is very small, such as a user management can act as a service.Every service is small, but it has all five Zang organs.
  • Service Oriented: Service Oriented means that each service exposes its service interface API.It does not care about the technical implementation of the service, it is independent of the platform and language, and it does not limit what technology can be used to implement it, as long as the interface of Rest is provided.
  • Autonomy: Autonomy is the persuasion that services are independent of each other and do not interfere with each other.
    • Team independence: Each service is an independent development team with a small number of people.
    • Technical independence: because it is service-oriented, provides Rest interfaces, and uses any technology without interference from others
    • Front-end and back-end separate development: front-end and back-end separate development, providing a unified Rest interface, back-end no longer need to develop different interfaces for PC, mobile segment
    • Database Separation: Each service uses its own data source
    • Deployment is independent, although there are calls between services, service restart does not affect other services.Favors continuous integration and delivery.Each service is a separate component, reusable, replaceable, less coupling, and easy to maintain

Microsoft Service Architecture:

2. Remote Call Method

Both micro-services and SOA face remote calls between services.So what are the remote calls between services?

There are several common ways to make remote calls:

  • RPC: Remote Produce Call remote procedure call, similar to RMI.Custom data format, based on native TCP communication, is fast and efficient.Early web services, now popular dubbo, were typical of RPC

  • Http:http is actually a network transport protocol, based on TCP, which specifies the format of data transmission.Now the communication between client browser and server is basically based on Http protocol.It can also be used to make remote service calls.The disadvantage is that message encapsulation is bloated.

    The popular Rest style can now be achieved through the http protocol.

2.1. Understanding RPC

RPC (Remote Procedure Call) is a computer communication protocol.This protocol allows programs running on one computer to call subprograms from another without additional programming for this interaction by the programmer.In general, computer A provides a service, and computer B can invoke the service of computer A just like local service.

From the above concepts, we can know that there are two main points to achieve RPC:

  • Implement remote invocation of services from other computers
    • To make remote calls, data must be transferred over the network.A program provides services, B program passes request parameters to A through the network, A gets results after local execution, and then returns the results to B program.There are two things to look at here:
      • 1) What kind of network communication protocol is used?
        • Now more popular RPC frameworks use TCP as the underlying transport protocol
      • 2) What is the data transmission format?
        • When two programs communicate, the data transmission format must be agreed upon.It's like two people chatting in the same language, otherwise they can't communicate.Therefore, we must define the format of requests and responses.In addition, data transmission over the network requires serialization, so a uniform way of serialization needs to be agreed upon.
  • Call remote services as if they were local services
    • If it's just a remote call, it's not an RPC, because RPC emphasizes procedure calls, which should be transparent to the user. Users should not care about the details of the call. They can call a remote service just like calling a local service.So RPC must encapsulate the calling process

RPC Call Flow Chart:

To learn more about the RPC implementation, we recommend an article: Manually Implement RPC

2.2. Understanding Http

Http protocol: Hypertext transfer protocol, is an application layer protocol.The request format, response format, resource location and operation mode of network transmission are specified.However, it is not specified what network transport protocol is used at the bottom level, but now TCP is used as the bottom transport protocol.Speaking of this, you may feel that Http and RPC remote calls are very similar, they all communicate network in a specified data format, have requests, and respond.Yes, they are very similar in this point of view, but there are some slight differences.

  • RPC does not specify a data transfer format, which can be arbitrarily specified. Different RPC protocols may not have the same data format.
  • The path to resource location is also defined in Http, which is not required in RPC
  • Most importantly, RPC s need to be able to call remote services just like local services, that is, to encapsulate the calling process at the API level.The Http protocol does not have this requirement, so the details of requests, responses, and so on, need to be implemented by ourselves.
    • Advantages: RPC mode is more transparent and convenient for users.Http is more flexible, has no defined API and language, cross-language, cross-platform
    • Disadvantages: The RPC approach requires encapsulation at the API level, limiting the language environment for development.

For example, when we visit a website through a browser, we use the Http protocol.It's just that browsers help us with encapsulating requests, initiating requests, receiving responses, and parsing responses.If it's not through a browser, these things need to be done by yourself.

2.3. How to choose?

Since remote calls can be made in both ways, how do we choose?

  • In terms of speed, RPC is faster than http. Although the underlying layer is TCP, the information of the HTTP protocol is often bloated, but gzip compression can be used.
  • In terms of difficulty, RPC implementation is more complex and http is relatively simple
  • For flexibility, http is better because it doesn't care about implementation details, cross-platform, cross-language.

Therefore, both have different usage scenarios:

  • RPC is good if efficiency is required and the development process uses a unified technology stack.
  • If you need more flexibility, cross-language, cross-platform, http is obviously more appropriate

So how do we choose?

Micro-services, more emphasis is on independence, autonomy, flexibility.However, the RPC mode has more limitations.Therefore, in the framework of microservices, Http-based Rest-style services are generally used.

3.Http Client Tools

Now that Http is chosen for microservices, we need to consider ourselves for request and response processing.However, there are many HTTP client tools in the open source world that can help us do these things, such as:

  • HttpClient
  • OKHttp
  • URLConnection

Next, let's take a look at one of the more popular client tools: HttpClient

3.1.HttpClient

3.1.1. Introduction

HttpClient is an Apache product and a component under Http Components.

Official address: http://hc.apache.org/index.html

Characteristic:

  • Standard-based, pure Java language.Implement Http1.0 and Http1.1
  • All Http methods (GET, POST, PUT, DELETE, HEAD, OPTIONS, and TRACE) are implemented in an extensible object-oriented architecture
  • Supports HTTPS protocol.
  • Make transparent connections through the Http proxy.
  • Automatically process cookies in Set-Cookie s.

3.1.2. Use

Code example:

Initiate a get request:

    @Test
    public void testGet() throws IOException {
        HttpGet request = new HttpGet("http://www.baidu.com");
        String response = this.httpClient.execute(request, new BasicResponseHandler());
        System.out.println(response);
    }

Initiate a Post request:

@Test
public void testPost() throws IOException {
    HttpPost request = new HttpPost("http://www.oschina.net/");
    request.setHeader("User-Agent",
                      "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36");
    String response = this.httpClient.execute(request, new BasicResponseHandler());
    System.out.println(response);
}

Attempt to access http://localhost/xxx

This interface returns a User object

@Test
public void testGetPojo() throws IOException {
    HttpGet request = new HttpGet("http://localhost/xxx");
    String response = this.httpClient.execute(request, new BasicResponseHandler());
    System.out.println(response);
}

What we actually get is a json string:

{
    "id": 8,
    "userName": "xxx",
    "password": "xxx",
    "name": "xxx",
}

It's cumbersome that we still need to do Json deserialization manually if we want to get the object.

3.1.3.Json Conversion Tool

HttpClient requests data followed by a JSON string, and we need to deserialize the Json string into objects ourselves, which we will use the Jackson Json tool to do.

Jackson json is SpringMVC's built-in json processing tool, which has an ObjectMapper class that facilitates json processing:

Object to json

String json = mapper.writeValueAsString(user);

// json processing tool
    private ObjectMapper mapper = new ObjectMapper();
    @Test
    public void testJson() throws JsonProcessingException {
        User user = new User();
        user.setId(8L);
        user.setAge(21);
        user.setName("xxx");
        user.setUserName("xxx");
        // serialize
        String json = mapper.writeValueAsString(user);
        System.out.println("json = " + json);
    }

The result shows the json object that User converted to in the console.

json to normal object

User result = mapper.readValue(json, User.class);

// json processing tool
private ObjectMapper mapper = new ObjectMapper();
@Test
public void testJson() throws IOException {
    User user = new User();
    user.setId(8L);
    user.setAge(21);
    user.setName("xxx");
    user.setUserName("xxx");
    // serialize
    String json = mapper.writeValueAsString(user);

    // Deserialize, receiving two parameters: json data, Deserialized target class byte code
    User result = mapper.readValue(json, User.class);
    System.out.println("result = " + result);
}

Result:

The result shows the User object that json converted to in the console.

json to set

json to a collection is cumbersome because you cannot pass both the class of the collection and the class of the element to a parameter.

So Jackson built a type factory to solve this problem:

// json processing tool
private ObjectMapper mapper = new ObjectMapper();
@Test
public void testJson() throws IOException {
    User user = new User();
    user.setId(8L);
    user.setAge(21);
    user.setName("xxx");
    user.setUserName("xxx");

    // Serialize to get the json string of the object collection
    String json = mapper.writeValueAsString(Arrays.asList(user, user));

    // Deserialize, receiving two parameters: json data, Deserialized target class byte code
    List<User> users = mapper.readValue(json, mapper.getTypeFactory().constructCollectionType(List.class, User.class));
    for (User u : users) {
        System.out.println("u = " + u);
    }
}

json to any complex type

When object generic relationships are complex, type factories are also difficult.At this point, Jackson provides a TypeReference to receive type generics, and the underlying layer uses reflection to get the specific types on the generics.Implement data conversion.

// json processing tool
private ObjectMapper mapper = new ObjectMapper();
@Test
public void testJson() throws IOException {
    User user = new User();
    user.setId(8L);
    user.setAge(21);
    user.setName("xxx");
    user.setUserName("xxx");

    // Serialize to get the json string of the object collection
    String json = mapper.writeValueAsString(Arrays.asList(user, user));

    // Deserialize, receiving two parameters: json data, Deserialized target class byte code
    List<User> users = mapper.readValue(json, new TypeReference<List<User>>(){});
    for (User u : users) {
        System.out.println("u = " + u);
    }
}

3.3.Spring's estTemplate

Spring provides a RestTemplate template tool class that encapsulates Http-based clients and facilitates the serialization and deserialization of objects and json s.RestTemplate does not limit the client type of Http, but is abstracted, with support for three commonly used types:

  • HttpClient
  • OkHttp
  • JDK native URLConnection (default)

First register a RestTemplate object in the project, which can be registered in the boot class location:

@SpringBootApplication
public class HttpDemoApplication {

	public static void main(String[] args) {
		SpringApplication.run(HttpDemoApplication.class, args);
	}

	@Bean
	public RestTemplate restTemplate() {
        // The default estTemplate, at the bottom, is the URLConnection of the JDK.
		return new RestTemplate();
	}
}

Direct @Autowired injection in the test class:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = HttpDemoApplication.class)
public class HttpDemoApplicationTests {

	@Autowired
	private RestTemplate restTemplate;

	@Test
	public void httpGet() {
		User user = this.restTemplate.getForObject("http://localhost/hello", User.class);
		System.out.println(user);
	}
}
  • By passing the url address and the byte code of the entity class through the getForObject() method of RestTemplate, RestTemplate automatically initiates a request, receives a response, and helps us deserialize the response results.
    Having learned the Http client tools, you can now officially learn about microservices.

4. Initial SpringCloud

Microservices are an architectural approach that ultimately necessitates a technical architecture to implement.

There are many ways to implement microservices, but Spring Cloud is the hottest.Why?

  • Background hard: As a member of the Spring family, with the entire Spring family barreled against the mountain, the background is very strong.
  • Strong technology: Spring, as the predecessor of Java, can be said to have great power.Strong technical team support, the average person really can't compare
  • Good mass base: It can be said that most programmers grow with the Spring framework, ask: How many companies are developing without Spring now?SpringCloud seamlessly integrates with Spring's frameworks, making everything a familiar recipe and taste for everyone.
  • Easy to use: I believe everyone appreciates the convenience SpringBoot brings to our development. SpringCloud fully supports SpringBoot development and can complete the micro-service framework with very few configurations

4.1. Introduction

SpringCloud is one of Spring's projects. Official address: http://projects.spring.io/spring-cloud/

Spring is best at integrating, bringing the best framework in the world into its own projects.

The same is true with SpringCloud, which combines some of the most popular technologies today and implements such functions as configuration management, service discovery, intelligent routing, load balancing, fuses, control bus, cluster state, and so on.Its main components include:

netflix

  • Eureka: Registry
  • Zuul: Service Gateway
  • Ribbon: Load Balancing
  • Feign: Service Call
  • Hystix: Fuse

The above is only part of it, architecture diagram:

Version 4.2

SpringCloud's version naming is special because it's not a component, but a collection of many components, named after a few words starting with the letters A through Z:


In our project, we will be in Finchley.

There are also versions of the components included, as shown in the following table:

Component Edgware.SR3 Finchley.RC1 Finchley.BUILD-SNAPSHOT
spring-cloud-aws 1.2.2.RELEASE 2.0.0.RC1 2.0.0.BUILD-SNAPSHOT
spring-cloud-bus 1.3.2.RELEASE 2.0.0.RC1 2.0.0.BUILD-SNAPSHOT
spring-cloud-cli 1.4.1.RELEASE 2.0.0.RC1 2.0.0.BUILD-SNAPSHOT
spring-cloud-commons 1.3.3.RELEASE 2.0.0.RC1 2.0.0.BUILD-SNAPSHOT
spring-cloud-contract 1.2.4.RELEASE 2.0.0.RC1 2.0.0.BUILD-SNAPSHOT
spring-cloud-config 1.4.3.RELEASE 2.0.0.RC1 2.0.0.BUILD-SNAPSHOT
spring-cloud-netflix 1.4.4.RELEASE 2.0.0.RC1 2.0.0.BUILD-SNAPSHOT
spring-cloud-security 1.2.2.RELEASE 2.0.0.RC1 2.0.0.BUILD-SNAPSHOT
spring-cloud-cloudfoundry 1.1.1.RELEASE 2.0.0.RC1 2.0.0.BUILD-SNAPSHOT
spring-cloud-consul 1.3.3.RELEASE 2.0.0.RC1 2.0.0.BUILD-SNAPSHOT
spring-cloud-sleuth 1.3.3.RELEASE 2.0.0.RC1 2.0.0.BUILD-SNAPSHOT
spring-cloud-stream Ditmars.SR3 Elmhurst.RELEASE Elmhurst.BUILD-SNAPSHOT
spring-cloud-zookeeper 1.2.1.RELEASE 2.0.0.RC1 2.0.0.BUILD-SNAPSHOT
spring-boot 1.5.10.RELEASE 2.0.1.RELEASE 2.0.0.BUILD-SNAPSHOT
spring-cloud-task 1.2.2.RELEASE 2.0.0.RC1 2.0.0.RELEASE
spring-cloud-vault 1.1.0.RELEASE 2.0.0.RC1 2.0.0.BUILD-SNAPSHOT
spring-cloud-gateway 1.0.1.RELEASE 2.0.0.RC1 2.0.0.BUILD-SNAPSHOT
spring-cloud-openfeign 2.0.0.RC1 2.0.0.BUILD-SNAPSHOT

Next, we'll learn about the important components of SpringCloud.

5. Simulation of micro-service scenarios

First, we need to simulate a scenario of service calls.Easy to learn later about microservice architecture

5.1. Service Providers

We've created a new project to provide inquiry services to users.

5.1.1.Spring Scaffold Creation Project

Add web, mybatis, JDBC, MySql dependencies:

Dependencies have also been fully automated:

<?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">
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.leyou.demo</groupId>
	<artifactId>user-service-demo</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<name>user-service-demo</name>
	<description>Demo project for Spring Boot</description>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.0.1.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-jdbc</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>1.3.2</version>
		</dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
</project>

Of course, to use a generic mapper, we need to add a dependency manually:

<dependency>
    <groupId>tk.mybatis</groupId>
    <artifactId>mapper-spring-boot-starter</artifactId>
    <version>2.0.2</version>
</dependency>

Very fast!

5.1.2. Writing code

Add an interface for external queries:

@RestController
@RequestMapping("user")
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("/{id}")
    public User queryById(@PathVariable("id") Long id) {
        return this.userService.queryById(id);
    }
}

Service:

@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    public User queryById(Long id) {
        return this.userMapper.selectByPrimaryKey(id);
    }
}

mapper:

@Mapper
public interface UserMapper extends tk.mybatis.mapper.common.Mapper<User>{
}

Entity class:

@Table(name = "tb_user")
public class User implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    // User name
    private String userName;

    // Password
    private String password;

    // Full name
    private String name;

    // Age
    private Integer age;

    // Gender, 1 male, 2 female
    private Integer sex;

    // Date of birth
    private Date birthday;

    // Creation Time
    private Date created;

    // Update Time
    private Date updated;

    // Remarks
    private String note;

   // ....Omit getters and setters
}

Property file, where we use yaml syntax instead of properties:

server:
  port: 8081
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/mydb01
    username: root
    password: xmgl0609
    hikari:
      maximum-pool-size: 20
      minimum-idle: 10
mybatis:
  type-aliases-package: com.leyou.userservice.pojo

Project structure:

5.1.3. Start and test:

Start the project, access interface: http://localhost:8080/xxx

5.2. Service Callers

5.2.1. Create a project

Similar to the above, let's not go into details here, but note that we call the user-service functionality, so we don't need mybatis-related dependencies.

pom:

<?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">
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.leyou.demo</groupId>
	<artifactId>user-consumer-demo</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<name>user-consumer-demo</name>
	<description>Demo project for Spring Boot</description>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.0.1.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
        <!-- Add to OkHttp Support -->
		<dependency>
			<groupId>com.squareup.okhttp3</groupId>
			<artifactId>okhttp</artifactId>
			<version>3.9.0</version>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

5.2.2. Writing code

First register RestTemplate in the startup class:

@SpringBootApplication
public class UserConsumerDemoApplication {

    @Bean
    public RestTemplate restTemplate() {
        // This time we used the OkHttp client, just inject it into the factory
        return new RestTemplate(new OkHttp3ClientHttpRequestFactory());
    }

    public static void main(String[] args) {
        SpringApplication.run(UserConsumerDemoApplication.class, args);
    }
}

Then write UserDao, noting that instead of calling mapper to look up the database, the interface in user-service-demo is queried remotely through RestTemplate:

@Component
public class UserDao {

    @Autowired
    private RestTemplate restTemplate;

    public User queryUserById(Long id){
        String url = "http://localhost:8081/user/" + id;
        return this.restTemplate.getForObject(url, User.class);
    }
}

Then write a user-service, looping through the UserDAO information:

@Service
public class UserService {

    @Autowired
    private UserDao userDao;

    public List<User> querUserByIds(List<Long> ids){
        List<User> users = new ArrayList<>();
        for (Long id : ids) {
            User user = this.userDao.queryUserById(id);
            users.add(user);
        }
        return users;
    }
}

Write controller:

@RestController
@RequestMapping("consume")
public class ConsumerController {

    @Autowired
    private UserService userService;

    @GetMapping
    public List<User> consume(@RequestParam("ids") List<Long> ids) {
        return this.userService.queryUserByIds(ids);
    }
}

5.2.3. Start the test:

Because we don't have a port configured, the default is 8080, we access: http://localhost:8080/consume?ids=6,7,8

A simple remote service call case is implemented.

5.3. Is there a problem?

To briefly review what we have just written:

  • use-service-demo: A microservice that provides a query for users based on their id
  • consumer-demo: A service caller who remotely calls user-service-demo through RestTemplate

The process is as follows:

What is the problem?

  • In consumer, we hard-coded the url address into the code for later maintenance
  • Consumerer needs to remember the address of the user-service. If a change occurs, it may not be notified and the address will become invalid
  • Consumerer does not know the status of user-service or service downtime
  • user-service has only one service and does not have high availability
  • Even if user-service s form clusters, consumer needs to achieve load balancing on its own

In fact, the above mentioned problems, to summarize, are the problems that distributed services must face:

  • Service Management
    • How to automatically register and discover
    • How to Realize State Supervision
    • How to implement dynamic routing url
  • How services achieve load balancing
  • How Services Solve Disaster Tolerance
  • How services are uniformly configured

All of these questions will be answered in SpringCloud.

6.Eureka Registry

6.1. Understanding Eureka

First, we will solve the first problem, the management of services.

problem analysis

In the previous case, user-service provides services to the outside world and needs to expose its address to the outside world.The consumer (caller) needs to record the address of the service provider.Address changes in the future require timely updates.It doesn't feel like much when there are few services, but in today's increasingly complex Internet environment, a project is bound to be split into dozens or even dozens of micro-services.Managing addresses manually at this time is not only difficult to develop, but also very difficult to test and publish in the future. This is contrary to DevOps'thinking.

Internet Appointment Car

It's like people can only call a taxi when they go out to call a car before the Internet Appointment.Some private cars that want to rent but are not qualified are called black cars.And many people want to book a car, but there are too few taxis to help.Private cars are numerous but dare not be blocked, and the streets are full of cars, who knows which one is willing to carry people.One want, one want to give, is lack of introduction and management.

At this time, an online car registration platform such as Drop Drop appears. All private cars that want to take passengers are registered with Drop Drop to record your car type (service type), identity information (contact method).In this way, private cars that provide services can be found everywhere at a glance.

To call a car at this time, just open the APP, enter your destination, select the type of car (service type), and automatically arrange a car to meet your needs to serve you, perfect!

What does Eureka do?

Eureka is like a drop-by-drop platform that manages and records information about service providers.Instead of looking for services themselves, service callers tell Eureka what they need, and Eureka tells you what services fit your needs.

At the same time, the heartbeat mechanism monitors between service providers and Eureka, and when a service provider has a problem, Eureka will naturally exclude it from the list of services.

This enables automatic registration, discovery, and status monitoring of services.

6.2. Schematic Diagram

Basic architecture:

  • Eureka: A service registry (which can be a cluster) that exposes its address to the outside world
  • Provider: Register your information (address, what service is provided) with Eureka after startup
  • Consumer: Subscribe to Eureka, which sends a list of all provider addresses for the corresponding service to the consumer and updates them regularly
  • Heartbeat (renewal): Providers regularly refresh their status to Eureka via http

6.3. Getting Started Cases

6.3.1. Writing Eureka Server

Next, we create a project to start an EurekaServer:

Still use spring's quick build tools and choose to rely on:

Complete Pom file:

<?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">
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.leyou.demo</groupId>
	<artifactId>eureka-demo</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<name>eureka-demo</name>
	<description>Demo project for Spring Boot</description>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.0.1.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
        <!-- SpringCloud Version, is up to date F series -->
		<spring-cloud.version>Finchley.RC1</spring-cloud.version>
	</properties>

	<dependencies>
        <!-- Eureka Server -->
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
		</dependency>
	</dependencies>

	<dependencyManagement>
		<dependencies>
            <!-- SpringCloud Dependency, must put in dependencyManagement Medium, just to manage versions -->
			<dependency>
				<groupId>org.springframework.cloud</groupId>
				<artifactId>spring-cloud-dependencies</artifactId>
				<version>${spring-cloud.version}</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

	<repositories>
		<repository>
			<id>spring-milestones</id>
			<name>Spring Milestones</name>
			<url>https://repo.spring.io/milestone</url>
			<snapshots>
				<enabled>false</enabled>
			</snapshots>
		</repository>
	</repositories>
</project>

Write the startup class:

@SpringBootApplication
@EnableEurekaServer // Declare that this application is an EurekaServer
public class EurekaDemoApplication {

	public static void main(String[] args) {
		SpringApplication.run(EurekaDemoApplication.class, args);
	}
}

Write the configuration:

server:
  port: 10086 # port
spring:
  application:
    name: eureka-server # Apply name, will be displayed in Eureka
eureka:
  client:
    register-with-eureka: false # Whether to register your own information to EurekaServer, default is true
    fetch-registry: false # Whether to pull information from other services, default is true
    service-url: # The address of the EurekaServer is now its own address, and if it is a cluster, you need to add the address of another Server.
      defaultZone: http://127.0.0.1:${server.port}/eureka

Start the service and access: http://127.0.0.1:10086/eureka

6.3.2. Register user-service with Eureka

Registering a service means adding a client dependency of Eureka to the service, and the client code automatically registers the service with Eureka Server.

We add Eureka client dependencies to user-service-demo:

Add SpringCloud dependencies first:

<!-- SpringCloud Dependency on -->
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>Finchley.RC1</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
<!-- Spring Warehouse address for -->
<repositories>
    <repository>
        <id>spring-milestones</id>
        <name>Spring Milestones</name>
        <url>https://repo.spring.io/milestone</url>
        <snapshots>
            <enabled>false</enabled>
        </snapshots>
    </repository>
</repositories>

Then the Eureka client:

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

Turn on Eureka client functionality on startup class

Turn on Eureka client functionality by adding @EnableDiscoveryClient

@SpringBootApplication
@EnableDiscoveryClient // Turn on the EurekaClient function
public class UserServiceDemoApplication {
	public static void main(String[] args) {
		SpringApplication.run(UserServiceDemoApplication.class, args);
	}
}

Write Configuration

server:
  port: 8081
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/mydb01
    username: root
    password: 123
    hikari:
      maximum-pool-size: 20
      minimum-idle: 10
  application:
    name: user-service # apply name
mybatis:
  type-aliases-package: com.leyou.userservice.pojo
eureka:
  client:
    service-url: # EurekaServer address
      defaultZone: http://127.0.0.1:10086/eureka
  instance:
    prefer-ip-address: true # When getHostname is called to get the hostname of an instance, ip is returned instead of the hostname
    ip-address: 127.0.0.1 # Specify your own ip information and look for it if you don't specify it

Be careful:

  • Here we add the spring.application.name attribute to specify the application name, which will be used as the application id in the future.
  • You do not need to specify register-with-eureka and fetch-registry because the default is true

Restart the project, access Eureka Monitoring Page See

We found the user-service service registered successfully

6.3.3. Consumers get services from Eureka

Next, we'll modify consumer-demo and try to get the service from EurekaServer.

Similar to consumers, you just need to add an EurekaClient dependency to your project to get information through the service name!

1) Add dependencies:

Add SpringCloud dependencies first:

<!-- SpringCloud Dependency on -->
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>Finchley.RC1</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
<!-- Spring Warehouse address for -->
<repositories>
    <repository>
        <id>spring-milestones</id>
        <name>Spring Milestones</name>
        <url>https://repo.spring.io/milestone</url>
        <snapshots>
            <enabled>false</enabled>
        </snapshots>
    </repository>
</repositories>

Then the Eureka client:

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

2) Open the Eureka client in the startup class

@SpringBootApplication
@EnableDiscoveryClient // Open Eureka Client
public class UserConsumerDemoApplication {
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate(new OkHttp3ClientHttpRequestFactory());
    }
    public static void main(String[] args) {
        SpringApplication.run(UserConsumerDemoApplication.class, args);
    }
}

3) Modify the configuration:

server:
  port: 8080
spring:
  application:
    name: consumer # apply name
eureka:
  client:
    service-url: # EurekaServer address
      defaultZone: http://127.0.0.1:10086/eureka
  instance:
    prefer-ip-address: true # Provide ip instead of hostname when other services get addresses
    ip-address: 127.0.0.1 # Specify your own ip information and look for it if you don't specify it

4) Modify the code and use the DiscoveryClient class method to get service instances according to the service name:

@Service
public class UserService {

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private DiscoveryClient discoveryClient;// Eureka client to get service instance information

    public List<User> queryUserByIds(List<Long> ids) {
        List<User> users = new ArrayList<>();
        // String baseUrl = "http://localhost:8081/user/";
        // Get service instance based on service name
        List<ServiceInstance> instances = discoveryClient.getInstances("user-service");
        // Because there is only one UserService, we get(0) directly
        ServiceInstance instance = instances.get(0);
        // Get ip and port information
        String baseUrl = "http://"+instance.getHost() + ":" + instance.getPort()+"/user/";
        ids.forEach(id -> {
            // We tested multiple queries,
            users.add(this.restTemplate.getForObject(baseUrl + id, User.class));
            // 500 milliseconds apart
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        return users;
    }
}

5) Debug Tracking Run:

Generated URL:

6.4.Eureka Details

Next, we will explain in detail the principle and configuration of Eureka.

6.4.1. Infrastructure

Three core roles in the Eureka architecture:

  • Service Registration Center

    The service-side application of Eureka, which provides service registration and discovery, is the eureka-demo we just built

  • Service Provider

    The applications that provide services can be SpringBoot applications or any other technology, as long as Rest-style services are available to the outside world.This is the user-service-demo we implemented

  • Service consumers

    Consumer apps get a list of services from the registry to learn about each service provider and where to invoke them.This is the consumer-demo we implemented

6.4.2. Highly available Eureka Server

Eureka Server is the registry for services. In the previous case, we only have one Eureka Server. In fact, Eureka Server can also be a cluster to form a highly available Eureka Center.

Service Synchronization

Multiple Eureka Servers are also registered as services with each other, and when a service provider registers with a node in the Eureka Server cluster, the node synchronizes information about the service to each node in the cluster, thereby enabling data synchronization.Therefore, no matter whether the client accesses any node in the Eureka Server cluster, the complete list of services can be obtained.

Build highly available Eureka Server manually

Let's assume you want to set up two clusters of Eureka Server with ports 10086 and 10087, respectively.

1) We modified the original EurekaServer configuration:

server:
  port: 10086 # port
spring:
  application:
    name: eureka-server # Apply name, will be displayed in Eureka
eureka:
  client:
    service-url: # Configure the address of other Eureka services, not yourself, such as 10087
      defaultZone: http://127.0.0.1:10087/eureka

The so-called highly available registry actually registers Eureka Server as a service so that multiple Eureka Servers can discover each other and form clusters.So we made the following changes:

  • Deleted register-with-eureka=false and fetch-registry=false configurations.Because the default value is true, you can register yourself with the registry.
  • Change the value of service-url to the address of another EurekaServer, not yourself

2) The other is the opposite:

server:
  port: 10087 # port
spring:
  application:
    name: eureka-server # Apply name, will be displayed in Eureka
eureka:
  client:
    service-url: # Configure the address of other Eureka services, not yourself, such as 10087
      defaultZone: http://127.0.0.1:10086/eureka

Note: An application in idea cannot start twice, we need to reconfigure a starter:

Then start it.

3) Start the test:

4) Client Registration Service to Cluster

Since there is more than one EurekaServer, the service-url parameter needs to change when registering a service:

eureka:
  client:
    service-url: # EurekaServer address, multiple addresses separated by', '
      defaultZone: http://127.0.0.1:10086/eureka,http://127.0.0.1:10087/eureka

6.4.3. Service Providers

The service provider registers the service with EurekaServer and completes work such as service renewal.

Service Registration

At startup, the service provider detects whether the eureka.client.register-with-erueka=true parameter in the configuration properties is correct, which in fact defaults to true.If the value is true, a Rest request is made to Eureka Server, which carries its own metadata information, which Eureka Server saves in a two-tier Map structure.The key of the first layer of Map is the name of the service, and the key of the second layer of Map is the instance id of the service.

Service Renewal

After the registration service is complete, the service provider maintains a heartbeat (periodically initiates a Rest request to Eureka Server) and tells Eureka Server, "I'm still alive."This is what we call a renew of the service;

There are two important parameters that can modify the behavior of a service renewal:

eureka:
  instance:
    lease-expiration-duration-in-seconds: 90
    lease-renewal-interval-in-seconds: 30
  • lease-renewal-interval-in-seconds: The interval between service renews, defaulting to 30 seconds
  • lease-expiration-duration-in-seconds: service expiration time, default 90 seconds

That is, by default, the service sends a heartbeat to the registry every 30 seconds to prove that it is still alive.If no heartbeat is sent for more than 90 seconds, EurekaServer will assume that the service is down and will be removed from the list of services. These two values do not need to be modified in the production environment and will be available by default.

But at development time, this value is a bit too long. Often when we turn off a service, we find that Eureka still thinks the service is alive.So we can adjust it appropriately during the development phase.

eureka:
  instance:
    lease-expiration-duration-in-seconds: 10 # Expires in 10 seconds
    lease-renewal-interval-in-seconds: 5 # 5 seconds heartbeat

Instance id

First, take a look at the service status information:

On the Eureka Monitoring page, view the service registration information:

In the status column, the following information is displayed:

  • UP(1): Represents that an example is now started without a cluster
  • DESKTOP-2MVEC12:user-service:8081: is the name of the example (instance-id),
    • The default format is: ${hostname} + ${spring.application.name} + ${server.port}
    • instance-id is the only criterion that distinguishes different instances of the same service and cannot be duplicated.

We can modify its composition by using the instance-id attribute:

eureka:
  instance:
    instance-id: ${spring.application.name}:${server.port}

Restart the service and try again:

6.4.4. Service consumers

Get a list of services

When the service consumer starts up, the value of the eureka.client.fetch-registry=true parameter is detected, and if true, a read-only backup from the list of Eureka Server services is cached locally.Data is retrieved and updated every 30 seconds.We can modify it with the following parameters:

eureka:
  client:
    registry-fetch-interval-seconds: 5

In a production environment, we do not need to modify this value.

However, in order to get the latest status of the service quickly in the development environment, we can set it a little smaller.

6.4.5. Failure culling and self-protection

Failure culling

Sometimes, our service provider may not be offline properly because of memory overflow, network failure, etc.Eureka Server needs to exclude such services from the list of services.Therefore, it will start a timed task to eliminate all invalid services (no response for more than 90 seconds) every 60 seconds.

It can be modified through the eureka.server.eviction-interval-timer-in-ms parameter in milliseconds, and the build environment should not be modified.

This will make a huge difference to our development, and it takes 60 seconds for Eureka to react to your service restart.The development phase can be adjusted appropriately, such as 10S

Self-protection

When we shut down a service, we see a warning in the Eureka panel:

[External chain picture transfer failed, source station may have anti-theft chain mechanism, it is recommended to save the picture and upload it directly (img-fSRViGPq-1580116963343)(assets/1525618396076.png)]

This triggered Eureka's self-protection mechanism.When a service fails to renew its heartbeat on time, Eureka counts whether more than 85% of service instances have failed a heartbeat in the last 15 minutes.In production environments, the percentage of instances with heart failure is likely to exceed the standard due to network latency, etc., but it is not appropriate to exclude the service from the list at this time because the service may not be down.Eureka protects the registration information for the current instance from being excluded.This works well in production environments, ensuring that most services are still available.

But this is a problem for our development, so we all turn off the self-protection mode during the development phase:

eureka:
  server:
    enable-self-preservation: false # Turn off self-protection mode (on by default)
    eviction-interval-timer-in-ms: 1000 # Interval between scans for failed services (default is 60*1000ms)

7. Load Balancing Robbin

In the previous case, we started a user-service, then used DiscoveryClient to get service instance information, and then acquired ip and port access.

However, in real-world environments, we tend to open many user-service clusters.At this point we will get more than one service in the list, which one should we visit?

In general, we need to write a load balancing algorithm to select from a list of multiple instances.

However, Eureka has helped us integrate the load balancing component: Ribbon, which can be used by simply modifying the code.

What is Ribbon:

Next, we'll use Ribbon for load balancing.

7.1. Start two service instances

First, we start two user-service instances, one 8081 and one 8082.

Eureka monitoring panel:

7.2. Turn on load balancing

Because Ribbon is already integrated in Eureka, we do not need to introduce new dependencies.Modify the code directly:

Add the @LoadBalanced comment to the RestTemplate configuration method:

@Bean
@LoadBalanced
public RestTemplate restTemplate() {
    return new RestTemplate(new OkHttp3ClientHttpRequestFactory());
}

Modify the invocation so that you don't get the ip and port manually anymore, but invoke it directly through the service name:

@Service
public class UserService {

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private DiscoveryClient discoveryClient;

    public List<User> queryUserByIds(List<Long> ids) {
        List<User> users = new ArrayList<>();
        // Write the service name directly at the address
        String baseUrl = "http://user-service/user/";
        ids.forEach(id -> {
            // We tested multiple queries,
            users.add(this.restTemplate.getForObject(baseUrl + id, User.class));
            // 500 milliseconds apart
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        return users;
    }
}

Visit the page to see the results

Perfect!

7.3. Source tracking

Why can we just enter the service name to access it?You also need to get ip and port before.

Apparently someone helped us get the ip and port of the service instance based on the service name.It's LoadBalancerInterceptor

We do source tracking:

Continue to follow the execute method: Discover the service that acquired port 8082

Next time, we found 8081:

7.4. Load Balancing Strategy

Ribbon's default load balancing strategy is simple polling, so we can test it:

Writing a test class, we see in the source code just now that the RibbonLoadBalanceClient is used for load balancing in interception, and there is a choose method, which is described as follows:

Now this is how load balancing gets instances.

We test the objects injected into this class:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = UserConsumerDemoApplication.class)
public class LoadBalanceTest {

    @Autowired
    RibbonLoadBalancerClient client;

    @Test
    public void test(){
        for (int i = 0; i < 100; i++) {
            ServiceInstance instance = this.client.choose("user-service");
            System.out.println(instance.getHost() + ":" + instance.getPort());
        }
    }
}

Result:

It fits our expectations and is really polling.

Can we modify the load balancing strategy?

Continue tracing the source code and find a code like this:

Let's see who this rule is:

The rule default here is a RoundRobinRule, see the introduction of the class:

That's not what polling means.

Notice that this class actually implements the interface IRule, so take a look:

Define the rule interface for load balancing.

It has the following implementations:

SpringBoot also provides us with a configuration entry to modify load balancing rules:

user-service:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

The format is: {service name}.ribbon.NFLoadBalancerRuleClassName, the value is the implementation class of IRule.

Test again and find that the result is random:

7.5. Retry mechanism

Eureka's service governance emphasizes AP, availability and reliability in CAP principles.The biggest difference between Eureka and a service governance framework like Zookeeper that emphasizes CP (consistency, reliability) is that Eureka sacrifices consistency for higher service availability, and in extreme cases it prefers to receive failure instances rather than lose health instances, as we mentioned above.

However, if we invoke these abnormal services at this time, the invocation will fail and other services will not work properly!This is obviously not what we would like to see.

We will now close a user-service instance:

Because of the delay in service culling, consumer will not get the latest service list immediately, and you will get an error prompt if you visit it again:

But at this time, the 8081 service is actually normal.

So Spring Cloud integrates Spring Retry to enhance the retry capability of RestTemplate, and when a service call fails, it does not throw it once but retries another service again.

Ribbon's retry can be accomplished with a simple configuration:

spring:
  cloud:
    loadbalancer:
      retry:
        enabled: true # Turn on the retry function of Spring Cloud
user-service:
  ribbon:
    ConnectTimeout: 250 # Ribbon's connection timeout
    ReadTimeout: 1000 # Ribbon's data read timeout
    OkToRetryOnAllOperations: true # Whether to retry all operations
    MaxAutoRetriesNextServer: 1 # Number of retries to switch instances
    MaxAutoRetries: 1 # Number of retries for the current instance

According to the configuration above, when access to a service times out, it will try to access the next service instance again, change it if it cannot, and return if it cannot.The number of switches depends on the value of the MaxAutoRetriesNextServer parameter

Introducing spring-retry dependencies

<dependency>
    <groupId>org.springframework.retry</groupId>
    <artifactId>spring-retry</artifactId>
</dependency>

We restarted user-consumer-demo and tested that even if user-service 2 was down, we could get results from another service instance!

44 original articles published, 18 praised, 4522 visited
Private letter follow

Tags: Spring JSON snapshot Maven

Posted on Sun, 02 Feb 2020 19:54:24 -0500 by dough boy