任务队列方案详解(一)JVM线程池
前言
我们都知道 web 服务的工作大多是接受 http 请求,并返回处理后的结果。服务器接受的每一个请求又可以看是一个任务。一般而言这些请求任务会根据请求的先后有序处理,如果请求任务的处理比较耗时,往往就需要排队了。而同时不同的任务直接可能会存在一些优先级的变化,这时候就需要引入任务队列并进行管理了。可以做任务队列的东西有很多,Java 自带的线程池,以及其他的消息中间件都可以。
同步与异步
这个问题在之前已经提过很多次了,有些任务是需要请求后立即返回结果的,而有的则不需要。设想一下你下单购物的场景,付完钱后,系统只需要返回一个支付成功即可,后续的积分增加、优惠券发放、安排发货等等业务都不需要实时返回给用户的,这些就是异步的任务。大量的异步任务到达我们部署的服务上,由于处理效率的瓶颈,无法达到实时处理,因此与需要用队列将他们暂时保存起来,排队处理。
线程池
在 Java 中提到队列,我们除了想到基本的数据结构之外,应该还有线程池。线程池自带一套机制可以实现任务的排队和执行,可以满足单点环境下绝大多数异步化的场景。下面是典型的一个处理流程:
// 注入合适类型的线程池
@Autowired
private final ThreadPoolExecutor asyncPool;
@RequestMapping(value = "/async/someOperate", method = RequestMethod.POST)
public RestResult someOperate(HttpServletRequest request, String params,String callbackUrl {
// 接受请求后 submit 到线程池排队处理
asyncPool.submit(new Task(params,callbackUrl);
return new RestResult(ResultCode.SUCCESS.getCode(), null) {{
setMsg("successful!" + prop.getShowMsg());
}};
}
// 异步任务处理
@Slf4j
public class Task extends Callable<RestResult> {
private String params;
private String callbackUrl;
private final IAlgorithmService algorithmService = SpringUtil.getBean(IAlgorithmServiceImpl.class);
private final ServiceUtils serviceUtils = SpringUtil.getBean(ServiceUtils.class);
public ImageTask(String params,String callbackUrl) {
this.params = params;
this.callbackUrl = callbackUrl;
}
@Override
public RestResult call() {
try {
// 业务处理
CarDamageResult result = algorithmService.someOperate(this.params);
// 回调
return serviceUtils.callback(this.callbackUrl, this.caseNum, ResultCode.SUCCESS.getCode(), result, this.isAsync);
} catch (ServiceException e) {
return serviceUtils.callback(this.callbackUrl, this.caseNum, e.getCode(), null, this.isAsync);
}
}
}
对于线程池这里就不具体展开讲了,仅仅简单理了下具体的流程:
- 收到请求后,参数校验后传入线程池排队。
- 返回结果:“请求成功,正在处理”。
- 任务排到后由相应的线程处理,处理完后进行接口回调。
上面的例子描述了一个生产速度远远大于消费速度的模型,普通面向数据库开发的企业级应用,由于数据库的连接池开发的连接数较大,一般不需要这样通过线程池来处理,而一些 GPU 密集型的应用场景,由于显存的瓶颈导致消费速度慢时,就需要队列来作出调整了。
带优先级的线程池
更复杂的,例如考虑到任务的优先级,还需要对线程池进行重写,通过 PriorityBlockingQueue 来替换默认的阻塞队列。直接上代码。
import lombok.Data;
import java.util.concurrent.Callable;
/**
* @author Fururur
* @create 2020-01-14-10:37
*/
@Data
public abstract class PriorityCallable<T> implements Callable<T> {
private int priority;
}
import lombok.Getter;
import lombok.Setter;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicLong;
/**
* 优先级线程池的实现
*
* @author Fururur
* @create 2019-07-23-10:19
*/
public class PriorityThreadPoolExecutor extends ThreadPoolExecutor {
private ThreadLocal<Integer> local = ThreadLocal.withInitial(() -> 0);
public PriorityThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, getWorkQueue());
}
public PriorityThreadPoolExecutor(int corePoolSize, int maximumPoolSize,
long keepAliveTime, TimeUnit unit, ThreadFactory threadFactory) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, getWorkQueue(), threadFactory);
}
public PriorityThreadPoolExecutor(int corePoolSize, int maximumPoolSize,
long keepAliveTime, TimeUnit unit, RejectedExecutionHandler handler) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, getWorkQueue(), handler);
}
public PriorityThreadPoolExecutor(int corePoolSize, int maximumPoolSize,
long keepAliveTime, TimeUnit unit, ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, getWorkQueue(), threadFactory, handler);
}
private static PriorityBlockingQueue getWorkQueue() {
return new PriorityBlockingQueue();
}
@Override
public void execute(Runnable command) {
int priority = local.get();
try {
this.execute(command, priority);
} finally {
local.set(0);
}
}
public void execute(Runnable command, int priority) {
super.execute(new PriorityRunnable(command, priority));
}
public <T> Future<T> submit(PriorityCallable<T> task) {
local.set(task.getPriority());
return super.submit(task);
}
public <T> Future<T> submit(Runnable task, T result, int priority) {
local.set(priority);
return super.submit(task, result);
}
public Future<?> submit(Runnable task, int priority) {
local.set(priority);
return super.submit(task);
}
@Getter
@Setter
protected static class PriorityRunnable implements Runnable, Comparable<PriorityRunnable> {
private final static AtomicLong seq = new AtomicLong();
private final long seqNum;
private Runnable run;
private int priority;
PriorityRunnable(Runnable run, int priority) {
seqNum = seq.getAndIncrement();
this.run = run;
this.priority = priority;
}
@Override
public void run() {
this.run.run();
}
@Override
public int compareTo(PriorityRunnable other) {
int res = 0;
if (this.priority == other.priority) {
if (other.run != this.run) {
// ASC
res = (seqNum < other.seqNum ? -1 : 1);
}
} else {
// DESC
res = this.priority > other.priority ? -1 : 1;
}
return res;
}
}
}
要点如下:
- 替换线程池默认的阻塞队列为
PriorityBlockingQueue,响应的传入的线程类需要实现Comparable<T>才能进行比较。 PriorityBlockingQueue的数据结构决定了,优先级相同的任务无法保证 FIFO,需要自己控制顺序。- 需要重写线程池的
execute()方法。看过线程池源码的会发现,执行submit(task)方法后,都会转化成RunnableFuture<T>再进一步执行,由于传入的 task 虽然实现了Comparable<T>到,但是内部转换成的RunnableFuture<T>并未实现,因此直接submit会抛出Caused by: java.lang.ClassCastException: java.util.concurrent.FutureTask cannot be cast to java.lang.Comparable这样一个异常,所以需要重写execute()方法,构造一个PriorityRunnable作为中转。
总结
JVM 线程池是实现异步任务队列最简单最原生的一种方式,本文介绍了基本的使用流程和带有优先队列需求的用法。这种方法可有满足到一些简单的业务场景,但也存在一定的局限性:
- JVM 线程池是单机的,横向扩展多个服务下做负载均衡时,就会存在多个线程池了他们是分开工作的,无法很好的统一和管理,不太适合分布式场景。
- JVM 线程池是基于内存的,一旦服务挂了,会出现任务丢失的情况,可靠性低。
- 缺少作为任务队列的 ack 机制,一旦任务失败不会重新执行,且无法很好地对线程池队列进行监控。
显然简单的 JVM 线程池是无法 handle 到负载的业务场景的,这就需要引入其他中间件了,在接下来的文章中我们会继续探讨。
参考文献
任务队列方案详解(一)JVM线程池的更多相关文章
- 莱特币ltc在linux下的多种挖矿方案详解
莱特币ltc在linux下的多种挖矿方案详解 4.0.1 Nvidia显卡Linux驱动Nvidia全部驱动:http://www.nvidia.cn/Download/index.aspx?lang ...
- 详解C3P0(数据库连接池)
详解C3P0(数据库连接池) 快速索引 一.基本定义 二.使用C3P0(数据库连接池)的必要性 1.JDBC传统模式开发存在的主要问题 三.数据库连接池的详细说明 四.使用连接池的明显优势 1.资源的 ...
- 基于rem的移动端响应式适配方案(详解) 移动端H5页面的设计稿尺寸大小规范
基于rem的移动端响应式适配方案(详解) : https://www.jb51.net/article/118067.htm 移动端H5页面的设计稿尺寸大小规范 http://www.tuyiyi.c ...
- Java中String的intern方法,javap&cfr.jar反编译,javap反编译后二进制指令代码详解,Java8常量池的位置
一个例子 public class TestString{ public static void main(String[] args){ String a = "a"; Stri ...
- App域名劫持之DNS高可用 - 开源版HttpDNS方案详解(转)
http://mp.weixin.qq.com/s?__biz=MzAwMDU1MTE1OQ==&mid=209805123&idx=1&sn=ced8d67c3e2cc3 ...
- OkHttp3源码详解(五) okhttp连接池复用机制
1.概述 提高网络性能优化,很重要的一点就是降低延迟和提升响应速度. 通常我们在浏览器中发起请求的时候header部分往往是这样的 keep-alive 就是浏览器和服务端之间保持长连接,这个连接是可 ...
- 高并发架构系列:Redis缓存和MySQL数据一致性方案详解
一.需求起因 在高并发的业务场景下,数据库大多数情况都是用户并发访问最薄弱的环节.所以,就需要使用redis做一个缓冲操作,让请求先访问到redis,而不是直接访问MySQL等数据库. 这个业务场景, ...
- 性能百万/s:腾讯轻量级全局流控方案详解
WeTest 导读 全新的全局流控实现方案,既解决了目前流控的实现难点,同时保证运行稳定且流控准确的前提下,实现更简单,部署成本更低,容灾能力更强. 该方案组件化之后,可以推广到别的有需要的部门使用, ...
- P2P技术详解(二):P2P中的NAT穿越(打洞)方案详解
1.内容概述 P2P即点对点通信,或称为对等联网,与传统的服务器客户端模式(如下图"P2P结构模型"所示)有着明显的区别,在即时通讯方案中应用广泛(比如IM应用中的实时音视频通信. ...
随机推荐
- 1级搭建类111-Oracle 19c SI FS(Windows Server 2019)公开
Oracle 19c 单实例文件系统在Windows Server 2019上的安装 在线查看
- docker镜像ubuntu封装jdk1.8.0【dockerfile】
github地址:https://github.com/laileman/Docker/Dockerfile/ubuntu-jdk1.8.0_172 1-目录结构 2- dockerfile内容 3- ...
- Eclispe WEB项目 转到 IDEA 后无法部署问题
IDEA是个强大的IDE 这个就不用多说了 Eclispe 的Web项目 转到IDEA之后,开始部署会出现大量的问题 项目从SVN下载下来的时候,大概就是这个样 第一步是先设置 项目结构 也就 ...
- url编码--url中含有空格问题
开发web服务中,发现当url中含有空格时,会报 400 error: bad request sytanx,经分析,url中含有特殊字符时,服务端可能无法识别.如+,空格,/,?,%,#,& ...
- python接口
用正则表达式提取数据: https://www.cnblogs.com/dwdw/p/9553192.html python unittest TestCase间共享数据(全局变量的使用): http ...
- Wannafly Camp 2020 Day 2H 叁佰爱抠的序列 - 欧拉遍历
转化为完全图的欧拉遍历 如果 n 是奇数,则欧拉遍历长度为 \(n(n-1)/2\) 条边 如果 n 是偶数,则欧拉遍历长度为 \(n*n/2-1\) 条边 (即将(n-1)/2对点配对,剩下的一对当 ...
- [USACO12FEB] 附近的牛 Nearby Cows - 树形dp,容斥
给你一棵 \(n\) 个点的树,点带权,对于每个节点求出距离它不超过 \(k\) 的所有节点权值和 \(m_i\) 随便定一个根,设\(f[i][j]\)表示只考虑子树,距离为\(j\)的权值和,\( ...
- js监听页面copy事件添加版权信息
个人博客 地址:http://www.wenhaofan.com/article/20180921103346 1.介绍 当页面需要做版权保护时,比如当用户copy我们网站的文章时,我们会希望在他co ...
- MySQL 8.0.18 在 Windows Server 2019 上的安装(ZIP)公开
AskScuti MySQL : Windows Server 2019 安装 MySQL 8.0 温馨提示:为了展现我最“魅力”的一面,请用谷歌浏览器撩我. 一切就绪,点我开撩
- 检测识别问题中的metrics
之前一直记不熟各种指标的具体计算,本文准备彻底搞定这个问题,涵盖目前遇到过的所有评价指标. TP,TN,FP,FN 首先是true-false和positive-negative这两对词.以二分类为例 ...