springCloud starts from scratch

Posted by shutat on Fri, 10 Dec 2021 17:26:11 +0100

***

1. Understand springCloud

1.1. What is springCloud

Spring cloud is one of the projects under spring, Official website address: http://projects.spring.io/spring-cloud/

Spring is best at integration, taking the best framework in the world and integrating it into its own project.

Spring cloud is the same. It integrates some popular technologies and realizes functions such as configuration management, service discovery, intelligent routing, load balancing, fuse, control bus, cluster status and so on. The main components involved include:

Netflix:

  • Eureka: Registration Center
  • Zuul: service gateway
  • Ribbon: load balancing
  • Feign: service call
  • Hystrix: fuse

2. Build a micro service scenario

2.1. Create parent project

In microservices, multiple projects need to be created at the same time. I first create a parent project, and then subsequent projects take this project as the parent to realize maven aggregation. In this way, you can see all projects in one window and simulate the microservice scenario. In actual development, each microservice is an independent project.

Then write POM XML dependency:

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.3.RELEASE</version>
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <spring-cloud.version>Greenwich.SR1</spring-cloud.version>
        <mapper.starter.version>2.1.4</mapper.starter.version>
        <mysql.version>8.0.27</mysql.version>
        <pageHelper.starter.version>1.2.5</pageHelper.starter.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <!-- springCloud -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!-- currency Mapper starter -->
            <dependency>
                <groupId>tk.mybatis</groupId>
                <artifactId>mapper-spring-boot-starter</artifactId>
                <version>${mapper.starter.version}</version>
            </dependency>
            <!-- mysql drive -->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>${mysql.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>

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

2.2. Service provider

Create a new project to provide the service of querying users

2.2. 1. Create module(userService)

Add dependency:

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>tk.mybatis</groupId>
            <artifactId>mapper-spring-boot-starter</artifactId>
        </dependency>
    </dependencies>

2.2. 2. Write code

yml:

server:
  port: 8081
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/makeke
    username: root
    password: root
    driver-class-name: com.mysql.jdbc.Driver

Startup class:

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

Entity class:

@Table(name = "user")
@Data
public class User {

    @Id
    @KeySql(useGeneratedKeys = true)
    private String id;
    private String name;
    private String age;
    private String mate;
    private String job;
}

mapper:

public interface UserMapper extends Mapper<User> {
}

service:

@Service
public class UserServiceImpl implements IUserService {

    @Autowired
    private UserMapper userMapper;
    
    @Override
    public User selectById(String id) {
        return userMapper.selectByPrimaryKey(id);;
    }
}

controller:

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

    @Autowired
    private IUserService userService;
    @GetMapping("/{id}")
    public User selectById(@PathVariable("id") String id){
        return userService.selectById(id);
    }
}

Project structure:

Start test:

An error is reported when starting the project. The mapper is not found because the mapper package is not scanned. Start the project again after adding the annotation @ MapperScan("com. Make. Mapper") to the startup class:

2.3. Service caller

2.3. 1. Create module(userConsumer)

Add dependency:

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

2.3. 2. Write code

Startup class

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

    //Create a restTemplate object for httpClient remote call
    @Bean
    public RestTemplate restTemplate() {
        RestTemplate restTemplate = new RestTemplate();
        //The default code is iso-8859-1 and changed to utf-8
        restTemplate.getMessageConverters().add(1,new StringHttpMessageConverter(Charset.forName("utf-8")));
        return restTemplate;
    }
}

Write the controller, directly call RestTemplate in the controller, and remotely access the service interface of userService:

@RestController
@RequestMapping("/consumer")
public class ConsumerController {

    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/{id}")
    public User selectById(@PathVariable("id") String id){
        return restTemplate.getForObject("http://127.0.0.1:8081/user/" + id,User.class);
    }
}

result:

2.4. problem

The calling of two services can be realized through httpClient technology, but this method has great problems

What's the problem?

  • In the consumer, we hard code the url address into the code, which is not convenient for later maintenance
  • The consumer needs to remember the address of the userService. If there is a change, it may not be notified, and the address will become invalid
  • The consumer does not know the status of the userService, nor does it know that the service is down
  • userService has only one service and does not have high availability
  • Even if userservices form a cluster, consumer s need to achieve load balancing by themselves

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

  • Service management
    • How to automatically register and discover
    • How to realize status supervision
    • How to implement dynamic routing
  • How do services achieve load balancing
  • How does the service solve the problem of disaster recovery
  • How to realize unified configuration of services

