高并发下的系统优化-请求合并,带你了解多线程的妙用,附代码

高并发下的系统优化-请求合并

在互联网的场景下, 总会有一些接口长期大量的被访问,如个人信息,订单查询,商品查询等。这些请求的都有一个特点,就是查询简单,只需要传入一个id就可以进行查询。应对这种大量频繁的查询,我们可以做请求合并。

前提:在做请求合并有个重要前提,就是系统必须支持一次传入多个查询条件,然后得到返回结果,通俗的讲就是支持in的查询。

模拟场景:根据产品ID查询产品信息,并发2000次/秒
请求流程如下

  1. 前台用户请求查询产品的接口 --> 后台controller接收请求 --> service调用mapper查询数据库或者第三方接口查询数据 --> 返回结果给前台
  2. 重复2000次如上请求。

造成的后果会在1s内查询2000次数据库或者第三方系统。

简单的代码示例:
controller

package springboot.controller;

import java.util.HashMap;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import springboot.model.Product;
import springboot.service.ProductService;

/**
 * 
 * @author liuhongya328
 *
 * @date 2019-11-21
 */
@RestController
public class ProductController {
	
	@Autowired
	private ProductService productService;
	
	/**
	 * 根据产品编号查询产品信息
	 * @throws Exception 
	 */
	@RequestMapping("/findProductById")
	@ResponseBody
	public Map<String, Object> findProductById(String productId) throws Exception{
		Map<String, Object> returnMap = new HashMap<String, Object>();
		Product product = productService.findProductById(productId);
		returnMap.put("returnValue",product);
		return returnMap;
	}
	
}

service

package springboot.service.impl;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import javax.annotation.PostConstruct;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import springboot.mapper.ProductMapper;
import springboot.model.Product;
import springboot.service.ProductService;

/**
 * 
 * @author liuhongya328
 *
 * @date 2019-11-21
 */
@Service
public class ProductServiceImpl implements ProductService {

	@Autowired
	private ProductMapper productMapper;
	
	//正常的业务逻辑,每次请求从controller传递来调用一次service的方法,去数据库或者调用远程接口获取一次数据。
	@Override
	public Product findProductById(String productId) {
		System.out.println(System.currentTimeMillis()+":"+Thread.currentThread().getName()+"--进行请求!");
		return productMapper.selectByPrimaryKey(productId);
	}
}

mapper

package springboot.mapper;

import java.util.List;
import java.util.Map;

import org.apache.ibatis.annotations.Mapper;

import springboot.model.Product;
@Mapper
public interface ProductMapper {

	Product selectByPrimaryKey(String productId);

	List<Product> findAllProduct(List<String> productIds);
}

用jmeter模拟1秒请求2000次的请求结果:

...
...
...
1574330132478:http-nio-18080-exec-16--进行请求!
1574330132478:http-nio-18080-exec-162--进行请求!
1574330132478:http-nio-18080-exec-189--进行请求!
1574330132479:http-nio-18080-exec-169--进行请求!
1574330132479:http-nio-18080-exec-163--进行请求!
1574330132479:http-nio-18080-exec-116--进行请求!
1574330132479:http-nio-18080-exec-200--进行请求!
1574330132480:http-nio-18080-exec-4--进行请求!
1574330132480:http-nio-18080-exec-185--进行请求!
1574330132478:http-nio-18080-exec-39--进行请求!
1574330132502:http-nio-18080-exec-116--进行请求!
1574330132503:http-nio-18080-exec-16--进行请求!
1574330132503:http-nio-18080-exec-39--进行请求!
1574330132504:http-nio-18080-exec-185--进行请求!
1574330132504:http-nio-18080-exec-169--进行请求!
1574330132504:http-nio-18080-exec-200--进行请求!
1574330132504:http-nio-18080-exec-189--进行请求!
1574330132503:http-nio-18080-exec-162--进行请求!
1574330132503:http-nio-18080-exec-4--进行请求!
1574330132503:http-nio-18080-exec-163--进行请求!

下面开始进行请求合并:
思路:需要设计一个线程每隔一定的时间将请求收集起来,批量去查询数据库或者调用第三方系统。

只需要改动service,代码如下

package springboot.service.impl;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import javax.annotation.PostConstruct;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import springboot.mapper.ProductMapper;
import springboot.model.Product;
import springboot.service.ProductService;

/**
 * 
 * @author liuhongya328
 *
 * @date 2019-11-21
 */
@Service
public class ProductServiceImpl implements ProductService {

	class Request{
		String productId;
		
		CompletableFuture<Product> future;
	}
	
	@Autowired
	private ProductMapper productMapper;

	//队列用来存储请求
	BlockingQueue<Request> queue = new LinkedBlockingQueue<Request>();
	
	@PostConstruct
	public void init() {
		//创建定时任务线程池,每隔50ms就执行一次,读取队列中的请求,放入到统一list中
		ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
		scheduledExecutorService.scheduleAtFixedRate(() -> {
			int size = queue.size();
			if(size == 0 )
				return;
			ArrayList<Request> requests = new ArrayList<Request>();
			for(int i = 0; i < size; i++) {
				Request request = queue.poll();
				requests.add(request);
			}
			System.out.println("现在批量请求了:"+size+"条请求");
			List<String> productIds = new ArrayList<String>();
			for(Request request : requests) {
				productIds.add(request.productId);
			}
			
			//调用批量查询的方法
			List<Product> responses = productMapper.findAllProduct(productIds);
			//请求合并完成,开始将结果分发返回给每一个具体的request
			Map<String,Product> returnMap = new HashMap<String,Product>();
			for(Product product : responses) {
				returnMap.put(product.getProductId(), product);
			}
			
			for(Request request : requests) {
				Product product = returnMap.get(request.productId);
				//返回结果传给future,即通知外部线程可以get到当前请求的数据。
				request.future.complete(product);
			}
			
		}, 0, 50, TimeUnit.MILLISECONDS);
	}

	//请求合并-->2000次请求同时执行,前提需要有批量查询的方法或者接口
	@Override
	public Product findProductById(String productId) throws Exception {
		Request request = new Request();
		request.productId = productId;
		CompletableFuture<Product> future = new CompletableFuture<>();
		request.future = future;
		//拦截请求,将请求放入容器
		queue.add(request);
		
		//放弃单一调用
        //return productMapper.selectByPrimaryKey(productId);
		
		//用future是因为需要从另外个线程中取数据,线程中的数据交互可以用future
		return future.get();
	}
}

用jmeter模拟1秒请求2000次的请求结果:

...
...
...
现在批量请求了:22条请求
现在批量请求了:31条请求
现在批量请求了:23条请求
现在批量请求了:30条请求
现在批量请求了:25条请求
现在批量请求了:27条请求
现在批量请求了:29条请求
现在批量请求了:165条请求
现在批量请求了:59条请求
现在批量请求了:197条请求
现在批量请求了:3条请求
现在批量请求了:35条请求
现在批量请求了:36条请求

效果实现,如有疑问或者错误,欢迎交流和指正!

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值