优惠券读服务优化

来源:互联网 发布:达内 java培训机构 编辑:程序博客网 时间:2024/04/27 10:52

优惠券读服务优化

前几天成功解决了发券时的并发问题,参考这里。但是用户领取得卡一多,读取速度也慢了下来,用户领取了200张卡以后,取得自己所有的卡需要5s左右。
主要业务场景,查询用户卡包,得到用户所有的卡券。
优化完成后第一次访问卡包示意图
- 用户根据id分在不同的表。
- 卡券根据不同的商户,分在不同的表。

  1. 先用用户id拿到用户和卡的对应关系。
  2. 再根据每个卡的商户和 卡券id查询对应的卡券,因为卡分在不同的表,不能成批拿卡,只能串行拿。
  3. 拼装卡模板相关信息返回。

第一步,建立卡券的缓存系统。
以商户和卡券Id在添加一个字符前缀做key,把卡缓存在redis里,并在卡券状 态变更后,作废钓redis里对应的卡券。
这样做后,构建一个200张卡的卡包,第一次还是需要5秒,第二次访问只需要1s。大并发后响应时间更长。这显然不能满足要求。
第二步,建立用户的缓存系统。
在有了卡券的缓存后,另外以用户id为主键,对每一个用户查询的返回结果,再做一级缓存,形成二级缓存机制。用户卡有变动的时候,作废缓存。
有了二级缓存,还是200张卡,第一次访问需要5s,第二次响应在200ms。100个并发访问在400ms左右。
缓存预刷机制。
有了二级缓存之后,由于每次卡有变动,用户访问卡包构建cache在1s以上,会影响体验,我又加入了预刷缓存机制,在用户卡变动后,作废掉用户级别的缓存,和变动的单个卡的缓存,随即抛出一个用户的id进入队列,队列的尾端拿到用户的主键后,访问用户卡包构建缓存,这样当用户再次访问卡包的时候,响应体验提升很大。
第三步,并行构建卡券
在没有任何缓存的情况下,一次访问200个卡券的卡包在6s以上。
在只有卡券这一级缓存的情况下,构建用户返回结果,也需要1s以上。
因为取200个卡券的执行是单个串行执行的,分析下来这里是性能的瓶颈。优化方案是: 在构建200个卡券的时候,每一批卡开一个线程去构建,200个卡,可以同时开20到40个线程去构建,来缩短用户卡多的时候,这里的时间消耗。
这样做后,先禁用用户级别的缓存,来压力测试这里的性能。
在没有任何cache的情况下,单次访问200个卡的卡包,响应 时间在500ms,在有卡缓存没有用户缓存的情况下,单次的响应时间在200ms,构建用户级别缓存的时间已经大大缩短了。但在高并发的情况下,会报错,一查是因为redis的连接不够了,每个线程需要20-40个redis连接,所以redis要扩大连接数。
下面是多线程部分代码,对象的名字做了处理,并除去了业务逻辑。


/** * Created by haoli */          if (itemsList != null                    && CollectionUtils.isNotEmpty(itemsList)) {                int totalCount = itemsList.size();                int taskNumber = (totalCount%GROUP_COUNT == 0 ? totalCount/GROUP_COUNT:totalCount/GROUP_COUNT+1);                List<FutureTask<List<YourObject>>> ft = new ArrayList<FutureTask<List<YourObject >>>();                for(int i=0; i<taskNumber ; i++) {                    int toSize = ((i+1)*GROUP_COUNT>totalCount-1 ?totalCount : (i+1)*GROUP_COUNT);                    List<ItemObject> tempList  = itemsList.subList(i*GROUP_COUNT,toSize);                    SubQueryYourObjectTask task = new SubQueryYourObjectTask (tempList,this.yourService);                    FutureTask<List<YourObject >> ftItem = new FutureTask<List<YourObject >>(task);                    ft.add(ftItem);                   ThreadPoolExeService.execute(ftItem);                }                for(FutureTask<List<YourObject >> ftItem : ft){                    try {                        totals.addAll(ftItem.get());                    } catch (InterruptedException e) {                        log.error(e.toString());                    } catch (ExecutionException e) {                        log.error(e.toString());                    }                }

上面用到的子任务类代码

 /** * Created by haoli */    public class SubQueryYourObjectTask implements Callable<List<YourObject >> {    private List<ItemObject > parameter;    private YourService yourService ;    public SubQueryYourObjectTask (List<ItemObject > rlist, YourService yours){        parameter = rlist;        this.yourService = yours ;    }    @Override    public List<YourObject > call() throws Exception {        List<YourObject > results= new ArrayList<YourObject >();        for(ItemObject cbur :parameter ) {        results .add(this.yourService .getXXXById(cbur ));        }        return results ;    }}

优化到现在,不论是第一次还是之后的访问,都不慢了,在有二级缓存的情况下,cpu和连接资源消耗也不会太大。而且有缓存预刷机制,卡包数据更新后也不会慢了。

0 0
原创粉丝点击