Microservice spring cloud five-piece integration, eureka, rabbitMQ, hystrix, zuul, config, feign

Table of contents

1. What are microservices?

2. Eureka Registration Center

1. What is a registration center?

2. Specific configuration and code

3. Rabbit implements load balancing

1. The difference between rabbit and Nginx

2. Specific configuration and code

4. hystrix fuse

1. What is hystrix fuse?

2. Specific configuration and code

5. zuul routing

1. What is zuul routing?

2. Specific configuration and code

6. Introduction to feign

1. What is feign?

2. Specific configuration and code

7. Config configuration

1. What is Config configuration?

2. Specific configuration and code

8. RabbitMQ message middleware

1. What is message middleware?

2. Installation of RabbitMQ

3. RabbitMQ workflow

4. Basic configuration and code of the producer

 5. How to ensure reliable delivery of messages (reliable production)

6. Consumer consumption news (reliable consumption)

7. Dead letter queue

Summarize


1. What are microservices?

Many people call the two situations microservices. One is to deploy the code in layers, such as the controller layer, server layer, entity layer, etc. are deployed on different servers. Many people also call this microservice architecture. , but I personally don’t agree with this statement, because microservices have several basic concepts, ① each service must have its own separate database, ② if one of the microservices collapses or is disconnected, other microservices can continue Normal operation, unaffected. Neither of the above two are satisfied, so I personally think this cannot be called a microservice architecture. The second is to split different modules or functions of a project and deploy them on different servers. The communication between the two services is implemented using message middleware or other stable and reliable communication technologies, such as: separate the payment module. It is made into a microservice, and the user's personal information is made into a separate service. When needed, communication is completed through the middleware of the message, such as publishing messages and subscribing to consume messages. In this way, several projects can share a microservice business, or they can The pressure resistance and scalability of the system are greatly improved. Even if one of the microservices is disconnected, the others will not be affected, and repeated development of the business can be avoided. There are many benefits, but the cost of the server will also increase accordingly, and developers also need to have certain technical requirements.

Before starting, take a look at the entire project directory:

2. Eureka Registration Center

1. What is a registration center?

You can think of the registration center as a unified administrator, where all microservices are registered, and then accessed directly through the microservice name, just like spring manages dependencies.

2. Specific configuration and code

maven dependency (only eureka uses eureka-server, because it manages registration, the others are eureka-client client registrants, and all registrants need to add eureka-client dependencies, I will not post this later Rely on it repeatedly, otherwise the length of this blog will be beyond the sky)

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

Three Eureka distributed services are used here

Service 1: springcloud-eureka-01

server:
  port: 9001

spring:
  application:
    name: springcloud-eureka-01

eureka:
  client:
    register-with-eureka: false #是否向自己注册
    service-url:
      defaultZone: http://springcloud-eureka-02:9002/eureka,http://springcloud-eureka-03:9003/eureka
    fetch-registry: false #是否检索其他服务
  instance:
    hostname: springcloud-eureka-01

Service 2: springcloud-eureka-02

server:
  port: 9002

spring:
  application:
    name: springcloud-eureka-02

eureka:
  client:
    register-with-eureka: false #是否向自己注册
    service-url:
      defaultZone: http://springcloud-eureka-01:9001/eureka/,http://springcloud-eureka-03:9003/eureka/
    fetch-registry: false #是否检索其他服务
  instance:
    hostname: springcloud-eureka-02

Service 3: springcloud-eureka-03

server:
  port: 9003

spring:
  application:
    name: springcloud-eureka-03

eureka:
  client:
    register-with-eureka: false #是否向自己注册
    service-url:
      defaultZone: http://springcloud-eureka-01:9001/eureka,http://springcloud-eureka-02:9002/eureka/
    fetch-registry: false #是否检索其他服务
  instance:
    hostname: springcloud-eureka-03

The startup classes of the three services are the same

package com.bug.springcloudeureka02;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@EnableEurekaServer
@SpringBootApplication
public class SpringcloudEureka02Application {

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

}

After Eureka is configured, there is basically no need to do anything else. It only needs to manage the registration of other services. Start it and see the effect!

 3. Rabbit implements load balancing

1. The difference between rabbit and Nginx

Those who can learn microservices should have some basic development knowledge. I believe they have all used nginx. Those who have not used it also have some understanding. nginx forwards requests on the server side. After the request enters nginx, it is then forwarded by nginx forwards requests to different service backends to achieve load balancing. Rabbit's load balancing is on the client side, and it is implemented through the registration list in Eureka. In microservices, don’t all microservices need to be registered in Eureka? Since all services are there, can requests be passed through different algorithms and handed over to different services for processing? Rabbit does this, and it also supports random, polling, weight, etc. algorithms.

