1.背景

在做活动或者抢购场景,系统查询的请求并发量非常高

如果并发的访问数据库,会给数据库带来很大的压力,

这时候我们可以考虑将多个查询请求合并成一个查询请求返回给客户端,

比如:根据id查询爆款产品

并发10000次

3000次查询 id=1的产品

4000次查询id=2的产品

2000次查询id=3的产品

1000次查询id=4的产品

如果请求不合并,将到数据库查询10000次,

如果采用请求合并,只需到数据库查询1次,共查询出4个产品,然后按照id把结果给每一个请求,

这样大大降低了数据库的压力

使用到的关于juc相关技术:

1.

2.

3.

2.代码

控制层代码:

 /**
* 查询订单
*
* @return
*/
@RequestMapping("/api/product")
public Object product(String id) throws ExecutionException, InterruptedException {
// 为了便于分析,设置一个线程号
Thread.currentThread().setName("thread-" + id);
Map<String, Object> map = productServiceImpl.queryList(id);
// 模拟随机耗时
ThreadUtil.sleepRandom();
return map;
}

业务实现层代码:

package com.ldp.jucproject.service.impl;

import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.util.*;
import java.util.concurrent.*; /**
* @author 姿势帝-博客园
* @address https://www.cnblogs.com/newAndHui/
* @WeChat 851298348
* @create 11/06 12:51
* @description
*/
@Service
public class ProductServiceImpl {
/**
* 请求类,code为查询的共同特征,例如查询商品,通过不同id的来区分
* CompletableFuture将处理结果返回
*/
class Request {
String code;
CompletableFuture completableFuture;
} /**
* 存放请求的队列
*/
LinkedBlockingQueue<Request> queue = new LinkedBlockingQueue(200); @PostConstruct
public void init() {
// 建立定时执行的线程
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);
scheduledExecutorService.scheduleAtFixedRate(() -> {
int size = queue.size();
//如果队列没数据,表示这段时间没有请求,直接返回
if (size == 0) {
return;
}
List<Request> list = new ArrayList<>();
System.out.println("合并请求数:" + size);
//将队列的请求消费到一个集合保存
for (int i = 0; i < size; i++) {
Request poll = queue.poll();
if (poll == null) {
System.out.println("无请求对象!");
break;
}
list.add(poll);
}
//拿到我们需要去数据库查询的特征,保存为集合
List<String> listParam = new ArrayList<>();
for (Request request : list) {
listParam.add(request.code);
}
//将参数传入service处理
Map<String, HashMap<String, Object>> response = queryByCodeBatch(listParam);
//将处理结果返回各自的请求
for (Request request : list) {
Map<String, Object> result = response.get(request.code);
//completableFuture.complete方法完成赋值,这一步执行完毕,阻塞的请求可以继续执行了
request.completableFuture.complete(result);
}
}, 0, 30, TimeUnit.MILLISECONDS);
} /**
* 模拟从数据库查询
*
* @param codes
* @return
*/
public Map<String, HashMap<String, Object>> queryByCodeBatch(List<String> codes) {
Map<String, HashMap<String, Object>> result = new HashMap();
for (String code : codes) {
HashMap<String, Object> hashMap = new HashMap<>();
hashMap.put("productCode", new Random().nextInt(999999999));
hashMap.put("code", code);
hashMap.put("productNome", "苹果-" + new Random().nextInt(5));
hashMap.put("price", new Random().nextInt(100));
result.put(code, hashMap);
}
return result;
} public Map<String, Object> queryList(String code) throws ExecutionException, InterruptedException {
Request request = new Request();
request.code = code;
CompletableFuture<Map<String, Object>> future = new CompletableFuture<>();
request.completableFuture = future;
//将对象传入队列
boolean offer = queue.offer(request);
if (!offer) {
// 放入队列失败,说明队列满了,返回系统忙
Map<String, Object> result = new HashMap<>();
result.put("code", "9999");
result.put("message", "系统忙!");
return result;
}
//如果这时候没完成赋值,那么就会阻塞,知道能够拿到值
return future.get();
}
}

3.测试

模拟请求:

 @Test
void product() throws InterruptedException {
// 并发请求数
int num = 1000;
CountDownLatch countDownLatch = new CountDownLatch(num);
for (int i = 1; i <= num; i++) {
// 计数器减一
countDownLatch.countDown();
Integer id = i;
new Thread(() -> {
try {
String url = "http://localhost:8001/api/product?id=" + id;
// 等待计数器归零,归零前都是处于阻塞状态
System.out.println("待查询订单号=" + id);
countDownLatch.await();
HttpRequest request = HttpUtil.createGet(url);
String response = request.execute().body();
System.out.println("response=" + response);
} catch (Exception e) {
log.error("模拟并发出错:{}", e);
}
}).start();
}
// 避免线程终止
Thread.sleep(90 * 1000);
}

4.完美!

