流量统计方案
华为甲方领导一拍脑门想要统计每个产品的浏览量记录,想把每个产品的页面浏览流量(字节数)记录下来,放入数据库.想以后管浏览这些信息的人收费。
初步设想:
1 继承HttpServletResponse类,写一个可获得输出流大小的类
2 配置编写过滤器,使用自己的Response,成功获得输出流大小
3 根据httpHeader :Referer 判断承载页面的url,必须是要统计的页面才进行拦截,统计静态文件大小
4 设计一个承载操作信息Map,在action端放入Session,详情如下。每次操作取出count加1,如果session消失了,从0开始。 Map.put("dataBean",dataBean)与数据库字段关联的bean;Map.put("dataInfoId",Userid+count)这是服务端记录全局操作的唯一标示;Map.put("UserOptionCount",count)这是该用户的操作次数的标示。然后将这个Map放进Session,Session.put("dataMap",Map),输出页面的时候将dataInfoId输出到页面上。待异步的ajax请求时用为参数使用
5 过滤器端也有一个全局Map(dataInfoId,int[])。过滤器将截获的流量大小放入id对应的int组数中.还有一个BeanMap(dataInfoId,dataBean)将session里的信息放进来
6 页面全部加载好之后,发送一个异步ajax请求,告诉服务器端口将Map(dataInfoId,int[])里的int数组全部加起来,然后set进BeanMap(dataInfoId,dataBean)的bean中
7将这个bean放入MQ中
*8发生用户连续点击的情况,页面加载完成的ajax异步请求如果不能发起。如果真实环境确实会有这种情形发生(需要验证)。就在ajax异步请求完成时在session里加上一个字段,completeCount。每次完成操作也自增1,正常情况下count和completeCount应该一致,如果没一致则触发6-7的操作
简单设计 第一步
1 在Acion里面获得其他所有信息(用户信息,时间,语言,IP,访问资源信息,调用Action类名,浏览器信息)组装成DataBean。ServletConetext.put("urlid",Map);urlid是url+user,这个Map里有DataBean和一个CopyOnWriteArrayList,这个List来装载这次点击所有访问的每一个流量,最后一起总和。
2 Filter获取输出流量,根据urlid(可根据Http Refer来获得)放入Map里的CopyOnWriteArrayList里;
3 页面加载好后,发送Ajax请求带上url,根据urlid,Filter将CopyOnWriteArrayList里的流量全加起来,放入DataBean中。再将这个DataBean放入流量管理类FlowManager的CopyOnWriteArrayList中,然后删掉。如果CopyOnWriteArrayList长度大于 100(可配置)就全部复制进另一个List,然后清空,马上继续接收bean。另一个List马上新起一个线程去批量调用webservice插入数据库中。
问题:
用了集群,每次请求有可能分配到不同的Node,那ServletConetext就不唯一了。另一种方式就是把dataBean放在Session中,因为Session在集群中可以复制。为尽量减少Session里数据,流量每次都同步计算加法直接放在dataBean中,不再最后统一加再放入。其他逻辑都一样。
结论:服务器集群采用的粘性集群,即一个浏览器发出的请求都分配向一个node。这样就可以使用全局Map,第一种方法了。
问题1 一个用户快点连续点击一个连接,那urlid就分辨不出是哪次操作了。
问题2 一个账号可以多个人同时登陆,点击一个连接,urlid也分辨不出了。
解决方式,加入cookie,这个cookie有个流水号,年月日第几次点击。将id变成user+流水号。
-----------
问题3 点击之后页面还没完全展示就关闭,就不会发起那个页面完成的ajax请求,这个databean就会一直在servletConext里不会被插入数据库添加队列中。databean加入创建时间字段,使用定时任务扫描,多少时间以上的databean就移到序列中。