2. Specific configuration and code

maven dependency

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            <version>2.2.6.RELEASE</version>
        </dependency>

We also use 3 services as an example to simulate a distributed environment (here are 3 service producers, producing messages)

The first:

server:
  port: 8001

spring:
  application:
    name: springcloud-service-01

eureka:
  client:
    register-with-eureka: true #是否向自己注册
    service-url:
      defaultZone: http://springcloud-eureka-01:9001/eureka/,http://springcloud-eureka-02:9001/eureka/,http://springcloud-eureka-03:9001/eureka/
    fetch-registry: true #是否检索其他服务
  instance:
    hostname: springcloud-service-01

Second station:

server:
  port: 8002

spring:
  application:
    name: springcloud-service-01

eureka:
  client:
    register-with-eureka: true #是否向自己注册
    service-url:
      defaultZone: http://springcloud-eureka-01:9001/eureka/,http://springcloud-eureka-02:9001/eureka/,http://springcloud-eureka-03:9001/eureka/
    fetch-registry: true #是否检索其他服务
  instance:
    hostname: springcloud-service-01

Channel 3:

server:
  port: 8003

spring:
  application:
    name: springcloud-service-01

eureka:
  client:
    register-with-eureka: true #是否向自己注册
    service-url:
      defaultZone: http://springcloud-eureka-01:9001/eureka/,http://springcloud-eureka-02:9001/eureka/,http://springcloud-eureka-03:9001/eureka/
    fetch-registry: true #是否检索其他服务
  instance:
    hostname: springcloud-service-01

Startup class (the startup classes are all the same, so I won’t repeat the code):

package com.bug.springcloudservice02;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@EnableEurekaClient
@SpringBootApplication
public class SpringcloudService02Application {

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

}

Specific controller (the controller code is also the same, the difference is that the printing is different: service provider 01 enters, service provider 02 enters, service provider 03 enters):

package com.bug.springcloudservice01.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class Service01Controller {

    @GetMapping("/testService")
    public String testService(){
        System.out.println("服务提供者01进入...");

        return "服务提供者01进入...";
    }

}

Consumer code, client simulates consumer call

maven dependencies:

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            <version>2.2.6.RELEASE</version>
        </dependency>

To use rabbit load balancing, you need to add a configuration file named: Client01Config

package com.bug.springcloudclient01.config;

import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import com.netflix.loadbalancer.RetryRule;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class Client01Config {

    @LoadBalanced //启用ribbon调用
    @Bean
    public RestTemplate restTemplate(){

        return new RestTemplate();
    }

    /**
     * ribbon的负载均衡策略配置
     * @return iRule
     */
    @Bean
    public IRule iRule(){

        return new RandomRule();//随机策略
        //return new RetryRule();//重试策略:正常轮训,出现问题后重试,重试不通过则调用其他服务提供者
    }

}

controller code

package com.bug.springcloudclient01.controller;

import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;

@RestController
public class Client01Controller {

    @Resource
    RestTemplate restTemplate;

    @GetMapping("/testClient01")
    public String testClient01(){

        String message = "消费者01进入...";
        String result = restTemplate.getForEntity("http://springcloud-service-01/testService",String.class).getBody();
        message += "调用服务提供者..." + result;
        System.out.println(message);

        return message;
    }

}

The startup class is the same, just add the @EnableEurekaClient annotation, so you don’t need to repeat the code.

The simulated three producers and one consumer are complete. Let’s take a look at the specific effects (visit a few more times, a random strategy is used here):

OK, done. I did a test and started seven services. My computer almost started to smoke. The computer fan kept buzzing and blowing...

After writing the entire article, I looked back and saw that the consumer and producer seemed to be written backwards. However, the load balancing code is no problem and the effect is the same. If I don’t change it, I will be too tired to write the entire article!

4. hystrix fuse

1. What is hystrix fuse?

A very simple requirement, what should you do if there is a bug in your code and the server reports an error? Feedback the error message directly to the user? Isn’t that too unfriendly? When users look at it, they think, oh, what kind of rubbish system is this? It’s so broken, so even your company is despised. What if it is to return a specified error page or return a specified error message to the user? This is what hystrix does, and it is very powerful because it can detect overloads, errors, etc. Without further ado, let’s go directly to the code (this is for technical testing, so it is only added on the client and consumer sides. Hystrix, if you want to add other places, it is the same).

