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. JavaScript将类数组转换为数组的三种方法

    // 类数组转换为数组 const list = [] // 假定为类数组 const arr1 = Array.from(list); const arr2 = Array.prototype.sp ...

  2. 燕千云 YQCloud 数智化业务服务管理平台发布1.11版本

    2022年3月25日,燕千云 YQCloud 数智化业务服务管理平台发布1.11版本.新增客户服务管理模块.优化IT服务管理功能.增强燕千云与其他平台的集成能力.支持更多的业务服务场景.全面提升企业数 ...

  3. mysql case when使用

    ## mysql case when使用 SELECT order_no,case is_test when 0 then '否'when 1 then '是'end as '是否测试' from ` ...

  4. Kotlin 编程语言详解:特点、应用领域及语法教程

    什么是 Kotlin? Kotlin 是一种现代.流行的编程语言,由 JetBrains 在 2016 年发布. 自发布以来,它已经变得非常流行,因为它与 Java 兼容(Java 是目前最流行的编程 ...

  5. JSP四个作用域和九个对象

    一.四个作用域 (1)Requset 请求作用域,就是客户端的一次请求 (2)Session 会话作用域,当用户首次访问时,产生一个新的会话,以后服务器就可以记住这个会话状态.生命周期:会话超时,或者 ...

  6. Python使用Matplotlib画以日期为X轴的图

    Python使用Matplotlib画以日期为X轴的图 步骤: 用pd把字符串格式的日期转成date格式. 使用 AutoDateLocator 设置x轴的属性. 1 from matplotlib ...

  7. 详解Web应用安全系列(4)失效的访问控制

    在Web安全中,失效的访问控制(也称为权限控制失效或越权访问)是指用户在不具备相应权限的情况下访问了受限制的资源或执行了不允许的操作.这通常是由于Web应用系统未能建立合理的权限控制机制,或者权限控制 ...

  8. gerrit权限控制

    gerrit权限控制 背景 在公司中使用到了Gerrit作为技术管理,在配置的时候发现一些问题:转载了这篇文章作为学习. 正文开始 原文链接:https://blog.csdn.net/chenjh2 ...

  9. 在centos开启防火墙没启动22/tcp or 22/udp的情况下是如何ssh连上的

    偶尔间查询防火墙的22/tcp or 22/udp ,看到是no的状态,而且此时也是ssh登陆的,然后就反复尝试,关闭22端口,开启,重载,重启.甚至连上另个服务器发现还是一样的情况.在群里问大佬们终 ...

  10. 【Autoware】Autoware安装教程

    此篇主要是从自己的csdn copy 在博客园上备份一下哈~ 前提:大家需要换源[软件源和pip源]:git clone的时候走博主给的Gitee的链接吧 不然得等个十万年... 如果想看最终是啥样的 ...