Skip to content

流量统计架构 ​

类关系图 ​

class

说明 ​
  1. PageViewEnum 流量统计枚举,统计商品sku流量、店铺流量、其他流量

  2. PageViewPoint 流量统计切点,注入到业务代码中,如商品服务中

    java
    // 获取商品详情API
     		@ApiOperation(value = "通过id获取商品信息")
        @ApiImplicitParams({
                @ApiImplicitParam(name = "goodsId", value = "商品ID", required = true, paramType = "path"),
                @ApiImplicitParam(name = "skuId", value = "skuId", required = true, paramType = "path"),
                @ApiImplicitParam(name = "distributionId", value = "分销商ID", dataType = "String", paramType = "query")
        })
        @GetMapping(value = "/sku/{goodsId}/{skuId}")
        @PageViewPoint(type = PageViewEnum.SKU, id = "#id")
        public ResultMessage<Map<String, Object>> getSku(@NotNull(message = "商品ID不能为空") @PathVariable("goodsId") String goodsId,
                                                         @NotNull(message = "SKU ID不能为空") @PathVariable("skuId") String skuId,
                                                         String distributionId) {
    
            Map<String, Object> map = goodsSkuService.getGoodsSkuDetail(goodsId, skuId);
    
            //判断如果传递分销员则进行记录
            if (CharSequenceUtil.isNotEmpty(distributionId)) {
                distributionService.bindingDistribution(distributionId);
            }
    
            return ResultUtil.data(map);
        }
  3. PageViewInterceptor 流量切入业务,对请求进行处理,记录商品流量/店铺流量

  4. TimedTaskJobHandler xxljob 分布式任务调度中心,负责每日任务调度,进行昨日流量汇总

  5. EveryDayExecute 每日任务接口,流量统计属于每日任务

  6. PageViewStatisticsExecute 流量统计执行,即流量业务汇总具体业务类

  7. Cache 高性能流量记录方法,后续会有具体说明及其示例、含义

  8. PlatformViewData 流量持久化对象,包含店铺id、pv、uv、日期等关键字段

流程图 ​

交互图

说明 ​
  1. 用户请求店铺信息、商品信息等需要流量统计的业务
  2. 切点被aop拦截执行
  3. redis高性能计数器和高性能去重统计,分别记录PV和UV
  4. 每日任务调度中心调用流量统计每日任务
  5. redis中模糊获取待入库的流量信息
  6. 由于任务是第二天执行,第二天可能还有产生流量数据,所以需要过滤不需要统计的信息
  7. 数据批量持久化到数据库
  8. 清除缓存中的流量信息

架构描述 ​

在这块代码中,主要用到了redis的计数器,以及redis的HyperLogLog,这两个工具类用的好,可以大大提升服务器性能,具体优势我已经加入超链接,可以点过去看一下,如果发现文章描述的不清楚,可以去百度,google 或者论坛,看看别人的使用方法以及对该功能的描述。

  1. 计数器用于PV统计,HyperLogLog用于UV统计,同时记录店铺UV、PV和平台的UV、PV。目前没有拆分到商品流量统计级别,如果需要这样处理,那么这块代码需要翻一倍,在代码测试中测试,单节点2000次循环,执行时间不到1秒,但是实际应用场景,商品流量的统计准备放在后边继续实现。示例代码中,通过缓存有规律的key来处理流量的累积。

    java
                //平台PV 统计48小时过期 留下一定时间予以统计累计数据库
                cache.incr(CachePrefix.PV.getPrefix() + StatisticsSuffix.suffix(), 60 * 60 * 48);
    
                //平台UV统计
                cache.cumulative(CachePrefix.UV.getPrefix() + StatisticsSuffix.suffix(), ip);
    
                //店铺PV 统计48小时过期 留下一定时间予以统计累计数据库
                cache.incr(CachePrefix.STORE_PV.getPrefix() + StatisticsSuffix.suffix(storeId), 60 * 60 * 48);
    
                //店铺UV 统计,则需要对id去重复,所以如下处理
                cache.cumulative(CachePrefix.STORE_UV.getPrefix() + StatisticsSuffix.suffix(storeId), ip);
  2. 流量结果处理,位于每日流量统计的代码的地方。通过模糊匹配key值的规则,得到系统中需要处理的数据,在key的获取中,我们看到一个filterKeys的方法。这个方法主要负责过滤今天的数据,即流量统计数据生成只生成昨天即以前的数据,另外在上一段代码中我们可以看到有效时间是48小时,也就是说,如果服务器连续down机3天,那么流量就会空缺,上一段代码的缓存有效时间可以适当加长。

    java
    				//1、缓存keys 模糊匹配
            //2、过滤今日的数据,即今天只能统计今日以前的数据
            //  4对key value 分别代表平台PV、平台UV、店铺PV、店铺UV
            List<String> pvKeys = filterKeys(cache.keys(CachePrefix.PV.getPrefix() + "*"));
            List<Integer> pvValues = cache.multiGet(pvKeys);
    
            List<String> storePVKeys = filterKeys(cache.keys(CachePrefix.STORE_PV.getPrefix() + "*"));
            List<Integer> storePvValues = cache.multiGet(storePVKeys);
  3. 流量结果处理后,批量保存数据库数据成功后,则会删除对应的缓存key,那么下次统计就不会在此统计这一天的数据了。流量统计到此结束。

    java
        /**
         * 批量保存数据&&清除保存数据的缓存
         *
         * @param pvKeys           PV key
         * @param uvKeys           UV key
         * @param platformViewData DOS
         */
        @Transactional(rollbackFor = Exception.class)
        void batchSave(List<String> pvKeys, List<String> uvKeys, List<PlatformViewData> platformViewData) {
            log.debug("批量保存流量数据,共计【{}】条", platformViewData.size());
            platformViewDataService.saveBatch(platformViewData);
            //批量删除缓存key
            cache.multiDel(pvKeys);
            cache.multiDel(uvKeys);
            log.debug("流量数据保存完成");
        }