JUC高并发编程(一)之请求合并案例的更多相关文章

  1. 《深入理解高并发编程:JDK核心技术》-冰河新书上市

    大家好,我是冰河~~ 废话说多了没用,并发编程技术一直是初级程序员进阶高级工程师的前提条件,也是成为大厂程序员的必备技能,更是突破自身技术瓶颈的必经之路. 2022年6月我出版了"冰河技术丛 ...

  2. Java 面试知识点解析(二)——高并发编程篇

    前言: 在遨游了一番 Java Web 的世界之后,发现了自己的一些缺失,所以就着一篇深度好文:知名互联网公司校招 Java 开发岗面试知识点解析 ,来好好的对 Java 知识点进行复习和学习一番,大 ...

  3. 关于Java高并发编程你需要知道的“升段攻略”

    关于Java高并发编程你需要知道的"升段攻略" 基础 Thread对象调用start()方法包含的步骤 通过jvm告诉操作系统创建Thread 操作系统开辟内存并使用Windows ...

  4. Java高并发编程基础三大利器之CountDownLatch

    引言 上一篇文章我们介绍了AQS的信号量Semaphore<Java高并发编程基础三大利器之Semaphore>,接下来应该轮到CountDownLatch了. 什么是CountDownL ...

  5. java线程高并发编程

    java线程具体解释及高并发编程庖丁解牛 线程概述: 祖宗: 说起java高并发编程,就不得不提起一位老先生Doug Lea,这位老先生可不得了.看看百度百科对他的评价,一点也不为过: 假设IT的历史 ...

  6. 多线程高并发编程(3) -- ReentrantLock源码分析AQS

    背景: AbstractQueuedSynchronizer(AQS) public abstract class AbstractQueuedSynchronizer extends Abstrac ...

  7. [ 高并发]Java高并发编程系列第二篇--线程同步

    高并发,听起来高大上的一个词汇,在身处于互联网潮的社会大趋势下,高并发赋予了更多的传奇色彩.首先,我们可以看到很多招聘中,会提到有高并发项目者优先.高并发,意味着,你的前雇主,有很大的业务层面的需求, ...

  8. java高并发编程(一)

    读马士兵java高并发编程,引用他的代码,做个记录. 一.分析下面程序输出: /** * 分析一下这个程序的输出 * @author mashibing */ package yxxy.c_005; ...

  9. Java 多线程高并发编程 笔记(一)

    本篇文章主要是总结Java多线程/高并发编程的知识点,由浅入深,仅作自己的学习笔记,部分侵删. 一 . 基础知识点 1. 进程于线程的概念 2.线程创建的两种方式 注:public void run( ...

  10. java高并发编程(五)线程池

    摘自马士兵java并发编程 一.认识Executor.ExecutorService.Callable.Executors /** * 认识Executor */ package yxxy.c_026 ...

随机推荐

  1. windows rabbitmq安装

    windows rabbitmq安装1.环境变量,关键是rabbitmq_server需要与erlang版本对应,否则无法使用. rabbitmq下载页https://github.com/rabbi ...

  2. python globals()[]将字符串转化类,并通过反射执行方法

    背景: 通过关键字设计ui自动化框架,将测试用例及其步骤存放到excel文件:其中步骤中包含了封装好的关键字方法,如打开浏览器.输入页面操作等,关键字保存的内容:具体类实例.方法 通过excel获取到 ...

  3. tar命令 --null -T 参数详解

    tar 命令的 --null 和 -T 参数可以一起使用,以从 null 设备读取文件名,并将这些文件名传递给 tar 命令来处理. --null 参数的作用是将文件名作为 null 字符分隔的字符串 ...

  4. Freertos学习:04-任务的调试函数

    --- title: rtos-freertos-04-任务的调试函数 EntryName: rtos-freertos-04-task-debug date: 2020-06-22 08:49:06 ...

  5. 合并STM32 iap的hex文件合并为一个hex文件

    --- title: 合并STM32 iap的hex文件合并为一个hex文件 date: 2020-06-15 04:32:26 categories: tags: - iap - stm32 --- ...

  6. 【论文阅读】TRO 2021: Fail-Safe Motion Planning for Online Verification of Autonomous Vehicles Using Convex Optimization

    参考与前言 Last edited time: August 3, 2022 10:04 AM Status: Reading Type: TRO Year: 2021 论文链接:https://ie ...

  7. Spring的@Configuration和@Bean注解定义第三方bean

    @Configuration和@Bean注解的使用 @Configuration标注在类上,相当于把该类作为spring的xml配置文件中<beans>,作用为:配置spring容器(应用 ...

  8. Spring里面bean的依赖和继承

    继承 bean继承:两个类之间大多数的属性都相同,避免重复配置,通过bean标签的parent属性重用已有的Bean元素的配置信息 继承指的是配置信息的复用,和java类的继承没有关系 video.j ...

  9. yolov5 筛选正样本流程 代码多图详解

    yolov5正样本筛选原理 正样本全称是anchor正样本,正样本所指的对象是anchor box,即先验框. 先验框:从YOLO v2开始吸收了Faster RCNN的优点,设置了一定数量的预选框, ...

  10. Simple WPF: S3实现MINIO大文件上传并显示上传进度

    最新内容优先发布于个人博客:小虎技术分享站,随后逐步搬运到博客园. 创作不易,如果觉得有用请在Github上为博主点亮一颗小星星吧! 目的 早两天写了一篇S3简单上传文件的小工具,知乎上看到了一个问题 ...