难度:较低
- 平台的访问量非常高,需要实时统计网站的访问次数,请设计一个计数器解决:
- 初级工程师,可能回答使用synchronized锁或重入锁,进一步探讨,synchronized锁太重,有没其他方式,可能回答atomic类,进一步问,atomic类原理,什么场景下适合用,什么场景下不适合用
- atomic和synchronized都是单机方案,当一个服务器不能满足性能要求时,线上使用集群,如何在集群场景下实现计数器
- 可能回答在DB中计数,则问,如何避免更新丢失,即A和B两个线程同时读取当前计数,加1后更新DB,出现更新丢失
- 可能回答使用redis原子操作incr,则问当访问量极大,不能满足性能需求,如何做sharding,考虑一致性hash
- 面试者,不仅是回答问题,还可以探讨,所谓的实时统计,要求的精度是多高,如果有延迟10-20秒的误差,是否可接受,若可接受,还有其他哪些方案能达到高性能和高可用
方案分析
网站访问量统计功能大多是分布式集群情况下,才需要进行统计,如果当前系统只有一台机器,通过AOP做核心接口的切面拦截就可以实现,考虑的难点也很少。
在集群模式下,使用redis存储统计数据是可行的,但随之而来的成本问题和性能问题就需要考虑。
- 成本问题:当一个网站访问量较大时,如QPS达到1w/s时,每次请求都需要调用redis进行计数累加。这里无疑会加重redis负担,单机redis无法保证和支持需求,从而又需要引入集群版redis来保证服务可靠性、可用性,这又增加系统复杂度,增加开发成本运维成本。
- 性能问题:每次接口请求都调用redis的话,相当于接口整体耗时都要增加,虽然redis一次请求也就5ms左右,但对于整体接口提高一定耗时来增加一个不算是关键功能的业务,并不太划算,还是有很大的优化空间。
- 一致性问题:综合上面两个问题,势必需要采用集群版redis,而接口统计也不能每次都进行累积,可以通过异步或定时/定量的方式进行统计,例如优先完成接口处理,然后再通过异步线程池的方式统计,异步的时候也并不一定每次都统计,可以间隔2s,5s进行数据统计,或者达到100次,500次这样的阈值再统计,这样能提升接口性能,减轻redis集群压力,但也有了新的问题,数据不一致了,或者说这一秒的统计量并不是真正的访问量。
Redis方案:
拆分统计key,数十台服务器同时访问更新一个redis-key不如根据不同服务器地址特征来进行拆分,每台服务器修改自己对应的key,在汇总统计功能里,遍历所有的key列表来累加所有访问量,也可以得到实时的访问统计。
上面的方案还是存在对redis大量请求的问题,可以创建一个异步线程池用来请求redis进行数据上报,每台服务器的接口使用变量来记录接口访问量,当达到100次时创建一个统计任务,提交到线程池中进行处理,阈值越大对接口的性能影响越小,同样的数据不一致性越高。
nginx方案
正常网站请求都会通过nginx作为前端请求入口,nginx会记录每次访问日志,可以通过在每台服务器上挂日志脚本的形式来分析日志数据,或者直接监控日志文件变更内容,也可以实时的上报统计数据,并且不侵入现有服务,对当前服务接口没有影响。