
文档教程后端【免费下载链接】CodeGuide:books: 本代码库是作者小傅哥多年从事一线互联网 Java 开发的学习历程技术汇总旨在为大家提供一个清晰详细的学习教程侧重点更倾向编写Java核心内容。如果本仓库能为您提供帮助请给予支持(关注、点赞、分享)项目地址https://gitcode.com/gh_mirrors/code/CodeGuide点击查看免费下载本文档是《拼团交易平台系统》第 2-3 节的技术指南聚焦于在通用规则树Strategy Router模型结构上扩展出异步数据加载区multiThread把拼团营销优惠试算接口所需的配置数据与商品数据改为异步线程并行加载、再统一写入上下文供后续节点使用。读完本文你将掌握以 FutureTask ThreadPoolExecutor 实现接口数据前置异步加载的落地写法理解为什么这种分区、分职责的模型设计能让代码具备文档属性并了解本节新增的 sku 商品信息表在整个试算折扣链路中的作用。一、为什么要做多线程异步数据加载互联网公司的业务功能开发非常重视接口的响应效率。一般来说整体接口响应要控制在350 毫秒以内细分到每个领域的接口往往会被压缩到50100 毫秒。对于简单接口这样的响应时间毫无压力但如果是复杂业务流程串联起来的接口控制响应时长就是一个非常大的挑战。比如拼团试算接口在返回优惠结果之前需要先拿到活动配置 商品信息等一组相互独立的数据如果采用串行方式逐个查询耗时会被线性累加。对此常见的优化手段就是引入异步线程加载数据把接口实现中所需的数据前置到异步加载区并发完成查询之后再做统一的逻辑处理从而显著降低接口响应时间。拼团交易平台正是把这个思路与上一节第2-2节试算模型抽象模板设计定义的通用规则树模型结合在一起形成一套异步加载 规则流转的完整链路。二、本章诉求与模型设计思路1. 本章诉求本章的核心诉求是扩展规则树模型结构增加异步数据加载区。将用于试算营销优惠的接口数据使用异步线程进行加载之后写入上下文DynamicContext用于后续节点的逻辑处理。这套模型设计非常巧妙——通过解耦逻辑和划分功能区让代码具有了文档属性看到对应的类、类下的方法区就可以轻松理解代码实现方式。这样的处理非常有利于后续功能的迭代也符合代码即文档的工程追求该理念在第2-7节人群标签节点过滤中也有呼应。2. 模型链路执行过程整体模型链路的执行过程如下第一步对通用设计模式树结构扩展出异步数据加载区。这样可以把接口实现中所需的数据前置到异步数据加载区完成加载操作以此提高接口的响应效率第二步串联功能节点并在MarketNode节点添加数据加载操作第三步新增一张sku商品信息表通过商品信息表获得当前商品的价格配置以此来做商品的折扣计算。其中规则树模型本身是在第 2-2 节完成的通用抽象结构涵盖StrategyMapper策略映射器、StrategyHandler策略处理器、AbstractStrategyRouterT, D, R策略路由抽象类三个核心组成部分通过泛型设计允许使用方自定义出入参和动态上下文。本节则是在这套通用结构之上为节点增加异步数据加载能力。三、MarketNode异步数据加载区的源码实现在通用规则树模型上扩展出异步加载区后营销节点MarketNode承担了异步加载数据 业务处理 路由流转三重职责。下面这段代码来自项目工程中的营销节点实现可以看到它清晰地划分出了三个方法区Slf4j Service public class MarketNode extends AbstractGroupBuyMarketSupportMarketProductEntity, DefaultActivityStrategyFactory.DynamicContext, TrialBalanceEntity { Resource private ThreadPoolExecutor threadPoolExecutor; Resource private MapString, IDiscountCalculateService discountCalculateServiceMap; Resource private ErrorNode errorNode; Resource private TagNode tagNode; Override protected void multiThread(MarketProductEntity requestParameter, DefaultActivityStrategyFactory.DynamicContext dynamicContext) throws ExecutionException, InterruptedException, TimeoutException { // 异步查询活动配置 QueryGroupBuyActivityDiscountVOThreadTask queryGroupBuyActivityDiscountVOThreadTask new QueryGroupBuyActivityDiscountVOThreadTask(requestParameter.getActivityId(), requestParameter.getSource(), requestParameter.getChannel(), requestParameter.getGoodsId(), repository); FutureTaskGroupBuyActivityDiscountVO groupBuyActivityDiscountVOFutureTask new FutureTask(queryGroupBuyActivityDiscountVOThreadTask); threadPoolExecutor.execute(groupBuyActivityDiscountVOFutureTask); // 异步查询商品信息 - 在实际生产中商品有同步库或者调用接口查询。这里暂时使用DB方式查询。 QuerySkuVOFromDBThreadTask querySkuVOFromDBThreadTask new QuerySkuVOFromDBThreadTask(requestParameter.getGoodsId(), repository); FutureTaskSkuVO skuVOFutureTask new FutureTask(querySkuVOFromDBThreadTask); threadPoolExecutor.execute(skuVOFutureTask); // 写入上下文 - 对于一些复杂场景获取数据的操作有时候会在下N个节点获取这样前置查询数据可以提高接口响应效率 dynamicContext.setGroupBuyActivityDiscountVO(groupBuyActivityDiscountVOFutureTask.get(timeout, TimeUnit.MINUTES)); dynamicContext.setSkuVO(skuVOFutureTask.get(timeout, TimeUnit.MINUTES)); log.info(拼团商品查询试算服务-MarketNode userId:{} 异步线程加载数据「GroupBuyActivityDiscountVO、SkuVO」完成, requestParameter.getUserId()); } Override public TrialBalanceEntity doApply(MarketProductEntity requestParameter, DefaultActivityStrategyFactory.DynamicContext dynamicContext) throws Exception { log.info(拼团商品查询试算服务-MarketNode userId:{} requestParameter:{}, requestParameter.getUserId(), JSON.toJSONString(requestParameter)); // 获取上下文数据 GroupBuyActivityDiscountVO groupBuyActivityDiscountVO dynamicContext.getGroupBuyActivityDiscountVO(); if (null groupBuyActivityDiscountVO) { return router(requestParameter, dynamicContext); } GroupBuyActivityDiscountVO.GroupBuyDiscount groupBuyDiscount groupBuyActivityDiscountVO.getGroupBuyDiscount(); SkuVO skuVO dynamicContext.getSkuVO(); if (null groupBuyDiscount || null skuVO) { return router(requestParameter, dynamicContext); } // 优惠试算 IDiscountCalculateService discountCalculateService discountCalculateServiceMap.get(groupBuyDiscount.getMarketPlan()); if (null discountCalculateService) { log.info(不存在{}类型的折扣计算服务支持类型为:{}, groupBuyDiscount.getMarketPlan(), JSON.toJSONString(discountCalculateServiceMap.keySet())); throw new AppException(ResponseCode.E0001.getCode(), ResponseCode.E0001.getInfo()); } // 折扣价格 BigDecimal deductionPrice discountCalculateService.calculate(requestParameter.getUserId(), skuVO.getOriginalPrice(), groupBuyDiscount); dynamicContext.setDeductionPrice(deductionPrice); return router(requestParameter, dynamicContext); } Override public StrategyHandlerMarketProductEntity, DefaultActivityStrategyFactory.DynamicContext, TrialBalanceEntity get(MarketProductEntity requestParameter, DefaultActivityStrategyFactory.DynamicContext dynamicContext) throws Exception { // 不存在配置的拼团活动走异常节点 if (null dynamicContext.getGroupBuyActivityDiscountVO() || null dynamicContext.getSkuVO() || null dynamicContext.getDeductionPrice()) { return errorNode; } return tagNode; } }1. multiThread异步加载数据区这是本节新增的核心方法区负责异步加载两路数据异步查询活动配置构造QueryGroupBuyActivityDiscountVOThreadTask入参为activityId、source、channel、goodsId和仓储repository包装成FutureTaskGroupBuyActivityDiscountVO后提交给线程池执行异步查询商品信息构造QuerySkuVOFromDBThreadTask包装成FutureTaskSkuVO后同样提交线程池执行。代码注释明确说明实际生产中商品数据有两种来源——外部接口查询或统一的商品同步库本节暂时使用 DB 方式查询写入上下文通过dynamicContext.setGroupBuyActivityDiscountVO(...)与dynamicContext.setSkuVO(...)将两路异步结果放入上下文。此处futureTask.get(timeout, TimeUnit.MINUTES)带有超时控制避免异步任务异常导致接口无限等待。这段设计精妙之处在于对于一些复杂场景数据的获取操作原本可能要分散到后续多个节点分别执行而这里把查询前置 并行后续节点直接从上下文取用接口响应效率自然大幅提升。2. doApply业务逻辑处理区doApply从上下文取出异步加载好的数据并完成业务判断获取GroupBuyActivityDiscountVO、GroupBuyDiscount、SkuVO任一为空则直接router流转交由后续get判断走向根据groupBuyDiscount.getMarketPlan()从discountCalculateServiceMap中取出对应的折扣计算服务不存在则抛出AppException(ResponseCode.E0001)并打印支持的折扣类型调用discountCalculateService.calculate(userId, skuVO.getOriginalPrice(), groupBuyDiscount)计算出折扣价格deductionPrice写入上下文最后router流转到下一节点。3. get节点路由区get负责判断下一步走向当上下文缺少活动配置、商品信息或折扣价格时路由到errorNode异常兜底节点否则路由到tagNode人群标签节点该节点在第2-7节中实现。可以看到一个节点被天然划分成异步加载、业务处理、路由决策三个功能区每个方法各司其职这正是让代码具有文档属性的具体体现。该模型结构可解决非常多类似的业务场景无论后续维护代码、迭代需求还是排查线上问题都会变得容易。四、新增 sku 商品信息表本节在库表层面新增了一张sku 商品信息表其作用是通过商品信息表获得当前商品的价格配置以此作为商品折扣计算的基础数据。关于商品数据来源实际生产中有两种实现方式每次调用外部接口获取商品——实时性最好但对下游依赖强、耗时长统一的商品同步库可以查询——通过数据同步任务把商品信息落到本地库查询快且稳定。本节采用第二种方式先通过一个统一的商品库进行处理。后续谁要对接这套拼团系统只需调用 sku 商品库、同步好商品即可库表结构已更新到工程下。在异步加载中QuerySkuVOFromDBThreadTask正是从这张 sku 商品表中查出SkuVO含originalPrice原价供doApply阶段的折扣计算使用。五、与后续折扣计算的衔接本节的异步数据加载完成之后第2-4节策略模式优惠折扣计算将开始使用这些数据做折扣计算MarketNode节点的数据异步加载工作已在上一节即本节完成后续节点开始使用上下文中的数据折扣在数据库中配置按类型包括ZJ - 直减、MJ - 满减、ZK - 折扣、N - N元购这些不同类型用策略模型进行包装每个实现类专门负责自己的逻辑计算。也就是说本节解决了数据怎么高效拿到的问题下一节解决拿到后怎么算的问题二者通过DynamicContext上下文无缝衔接。六、面试视角为什么试算用多线程异步加载而不是缓存在项目面试问答中详见 notes.md 面试问题汇总有一个高频问题与本节内容强相关优惠试算使用了多线程异步加载为什么这里不用缓存答案是试算加载的是当前用户行为的最新数据缓存不适用于此场景。当然如果试算中有一些不频繁变化的偏固定配置类数据则可以通过缓存处理。另外在美团、京东、字节、滴滴等公司的拼团场景中试算时需要的数据量更多、链路更长多线程异步加载会更体现出必要性。这提醒我们异步加载与缓存是两种不同维度的优化手段——前者解决多数据源串行等待的耗时问题后者解决重复查询的 IO 问题需要根据数据特性选择合适的手段。七、本章小结本节完成了三件事模型扩展在通用规则树模型结构上扩展出异步数据加载区multiThread让节点的职责边界更清晰功能串联在MarketNode节点落地异步加载操作通过FutureTaskThreadPoolExecutor并行加载活动配置与商品信息写入上下文供后续节点使用从串行改为并行显著降低试算接口响应时间库表补充新增 sku 商品信息表作为折扣计算的价格数据来源并明确了商品统一同步库的对接方式。结合项目总览可以看到这套规则树 异步数据加载的通用设计模式框架正是整个拼团交易平台解决复杂试算场景的核心组件之一——既提高了接口响应效率也让整个框架的灵活性和可扩展性非常高。后续的折扣计算第2-4节、人群标签过滤第2-7节等节点都建立在本节打下的异步加载基础之上。赞分享文档教程后端【免费下载链接】CodeGuide:books: 本代码库是作者小傅哥多年从事一线互联网 Java 开发的学习历程技术汇总旨在为大家提供一个清晰详细的学习教程侧重点更倾向编写Java核心内容。如果本仓库能为您提供帮助请给予支持(关注、点赞、分享)项目地址https://gitcode.com/gh_mirrors/code/CodeGuide点击查看免费下载相关推荐如何用多线程数据加载加速PyTorch图像分割模型训练如何用多线程数据加载加速PyTorch图像分割模型训练 Segmentation Models PyTorch是一个功能强大的图像语义分割库提供了12种预训练人工智能深度学习计算机视觉CodeGuide 拼团交易平台小商城与营销锁单接口对接实战指南CodeGuide 拼团交易平台小商城与营销锁单接口对接实战指南 本文基于 CodeGuide 仓库《拼团交易平台系统》项目文档讲解小型支付商城s pay文档教程后端在Android主屏幕一键执行Termux命令Termux Widget完全指南在Android主屏幕一键执行Termux命令Termux Widget完全指南 你是否经常需要在Termux中执行重复的命令每次都要打开Termux应用、移动开发上一篇3步搞定OpenMetadata部署从零搭建企业级元数据平台下一篇Java反编译完整教程JD-GUI从入门到精通指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考