异步编程支持
温馨提示
在这里,你将系统学习了解 MemorySearch 忆搜阁
中异步编程支持
的实际运用场景
🍚 推荐阅读:异步编程利器:CompletableFuture 详解 | Java 开发实战 - 掘金 (juejin.cn) (opens new window)
# 🍠 异步编程
# CompletableFuture 的优点
异步函数式编程,实现优雅,易于维护;
它提供了异常管理的机制,让你有机会抛出、管理异步任务执行中发生的异常,监听这些异常的发生;
拥有对任务编排的能力。借助这项能力,可以轻松地组织不同任务的运行顺序、规则以及方式。
# 异步处理耗时请求
@Override
public SearchVO searchAll(SearchQueryRequest searchQueryRequest, HttpServletRequest request) {
// 1.校验参数type
// 1.1.检查type类型
String type = searchQueryRequest.getType();
// 1.2.校验参数正确性
ThrowUtils.throwIf(StringUtils.isEmpty(type), ErrorCode.PARAMS_ERROR);
// 1.3.获取页面类型
SearchTypeEnum enumByValue = SearchTypeEnum.getEnumByValue(type);
String searchText = searchQueryRequest.getSearchText();
long pageSize = searchQueryRequest.getPageSize();
long current = searchQueryRequest.getPageNum();
// 2.执行查询全部数据
SearchVO searchVO = null;
if (enumByValue == null) {
// 诗词搜索
CompletableFuture<Page<PostVO>> postTask = CompletableFuture.supplyAsync(() ->
postDataSource.search(searchText, pageSize, current));
// 博文搜索
CompletableFuture<Page<ArticleVO>> articleTask = CompletableFuture.supplyAsync(() ->
articleDataSource.search(searchText, pageSize, current));
// 图片搜索
CompletableFuture<Page<Picture>> pictureTask = CompletableFuture.supplyAsync(() ->
pictureDataSource.search(searchText, pageSize, current));
CompletableFuture.allOf(postTask, pictureTask, articleTask).join();
try {
Page<PostVO> postVOPage = postTask.get();
Page<Picture> picturePage = pictureTask.get();
Page<ArticleVO> articlePage = articleTask.get();
searchVO = new SearchVO();
// searchVO.setPostVOList(postVOPage.getRecords());
// searchVO.setPictureList(picturePage.getRecords());
// searchVO.setArticleList(articlePage.getRecords());
searchVO.setPostVOPage(postVOPage);
searchVO.setArticleVOPage(articlePage);
searchVO.setPicturePage(picturePage);
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
} else {
// 分类查询
searchVO = new SearchVO();
DataSource<?> dataSourceByType = dataSourceRegistry.getDataSourceByType(type);
try {
Page<?> page = dataSourceByType.search(searchText, pageSize, current);
searchVO.setDataPage(page);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return searchVO;
}
# 批量导入数据
# 🥩 诗词抓取
提示
对于这部分内容中异步编程支持
的深入了解,您可以跳转到 数据抓取 (opens new window) 页面,其中详细阐述了该过程的原理、最佳实践以及应用场景
- 初始化一个
计时器
StopWatch 来追踪整个过程的耗时。接着,定义了一个包含多位著名诗人名字的字符串数组
,并将其转换为列表 authorList,以便后续按作者批量获取诗词数据
。此时,计时器开始计时,为后续操作提供精确的耗时统计
:
// new一个StopWatch对象
StopWatch stopWatch = new StopWatch();
// 计时开始
stopWatch.start();
// 按作者批量获取诗词
String[] stringArray = {"李白", "杜甫", "苏轼", "王维", "杜牧", "陆游", "李煜", "元稹", "韩愈", "岑参",
"齐己", "贾岛", "柳永", "曹操", "李贺", "曹植", "张籍", "孟郊", "屈原", "王勃", "高适",
"白居易", "辛弃疾"};
List<String> authorList = Arrays.asList(stringArray);
首先,设定了用于请求诗词数据的
URL 地址模板
。接着,创建了一个固定大小为 10 的自定义线程池
,用于执行并发任务。此外,初始化了一个列表来存储所有异步任务的 CompletableFuture 对象,以便后续管理
和监控
这些任务的状态。这一系列配置为后续的
异步爬取
操作提供了必要的基础设施和资源管理。
String originUrl = "https://so.gushiwen.cn/shiwens/default.aspx?page=%d&tstr=&astr=%s&cstr=&xstr=";
// 创建自定义线程池
ExecutorService executorService = Executors.newFixedThreadPool(10);
// 存储所有的异步任务
List<CompletableFuture<Void>> futures = new ArrayList<>();
并发爬取与存储诗词信息:对于给定的作者列表,程序创建多个线程,每个线程异步地抓取指定 URL 的 HTML 文档。
使用 Jsoup 解析文档,提取诗词的
标题
、作者
和内容
,并将这些信息封装为对象。随后,批量将对象列表存储至数据库。此过程利用异步编程
提高爬虫效率,确保数据的快速获取与存储。
for (String authorStr : authorList) {
for (int i = 1; i < 5; i++) {
String url = String.format(originUrl, i, authorStr);
// 创建异步任务
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
try {
// 1. 获取数据
Document doc = Jsoup.connect(url)
.userAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 Edg/116.0.1938.81")
.get();
// 捕获 id = "leftZhankai"
Element leftZhankai = doc.getElementById("leftZhankai");
Elements heads = leftZhankai.select(".sons .cont div:nth-of-type(2)");
ArrayList<Post> postList = new ArrayList<>();
for (Element head : heads) {
Post post = new Post();
String title = head.select(">p:nth-of-type(1)").text();
String author = head.select(">p:nth-of-type(2)").text();
String content = head.select(".contson").text();
post.setTitle(title);
post.setAuthor(author);
post.setContent(content);
postList.add(post);
}
boolean saveBatch = postService.saveBatch(postList);
ThrowUtils.throwIf(!saveBatch, ErrorCode.OPERATION_ERROR, "批量插入诗词失败");
} catch (IOException e) {
throw new RuntimeException(e);
}
}, executorService);
// 将异步任务添加到列表中
futures.add(future);
}
}
在完成所有并发爬取任务后,程序会等待所有异步任务完成执行。随后,关闭用于执行这些任务的线程池,以确保资源的正确释放。接着,计时器停止计时,并输出整个爬取和存储过程的总耗时,以便进行
性能分析
和优化。在按作者批量获取诗词的场景中,通过
异步编程方式
处理,显著提高了批量插入数据库的效率。这里给出普通批量插入
的代码实现:
// new一个StopWatch对象
StopWatch stopWatch = new StopWatch();
// 计时开始
stopWatch.start();
// 按作者批量获取诗词
String[] stringArray = {"李白", "杜甫", "苏轼", "王维", "杜牧", "陆游", "李煜", "元稹", "韩愈", "岑参",
"齐己", "贾岛", "柳永", "曹操", "李贺", "曹植", "张籍", "孟郊", "屈原", "王勃", "高适",
"白居易", "辛弃疾"};
List<String> authorList = Arrays.asList(stringArray);
String originUrl = "https://so.gushiwen.cn/shiwens/default.aspx?page=%d&tstr=&astr=%s&cstr=&xstr=";
for (String authorStr : authorList) {
for (int i = 1; i < 5; i++) {
String url = String.format(originUrl, i, authorStr);
// 1. 获取数据
Document doc = Jsoup.connect(url)
.userAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 Edg/116.0.1938.81")
.get();
// 捕获 id = "leftZhankai"
Element leftZhankai = doc.getElementById("leftZhankai");
Elements heads = leftZhankai.select(".sons .cont div:nth-of-type(2)");
ArrayList<Post> postList = new ArrayList<>();
for (Element head : heads) {
Post post = new Post();
String title = head.select(">p:nth-of-type(1)").text();
String author = head.select(">p:nth-of-type(2)").text();
String content = head.select(".contson").text();
post.setTitle(title);
post.setAuthor(author);
post.setContent(content);
postList.add(post);
}
boolean saveBatch = postService.saveBatch(postList);
ThrowUtils.throwIf(!saveBatch, ErrorCode.OPERATION_ERROR, "批量插入诗词失败");
}
}
// 计时结束
stopWatch.stop();
// 计算插入所用总时间
System.out.println(stopWatch.getTotalTimeMillis() + "ms");
- 针对 24 位作者,每位作者 4 页诗词,每页 10 首,共计 960 首诗词的记录插入操作,普通批量插入耗时
4168 毫秒
,而采用异步编程后,该过程仅用时1719 毫秒
,性能提升近60%
。这一优化表明,异步编程在处理大量数据插入时,能够大幅度减少等待时间,提升整体处理效率。