健壮性(Reliability)
即便系统发生了某些错误(包括硬件故障,软件bug和人误操作等),系统仍旧保持正常运行。
对于一个数据系统而言,健壮性通常是指:
整个系统的运行结果符合设计开发者的预期;
可以在一定限度内容忍,在最大程度内避免操作人员的误操作;
系统的运行性能能够满足常见用户场景和预计数据吞吐量下的需求;
系统防止越权行为和其他的用户不合法操作;
...
上面这个列表显然不完整,一个健壮的数据系统需要面对不同用户在不同使用场景下的不同考验。满足上面列表里的要求可以简单看作是满足里“即便在某些条件不满足的情况下(例如部分服务器宕机、大量数据请求、存在恶意用户等等),系统依旧可以运行”。所以,健壮性通常表现在系统容错(fault-tolerant)方面。也就是说在错误发生时候的处理机制而非尽力去避免错误的发生,为了演练处理机制的效果,有的时候甚至需要去主动触发错误(比如说Netflix著名的Chaos Monkeys)。当然,这并非说避免错误完全没有用(比如说设置登陆密码,密钥等措施防止恶意越权攻击等)。
可扩展性(Scalability)
在一定程度内的负载增长不会影响应用预期表现的能力。
描述负载:下面是Tweet中的一个例子(数据来自于2012年11月)。
Tweet中发推(Post tweet类似微博中发微博的操作)的动作频率是4.6k/s,峰值会超过12k/s。
Tweet中阅读时间线(Home timeline类似微博中阅读自己微博的操作)的动作频率会达到300k/s。
简单的12k/s的写入操作并不会带来太大的冲击(相对于tweet的服务器数量而言),真正的挑战是:多对多的复杂用户关注网的情况。结合这种情况,有两种实施方式:
发推的动作就简单的往全局推文表里插入数据,当用户阅读时间线时,首先从关注关系表中获取该用户所有的关注人,再按关注人去推文表中获取所有推文,再按时间顺序将推文排序展示。这种方式最容易想到,实施起来也最方便:
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
常见关系数据库设计实现
为每个用户维护一个形如队列的缓存,就像是收件箱一样的作用:当有人发推的时候,同时将这条推文插入到发推人的所有关注者的队列缓存中,阅读时间线的时候直接拉取队列缓存。
队列缓存设计实现
在实际生产环境当中,tweet首先尝试使用第一种方法来实现,但是这种方式在用户读取时间线的时候可能会很慢(考虑到会有一个人关注许多人和一个人被许多人关注的情况)。
那么第二种方法有没有什么问题呢?第二种方法的问题在于在发推操作的时候还需要做许多额外工作:平均一条推文会被75个人在时间线中看到,所以4.6k/s的发推写入操作还需要附带有345k/s的队列缓存写入操作(考虑更极端的情况,有的人有将近1亿的关注者,也就是说,他的一次发推操作需要有将近1亿次队列缓存写入操作)。
权衡两种方式的利弊,tweet现有模式是:对于大多数用户采用方式2来提升阅读时间线的响应速度,对于拥有大量粉丝的人,采用方式1以减少大量的队列缓存写入操作。所以,在阅读时间线的时候,实际上是两种操作获取结果再合并的产物。
在可扩展性方面的尝试常常涉及到分布式系统(强烈推荐课程CS 6.824)方面的内容。
可维护性(Maintainability)
我们或多或少要和遗留系统打交道,而你负责的系统也总将作为遗留系统而被他人维护。
可运维(Operability)
系统不应当只是对于用户友好,对于开发者和运维团队而言也应该是友好的。
系统的运行维护需要做到以下几点:
对于系统内部的操作运行做监控;
减少外部系统强依赖;
人机交互友好,最大限度降低误操作可能,并有良好的文档;
有一定自愈功能;
最小惊喜原则,产品行为符合大多数人预期;