2. Specific configuration and code

maven dependencies:

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-hystrix</artifactId>
            <version>1.3.0.RELEASE</version>
        </dependency>

controller code:

package com.bug.springcloudclient01.controller;

import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;

@RestController
public class Client01Controller {

    @Resource
    RestTemplate restTemplate;

    /**
     * 设置熔断时间,单位毫秒,超过设置时间没返回则触发error方法
     * ignoreExceptions = RuntimeException.class //忽略熔断,取消熔断
     */
    @HystrixCommand(fallbackMethod="error",
            commandProperties = {@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000")},
            ignoreExceptions = RuntimeException.class
    )
    @GetMapping("/testClient01")
    public String testClient01(){

        String message = "消费者01进入...";
        String result = restTemplate.getForEntity("http://springcloud-service-01/testService",String.class).getBody();
        message += "调用服务提供者..." + result;
        System.out.println(message);

        return message;
    }

    //hystrix抛出异常或者超时调用的方法
    public String error(Throwable throwable){
        System.out.println("服务出错啦...");
        return "服务出错啦...";
    }

}

It is still the consumer above, but a circuit breaker is added to it. When the specified time is exceeded, the following error method will be called. How to handle the error and what to return depends on the specific needs of the company.

Startup class code (just add the annotation @EnableCircuitBreaker to start circuit breaker, please post the code):

package com.bug.springcloudclient01;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@EnableCircuitBreaker
@EnableEurekaClient
@SpringBootApplication
public class SpringcloudClient01Application {

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

}

I won’t demonstrate this because I haven’t written down any specific business, just get it done and call it a day.

5. zuul routing

1. What is zuul routing?

Many people should know routing. If you don’t understand, you can search it on Baidu. After searching a lot, I won’t go into too much detail here. The core of zuul is actually a filter, but it can also implement routing, exception handling, etc., and through filters it can also implement dynamic routing, request monitoring, request authentication, grayscale publishing, etc., which is very powerful.

2. Specific configuration and code

maven dependencies:

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
            <version>2.2.1.RELEASE</version>
        </dependency>

application.yml configuration:

server:
  port: 5001

spring:
  application:
    name: springcloud-zuul-01

eureka:
  client:
    register-with-eureka: true #是否向自己注册
    service-url:
      defaultZone: http://springcloud-eureka-01:9001/eureka/,http://springcloud-eureka-02:9001/eureka/,http://springcloud-eureka-03:9001/eureka/
    fetch-registry: true #是否检索其他服务
  instance:
    hostname: springcloud-zuul-01
zuul:
  routes:
    springcloud-feign-02.path: /bug/**  #加上这个后,所有的请求就必须有/bug/这个路径前缀,不然就会404
  ignored-services: "*"

Startup class:

package com.bug.springcloudzuul01;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;

@EnableZuulProxy
@SpringBootApplication
public class SpringcloudZuul01Application {

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

}

A configuration file needs to be added, file name: ZuulFilter01

package com.bug.springcloudzuul01.filter;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;

@Component
public class ZuulFilter01 extends ZuulFilter {

    /**
     * 过滤器的在请求中的执行顺序,在哪个生命周期前执行,也可以自定义
     * @return pre:在路由之前执行,post、route、static
     */
    @Override
    public String filterType() {
        return "pre";
    }

    /**
     * 所有过滤器的执行顺序
     * @return int 0
     */
    @Override
    public int filterOrder() {
        return 0;
    }

    /**
     * 是否启动该过滤器,true执行,false不执行
     * @return boolean true
     */
    @Override
    public boolean shouldFilter() {
        return true;
    }

    /**
     * 过滤器的具体逻辑,可以在这里面做一些操作,如:增加token判断,没token的请求直接跳转到一个错误的页面
     * @return Object
     * @throws ZuulException 异常抛出
     */
    @Override
    public Object run() throws ZuulException {

        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();
        String token = request.getParameter("token");
        if(token == null){
            ctx.setSendZuulResponse(false);
            ctx.setResponseStatusCode(401);
            ctx.addZuulResponseHeader("content-type","text/html;charset=utf-8");
            ctx.setResponseBody("非法访问");
        }
        return null;
    }
}

There are comments in the code, so I won’t explain much, just get it done and call it a day!

6. Introduction to feign

1. What is feign?

Are you wondering, isn't the five-piece set of spring cloud eureka, rabbitMQ, zuul, hystrix, and Config? Why are rabbit and feign added? Hehe, this is the difference between theory and practice. Feign actually integrates rabbit and hystrix. Because it is not convenient to use them alone in actual development, some people integrated them, and feign came into being.

