Use Hystrix to implement request merging and reduce server concurrency pressure

1. Introduce Hystrix

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

2. Enable Hystrix function on the startup class

@EnableHystrix

3. Request to merge the implementation code

import com.bzcst.bop.oms.orm.model.po.Product;
import com.bzcst.bop.oms.orm.service.ProductService;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCollapser;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import com.netflix.hystrix.contrib.javanica.conf.HystrixPropertiesManager;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;
import java.util.concurrent.Future;

/**
 * @author Xiang Zhenhua
 * @date 2023/10/26 14:37
 */
@Slf4j
@Service
public class BatchService {

    @Resource
    private ProductService productService;

    @HystrixCollapser(
            batchMethod = "getByIds",
            scope = com.netflix.hystrix.HystrixCollapser.Scope.GLOBAL,
            collapserProperties = {
                    @HystrixProperty(name = HystrixPropertiesManager.MAX_REQUESTS_IN_BATCH, value = "10"),
                    @HystrixProperty(name = HystrixPropertiesManager.TIMER_DELAY_IN_MILLISECONDS, value = "20")
            }
    )
    public Future<Product> getById(Long id) {
        return null;
    }

    @HystrixCommand
    public List<Product> getByIds(List<Long> ids) {
        log.info("Batch query ---> " + ids);
        return productService.listByIds(ids);
    }
}

The getById(Long id) method will not enter.

Among them, ProductService is a query interface based on mybatisplus.

import com.bzcst.bop.oms.orm.model.po.Product;
import com.baomidou.mybatisplus.extension.service.IService;

public interface ProductService extends IService<Product> {

}

4. Call

import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.bzcst.bop.oms.orm.model.po.Product;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

/**
 * @author Xiang Zhenhua
 * @date 2023/10/26 14:32
 */
@RestController
public class TestController {

    @Resource
    private BatchService batchService;

    @GetMapping("/product/{id}")
    public Object product(@PathVariable Long id) throws Exception {
        Future<Product> productFuture = batchService.getById(id);
        return productFuture.get();
    }

    public static void main(String[] args) {
        //Create thread pool
        ExecutorService executorService = Executors.newFixedThreadPool(20);
        // Simulate 20 concurrent requests
        CountDownLatch countDownLatch = new CountDownLatch(20);
        for (int i = 1; i <= 20; i + + ) {
            int fi = i;
            executorService.execute(() -> {
                try {
                    countDownLatch.await();
                    HttpRequest httpRequest = HttpRequest.get("http://localhost:8080/bop-oms/product/" + fi);
                    String body = httpRequest.execute().body();
                    System.out.println(fi + " ---> " + body);
                } catch (Exception e) {
                }
            });
            countDownLatch.countDown();
        }
    }
}

Log records can be seen as batch processing

