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. AnnotationTransactionAttributeSource is only available on Java 1.5 and higher和windows同时安装jdk7和jdk8

    AnnotationTransactionAttributeSource is only available on Java 1.5 and higher和windows同时安装jdk7和jdk8 出 ...

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

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

  3. 一个基于SSM的CRUD的标准写法

    CRUD即CREATE,READ,UPDATE,DELETE的首字母的合写,意思是增读改删.前人为了便于发音和理解,改为增删改查. CRUD基本上是软件开发中中相当部分功能的最小功能模块构成,虽然软件 ...

  4. Stable Diffusion 生成个性图片指南

    在当今人工智能领域,midjourney无疑是生成图片的王者,但是苦于付费才能使用,今天我就给大家分享一下midjourney平替stable diffusion,实现本地生成不逊色于midjourn ...

  5. LED虚拟拍摄-跟踪算法

    LED虚拟拍摄-跟踪算法 图引用拍摄黑科技,LED虚拟影棚揭秘 标定流程 上面是一台Track设备,现精度比较高的主要是Redspy,Mosys,一般影视用这二种,其底层技术参考SMAL单目+惯性传感 ...

  6. 记一次RocketMQ消费非顺序消息引起的线上事故

    应用场景 C端用户提交工单.工单创建完成之后.会发布一条工单创建完成的消息事件(异步消息).MQ消费者收到消息之后.会通知各处理器处理该消息.各处理器处理完后都会发布一条将该工单写入搜索引擎的消息.最 ...

  7. ubuntu20使用php+apache+adminer搭建mysql网页管理工具

    前言   使用 php7+apache2+adminer 搭建网页版mysql管理工具,现将自己的搭建过程记录下来,留作后续参考.可参考其中配置,由于只配置了一次环境,可能有的步骤是多余的,后续可能会 ...

  8. 在Java中如何通过优化代码来节省内存

    Java 程序的一个常见问题是高内存使用率,这会导致性能问题甚至崩溃.因此,需要使用内存节省技术来优化 Java 代码并减少内存使用非常重要. 选择正确的数据类型: 使用适当大小的数据类型可以避免不必 ...

  9. leetcode简单(数组,字符串,链表):[66, 67, 70, 83, 121, 141, 160, 169, ,206, 338]

    目录 66. 加一 67. 二进制求和 70. 爬楼梯 83. 删除排序链表中的重复元素 121. 买卖股票的最佳时机 141. 环形链表 160. 相交链表 169. 多数元素 206. 反转链表 ...

  10. 利用FastAPI和OpenAI-Whisper打造高效的语音转录服务

    最近好久没有写博客了,浅浅记录下如何将OpenAI-Whisper做成Web服务吧 介绍 在这篇指导性博客中,我们将探讨如何在Python中结合使用FastAPI和OpenAI-Whisper.Ope ...