Rabbit and hystrix have already been discussed above, so I won’t repeat them here. The usage is similar. Let’s talk about how to use the factory method of feign fuse.

2. Specific configuration and code

maven dependencies:

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            <version>2.2.6.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>com.buession.springcloud</groupId>
            <artifactId>buession-springcloud-common</artifactId>
            <version>2.0.2</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-feign</artifactId>
            <version>1.4.3.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
            <version>2.1.1.RELEASE</version>
        </dependency>

We add a test interface to simulate the official development environment: FeignServise02

package com.bug.springcloudfeign02.servise;


import com.bug.springcloudfeign02.fallback.FallbackFactory;
import com.bug.springcloudfeign02.fallback.FeignFallback;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;

@FeignClient(name = "springcloud-service-01", /*fallback = FeignFallback.class*/ fallbackFactory = FallbackFactory.class)
public interface FeignServise02 {

    @RequestMapping("/testService")
    public String testService();
}

Then there is the specific factory class:

package com.bug.springcloudfeign02.fallback;

import com.bug.springcloudfeign02.servise.FeignServise02;
import org.springframework.stereotype.Component;

@Component
public class FeignFallback implements FeignServise02 {

    @Override
    public String testService() {
        System.out.println("feign熔断进入...");

        return "feign熔断进入...";
    }
}

controller class:

package com.bug.springcloudfeign02.controller;

import com.bug.springcloudfeign02.servise.FeignServise02;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

@RestController
public class Feign02Controller {

    @Resource
    private FeignServise02 feignServise02;

    @GetMapping("/testFeign02")
    public String testFeign02(){

        return feignServise02.testService();
    }

}

It's as simple as calling it a day. It will listen by itself and then execute the testService method in the factory class FeignFallback.

7. Config configuration

1. What is Config configuration?

Through the above introduction, did you find that all microservices must be configured with an application.yml file? A few microservices are fine, but what about a dozen or dozens? Is it very troublesome to modify the configuration? And sometimes there will be missed changes, after all, humans are not machines. config solves this problem. It unifies all configurations and puts them in one place for management. All microservices only need to read their own configurations.

2. Specific configuration and code

maven dependencies:

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-server</artifactId>
            <version>3.1.1</version>
        </dependency>

Add annotation to startup class: @EnableConfigServer

application.properties configuration:

server.port=1001
spring.application.name=springcloud-config-01

spring.cloud.config.server.git.uri=git项目的地址https://******
spring.cloud.config.server.git.search-paths=项目下的子目录
spring.cloud.config.server.git.username=git用户名
spring.cloud.config.server.git.password=git密码

You need to pay attention to make sure that this project exists, otherwise an error will be reported. The sub-path can be present or not, but usually it is. Be careful not to write it wrong, otherwise you will not be able to access it. How to access it after configuration?

Assume that there are three configuration files in the subdirectory of this project

application.properties
application-dev.properties
application-test.properties

The wildcard path we access: service access url / configuration file name / environment name / git branch name, such as http://localhost:1001/application/dev/master, the data returned is a json format, the data is too long I won’t post it, it’s easy to understand if you visit it yourself.

This is the server. How do we access the client directly in the configuration file?

Add dependencies to the microservice:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-config</artifactId>
    <version>3.1.1</version>
</dependency>

bootstrap.properties (note it must be bootstrap, the application level is not enough):

