zoukankan      html  css  js  c++  java
  • 分布式高级篇(五)

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

    异步&线程池

    线程回顾

    初始化线程的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

  • 相关阅读:
    win7为IIS7添加程序映射
    【整理】在一亿个数中寻找出现频率最多的4个 <转>
    优化SQL Server数据库的几个大招<转>
    最长单调子序列(DP)
    详细解说 STL 排序
    获取word中的原始大小图片
    MD5 算法实现(c++)
    udp 通信中的端口问题
    Yacc 与 Lex 快速入门
    HDOJ (HDU) 1561 The more, The Better (树形DP)
  • 原文地址:https://www.cnblogs.com/w3angb/p/14308390.html
Copyright © 2011-2022 走看看