Spring cloud provides solutions to the above problems

3.Eureka registry

renewal: renewal

  • Eureka server: a service registry (which can be a cluster) that exposes its own address.
  • Provider: register your information (address, service name, etc.) with Eureka after startup, and renew the service contract regularly
  • Consumer: the service caller will regularly go to Eureka to pull the service list, and then use the load balancing algorithm to select a service to call.
  • Heartbeat (renewal): the provider periodically updates its status to Eureka through http

3.1. Write EurekaServer

Dependency:

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

Write startup class:

@SpringBootApplication
@EnableEurekaServer // Declare that the application is an Eureka server
public class EurekaServerApplication {
	public static void main(String[] args) {
		SpringApplication.run(EurekaServerApplication.class, args);
	}
}

Write configuration:

server:
  port: 10086
spring:
  application:
    name: eureka-server # The application name will be used as the service id (serviceId) in Eureka
eureka:
  client:
    service-url: # The address of EurekaServer is now your own address. If it is a cluster, you need to write the addresses of other servers.
      defaultZone: http://127.0.0.1:10086/eureka
    register-with-eureka: false # Don't register yourself
    fetch-registry: false #No pull service

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

3.2. Service registration

Registering a service is to add Eureka's client dependency to the service. The client code will automatically register the service with Eureka server.

Add Eureka client dependency in userService module:

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

Enable Eureka client function on startup class

Enable Eureka client functionality by adding @ EnableDiscoveryClient

@SpringBootApplication
@MapperScan("com.makeke.mapper")
@EnableDiscoveryClient
public class UserServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserServiceApplication.class,args);
    }
}

Write configuration:

eureka:
  client:
    service-url: # EurekaServer address
      defaultZone: http://127.0.0.1:10086/eureka

Add spring application. Name attribute to specify the application name, which will be used as the id of the service in the future.

Restart project access: http://127.0.0.1:10086

3.3. Service discovery

Modify the calling end code and use the method of DiscoveryClient class to obtain the service instance according to the service name:

@RestController
@RequestMapping("/consumer")
public class ConsumerController {

    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private DiscoveryClient discoveryClient;

    @GetMapping("/{id}")
    public User selectById(@PathVariable("id") String id){
        List<ServiceInstance> userServiceList = discoveryClient.getInstances("userService");
        URI uri = userServiceList.get(0).getUri();
        return restTemplate.getForObject(uri +"/user/"+ id,User.class);
    }
}

3.4. Highly available EurekaServer

Multiple eurekaservers will also register as services. When the service provider registers with a node in EurekaServer cluster, the node will synchronize the service information to each node in the cluster, so as to realize high availability cluster. Therefore, no matter the client accesses any node in the Eureka Server cluster, it can obtain the complete service list information

Suppose we want to build two Eureka server clusters with ports 10086 and 10087 respectively

1) We modified the original EurekaServer configuration:

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

2) The other configuration is just the opposite:

server:
  port: 10087
spring:
  application:
    name: eureka-server # The application name will be used as the service id (serviceId) in Eureka
eureka:
  client:
    service-url: # The address of EurekaServer is now your own address. If it is a cluster, you need to write the addresses of other servers.
      defaultZone: http://127.0.0.1:10086/eureka

3) The client registers the service to the cluster

Since there is more than one EurekaServer, the service URL parameter needs to be changed when registering the service:

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

4) Start test:

  1. Service renewal:

After the registration service is completed, the service provider will maintain a heartbeat (regularly send a Rest request to EurekaServer) and tell EurekaServer: "I'm still alive". This is called renewal of service;

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

eureka:
  instance:    
    lease-renewal-interval-in-seconds: 30
    lease-expiration-duration-in-seconds: 90
  • Lease renewal interval in seconds: the interval of service renewal. The default is 30 seconds
  • Lease expiration duration in seconds: the default value is 90 seconds

In other words, by default, the service will send 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 consider the service down and remove it from the service list.

Topics: Java Spring Boot Spring Cloud