spring.application.name=配置文件名称(application)
spring.cloud.config.profile=环境名称(dev)
spring.cloud.config.label=git分支名称(git分支名称)
spring.cloud.config.uri=服务的访问url(http://localhost:1001/)

In this way, config will automatically load the configuration file on git, which has the same effect as the application.properties we originally configured in the project.

8. RabbitMQ message middleware

1. What is message middleware?

Message middleware is the focus of microservices. Communication between microservices is completely handled by message middleware, which cannot be ignored, so I will explain it in detail here, and the article will be a bit long.

The so-called middleware is to add a layer and an application between two services. There are many middlewares, such as message middleware, database middleware, etc. "Message" refers to the direct communication and data interaction between two services, so it is called message middleware. Spring Cloud integrates rabbitMQ message middleware by default. Compared with traditional http calls, rabbitMQ has many advantages. ① The reliability of the message. If you are disconnected from the network, the service crashes, or the data processing fails, it can be restored again. ②High concurrency peak-shaving capability can greatly reduce the pressure on the server. ③Decoupling of applications, as well as subscription and publishing of messages, etc.

2. Installation of RabbitMQ

What is demonstrated here is the installation through docker in Linux, because it is very simple and convenient to install using docker. If you are lazy, haha, if you don’t have docker installed, just go to Baidu. There are many tutorials, or you can install it in other ways.

① Enter the Linux command terminal and execute the command: docker images. This is of no practical use, so just check it.

② Execute the command: docker search rabbitmq. This is of no practical use. It is used to query the rabbitMQ image.

③ Execute the command: docker pull rabbitmq, this is the installation image.

④ Execute the command: docker run -d --name myRabbitMQ -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=123456 -p 15672:15672 -p 5672:5672 rabbitmq. This line of command is the most important. Please explain:

myRabbitMQ :是docker中的应用名称。
RABBITMQ_DEFAULT_USER:后面登录web页面时的用户名。
RABBITMQ_DEFAULT_PASS:后面登录web页面时的密码。
15672、5672:15672是web访问的端口,5672是应用访问的端口。

OK Done! The access path on the web side is: your service IP + port number (15672), such as: http://127.0.0.1:15672. Be sure to open ports 15672 and 5672.

I also encountered a strange problem during testing. RabbitMQ was started successfully, but the web side could not be accessed. Finally, I restarted the docker container and application and the problem was solved. Sure enough, restarting solved 90% of the bugs, and the remaining Ten percent of them are browser problems, and programmers will never admit that their code has bugs.

重启docker容器:systemctl restart docker
重启容器里的应用:docker start myRabbitMQ,myRabbitMQ是自己设定的应用名称

Finally, take a look at the effect on the web side:

3. RabbitMQ workflow

Let’s talk about the MQ process first. First, the producer (client) produces a message --> puts it in the rabbitMQ message middleware --> the consumer (server) consumes the message. All middleware basically follows this process. rabbitMQ itself also has an internal process. First connect rabbitMQ to establish a channel-->then create a switch-->create a queue. Switch and queue are two very important concepts. The message will first enter the switch and then be stored in the queue by the switch. , the consumer consumes messages from the queue. rabbitMQ also has two important knowledge points, which must be dealt with during formal development, namely the accurate enqueuing of messages and the reliable consumption of messages. How to deal with them is described below.

4. Basic configuration and code of the producer

maven dependencies:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
            <version>2.7.3</version>
        </dependency>

application.properties configuration

#rabbitMQ的服务IP地址
spring.rabbitmq.host=***.***.***.***
#端口号
spring.rabbitmq.port=5672
#用户名
spring.rabbitmq.username=***
#密码
spring.rabbitmq.password=*****
spring.rabbitmq.virtual-host=/

Create a config configuration file to uniformly manage rabbitMQ switches and queues: RabbitMQConfig

package com.bug.springcloudrabbitmq01.config;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * rabbitMQ的配置类,正式开发的话一定要分开写
 * 不然后面业务增加,队列和交换机也增加,多了全在一个文件里就会很乱,可读性很差
 */
@Configuration
public class RabbitMQConfig {
    //队列名称--自定义
    public static final String DIRECT_QUEUE = "BUG_TEST_QUEUE";
    //交换机名称--自定义
    public static final String DIRECT_EXCHANGE = "BUG_TEST_EXCHANGE";
    //交换机key名称,也就是路由名称--自定义
    public static final String DIRECT_ROUTING_KEY = "BUG_KEY_TEST_QUEUE_EXCHANGE";

    /**
     * 创建队列
     * @return Queue
     */
    @Bean("directQueue")
    public Queue directQueue() {
        return new Queue(DIRECT_QUEUE, true);
    }

    /**
     * 创建交换机,rabbitMQ的交换机是有很多种的
     * DirectExchange 直连交换机
     * DirectExchange 扇形交换机
     * DirectExchange 主题交换机
     * DirectExchange 头部交换机
     * 不同的交换机可以适用不同的业务,其实就是一个树形的匹配问题,一对一、一对多等,一个消息想发送给一台还是要发送给多台,模糊匹配还是精确匹配等。
     * 一篇文章很难把所有的都写到,所以这里就只用DirectExchange直连交换机进行测试了。
     * 之前有说,我们的消息是先发送给交换机的,但是我们怎么去发送给指定交换机呢,DIRECT_ROUTING_KEY通过上面的这个名称进行匹配,简称路由key。
     * @return Exchange
     */
    @Bean("directExchange")
    public DirectExchange directExchange() {
        return new DirectExchange(DIRECT_EXCHANGE, true, false);
    }

    /**
     * 匹配交换机和队列,可以理解为是把某一个交换机和某一个队列连接起来
     * @param directQueue Queue
     * @param directExchange Exchange
     * @return Binding
     */
    @Bean
    public Binding bindingDirectExchange(Queue directQueue, DirectExchange directExchange) {
        return BindingBuilder.bind(directQueue).to(directExchange).with(DIRECT_ROUTING_KEY);
    }

}

There are detailed comments in the code, so I won’t introduce them here. In this way, our code is basically finished. You can try to start it to see if there are any errors. The dependent version of rabbitMQ has pitfalls. Be sure to pay attention to it and yourself The springboot version matches.

It should be noted that if you do not use it after configuration, the switch and queue will not be registered in the service. Therefore, after starting the above, you will find that there is no switch in the rabbitMQ client. This is normal.

Create a test controller class: TestRabbitMQController

package com.bug.springcloudrabbitmq01.controller;

import com.bug.springcloudrabbitmq01.config.RabbitMQConfig;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

@RestController
public class TestRabbitMQController {

    @Resource
    private RabbitTemplate rabbitTemplate;

    @GetMapping("/testRabbitMQ")
    public void testRabbitMQ(){
        //指定交换机,就是config配里的那个交换机名称
        rabbitTemplate.setExchange(RabbitMQConfig.DIRECT_EXCHANGE);
        //指定路由key
        rabbitTemplate.setRoutingKey(RabbitMQConfig.DIRECT_ROUTING_KEY);
        //发送消息
        rabbitTemplate.convertAndSend("发送一条消息测试一下下啦!");
    }

}

Take a look at the effect:

 5. How to ensure reliable delivery of messages (reliable production)

Reliable delivery and reliable consumption of messages are the focus of rabbitMQ. All are divided into a small chapter. As mentioned above, the producer of the message and rabbitMQ are not on the same server, so from one server to another server, There must be communication between the two. If there is communication, various unexpected situations may occur. So how to ensure that the message must enter rabbitMQ?

From the message producer to the final message consumer, there are actually four parts in the middle. Producer-->rabbitMQ switch-->rabbitMQ queue-->consumer. All reliable delivery has two parts. ① Ensure The message enters the rabbitMQ switch, ② ensure that the message enters the queue from the switch. The second step is completed by rabbitMQ, but results will also be returned. Get on the code!

Properties.application adds a line of configuration:

#确认机制
spring.rabbitmq.publisher-confirm-type=correlated

controller code:

package com.bug.springcloudrabbitmq01.controller;

import com.bug.springcloudrabbitmq01.config.RabbitMQConfig;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;

@RestController
public class TestRabbitMQController {

    @Resource
    private RabbitTemplate rabbitTemplate;

    @GetMapping("/testRabbitMQ")
    public void testRabbitMQ(){
        //指定交换机,就是config配里的那个交换机名称
        rabbitTemplate.setExchange(RabbitMQConfig.DIRECT_EXCHANGE);
        //指定路由key
        rabbitTemplate.setRoutingKey(RabbitMQConfig.DIRECT_ROUTING_KEY);
        //发送消息
        rabbitTemplate.convertAndSend("发送一条消息测试一下下啦!");
    }

    /**
     * PostConstruct 这个注解是在项目启动的时候执行的,是自动调用的
     * 可靠生产的整个流程:
     * 生产者-->rabbitMQ的交换机-->rabbitMQ的队列
     * 而生产者也不是直接就发送消息到rabbitMQ的,最好的处理方式是:生产一条消息后,先把这条消息存入数据库表(也叫消息冗余表),
     * 然后加上一些字段做标记(是否需要发送、是否发送成功、发送次数等),
     * 发送失败的通过定时器去再次发送(如果有消息一直发送失败,那应该就是消息本身有问题了,要设置一个报警,当发送测试达到多少时,提醒工作人员人工处理),
     * 这样就能确保,如果当时消息发送失败了,消息也不会丢失,并且能通过定时器再次发送,还能当成一种日志记录,
     * 因为这里是测试,就不去完成这一步了,但是正式开发的话,是一定要这样处理的。
     */
    @PostConstruct
    public void regCallback(){
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
            @Override
            public void confirm(CorrelationData correlationData, boolean ack, String s) {
                //消息就是在correlationData里,可以直接取的,如消息id:correlationData.getId();
                //ack就是判断是否发送成功,true成功,false失败
                if(!ack){
                    //发送失败
                    System.out.println("rabbitMQ消息发送失败啦,消息id是:" + correlationData.getId());
                    //也可以做一些其他处理,看具体业务需求,处理完就return;即可
                }
                if(ack){
                    //发送成功,发送成功一般是要修改数据库那张冗余表的状态的,看具体业务需求处理即可
                    System.out.println("rabbitMQ消息发送成功!");
                }
            }
        });
    }

}

