MemorySearch 开发者文档 MemorySearch 开发者文档
首页
    • 概述
    • 系统设计
    • 维护升级
    • 高效多元搜索
    • 互动创作平台
    • 流量统计分析
    • 个人中心管理
    • 资源全面管理
    • 概览
    • Ant Design Vue 脚手架
    • Vuepress 静态文档站点
    • 定制前端项目初始模板
    • 基础信息管理
    • 高效多元搜索
    • Elastic Stack 全家桶
    • 设计模式荟萃
    • 外源数据抓取
    • 数据实时同步
    • 流量速率管控
    • 缓存性能调优
    • 定时任务调度
    • 权限校验机制
    • 异步编程支持
    • 初始模板定制
    • 全局逻辑梳理
  • 简介
  • 常见问题与解答
首页
    • 概述
    • 系统设计
    • 维护升级
    • 高效多元搜索
    • 互动创作平台
    • 流量统计分析
    • 个人中心管理
    • 资源全面管理
    • 概览
    • Ant Design Vue 脚手架
    • Vuepress 静态文档站点
    • 定制前端项目初始模板
    • 基础信息管理
    • 高效多元搜索
    • Elastic Stack 全家桶
    • 设计模式荟萃
    • 外源数据抓取
    • 数据实时同步
    • 流量速率管控
    • 缓存性能调优
    • 定时任务调度
    • 权限校验机制
    • 异步编程支持
    • 初始模板定制
    • 全局逻辑梳理
  • 简介
  • 常见问题与解答
  • 内容概览

    • 概览
  • 前端

    • Ant Design Vue 脚手架
    • Vuepress 静态文档站点
    • 定制前端项目初始模板
  • 后端

    • 基础信息管理
    • 高效多元搜索
    • Elastic Stack 全家桶
    • 设计模式荟萃
    • 外源数据抓取
    • 数据实时同步
    • 流量速率管控
    • 缓存性能调优
    • 定时任务调度
    • 权限校验机制
    • 异步编程支持
      • CompletableFuture 的优点
      • 异步处理耗时请求
      • 批量导入数据
      • 🥩 诗词抓取
    • 初始模板定制
    • 全局逻辑梳理
目录

异步编程支持

温馨提示

在这里,你将系统学习了解 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");

image-20240213150326802

  • 针对 24 位作者,每位作者 4 页诗词,每页 10 首,共计 960 首诗词的记录插入操作,普通批量插入耗时 4168 毫秒,而采用异步编程后,该过程仅用时 1719 毫秒,性能提升近 60%。这一优化表明,异步编程在处理大量数据插入时,能够大幅度减少等待时间,提升整体处理效率。
权限校验机制
初始模板定制

← 权限校验机制 初始模板定制→

Theme by Vdoing | Copyright © 2023-2024 回忆如初
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式