2023-10-26 17:54:31.708 INFO 10524 --- [-BatchService-9] c.bzcst.bop.oms.controller.BatchService: Batch query ---> [10, 5, 9, 15 , 4, 12, 11, 16, 8, 14]
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@58dfec5b] was not registered for synchronization because synchronization is not active
parser sql: SELECT id, xxx FROM oms_product WHERE id IN (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
2023-10-26 17:54:31.712 INFO 10524 --- [-BatchService-8] c.bzcst.bop.oms.controller.BatchService: Batch query ---> [3, 18, 1, 13, 7, 19, 20, 2, 6, 17]
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4235ea19] was not registered for synchronization because synchronization is not active
parser sql: SELECT id, xxx FROM oms_product WHERE id IN (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
JDBC Connection [HikariProxyConnection@935862209 wrapping com.mysql.cj.jdbc.ConnectionImpl@130542a5] will not be managed by Spring
==> Preparing: SELECT id, xxx FROM oms_product WHERE id IN (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
==> Parameters: 10(Long), 5(Long), 9(Long), 15(Long), 4(Long), 12(Long), 11(Long), 16(Long), 8(Long), 14(Long)
JDBC Connection [HikariProxyConnection@205926351 wrapping com.mysql.cj.jdbc.ConnectionImpl@67b600cc] will not be managed by Spring
==> Preparing: SELECT id, xxx FROM oms_product WHERE id IN (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
==> Parameters: 3(Long), 18(Long), 1(Long), 13(Long), 7(Long), 19(Long), 20(Long), 2(Long), 6(Long), 17(Long)
<== Columns: id, tenant_id, category_id, name, code, price, ladder_price, is_ladder_price, unit, is_enabled, description, version, create_by, create_time, update_by, update_time, delete_flag
<== Row: 4, 1, 0, transfer power supply, 4, 4.00, , 0, , 1, , 1, , 2022-12-29 17:01:09, , 2022-12-29 17:01:09 , 0
<== Row: 5, 1, 0, sewer, XSD, 5.00, , 0, , 1, , 1, , 2023-05-23 18:24:30, , 2023-05-23 18:24:30 , 0
<== Row: 8, 1, 0, door card deposit, 0302, 8.00, , 0, , 1, , 1, , 2023-03-04 10:57:50, , 2023-04-14 09:53: 17,0
<== Row: 9, 1, 0, tiered unit price, 12321, 9.00, , 1, , 1, , 1, , 2023-10-13 16:39:09, , 2023-10-13 16:39:09 , 0
<== Row: 10, 1, 0, Assad, 123333, 10.00, , 0, , 1, , 1, , 2023-09-14 10:43:01, , 2023-09-14 10:43: 01, 0
<== Row: 11, 1, 0, subsidized vacancy fee, 444, 11.00, , 0, , 1, , 1, , 2023-03-02 17:05:10, , 2023-03-02 17:05: 10, 0
<== Row: 12, 1, 0, street light fee, B<N, 12.00, , 0, , 1, , 1, , 2023-03-03 10:55:23, , 2023-03-03 10:55 :23,0
<== Row: 14, 1, 0, scattered properties, 3, 14.00, , 0, , 1, , 1, , 2023-03-02 16:50:05, , 2023-03-02 16:50:05 , 0
<== Row: 15, 1, 0, sporadic electricity bill, 2, 15.00, , 0, , 1, , 1, , 2023-03-02 16:49:46, , 2023-03-02 16:49:46 , 0
<== Row: 16, 1, 0, non-domestic garbage removal fee, 16, 16.00, , 0, , 1, , 1, , 2022-12-29 17:01:09, , 2022-12-29 17: 01:09, 0
<== Total: 10
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@58dfec5b]
<== Columns: id, tenant_id, category_id, name, code, price, ladder_price, is_ladder_price, unit, is_enabled, description, version, create_by, create_time, update_by, update_time, delete_flag
<== Row: 1, 1, 0, special maintenance fund, 1, 1.00, , 0, , 1, , 1, , 2022-12-29 17:01:09, , 2022-12-29 17:01: 09,0
<== Row: 2, 1, 0, parking space vacancy fee, 2, 2.00, , 0, , 1, , 1, , 2022-12-29 17:01:09, , 2022-12-29 17:01: 09,0
<== Row: 3, 1, 0, parking fee, CWF01, 3.00, , 0, , 1, , 1, , 2023-10-10 09:57:07, , 2023-10-10 09:57:07 , 0
<== Row: 6, 1, 0, Service fee, 6, 6.00, , 0, , 1, , 1, , 2022-12-29 17:01:09, , 2022-12-29 17:01:09 , 0
<== Row: 7, 1, 0, sales matching fee, s, 7.00, , 0, , 1, , 1, , 2022-12-29 17:01:09, , 2022-12-29 17:01: 09,0
<== Row: 13, 1, 0, Odd water charges, 1, 13.00, , 0, , 1, , 1, , 2023-03-02 16:49:18, , 2023-03-02 16:49: 18,0
<== Row: 17, 1, 0, Consulting fee, 17, 17.00, , 0, , 1, , 1, , 2022-12-29 17:01:09, , 2022-12-29 17:01:09 , 0
<== Row: 18, 1, 0, Catering waste fee, 18, 18.00, , 0, , 1, , 1, , 2022-12-29 17:01:09, , 2022-12-29 17:01: 09,0
<== Row: 19, 1, 0, rental advertising fee, 1000123, 19.00, [], 0, , 1, , 1, , 2023-06-08 10:51:16, , 2023-06-08 10: 51:16, 0
<== Row: 20, 1, 0, Rent, ZJ002, 20.00, [], 0, , 1, , 1, , 2023-03-15 17:40:12, , 2023-03-15 17:40: 12,0
<== Total: 10
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4235ea19]

Or you can request like this

 @GetMapping("/product")
    public void product() throws Exception {
        List<Future> list = new ArrayList<>();
        for (long i = 1; i <= 20; i + + ) {
            Future<Product> productFuture = batchService.getById(i);
            list.add(productFuture);
        }

        for (Future future : list) {
            Object o = future.get();
            System.out.println(o);
        }
    }

If the scope is REQUEST mode, you need to use HystrixRequestContext

 @GetMapping("/product")
    public void product() throws Exception {
        HystrixRequestContext context = HystrixRequestContext.initializeContext();
        try {
            List<Future> list = new ArrayList<>();
            for (long i = 1; i <= 20; i + + ) {
                Future<Product> productFuture = batchService.getById(i);
                list.add(productFuture);
            }

            for (Future future : list) {
                Object o = future.get();
                System.out.println(o);
            }
        } finally {
            context.shutdown();
        }
    }

5.@HystrixCollapser parameter introduction

batchMethod, request merge method

scope, request merging method, REQUEST: request scope, GLOBAL: global scope, default com.netflix.hystrix.HystrixCollapser.Scope.GLOBAL

collapserProperties
HystrixPropertiesManager.MAX_REQUESTS_IN_BATCH Maximum number of merge requests, default Integer.MAX_VALUE
HystrixPropertiesManager.TIMER_DELAY_IN_MILLISECONDS request merge interval millisecond value, default 10ms

Note that when the request merging interval millisecond value is reached and the maximum number of request merging is not reached, request merging will also be performed.

@HystrixCollapser is implemented based on Spring’s AOP

com.netflix.hystrix.contrib.javanica.aop.aspectj.HystrixCommandAspect

6. Request merge workflow chart