Effect:

 This is OK. In fact, rabbitMQ's ConfirmCallback mechanism is used.

6. Consumer consumption news (reliable consumption)

There are also two problems on the consumer side. ① How to avoid repeated consumption. There are two ways to deal with it (of course there may be others): The first one is to use redis's ex or other caching mechanisms. The principle is actually to save the ID of the message, and then Then use the ID to determine whether it has been consumed. The second is to use optimistic locking or consume redundant tables, and verify it before processing. It is recommended to use the first method, and I will not demonstrate the processing of redis here. ② If the consumer side reports an error or is abnormal or down when processing the message, this requires the use of rabbitMQ's ack mechanism. RabbitMQ does not enable ack by default. That is, no matter whether your consumer side processes the message successfully or not, I can only give you the message. Once the task is done, remove the message. This is definitely not good, so you need to enable ack. The function of ack is that the consumer gets the message --> the message is processed --> and finally calls back to rabbitMQ, and rabbitMQ then removes the message. This way the message will not be lost!

My production end and consumer end are written in one project, so other configurations are the same as Nos. 4 and 5 above. The difference is that a new configuration is added to the configuration file:

#开启ack手动应答manual,默认是自动应答
spring.rabbitmq.listener.direct.acknowledge-mode=manual

Create a new consumer class:

