分布式高级篇(五) - 商城业务 - 异步和商品详情-cnblog
# 分布式高级篇(五) - 商城业务 - 商品详情
异步&线程池
线程回顾
初始化线程的4种方式
-
1、继承Thread
-
2、实现Runnable接口
-
3、实现Callable接口 + FutureTask (可以拿到返回结果,可以处理异常)
-
4、线程池
方式1和方式2:主进程无法获取线程的运算结果,不适合当前场景
方式3:主进程可以获取线程的运算结果,但是不利于控制服务器中的线程资源。可能导致服务器资源耗尽
方式4:通过如下两种方式初始化线程池
// 【将所有的多线程异步任务交给线程池执行】 // 当前系统中池只有一两个,每个异步任务,提交给线程池,让它自己调度
// 可以控制资源,性能稳定
//第一种:Executors
//第二种:ThreadPoolExecutor(原生)
通过线程池性能稳定,也可以获取执行结果,并捕获异常。但是**在业务复杂的情况下,一个异步调用可能会依赖另一个异步调用的执行结果**
#### 线程池的7大参数
* ThreadPoolExecutor
![image-20210119113734089](https://img2020.cnblogs.com/blog/1875400/202101/1875400-20210121151307073-240937379.png)
![image-20210119152305430](https://img2020.cnblogs.com/blog/1875400/202101/1875400-20210121151306847-114976845.png)
* 核心线程数:corePoolSize(只要线程池不销毁,就会一直存在,除非设置了 `allowCoreThreadTimeOut`)
线程池创建好以后,就准备就绪的线程数量,就等待异步任务来执行
* 最大线程数量:maximumPoolSize(控制资源)
* 存活时间:keepAliveTime
如果当前线程数量大于核心线程数,只要**线程空闲时间大于指定的keepAliveTime**,释放空闲线程(maximumPoolSize - corePoolSize)
* TimeUnit unit
keepAliveTime的时间单位
* 阻塞队列:BlockingQueue<Runnable> workQueue
如果任务有很多,就会将目前多的任务放在队列里。只要线程空闲,就会去队列里取出新的任务继续执行
* 线程的创建工厂:threadFactory
* RejectedExecutionHandler handler:如果队列满了,按照我们指定的拒绝策略拒绝执行任务
##### 运行流程(*)
1. 线程池创建,准备好core(核心线程数)数量的核心线程数,准备接受任务
2. 新的任务进来,用core准备好的空闲线程执行
* 核心线程数满了,将再进来的任务放入阻塞队列中,空闲的core会自动去阻塞队列获取任务执行
* 阻塞队列满了,就直接开新的线程执行,最大只能开到max指定的数量
* max都执行好了,Max-core个数的空闲线程会在 `keepAliveTime`指定的时间后自动销毁,最终保持到core大小
* 如果线程数开到了max数量,还有新任务进来,就会使用`RejectedExecutionHandler` 指定的拒绝策略进行处理
3. 索引的线程创建都是由指定的factory创建的
##### 线程池设计题
* 一个线程池 core:7、max:20、queue:50,、这时候100并发进来怎么分配?
答:7个会立即执行,50个会进入阻塞队列,再开13个线程进行执行,总计70个,剩下的30个就会使用拒绝策略进行处理(一般使用抛弃策略AbortPolicy,如果不想抛弃还想继续执行,可以使用CallerRunsPolicy,以同步的方式执行)
#### 常见的4种线程池
* newCachedThreadPool(core是0,所有都可回收)
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程
* newFixedThreadPool(core==max)
创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待
* newScheduledThreadPool
创建一个定长线程池,支持定时以及周期性任务执行
* newSingleThreadExecutor
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务顺序执行
#### 为什么使用线程
* 降低资源的消耗
* 通过重复利用已经创建好的线程降低线程的创建和销毁带来的损耗
* 提高响应速度
* 因为线程池中的线程数没有超过线程池的最大上限时,有的线程处于等待分配任务的状态,当任务来时无需创建新的线程就能执行
* 提高线程的可管理性
* 线程池会根据当前系统特点对池内的线程进行优化处理,减少创建和销毁线程带来的系统开销,无限的创建和销毁线程不仅消耗系统资源,还降低系统的稳定性,使用线程池进行统一分配
* **例子**:以单核CPU为例,不使用线程池的情况下:假设当前有1000个线程同时运行,看起来是同时执行,但CPU需要拿到它的**时间片**才会执行。cpu需要再1000个线程上来回切换、线程恢复,**整个过程非常耗费资源**
如果是以线程池的方式控制,最多只有max个线程同时执行,cpu的时间片切换和线程恢复也只需要在max个之间进行,降低了资源消耗,提高了效应速度
### CompletableFuture异步编排
* 业务场景:
查询商品详情页的逻辑比较复杂,有些数据还需要远程调用,必然需要花费更多的时间
```java
//1、获取sku的基本信息 0.5s
//2、获取sku的图片信息 0.5s
//3、获取sku的促销信息 1s
//4、获取spu的所有销售属性 1s
//5、获取规格参数、组以及组下的规格参数 1.5s
//6、spu详情 1s
假如商品详情页的每个查询,需要如下标注的时间才能完成
那么用户需要5.5s后才能看到商品详情页的内容,很显然是不能接受的
如果有多个线程同时完成这6步操作,也许只需要1.5s即可完成响应
创建异步对象
-
CompletableFuture 提供了四个静态方法来创建一个异步操作
-
1、runXXX都是没有返回结果的,supplyXXX都是可以获取返回结果的
-
2、可以传入自定义的线程池,否则就用默认的线程池
-
计算完成时回调方法
-
whenComplete 和 exceptionally
-
whenComplete 可以处理正常和异常的计算结果,whenComplete 处理异常情况
-
whenComplete 和 whenCompleteAsync 的区别:
whenComplete :是执行当前任务的线程继续执行 whenComplete 的任务
whenCompleteAsync : 是执行把 whenCompleteAsync 这个任务继续提交给线程池来执行
-
exceptionally:感知异常、处理异常,并可以设置返回的默认值
方法不以Async结尾,意味着Action使用相同的线程执行,而Async可能会使用其他线程执行(如果是使用相同的线程池,也可能会被同一个线程选中执行)
-
-
举例说明
handle方法
-
hanlde
whenComplete 是方法执行完成后的处理
handle 是无论方法成功还是失败,执行的处理(可以感知异常、处理异常,改变返回值)
-
举例说明
线程串行化方法
-
串行化
thenRun / thenRunAsync :只要上一个任务执行完成,就开始执行thenRun,只是执行完成后,执行thenRun的后续逻辑
thenAccept / thenAcceptAsync:消费处理结果;接收任务的处理结果,并消费结果,无返回结果
thenApply / thenApplyAsync:当一个线程依赖另一个线程时,获取上一个任务返回的结果,并返回当前任务的返回值
带有Async 默认是异步执行的,不是使用同一个线程完成
-
举例说明
两任务组合 - 都要完成
-
两个任务必须都完成,触发该任务
- thenCombine:组合两个future,获取两个future的返回结果,并返回当前任务的返回值
- thenAcceptBoth:组合两个future,获取两个future任务的返回结果,然后处理任务,没有返回值
- runAfterBoth:组合两个future,不需要获取两个future的结果,只需要在两个future处理完成后,处理该任务
-
举例说明
两任务组合 - 一个完成
-
当两个任务中,任意一个future任务完成的时候,执行任务
- applyToEither:两个任务有一个执行完成,获取它的返回值,处理任务并有新的返回值
- acceptEither:两个任务有一个执行完成,获取它的返回值,处理任务,没有新的返回值
- runAfterEither:两个任务有一个执行完成,获取它的返回值,不需要获取future的返回值,处理任务,也没用返回值
-
举例说明
多任务组合
-
allOf:等待所有任务完成
-
anyOf:只要有一个任务完成
-
举例说明
商品详情
搭建商品详情页面环境
-
添加详情页的域名 修改hosts文件
C:\Windows\System32\drivers\etc\hosts
-
添加nginx配置
item.mall.com
包含在现有的规则中,可以不用修改(*.mall.com
) -
添加网关路由规则(配置完成,重启网关服务)
浏览器输入 item.mall.com -->nginx -->转发给 gateway --> 商品服务
-
浏览器输入:item.mall.com 测试
-
静态资源 存放到nginx
拷贝到 nginx/html/static/item 目录下
修改 详情页.html 的所有静态资源请求路径
-
修改检索服务,商品点击的跳转为:
-
最终效果
查询商品详情
商品详情数据模型抽取
-
页面需要的所有属性
//1、sku基本信息获取 pms_sku_info //2、sku的图片信息 pms_sku_images //3、获取spu的销售属性 //4、获取spu的介绍 //5、获取spu的规则参数信息
查询商品详情
-
查出当前spu对应的所有属性的分组信息,以及属性对应的值
-
sql语句
# 使用别名,方便结果封装成我们所需要的对象 SELECT ppav.spu_id, pag.attr_group_name groupName, pag.attr_group_id, paar.attr_id attrId, pa.attr_name atrrName, ppav.attr_value attrValue FROM `pms_attr_group` pag LEFT JOIN pms_attr_attrgroup_relation paar on pag.attr_group_id=paar.attr_group_id LEFT JOIN pms_attr pa ON pa.attr_id=paar.attr_id LEFT JOIN pms_product_attr_value ppav ON ppav.attr_id=paar.attr_id WHERE pag.catelog_id=225 AND ppav.spu_id=10
-
xml
<!--只要有嵌套结果,就需要封装自定义结果集--> <resultMap id="spuItemGroupAttrVo" type="com.touch.air.mall.product.vo.SpuItemGroupAttrVo"> <result property="groupName" column="attr_group_name"></result> <collection property="attrs" ofType="com.touch.air.mall.product.vo.SpuBaseAttrVo"> <result property="attrValue" column="attr_value"></result> <result property="attrName" column="attr_name"></result> </collection> </resultMap> <select id="getAttrGroupWithAttrsBySpuId" resultMap="spuItemGroupAttrVo"> SELECT ppav.spu_id, pag.attr_group_name, pag.attr_group_id, paar.attr_id , pa.attr_name, ppav.attr_value FROM `pms_attr_group` pag LEFT JOIN pms_attr_attrgroup_relation paar on pag.attr_group_id=paar.attr_group_id LEFT JOIN pms_attr pa ON pa.attr_id=paar.attr_id LEFT JOIN pms_product_attr_value ppav ON ppav.attr_id=paar.attr_id WHERE pag.catelog_id=#{catalogId} AND ppav.spu_id=#{spuId} </select>
-
-
获取spu的销售属性
-
sql语句
#传入spuId # 1、分析当前spu的有多少个sku,所有sku涉及到的属性组合 SELECT pssav.attr_id attrId, pssav.attr_name attrName, GROUP_CONCAT(DISTINCT pssav.attr_value) attrValue FROM pms_sku_info psi LEFT JOIN pms_sku_sale_attr_value pssav ON pssav.sku_id=psi.sku_id WHERE psi.spu_id=2 GROUP BY pssav.attr_id,pssav.attr_name
-
xml
<select id="getSaleAttrsBySpuId" resultType="com.touch.air.mall.product.vo.SkuItemSaleAttrVo"> SELECT pssav.attr_id attrId, pssav.attr_name attrName, GROUP_CONCAT(DISTINCT pssav.attr_value) attrValue FROM pms_sku_info psi LEFT JOIN pms_sku_sale_attr_value pssav ON pssav.sku_id=psi.sku_id WHERE psi.spu_id=#{spuId} GROUP BY pssav.attr_id,pssav.attr_name </select>
-
-
单元测试
详情页渲染
-
页面标签位置
- 标题 :
class="box-name"
- 副标题:
class="box-hide"
- 大图:
class="probox"
- 大图放大:
class="showbox"
- 小图:
class="box-lh-one"
- 价格:
class="box-summary clear"
- 有货无货
- 选择属性(颜色、版本、内存):
class="box-attr-3"
- 商品介绍:
class="jieshoa actives"
- 规格包装:
class="baozhuang actives"
- 标题 :
销售属性渲染(切换)
-
渲染需求:根据选择的机身颜色,内存,切换不同商品的详情
分析:修改上一步:获取spu的销售属性 需要获取每种组合的skuId
-
改造 获取spu的销售属性
-
sql语句
#传入spuId # 1、分析当前spu的有多少个sku,所有sku涉及到的属性组合 SELECT pssav.attr_id attrId, pssav.attr_name attrName, pssav.attr_value attrValue, GROUP_CONCAT(DISTINCT psi.sku_id) skuIds FROM pms_sku_info psi LEFT JOIN pms_sku_sale_attr_value pssav ON pssav.sku_id=psi.sku_id WHERE psi.spu_id=2 GROUP BY attrId,attrName,attrValue
-
mapper.SkuSaleAttrValueDao.xml
<resultMap id="skuItemSaleAttrVo" type="com.touch.air.mall.product.vo.SkuItemSaleAttrVo"> <result column="attrId" property="attrId"></result> <result column="attrName" property="attrName"></result> <collection property="attrValue" ofType="com.touch.air.mall.product.vo.AttrValueWithSkuIdVo"> <result column="attrValue" property="attrValue"></result> <result column="skuIds" property="skuIds"></result> </collection> </resultMap> <select id="getSaleAttrsBySpuId" resultMap="skuItemSaleAttrVo"> SELECT pssav.attr_id attrId, pssav.attr_name attrName, pssav.attr_value attrValue, GROUP_CONCAT(DISTINCT psi.sku_id) skuIds FROM pms_sku_info psi LEFT JOIN pms_sku_sale_attr_value pssav ON pssav.sku_id=psi.sku_id WHERE psi.spu_id=#{spuId} GROUP BY attrId,attrName,attrValue </select>
-
单元测试
-
-
重新渲染页面
-
选择属性(颜色、版本、内存):
class="box-attr-3"
-
回显当前商品属性
-
SKU属性切换商品
-
切换js实现
//给checked 加上颜色 $(function () { $("a[class='sku_attr_value']").parent().css({"border": "solid 1px #CCC"}); $("a[class='sku_attr_value checked']").parent().css({"border": "solid 1px red"}); }) $(".sku_attr_value").click(function () { //1、获取到所有checked 当前的skus组合 // 点击的元素先添加上自定义的属性,为了识别我们是刚才被点击的 //注意:当前选择所在的属性行,以clicked属性为准,其他属性行 以checked为准 let skus = new Array(); $(this).addClass("clicked"); let cur = $(this).attr("skus").split(','); skus.push(cur); //去掉clicked 同一行的所有 checked $(this).parent().parent().find(".sku_attr_value").removeClass('checked') //找到其他销售属性行的skus $("a[class='sku_attr_value checked']").each(function () { skus.push($(this).attr("skus").split(',')); }) //console.log(skus); //2、取出交集,得到skuId // let skuId = $(skus[0]).filter(skus[1])[0]; let filterEle = skus[0]; for (var i = 1; i < skus.length; i++) { filterEle = $(filterEle).filter(skus[i]); } // console.log(filterEle[0]); //3、跳转 location.href = "http://item.mall.com/"+filterEle[0]+".html";
异步编排优化
配置线程池
-
参数可配置
商品详情异步编排
-
异步编排目的,提高系统资源利用率,加速查询,提高吞吐量等
-
商品详情异步思路
//1、sku基本信息获取 pms_sku_info //2、sku的图片信息 pms_sku_images //3、获取spu的销售属性 //4、获取spu的介绍 //5、获取spu的规则参数信息 //查询1和查询2 互不相关 :两个异步任务 //查询3、4、5都依赖于查询1的结果 :查询1.thenAccept()