## 数据敏感应用的普遍功能以及问题 ##
常有功能
1. 存储数据(database)
2. 缓存(cache)
3. 根据关键词查找(search indexes)
4. 发送消息异步处理(stream process)
5. 定期处理大量数据(batch process)
问题
针对以上5种功能,每种都有多种技术,这些技术如何选取,如何组合已完成目标哦,这是个技术活
## 站在数据系统的角度上思考,5个合起来考虑 ##
看上去,上面5个大功能互相不搭干,为什么要把他们合在一起考虑呢?
1. 一个组建可能有多种功能,例如redis可以作为存储,也可以作为消息队列,kafka既可以用作消息队列同时,他也有持久化的功能,这在一定程度上表现了数据库的特性
2. 我们的应用需求已经复杂到单个模块不能支持好,需要多个模块组合来搞才可能
当你需要把多个组件组合在一起的时候,就有很多尖锐的问题,例如缓存何时有效何时失效,系统内部错误了怎么办,系统如何扩展,这个系统的API应该如何设计。这里面有很多影响一个数据系统设计的因素,例如开发者的经验和技术模型,遗留系统以来(lecacy system dependency),组织上对于不同技术的风险容忍程度(这个还真的挺重要的……),技术转化的时间开销。
---
心得:
很多时候,技术选型不是哪个看上去好就好的,我们会因为会scala的人少而放弃原生spark,转用pyspark,尽管我知道原生的最好。我也知道TAF在设计很多东西的时候不方便,但是因为他一整套运维而去用他。所以在选型时,最好的方法是列出所有已知技术的优势劣势, 在列出我们目前的需求,最后扔掉不能接受的方案,剩下的就是最好的。而这件事的难度就在于你对每个技术的理解甚至对每个人的技术的理解有多少。
## 本书关注的主要指标 ##
1. 可靠性
2. 可扩展性
3. 可维护性
##可靠性 ##
1. 应用正常情况下满足需求
2. 可以接受用户出现错误,或者未按照预期使用(必要的错误检查)
3. 在预期负载情况下,性能足够好,卡成狗是不能接受的
4. 抵挡所有的未授权或滥用(当年poiround被开平压死就是这个问题没做好)
一句话就是无论发生什么,你都不能崩,但是这其实做不到(地球爆炸)。所以我们可以在设计的时候明确规定在某些情况下(单机挂掉,单个机房挂掉,某一地区机房全挂,某些程序错误),系统不崩,然后大家来聊是不是可以接受。
异常时不能避免的,所以我们需要设计容灾的系统,判断容灾好不好的方法就是演习,比如故意搞挂一些正确运行的进程, Netflix chaos Monkey就是一个这方面的而例子。
---
硬件错误
特点:随机性强,错误彼此独立
硬件错误其实很频繁,最简单的防范是做冗余,比如说磁盘做RAID,服务器或者数据中心有自己的供电系统。目前单机的硬件冗余做的很好了,剩下一小部分对稳定性要求极高的应用要做多机冗余。
目前上云其实是一个很好的思路,就是不在care硬件错误,服务可以在机器来回迁移,这样一旦类似面对物理机要重启的问题,就很好解决了。不用停机。
---
软件错误
特点:错误重复性强,彼此相关,例如
1. 程序bug导致所有进程全部down掉
2. 一个进程用光了所有资源,CPU,内存,贷款,硬盘
3. 下游服务崩溃
4. 传递性错误,例如组件1挂了,然后2因为1挂了也挂了,结果都挂了
这种问题基本没辙,有些简单建议
1. 仔细思考你的系统假设和交互是否正确
2. 好好测试
3. 进程间独立(docker是个好东西)
4. 进程异常和重启(守护进程监控)
5. 监控报警
---
人为错误
人是不靠谱的,那怎么让不靠谱的人设计靠谱的服务呢?
1. 系统设计时尽可能的减少出错的可能,但是如果系统设计的太过严苛,人就不太好用,因为往往不直观,这里面有一个权衡
2. 沙箱环境,可以在里面自由测试,但是又不影响线上环境,就是我们的测试环境
3. 单元测试+集成测试
4. 快速修复错误,减少问题影响面。具体包括快速回滚,渐进发布(先上一台尝尝),针对已经出现错误的用户,提供重新计算的功能.客户情绪很重要啊。
5. 详细清晰的监控体系。(详细和清晰同样重要,太少就好像poiround被开平压超时,不知道为什么,不清晰就好像poiana每天好几十个报警一样,也不知道有没有用)
6. 务虚就是培训和管理,来个新人不知道直接搞挂也是有过经验教训的。
## 可扩展性 ##
可扩展性要求解决两个2题,如果负载涨了,我们应该怎么办?我们应该如何添加计算资源来应对增长的负载?(个人感觉这两个问题有点重复)
### 如何描述负载 ###
找到几个对你来说最重要的指标,例如每秒请求量,数据库读写比例,缓存命中率等等,有可能这些数值困扰你的是平均值也可能是极值。(例如定位每天要应对上百亿次的调用,也可能是体验系统每个月就一天超过上千qps,但是平常都小于10).
----
举个栗子
twitter主要有2个功能
1. 发消息,4600 qps, 最高12k
2. 收订阅人的消息(Home timeline), 300k qps
12K发消息还是很简单的,但是问题在于一算订阅的负载就疯了
书中有两个方案
+ 第一个吧所有的tweet放到一个大表里面,用户每次拉自己订阅的消息,就写一个类似的sql搞定
```
SELECT tweets.*, users.* FROM tweets
JOIN users ON tweets.sender_id = users.id
JOIN follows ON follows.followee_id = users.id
WHERE follows.follower_id = current_user
```
+ 另一种为每个用户维护一个自己收消息的列表的cache,类似一个自家邮箱, 当一个用户发一条tweet时候,把内容写进每个订阅者的cache中。示意图如下
![clipboard.png](/img/bV2N2l)
这个方案的好处是用户拉自己的订阅消息特别快,因为已经算好了,而且拉的订阅的请求量要远远大于发tweet的量(100倍), 但是劣势在于发消息的压力会变大,因为他需要把新发的tweet写到每个订阅用户的cache中,如果这个人有30个订阅用户,就写30遍,如果一个人有3000w订阅用户,这个发消息就死慢死慢的了。
所以最终方案是一个混合方法,针对一般用户,他发的消息会直接投递到订阅者的cache中,但是follower特别的多的人的tweet是单独拉的。用户在拉订阅时候,分别拉cache和热门用户的tweet然后合并,这样就都兼容了
### 如何描述性能 ###
有2种方式来描述你的当前负载状况
1. 负载涨了资源不变,你的服务表现怎么样
2. 负载涨了,你要加多少资源才能保证你的服务表现不变
这就需要一些性能指标来度量这件事情了,以hadoop为例,我们主要考量他的吞吐量即每秒处理多少条数据,而线上服务普遍用响应时间来作为衡量指标
针对响应时间,平均耗时是一个没有太大意义但是很好理解的指标,另外更好的一种方式是百分比耗时,例如90%的请求耗时在多久之内,95%的请求在多少之内,一般多用95%,99%,99.9%这几个数字
----------
注意!!!
耗时具有长尾效应,一定要结合业务情况分析不同比例的问题及是否需要优化,以amazon为例,最慢的1%的请求优化耗时十分有必要因为这部分人可能是因为买了大量东西而导致耗时偏慢,优化这部分速度十分有意义,但是优化0.01%的慢请求就没有意义了,因为这部分可能是因为各种不确定性导致的。所以不同比例的耗时是否达标以及是否需要优化需要结合自身业务情况进行分析。不要搞一刀切,类似99%就可以了,他会让你损失重要用户
测量耗时以客户端为准,尽量不要用服务测的耗时,因为在服务器处理之前请求可能就已经等了很久了。
### 应对负载的方法 ###
最简单的分为**垂直扩展**,用更好的机器,以及**水平扩展**,用更多的机器
正常来说水平扩展更好,因为更便宜,垂直扩展更简单,但是也更贵
---
如何选择垂直扩展和水平扩展
+ **根据组件重要度**, 关键地方用好机器,其他部件用便宜机器水平扩展(hadoop的nn就是好机器,其他的都是垃圾机器堆上去就好了)
+ **根据服务是否状态** 无状态服务用水平扩展,类似简单计算这种,有状态服务用垂直扩展,比如数据库, 否则你的数据同步就很麻烦了
另外一个值得说的就是**弹性伸缩**,针对当前负载,自己去加机器或者减机器,不用人工参与