Hotjar 提供了帮网站主了解用户行为的服务,网站接上此服务后,可以生成用户的点击热区,录制用户的行为,查看各个页面的跳出路径以及停留时间等,根据这些统计数据,网站主可以发现问题,有针对性的地完善产品
现在为 15万以上 的网站提供服务,脚本每天被请求 5亿次以上,数据库中的数据量达到 37.25TB,每天 5GB 左右的增长,每秒有 1500次 左右的写请求
从起步,到这个成绩,Hotjar只发展了两年,短时间内的快速发展,对 Hotjar的架构提出了挑战,通过不断的研究调整,顺利支撑住了网站的成长,并总结分享了几条经验
架构的演进过程
V1
初期架构非常简单,后台 Python,前台 AngularJS,数据库 PostgreSQL,使用 Amazon 的负载均衡服务 ELB 把请求分发给两台应用服务器,NewRelic 负责性能管理和监控
特点是简单,适合起步,并且具有很好的水平扩展能力
现在的架构
随着网站规模的增加,虽然V1架构经过扩充还是可以不错的提供服务,但技术栈过于单薄,需求场景增多,需要更多适合的技术来处理,到现在,技术栈已经相当丰富
负载均衡后面有8个应用服务器
性能监控还是使用 NewRelic
通过 Librato 监控实时操作和获取性能分析
StatusCake 和 VictorOps 在出现错误时发送通知
Intercom 和 Mixpanel 用来辅助市场团队
使用 Graylog 记录 JS和 Python的错误日志
数据存储使用 PostgreSQL,S3,Elasticsearch
Amazon 的 SES 服务给用户发送Email
Redis 和 Memcached 做为内存缓存
经验
(1)不要低估网站的成长速度,基础架构要能支持快速扩展
hotjar 刚开始只有2台应用服务器,共2个CPU和3.4G内存,现在有8台,64个CPU和120G内存
能够轻松和高效的进行扩展,是因为在初期架构设计中就非常重视水平扩展能力,这是非常重要的,一定要尽早考虑
(2)频繁访问的静态内容尽量使用 CDN
开始时,用户直接从 hotjar的服务器上加载统计脚本,加载量大后,性能变差,hotjar把脚本转到了CDN,加载速度和稳定性都大幅提升
(3)在重要的性能点上,不要局限在你的开发语言上,可以考虑使用更适合的语言
越多的用户使用 hotjar,就有越多的统计数据发送到服务器,数据处理的性能成为关键
hotjar的后台服务使用 python 开发,经过一系列的代码优化和性能测试,最后决定在这个功能点上不再使用 python,改用 Lua 开发
Lua 是一个强大的轻量级嵌入式脚本语言,非常快,自从使用 nginx lua 后,性能立即大幅提升,错误率降低,可以处理更多的请求
(4)如果某些数据对延时要求不高,并且获取简单,例如通过主键就可以查询到,这时可以考虑使用云存储,不必非要放在自己的数据库中
初期,hotjar把所有数据都存储在自己的关系数据库中,经过高速增长之后,发现数据库中有大量数据是很少访问的,便把此类数据迁移到云存储,并修改了相应代码
这样可以节省数据库空间,提升数据库查询性能
(5)你的核心数据库不一定适合所有场景,可以考虑使用更多的数据库来适应不同的需求
hotjar 发展了6个月后,每天需要处理 15万条记录,这时开始有用户反馈,浏览记录列表时非常慢,技术团队开始优化他们的数据库PostgreSQL
但结果并不理想,团队便寻找更加合适的技术,Elasticsearch 很快成为首选,
转换过程并不容易,先修改代码,把新记录同时写入 PostgreSQL 和 Elasticsearch,同时,后台进行历史记录拷贝,当数据迁移完成后,马上把查询切换到 ES
之后,用户立即就感受到了这个巨大的性能变化,效果很好
(6)有时,即使是一个小小的结构性变化,就可以在成本和性能上产生大的收益
尽管脚本放到了CDN上,但在优化脚本加载这个事儿上还可以做更多
每个用户都有一个唯一的脚本,如果用户修改了他的后台配置,这个脚本需要重新生成,导致脚本缓存失效,需要从新加载,大概40K左右,看似不大,但请求多了也受不了,CDN成本上去了
为此,决定把脚本分为两个,一个是 hotjar的业务代码,改动频率低,一个是用户的配置信息,体积很小,每次用户改动后,只影响这个小脚本
这个改动虽然简单,但是节省了很多成本,并且加载更快了
(7)尽管你无法在早期仔细考虑数据库的schema,但要确保有适当的监控,并思考如何在修改schema时减少对数据库的影响
hotjar 在数据库设计上吃过亏,开始时,所有表的ID字段类型设为了int4类型,这在 postgres 中是非常标准的选择,但后来问题来了
几个月之后,对所有用户网站的数据收集工作停止了,因为ID字段的值达到了上限,int4 能存储的最大数是 2,147,483,647
必然要修改数据类型,但数据库中已经有数十亿的记录,这个简单的更新操作将需要运行数天
为尽量降低停机时间,只能新建库,使用新的数据类型,然后进行数据迁移,修复这个错误最后花费了数周的工作
没有一个适合的监控,没能尽早发现问题,是一个重要教训
(8)监控是非常重要的,有越多的监控,就可以越快的识别问题
初期,处理用户的问题反馈时非常迷茫,常常无法复现问题,不知道这个问题是个例,还是影响了很多用户
后来加了一套监控,找问题方便多了,可以快速定位,也可以知道发生频率,大量减少了花在调查上的时间