当前位置: 首页 > news >正文

PV统计优化设计

一 起因

公司要对之前的pv,uv统计进行重构,原先的不准,而且查询速度很慢。

经调研发现这绝对是一个坑,pv、uv统计存在的设计看起来简单,但是瞬间流量大,特别是有抢购等功能时,设计不良会导致数据库访问压力大,还存在被用心不良者利用等情况。

系统原先设计是将用户的请求放到redis中去,而后每天晚上一次将数据同步到数据库,在redis中并没有保存每个用户的访问时间,而只是保存的是每分钟有多少pv、uv。
这种设计存在这样一些问题:

  1. 只统计到分钟,并不统计每个访问的具体时间,数据参考价值有限
  2. 对pv的查询会从redis中查一部分,从数据库中再查一部分,合并起来返回前端,开发实现上代码比较复杂。更别说在数据库中查询居然是用where min=xx来实现,1000分钟时间段的查询会查询1000次数据库,查询返回奇慢无比,能写出这个sql的简直是天才。
  3. 统计一次pv的redis操作要操作6次,数据结构的使用上存在问题。
redisTemplate.opsForValue().increment(nowMin,1);
int pv = redisTemplate.opsForValue().get(nowMin);
redisTemplate.opsForHash().put(PV_KEY, nowMin, pv);
redisTemplate.opsForSet().put(nowMin,uid);
int uv = redisTemplate.opsForSet().size(nowMin);
redisTemplate.opsForHash().put(UV_KEY, nowMin, pv);

原代码甚至要8次,这里无力吐槽,完全不把redis当资源,你知道如何能优化成一个redis操作么?

而且进行pv,uv统计肯定是要精确到用户的,这样才能看出什么用户进行了什么访问,方便后期的用户画像以及访问数统计。但如此一来带来的问题就是

  1. 数据库记录会急剧增长,以前只是统计分钟,一天也就1000+条数据,而如果粒度是细到用户的话,如果PV到千万级,即使每天同步也受不了
  2. 实时查询会从redis中取数据,redis中资源本来就稀缺,如果每天同步一次,意味着要从redis中取百万、甚至千万级数据,不仅同步会非常慢,而且无法满足实时查询的需求。

系统本身存在如下限制

  1. 必须使用oracle数据库,而且pv表与业务表就在同一个实例,要考虑不能有瞬时过大的流量影响到业务操作。
  2. 必须使用同一个微服务网关。

二 第一步

经思考实现了如下方案:

  1. 使用浏览器指纹来记录每一个用户,来记录uv,而不是使用用户id,将pv、uv统计与业务隔离。浏览器指纹是根据客户端的一些参数计算而成,业内已经有成熟解决方案,准确率能达到94%
  2. 优化入统计pv、uv时入redis的操作,重新设计redis的数据格式,将6次缩减为1次。

    redisTemplate.opsForHash().put(bizKey, devFinger + dateTime);

    只需一次redis操作, 落到数据库后用group by查询就能非常方便的按照分钟,小时,天来分组了

  3. 请求进来后不直接入库,也不直接入redis,而是放入mq,通过mq再入redis,起到削峰填谷的作用。测试环境redis存的速度大概能达到 3w/s, 3w的qps,taobao抢购系统恐怕都支撑起来了。
  4. 每分钟将redis的数据批量入数据库,而不是每天统计一次,因为千万级的数据统计不仅对oracle数据库,而且对redis都是巨大的压力,甚至很可能会导致读redis超时。同时将写数据库的操作分配到每分钟,降低数据库的压力
  5. 查询只从数据库中查,加上对应的索引,查询数据不会太慢。同时也降低了程序的复杂性,不用到redis中查了。

三 第二步

压测发现有两个问题

  1. mq挂了
  2. 同步到数据库时数据库也处理不过来。

我们mq是公用的,就是说所有的服务,而且不止我们的服务,都用到了mq,而pv,uv操作是一个超级大数量级别的操作,而且并非核心业务,所以不能把主要的资源都放到mq上,所以我们又继续进行了处理:

  1. 后端用了缓存队列,当pv满足10个的时候才发送,否则不发。此处要加synchonize,否则会出现异常的
  2. 数据库同步时做了柔性处理,当pv数据量过大的时候不处理,而是延后再做,等到pv量降下来后再处理

相关文章:

  • 非常好!讲逻辑回归的,讲得很透彻
  • Redis 分布式锁的正确实现方式
  • 协议适配器错误的问题
  • 2017-2018-1 20155229 《信息安全系统设计基础》第十一周学习总结
  • ORACLE常用数值函数、转换函数、字符串函数
  • MYSQL分表与分区
  • spring security oauth2 authorization code模式
  • 刷新页面清空 input text的值
  • 服务器数据库不用开通远程连接通过工具在本地连接操作的方法
  • 温故·我的笔记
  • 【转】NGUI版虚拟摇杆
  • 探索 DWARF 调试格式信息
  • java静态代码块,静态方法和非静态方法的加载顺序和执行顺序
  • 使用NVelocity0.5实现服务器端页面自动生成
  • 回顾2016
  • 【译】React性能工程(下) -- 深入研究React性能调试
  • ES10 特性的完整指南
  • JavaScript新鲜事·第5期
  • React组件设计模式(一)
  • Redis提升并发能力 | 从0开始构建SpringCloud微服务(2)
  • 基于Volley网络库实现加载多种网络图片(包括GIF动态图片、圆形图片、普通图片)...
  • 微信小程序开发问题汇总
  • 无服务器化是企业 IT 架构的未来吗?
  • 怎样选择前端框架
  • 追踪解析 FutureTask 源码
  • # .NET Framework中使用命名管道进行进程间通信
  • #Lua:Lua调用C++生成的DLL库
  • #我与Java虚拟机的故事#连载11: JVM学习之路
  • (AngularJS)Angular 控制器之间通信初探
  • (Matalb分类预测)GA-BP遗传算法优化BP神经网络的多维分类预测
  • (vue)页面文件上传获取:action地址
  • (附源码)springboot太原学院贫困生申请管理系统 毕业设计 101517
  • (每日持续更新)信息系统项目管理(第四版)(高级项目管理)考试重点整理 第13章 项目资源管理(七)
  • (转)C语言家族扩展收藏 (转)C语言家族扩展
  • (转载)hibernate缓存
  • ***linux下安装xampp,XAMPP目录结构(阿里云安装xampp)
  • .bat批处理(一):@echo off
  • .NET 4.0中使用内存映射文件实现进程通讯
  • .NET CF命令行调试器MDbg入门(四) Attaching to Processes
  • .NET CORE 2.0发布后没有 VIEWS视图页面文件
  • .NET Core 2.1路线图
  • .net 无限分类
  • .NET平台开源项目速览(15)文档数据库RavenDB-介绍与初体验
  • @javax.ws.rs Webservice注解
  • [ IOS ] iOS-控制器View的创建和生命周期
  • [ NOI 2001 ] 食物链
  • [ vulhub漏洞复现篇 ] Hadoop-yarn-RPC 未授权访问漏洞复现
  • [1204 寻找子串位置] 解题报告
  • [ABP实战开源项目]---ABP实时服务-通知系统.发布模式
  • [Android Studio] 开发Java 程序
  • [BZOJ 3680]吊打XXX(模拟退火)
  • [C/C++]数据结构 栈和队列()
  • [CareerCup] 13.1 Print Last K Lines 打印最后K行
  • [CLickhouse] 学习小计
  • [Deep Learning] 神经网络基础