分布式高级篇(五)

分布式高级篇(五) - 商城业务 - 商品详情

异步&线程池

线程回顾

初始化线程的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 提供了四个静态方法来创建一个异步操作

    image-20210120084908719

    • 1、runXXX都是没有返回结果的,supplyXXX都是可以获取返回结果的

    • 2、可以传入自定义的线程池,否则就用默认的线程池

      image-20210120090250913

计算完成时回调方法

  • whenComplete 和 exceptionally

    image-20210120090631739

    image-20210120090735779

    • whenComplete 可以处理正常和异常的计算结果,whenComplete 处理异常情况

    • whenComplete 和 whenCompleteAsync 的区别:

      whenComplete :是执行当前任务的线程继续执行 whenComplete 的任务

      whenCompleteAsync : 是执行把 whenCompleteAsync 这个任务继续提交给线程池来执行

    • exceptionally:感知异常、处理异常,并可以设置返回的默认值

    方法不以Async结尾,意味着Action使用相同的线程执行,而Async可能会使用其他线程执行(如果是使用相同的线程池,也可能会被同一个线程选中执行)

  • 举例说明

    image-20210120092403505

handle方法

  • hanlde

    whenComplete 是方法执行完成后的处理

    handle 是无论方法成功还是失败,执行的处理(可以感知异常、处理异常,改变返回值)

    image-20210120093103001

  • 举例说明

    image-20210120093953906

线程串行化方法

  • 串行化

    thenRun / thenRunAsync :只要上一个任务执行完成,就开始执行thenRun,只是执行完成后,执行thenRun的后续逻辑

    thenAccept / thenAcceptAsync:消费处理结果;接收任务的处理结果,并消费结果,无返回结果

    thenApply / thenApplyAsync:当一个线程依赖另一个线程时,获取上一个任务返回的结果,并返回当前任务的返回值

    带有Async 默认是异步执行的,不是使用同一个线程完成

    image-20210120101713295

  • 举例说明

    image-20210120110505611

两任务组合 - 都要完成

  • 两个任务必须都完成,触发该任务

    • thenCombine:组合两个future,获取两个future的返回结果,并返回当前任务的返回值
    • thenAcceptBoth:组合两个future,获取两个future任务的返回结果,然后处理任务,没有返回值
    • runAfterBoth:组合两个future,不需要获取两个future的结果,只需要在两个future处理完成后,处理该任务

    image-20210120110637414

    image-20210120110725539

  • 举例说明

    image-20210120113943551

两任务组合 - 一个完成

  • 当两个任务中,任意一个future任务完成的时候,执行任务

    • applyToEither:两个任务有一个执行完成,获取它的返回值,处理任务并有新的返回值
    • acceptEither:两个任务有一个执行完成,获取它的返回值,处理任务,没有新的返回值
    • runAfterEither:两个任务有一个执行完成,获取它的返回值,不需要获取future的返回值,处理任务,也没用返回值

    image-20210120114604327

  • 举例说明

    image-20210120133550196

多任务组合

  • allOf:等待所有任务完成

  • anyOf:只要有一个任务完成

    image-20210120133731003

  • 举例说明

    image-20210120135212496

商品详情

搭建商品详情页面环境

  • 添加详情页的域名 修改hosts文件

    C:WindowsSystem32driversetchosts

    image-20210120141348313

  • 添加nginx配置

    item.mall.com 包含在现有的规则中,可以不用修改(*.mall.com

    image-20210120141703077

  • 添加网关路由规则(配置完成,重启网关服务)

    浏览器输入 item.mall.com -->nginx -->转发给 gateway --> 商品服务

    image-20210120142028230

  • 浏览器输入:item.mall.com 测试

    image-20210120142216906

  • 静态资源 存放到nginx

    拷贝到 nginx/html/static/item 目录下

    image-20210120143015862

    修改 详情页.html 的所有静态资源请求路径

    image-20210120143144233

    image-20210120143320245

  • 修改检索服务,商品点击的跳转为:

    image-20210120144436760

  • 最终效果

查询商品详情

商品详情数据模型抽取

  • 页面需要的所有属性

    //1、sku基本信息获取 pms_sku_info
    //2、sku的图片信息 pms_sku_images
    //3、获取spu的销售属性
    //4、获取spu的介绍
    //5、获取spu的规则参数信息
    

    image-20210120160645096

查询商品详情

  • 查出当前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>
      
  • 单元测试

    image-20210121095945809

详情页渲染

  • 页面标签位置

    • 标题 :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"

    image-20210121110308442

销售属性渲染(切换)

  • 渲染需求:根据选择的机身颜色,内存,切换不同商品的详情

    image-20210121110444413

    分析:修改上一步:获取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
      

      image-20210121111519482

    • 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>
      
    • 单元测试

      image-20210121112727919

  • 重新渲染页面

    • 选择属性(颜色、版本、内存):class="box-attr-3"

    • 回显当前商品属性

      image-20210121133929876

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";
    

异步编排优化

配置线程池

  • 参数可配置

    image-20210121143759229

商品详情异步编排

  • 异步编排目的,提高系统资源利用率,加速查询,提高吞吐量等

  • 商品详情异步思路

    //1、sku基本信息获取 pms_sku_info
    //2、sku的图片信息 pms_sku_images
    //3、获取spu的销售属性
    //4、获取spu的介绍
    //5、获取spu的规则参数信息
    
    //查询1和查询2 互不相关 :两个异步任务
    //查询3、4、5都依赖于查询1的结果 :查询1.thenAccept()
    

    image-20210121144157588

原文地址:https://www.cnblogs.com/w3angb/p/14308390.html