# 每个程序员都应该知道的数字
这组数据同样来自演进: Building Scalable Web Applications with Google App Engine,
highscalability 网站上有整理这个演讲主要内容: http://highscalability.com/blog/2009/2/18/numbers-everyone-should-know.html
读完之后,感觉对于理解架构和系统设计有一定帮助,我把它部分内容翻译为中文,如下:
# 一、写操作成本很高
- 数据存储是事务性的:写入操作需要访问磁盘
- 访问磁盘意味着磁盘寻道
- 经验法则:一个磁盘寻道大约需要10毫秒
- 简单的数学计算:1秒 / 10毫秒 = 每秒最多100次寻道
- 这取决于:
- 你的数据的大小和形状
- 批量操作(批量插入和获取)
# 二、读取成本很低
- 读取操作不需要是事务性的,只需要保持一致性
- 数据从磁盘读取一次后,就可以很容易地被缓存
- 所有随后的读取操作都直接从内存中完成
- 经验法则:从内存中读取1MB的数据大约需要250微秒
- 简单的数学计算:1秒 / 250微秒 = 每秒最多4GB
- 对于一个1MB的实体,这意味着每秒可以进行4000次获取操作
# 三、各项延迟数据
详情请参考阅读这篇文章: 计算机各种访问操作的耗时对比数据 (opens new window)
# 四、主要经验
- 写入操作的代价是读取操作的40倍。
- 全局共享数据非常昂贵。这是分布式系统的一个基本限制。在大量写入的共享对象中,锁竞争会导致事务序列化并降低性能。
- 为扩展写入操作进行架构设计。
- 尽量减少写入竞争。
- 进行广度优化。尽可能使写入操作并行。
# 五、分片计数器设计思想
我们似乎总是想要记录存储的事务的数量。
但是BigTable并不会记录实体的数量,因为它是一个键值存储。它非常擅长通过键获取数据,但对拥有多少并不感兴趣。
因此,记录数量的任务就转嫁给了你。
最初级的计数器实现方式是锁定-读取-增加-写入。
如果写入操作较少,这种方式就没问题。
但是,如果经常有更新,就会产生高度的竞争。
鉴于每秒可进行的写入操作数量如此有限,高写入负载会使整个过程序列化并变慢。
解决方案是使用分片计数器。这意味着:
- 并行创建N个计数器。
- 对每个计数的条目随机选择一个分片进行事务性增加。
- 要获取真正的当前计数,只需将所有分片计数器的总和相加即可。
- 通过1/N减少了竞争。因为它们已经被分布在不同的分片上,所以写入操作已经被优化。已经移除了围绕共享状态的瓶颈。
这种方法似乎违反直觉,因为我们习惯于将计数器视为一个可以递增的单一变量。
读取操作很便宜,因此我们用需要多次读取来恢复实际计数的方法,替换了拥有一个容易读取的单一计数器。
频繁更新的共享变量非常昂贵,所以我们对这些写入操作进行分片和并行化。
对于集中式数据库,让数据库成为序列号的来源是可行的。
但要扩展写入操作,你需要进行分区,一旦你进行了分区,保持任何共享状态,如计数器,就变得困难了。
# 六、评论系统设计
如何存储评论,使得我们能按照大致的输入顺序浏览它们?
在高写入负载的情况下,这个问题的答案出奇地难找。
显然,我们想要的只是一个计数器。当有新的评论发表时,获取一个序列号,这就是显示评论的顺序。
但是,正如在上一节中所看到的,像单一计数器这样的共享状态在高写入环境中是无法扩展的。
在这种情况下,分片计数器也不能工作,因为对共享计数器求和并不是事务性的。
没有办法保证每个评论都能获取它分配的序列号,所以我们可能会有重复。
在BigTable中,搜索返回的数据是按字母顺序排列的。
所以,我们需要的键是唯一的,且按字母顺序排列,这样在浏览评论时,你可以只使用键来前后移动。
很多分页算法使用计数。
给我1-20条记录,21-30条记录,等等。
SQL使这变得简单,但它对BigTable并不适用,BigTable知道如何通过键获取事物,所以你必须创建能按正确顺序返回数据的键。
在制作唯一键的古老传统中,我们只是不断添加东西,直到它变得唯一。
对于GAE(Google App Engine),建议的键是:时间戳 + 用户ID + 用户评论ID。
按日期排序是显然的,好处是,获取时间戳是一个本地决策,它不依赖于写入操作,且可扩展。
问题是时间戳并不唯一,尤其是在有很多用户的情况下。
所以,我们可以将用户名添加到键中,以便将其与在同一时间发表的所有其他评论区分开,我们已经有了用户名,所以这也是一个便宜的调用。
理论上,即使是单个用户的时间戳也不够,所以,我们需要的是每个用户评论的序列号。
这就是GAE解决方案变成某种完全出乎意料的东西的地方。
我们的目标是消除写入竞争,以实现并行化写入,我们有很多可用的存储空间,所以我们不用担心这个。
考虑到这些因素,想法是为每个用户创建一个计数器。当用户添加评论时,它将添加到用户的评论列表中,并分配一个序列号。在Entity Groups的事务性上下文中,按用户为单位添加评论。
所以,每次添加评论都保证是唯一的,因为在Entity Group中的更新是序列化的。
结果键保证是唯一的,并在字母顺序中正确排序。当浏览时,可以使用ID索引跨实体组进行查询。结果将按正确的顺序排列。分页就是在当前页面的查询中获取前一个和后一个键。这些键可以用来在索引中移动。
我肯定是想不到这种方法的。保持每个用户评论索引的想法是出乎意料的。
但它巧妙地遵循了在分布式系统中扩展的规则。
写入和读取是并行的,这就是目标。
写入竞争被移除了。
# 参考
最新原创的文章都先发布在公众号,欢迎关注哦~,
扫描下方二维码回复「CS」可以获得我汇总整理的计算机学习资料~