package com.bug.springcloudrabbitmq01.server;

import com.bug.springcloudrabbitmq01.config.RabbitMQConfig;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.AmqpHeaders;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.io.IOException;

@Component
public class RabbitListenerACK {

    @Resource
    private RabbitTemplate rabbitTemplate;

    /**
     * queues队列的名称,监听的队列名称
     */
    @RabbitListener(queues = RabbitMQConfig.DIRECT_QUEUE)
    public void testACK(Message message, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long tag) throws IOException {
        System.out.println("消费端进入。。。");
        System.out.println("消息:" + message);
        //处理完后最好在try cache里调用ack回执,这里演示就直接回调了,只有执行了下面这条代码,rabbitMQ里才会移除掉那条消息
        channel.basicAck(tag, true);
    }

}

Effect:

7. Dead letter queue

Now that you have finished writing about reliable production and reliable consumption, do you feel that there are no other problems? no no no, what if there is a problem with rabbitMQ? Or failed to process the message? Or something else? Let the message keep retrying and then failing? If it continues like this, wouldn't it become an endless cycle? So, the dead letter queue came out. Dead letter queue: accommodates rejected messages, timed-out messages, and messages inserted when the number of messages in the message queue has exceeded the maximum queue length;

We declared a normal switch and a normal queue earlier, and then associated the normal switch with the queue to form a normal business channel for messages. The dead letter queue is the same, but the normal queue needs to be associated with the dead letter switch.

Dead letter queue process:

Normal switch + normal queue --> association

Dead letter switch + dead letter queue --> association

Dead letter switch + normal queue --> association

As long as the above three parts are completed, it will be OK. Normal messages are still fetched from the normal queue, while bad letter messages have to be fetched from the dead letter queue. The method of fetching is the same. If there are multiple normal business queues that need to use dead letter, the dead letter switch can be shared. Associate the normal queues with the dead letter switch, then create multiple dead letter queues, and then use different routing keys to distinguish the different queues. A dead letter message will do.

Get on the code! The other configurations are the same as above and will not be posted again. Only the config file needs to add a dead letter queue.

package com.bug.springcloudrabbitmq01.config;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * rabbitMQ的配置类,正式开发的话一定要分开写
 * 不然后面业务增加,队列和交换机也增加,多了全在一个文件里就会很乱,可读性很差
 */
@Configuration
public class RabbitMQConfig {
    //队列名称--自定义
    public static final String DIRECT_QUEUE = "BUG_TEST_QUEUE";
    //交换机名称--自定义
    public static final String DIRECT_EXCHANGE = "BUG_TEST_EXCHANGE";
    //交换机key名称,也就是路由名称--自定义
    public static final String DIRECT_ROUTING_KEY = "BUG_KEY_TEST_QUEUE_EXCHANGE";
    //死信交换机名称--自定义
    public static final String DLX_DIRECT_EXCHANGE = "BUG_TEST_DLX_EXCHANGE";
    //死信队列名称--自定义
    public static final String DLX_DIRECT_QUEUE = "BUG_TEST_DLX_QUEUE";
    //死信的路由key
    public static final String DLX_DIRECT_ROUTING_KEY = "BUG_KEY_TEST_DLX_QUEUE_EXCHANGE";
    /**
     * 创建队列
     * @return Queue
     */
    @Bean("directQueue")
    public Queue directQueue() {
        return new Queue(DIRECT_QUEUE, true);
    }

