案例讲解

电商 app 都有用过吧,商品详情页,需要给他们提供一个接口获取商品相关信息:

  1. 商品基本信息(名称、价格、库存、会员价格等)
  2. 商品图片列表
  3. 商品描述信息(描述信息一般是由富文本编辑的大文本信息)

数据库中我们用了 3 张表存储上面的信息:

  1. 商品基本信息表:t_goods(字段:id【商品 id】、名称、价格、库存、会员价格等)
  2. 商品图片信息表:t_goods_imgs(字段:id、goods_id【商品 id】、图片路径),一个商品会有多张图片
  3. 商品描述信息表:t_goods_ext(字段:id,goods_id【商品 id】、商品描述信息【大字段】)

这需求对于大家来说很简单吧,伪代码如下:
public Map detail(long goodsId){
//创建一个map
//step1:查询商品基本信息,放入map
map.put(“goodsModel”,(select from t_goods where id = #gooldsId#));
//step2:查询商品图片列表,返回一个集合放入map
map.put(“goodsImgsModelList”,(select
from t_goods_imgs where goods_id = #gooldsId#));
//step3:查询商品描述信息,放入map
map.put(“goodsExtModel”,(select from t_goods_ext where goods_id = #gooldsId#));
*return
map;
}

上面这种写法应该很常见,代码很简单,假设上面每个步骤耗时 200ms,此接口总共耗时>=600 毫秒,其他还涉及到网络传输耗时,估计总共会在 700ms 左右,此接口有没有优化的空间,性能能够提升多少?我们一起来挑战一下。
再看一下上面的逻辑,整个过程是按顺序执行的,实际上 3 个查询之间是没有任何依赖关系,所以说 3 个查询可以同时执行,那我们对这 3 个步骤采用多线程并行执行,看一下最后什么情况,代码如下:
package com.itsoku.chat26;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;

/
微信公众号:程序员路人,个人博客:http://www.itsoku.com
/
public class Demo1** {

  1. /**<br /> * 获取商品基本信息<br /> *<br /> * **@param** goodsId 商品id<br /> * **@return** 商品基本信息<br /> * **@throws** InterruptedException<br /> */<br /> **public** String **goodsDetailModel**(**long** goodsId) **throws** InterruptedException {<br /> //模拟耗时,休眠200ms<br /> TimeUnit.MILLISECONDS.sleep(200);<br /> **return** "商品id:" + goodsId + ",商品基本信息....";<br /> }
  2. /**<br /> * 获取商品图片列表<br /> *<br /> * **@param** goodsId 商品id<br /> * **@return** 商品图片列表<br /> * **@throws** InterruptedException<br /> */<br /> **public** List<String> **goodsImgsModelList**(**long** goodsId) **throws** InterruptedException {<br /> //模拟耗时,休眠200ms<br /> TimeUnit.MILLISECONDS.sleep(200);<br /> **return** Arrays.asList("图1", "图2", "图3");<br /> }
  3. /**<br /> * 获取商品描述信息<br /> *<br /> * **@param** goodsId 商品id<br /> * **@return** 商品描述信息<br /> * **@throws** InterruptedException<br /> */<br /> **public** String **goodsExtModel**(**long** goodsId) **throws** InterruptedException {<br /> //模拟耗时,休眠200ms<br /> TimeUnit.MILLISECONDS.sleep(200);<br /> **return** "商品id:" + goodsId + ",商品描述信息......";<br /> }
  4. //创建个线程池<br /> ExecutorService executorService = Executors.newFixedThreadPool(10);
  5. /**<br /> * 获取商品详情<br /> *<br /> * **@param** goodsId 商品id<br /> * **@return**<br /> * **@throws** ExecutionException<br /> * **@throws** InterruptedException<br /> */<br /> **public** Map<String, Object> **goodsDetail**(**long** goodsId) **throws** ExecutionException, InterruptedException {<br /> Map<String, Object> result = **new** HashMap<>();
  6. //异步获取商品基本信息<br /> Future<String> gooldsDetailModelFuture = executorService.submit(() -> goodsDetailModel(goodsId));<br /> //异步获取商品图片列表<br /> Future<List<String>> goodsImgsModelListFuture = executorService.submit(() -> goodsImgsModelList(goodsId));<br /> //异步获取商品描述信息<br /> Future<String> goodsExtModelFuture = executorService.submit(() -> goodsExtModel(goodsId));
  7. result.put("gooldsDetailModel", gooldsDetailModelFuture.get());<br /> result.put("goodsImgsModelList", goodsImgsModelListFuture.get());<br /> result.put("goodsExtModel", goodsExtModelFuture.get());<br /> **return** result;<br /> }
  8. **public** **static** **void** **main**(String[] args) **throws** ExecutionException, InterruptedException {<br /> **long** starTime = System.currentTimeMillis();<br /> Map<String, Object> map = **new** Demo1().goodsDetail(1L);<br /> System.out.println(map);<br /> System.out.println("耗时(ms):" + (System.currentTimeMillis() - starTime));<br /> }<br />}

输出:
{goodsImgsModelList=[图1, 图2, 图3], gooldsDetailModel=商品id:1,商品基本信息…., goodsExtModel=商品id:1,商品描述信息……}
耗时(ms):208

可以看出耗时 200 毫秒左右,性能提升了 2 倍,假如这个接口中还存在其他无依赖的操作,性能提升将更加显著,上面使用了线程池并行去执行 3 次查询的任务,最后通过 Future 获取异步执行结果。

整个优化过程

  1. 先列出无依赖的一些操作
  2. 将这些操作改为并行的方式

    用到的技术有

  3. 线程池相关知识

  4. Executors、Future 相关知识

    总结

  5. 对于无依赖的操作尽量采用并行方式去执行,可以很好的提升接口的性能

  6. 大家可以在你们的系统中试试这种方法,感受一下效果,会让你感觉很爽