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. const 和 volatile 指针

    关键字 const 和 volatile 规定了指针的处理方式: const 规定指针在初始化后是受保护的,不能够再修改. volatile 规定了变量的值能够被用户应用程序外部的操作所修改. 因此, ...

  2. 幻想领域图床系统V1.2正式版发布

    Tips:当你看到这个提示的时候,说明当前的文章是由原emlog博客系统搬迁至此的,文章发布时间已过于久远,编排和内容不一定完整,还请谅解` 幻想领域图床系统V1.2正式版发布 日期:2018-4-1 ...

  3. Nuxt 3组件开发与管理

    title: Nuxt 3组件开发与管理 date: 2024/6/20 updated: 2024/6/20 author: cmdragon excerpt: 摘要:本文深入探讨了Nuxt 3的组 ...

  4. 终端读取iOS项目所有设置参数(版本号、应用名等)

    在某些场景下(比如自动化打包等),我们需要从终端来读取到iOS项目的数据,首先先上代码 xcodebuild -showBuildSettings -target 项目target 但有时候我们需要将 ...

  5. BST-Treap名次树指针实现板子 Ver2.1

    为了更好的阅读体验,请点击这里 这里只有板子没有原理QWQ 可实现 1.插入 x 数 2.删除 x 数(若有多个相同的数,只删除一个) 3.查询 x 数的排名(排名定义为比当前数小的数的个数 +1) ...

  6. 使用 nginx 共享文件

    1. 安装nginx 2. 在nginx的配置文件的server部分加上如下的配置: location /shared/ { autoindex on; autoindex_exact_size on ...

  7. Android日志系统(logging system)

    Android日志系统(logging system) 背景 不管是做Android应用还是做Android中间层和底层,在做一些调试工作的时候,使用adb logcat非常关键.特意学习了一下安卓的 ...

  8. docker部署微服务之注册中心

    1.首先要对对应服务的pom.xml文件进行修改,添加如下配置. 2.在微服务的pom.xml目录下建立Dockerfile文件 3.在Dockerfile当前目录下执行mvn clean insta ...

  9. typora markdown笔记

    前言 此为个人markdown笔记,不定时更新. 正文 1. mermaid 使用 横向流程图 源码 graph LR a(起始)-->b(中间) b-->c1<-->d b- ...

  10. Vue 处理异步加载顺序问题:在Konva中确保文本在图片之上显示

    Vue 处理异步加载顺序问题:在Konva中确保文本在Konva之上显示 在使用Konva开发应用时,我们经常会遇到需要将文本绘制在图片之上的情况.一个常见的问题是,由于图像加载是异步的,文本有时会显 ...