    /**
     * 创建交换机,rabbitMQ的交换机是有很多种的
     * DirectExchange 直连交换机
     * DirectExchange 扇形交换机
     * DirectExchange 主题交换机
     * DirectExchange 头部交换机
     * 不同的交换机可以适用不同的业务,其实就是一个树形的匹配问题,一对一、一对多等,一个消息想发送给一台还是要发送给多台,模糊匹配还是精确匹配等。
     * 一篇文章很难把所有的都写到,所以这里就只用DirectExchange直连交换机进行测试了。
     * 之前有说,我们的消息是先发送给交换机的,但是我们怎么去发送给指定交换机呢,DIRECT_ROUTING_KEY通过上面的这个名称进行匹配,简称路由key。
     * @return Exchange
     */
    @Bean("directExchange")
    public DirectExchange directExchange() {
        return new DirectExchange(DIRECT_EXCHANGE, true, false);
    }

    /**
     * 匹配交换机和队列,可以理解为是把某一个交换机和某一个队列连接起来
     * @param directQueue Queue
     * @param directExchange Exchange
     * @return Binding
     */
    @Bean
    public Binding bindingDirectExchange(Queue directQueue, DirectExchange directExchange) {
        return BindingBuilder.bind(directQueue).to(directExchange).with(DIRECT_ROUTING_KEY);
    }

    /**
     * 创建死信交换机
     * @return dlxDirectExchange
     */
    @Bean("dlxDirectExchange")
    public DirectExchange dlxDirectExchange() {
        return new DirectExchange(DLX_DIRECT_EXCHANGE, true, false);
    }

    /**
     * 创建死信队列
     * @return Queue
     */
    @Bean("dlxDirectQueue")
    public Queue dlxDirectQueue() {
        return new Queue(DLX_DIRECT_QUEUE);
    }

    /**
     * 死信队列关联死信交换机
     * @param dlxDirectQueue dlxDirectQueue
     * @param dlxDirectExchange dlxDirectExchange
     * @return Binding
     */
    @Bean
    public Binding dlxBindingDirectExchange(Queue dlxDirectQueue, DirectExchange dlxDirectExchange) {
        return BindingBuilder.bind(dlxDirectQueue).to(dlxDirectExchange).with(DLX_DIRECT_ROUTING_KEY);
    }

    /**
     * 正常队列关联死信交换机
     * @param directQueue directQueue
     * @param dlxDirectExchange dlxDirectExchange
     * @return Binding
     */
    @Bean
    public Binding dlxBindingDirectExchangeQueue(Queue directQueue, DirectExchange dlxDirectExchange) {
        return BindingBuilder.bind(directQueue).to(dlxDirectExchange).with(DIRECT_ROUTING_KEY);
    }

}

After the new dead letter switch and dead letter queue were added, they stopped running because I had created them before. If I wanted to create them again, I would need to delete the previous ones. So if there are problems after copying, you may need to deal with them yourself. Anyway, The principle is as I said above. The purpose of the dead letter queue is to collect messages that have failed to be processed normally, expired messages, and messages received after exceeding the length, and then process them through separate logic (which can also be understood as another consumer) (according to your own business logic) Just process it), and the way of receiving messages is the same as that of ordinary consumers.

Summarize

Okay, I have finished writing the five-piece set of springcloud microservices, which is more than 26,000 words. It’s tiring! In fact, there are still many places that have not been written, but there is no way, there are too many things, but as long as you can completely handle the above things, it is completely fine to do ordinary development. I will have time to write them separately after the advanced version. .

Message middleware is the focus of microservices. Finally, let’s take a look at the process:

Reliable production:

The database establishes the subsidiary table (redundant table) of the business message + timer + confirmCallback confirmation mechanism + returnCallback return mode;

Reliable consumption:

Manual ack mechanism + try cache + dead letter queue + dead letter queue attachment table. If the logic of the dead letter queue also fails to be processed, manual intervention will be required in the end;

Repeat consumption:

Use redis cache and message id to judge (because id is unique), or use database optimistic locking. Of course, you can use both, or other caching technologies, just judge according to your own business.

Guess you like

Origin blog.csdn.net/xiaobug_zs/article/details/130631473