preface

At present, I am developing the background system of the company. There is a requirement that the background system configuration needs to be updated after modification. When I saw this requirement, I asked: what the hell??

The situation is like this: the system configuration of our project is in the database table, but the system configuration is used in so many places that it is impossible to check MySQL every time we need to configure, and the frequency of modification is not high, so it is completely unnecessary… So our configuration information will be read in the database when the service is started, loaded into memory, and then go directly to memory where the configuration is needed.

This is what we did before there was no backstage… Direct request service refresh memory interface, because it is a micro service will deploy multiple instances, your request will be distributed to one of the instances through load balancing, this time to which server I do not know, so directly take Postman repeatedly send multiple requests, no accident every service will go over.

it

The solution that I use below is not difficult, but still because step pit (be too dish) actually consumed me one afternoon of time, at that time cold sweat all come out 😰, do of time already 11.10 afternoon, say to want double eleven before going on line…

Loading Configuration Information

Storage of system configuration (loading system configuration into instance memory when the service starts)

@Component

public class InitApplicationRunner {



    @Autowired

    private SystemConfigService systemConfigService;



    public static Map<String, SystemConfig> SYSTEM_CONFIG = new HashMap<>();



    @PostConstruct

    public void init(a) {

        // Save to memory

        SYSTEM_CONFIG = systemConfigService.listSystemConfigs();

    }

}

Copy the code

Every time a request is made to read the system configuration, it goes to the InitApplicationRunner class to get SYSTEM_CONFIG and fetch the required configuration.

Implementation approach

throughRabbitMQI’m sending a message to my service that says: You’re going to refresh my configuration

The way RabbitMQ is usually used is that the service acts as a consumer and listens to a queue, which sends a message to the consumer: I’m asking you to do something

Boy! MQ is also the default polling mechanism for sending messages to a queue to one of the consumers, not to all the listening services. That is, the message queue is sent to service 1 this time, to service 2 the next time, to service 3 the next time, and then to service 4…

Breakthrough: each service binding a queue, there are several services that there are several queues, and then change the switch to broadcast FANout type, the message, the switch unified distribution of the message to all queues, so that all services will naturally receive the message.

All right, let’s do it, shall we?

The specific implementation

Guide rely on

<dependency>

 <groupId>org.springframework.boot</groupId>

 <artifactId>spring-boot-starter-amqp</artifactId>

</dependency>

Copy the code

configuration

spring:

  rabbitmq:

    host: localhost

    username: guest

    password: guest

Copy the code

The consumer declares that the queue is bound to the switch

Since each service needs a separate queue bound to it, the queue name cannot be written to death in the code. If the queue name changes, since it cannot be written to death in the code, it can be dynamically generated when the service is started.


When we bind the queue switch at service startup, we add a random number after the queue name so that each of our consumers has a queue to themselves.


However, thinking very simple, really write up, intelligent IDE with you say: even I this close all through!!


Boy! The value of queue name needs to be a fixed value.


String is a fixed value 😂

What good would I want you for if I had to die?

Wait a minute… The queue name or “” for a generated queue name (default)

This value will be used as your queue name. If it is an empty string, a queue name will be generated, and its default value will be “”.

(To be honest, I only discovered this while writing this article… Crying)

All right, keep building!

Simply remove the name declaration

@RabbitListener(

        bindings = @QueueBinding(

                exchange = @Exchange(name = "admin.ex", type = "fanout", autoDelete = "true"),

                value = @Queue(durable = "true", autoDelete = "true")

        )

)

Copy the code

Switch: name admin.ex, type fanout, autoDelete = “true” means automatically deleted (once all queues are unbound from it, the switch will be deleted), and in our scenario, false is really not necessary.

Because the switch does not have the ability to store data, if the switch is not bound to any queues, the data sent to the switch will be lost; If the switch does not exist, the message will at least have an error log saying that the switch does not exist.

Queue: The name is not declared actively and is generated randomly. Durable = “true” Indicates persistent queues. In this scenario, it does not matter. It is added habitually.

AutoDelete = “true” denotes a queue that will be automatically deleted once all consumers have unlistened, which is also necessary for our current requirements.

Start multiple consumers

Service more open also do not copy the module, a little operation is good.


➤ Instead of restarting a class, open another service. We just need to change the port name before startup so that multiple instances will be running.


Our two queues are also created, with both names randomly generated by Spring.


Producer sends message

@Autowired

private RabbitTemplate rabbitTemplate;



public void refreshConfig(a) {

    // fanout cannot be null if there is no routing key. If there is an empty string, the message will be passed with an empty string

    rabbitTemplate.convertAndSend("admin.ex".""."");

}

Copy the code

The consumer received the message successfully



Ok, the consumer has successfully received a message to refresh the configuration.

Harm, this writing method I also just discovered… So let me code it another way

Java native

Guide rely on

<dependency>

    <groupId>com.rabbitmq</groupId>

    <artifactId>amqp-client</artifactId>

    <version>5.7.3</version>

</dependency>

Copy the code

The consumer declares that the queue is bound to the switch

@Slf4j

@Component

public class MqListener {

    

    @Autowired

    private SystemConfigService systemConfigService;



    / * *

* Define the switch name

* /


    private static final String EXCHANGE_NAME = "admin.ex";



    @PostConstruct

    public void msgHandler(a) throws IOException, TimeoutException {

        // 1. Connect to RabbitMQ

        ConnectionFactory factory = new ConnectionFactory();

        factory.setHost("localhost");

        factory.setPort(5672);

        factory.setUsername("guest");

        factory.setPassword("guest");

        Connection connection = factory.newConnection();

        // 2. Get the channel object

        Channel channel = connection.createChannel();



        // 3. Declare the switch type (FANout) to be consistent with the producer

        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);

        // 4.* Declare random queue

        String queueName = channel.queueDeclare().getQueue();

        // 5. Bind the queue switch

        channel.queueBind(queueName, EXCHANGE_NAME, "");

        // 6. Obtain information

        channel.basicConsume(queueName, true.new DefaultConsumer(channel) {

            @Override

            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) {

                log.info("Queue [{}] refresh configuration", queueName);

                InitApplicationRunner.SYSTEM_CONFIG = systemConfigService.listSystemConfigs();

            }

        });

        // 7. Cannot close the connection (because you need to keep listening)

    }

    

}

Copy the code

The class is loaded when the consumer instance startsMqListener, to be used withMQConnect (constantly open) and keep listening.

Start multiple consumers

The way is the same as above.

After startup, queues are created and their names are randomly generated.


Producer sends message

public void refreshConfig(a) throws IOException, TimeoutException {

    // 1. Connect to RabbitMQ

    ConnectionFactory factory = new ConnectionFactory();

    factory.setHost("localhost");

    factory.setPort(5672);

    factory.setUsername("guest");

    factory.setPassword("guest");

    Connection connection = factory.newConnection();

    // 2. Get the channel object

    Channel channel = connection.createChannel();

    

    // 3. Declare the switch name and type (fanout)

    String exchangeName = "admin.ex";

    channel.exchangeDeclare(exchangeName, BuiltinExchangeType.FANOUT);

    // The queue is not specified here. The queue is specified by the consumer

    

    // 4. Publish the message with an empty string

    channel.basicPublish(exchangeName, "".null."".getBytes("UTF-8"));

    // 5. Close connections and channels

    channel.close();

    connection.close();

}

Copy the code

The consumer received the message successfully



Anyway, Spring Boot is better. I’m going to change the code

The last

Is there a better way to communicate about the project’s system configuration loading in the comments section

If this article has helped you, go to 👍.

Share technology, hold on, we can win 💪!