后台开发那些常用技术再次小结(三):存储部分

  原书对于存储模块,主要就是描述的数据库的设计和优化,和我通常想到的文件系统的存储有点差异,其实数据库的底层还是放在文件系统上面的(原谅我又不禁想到了GitLab删库事件),只不过随着数据量增加数据库文件的尺寸会不断的变大(在mysql中查找datadir的变量就可以看见数据文件的位置)。
  互联网公司很多数据都塞在数据库中的,而且数据库这一模块的使用和优化经验也比较的丰富,更加有文章可做吧!

一、文件存储

  在绝大多数情况下磁盘一直都被诟病是拖慢整个系统的罪魁祸首,现在看来网络速度都会比磁盘要快得多。
  如果是自己的服务器托管,建议尽可能的增加物理内存(云主机就算了,增加内存配置价格贼贵),因为操作系统的缓存机制,较大的物理内存能减少IO操作,从而改善文件系统访问性能。
  现在优化磁盘效率的方法除了操作系统和文件系统的参数调优之外,最直接的方法就是上SSD。想到前些年很多人对SSD还是持观望态度,时常会有SSD无故掉盘、数据无法恢复的情况,不过从这些年看来随着SSD技术的完善,各大厂商和数据中心都以SSD硬盘作为主机的卖点了,可见SSD还是接受住了现实的考验了(虽然现在随着工艺水平的提高和TLC的推广,据称SSD的寿命甚至还不如之前),工业上通过SSD将业务性能提高几倍甚至几十倍的案例数见不鲜。其实,现实世界没有什么硬件是绝对可靠的,所谓的可靠性缺陷可以用软件来弥补。想当初我在2012年花了六百多大洋买了Crucial 64GB m-sata接口的固态硬盘,该硬盘作为系统盘现在仍然工作的杠杠地,所以增加内存和上固态硬盘是任何工作或者生产机器实现“垂直扩展“的最佳捷径了。对于固态硬盘如果是SATA3接口,其最高速率是6Gbps,而现在很多SSD的传输速度已经受限于该接口速率了,那么你可以考虑采用PCI-E接口的SSD存储来获取更高的性能了。
  如果你对SSD的可靠性还是不放心的话,将SSD作为一个缓存介质而不做数据持久化的存储,也可以极大增加IOPS的性能指标,对随机化IO密集型的服务优化效果十分明显。虽然某些情况下SSD中的数据没有写回之前挂掉,还是会有丢数据的风险,但是相比之前安全系数已经高了好多。各个大厂针对此种情形的解决方案貌似很多啊!
  RAID(独立冗余磁盘阵列)算是一个历史悠久但却极为有用的存储技术了,通过多个廉价独立的磁盘进行组合达到多种组合,将IO操作分散到多个物理磁盘上去,同时具有增加副本备份恢复的功能,让整个存储系统在可靠性、性能、成本之间做出权衡,RAID技术算是在存储最底层层次上所做出的备份和优化操作。RAID的类别可分为几十种,比如:RAID0是把数据切成块,分别存储在两个或者多个磁盘上,虽然读写性能都增加了N倍,但是安全性最低,任何一块磁盘挂掉数据就无法恢复了;RAID1是做的存储冗余镜像,读性能能增加N倍,但是写操作需要在多块磁盘上写出完整副本,性能可能会有所降低,而且磁盘空间利用率最低;RAID5和RAID6也是将数据分块,同时将可恢复的校验数据也作为一个数据块,他们依次分布在不同的磁盘上,这样在增加读写性能的同时,也允许少量磁盘坏掉后可以恢复原来的数据,工业上使用的较多;RAID10和RAID01算是RAID1和RAID0的合体也比较流行(两者镜像和条带操作的顺序不同),虽然磁盘利用率低,但是大家觉得在可靠性和性能都能达到较好的效果(也不一定,工业上不都是认为3-5份的数据备份才是比较安全的嘛)。
  让我印象最深刻的,还是当时在《淘宝技术这十年》这本书里面看到的关于淘宝自建存储部分的章节。对于一般规模的公司觉得数据库的问题显得更为突出,而文件存储的需求只有特定业务类型公司才会比较急切。比如淘宝、腾讯、Facebook这类公司对于自身海量级别的数据,基本都不约而同地开发了适合自己的文件系统应对存储问题。
  现在各大云厂商也提供了对象存储技术,除了在可靠性方面达到了99.9999999%的可用性,还提供水平弹性扩充、异地容灾、权限控制、线路优化、冷热存储、标准RESTful接口等各种特色服务,达到了开箱既用的便捷性。其实从硅谷的那些独角兽公司来说,基本初始阶段都使用Amazon这类成熟的云服务,等到自己的业务发展的比较规模后,再考虑自建数据中心节省成本,不过也有失败的案例(请原谅我在此又一次联想到了GitLab)。

二、关系型数据库伸缩性

  数据库虽然是一个再常见不过的组件了,但是其中学问大的很。在保证可靠性的同时,其性能直接关系到业务的可用性,同时作为有状态的存储部件也不像较上层的业务容易扩展,更可怕的是如果开始设计不够充分或是没有好好规划,那将会是埋下的一个巨坑,对后续维护、扩展来说后患不已、贻害无穷。

2.1 复制

  MySQL自带同步机制,在一台主机作为主节点的时候,可以将主节点的修改同步到多台从节点。
  对于数据库的修改命令,都应该在主节点上面操作,主服务器将这些修改操作记录到binlog日志文件中,一旦操作被记录到binlog日志中,那么主节点就可以向从节点发送了。MySQL的同步操作是异步的,所以主服务执行完更改后会立即返回给客户端响应,而不用等待从服务日志复制过程,从服务器可以在任意时刻连接到主服务器上,同时从服务器和主服务器可以快速进行增量更新,同时任何一个新的从服务器都可以连接到主服务器上面去。其好处有:
  (1) 增加副本,那么只读操作就可以分布在多个从服务器上面,进而减轻主库的负载,而且这样的从服务器可以添加多个副本,不仅是水平的,在垂直的方向上海可以进行多层次的同步。
  (2) 可以针对不同类型的查询使用不同的从数据库,比如将过慢的、对数据更新要求不高的查询放到单独的从数据库上面。因为数据库同步的异步性,从服务器的数据副本很可能不是最新的,所以对于数据更新需求强的读请求应该被路由到主库上面以确保得到最新的副本。
  (3) 根据同步的异步性,可以在某个时间将从服务器停止同步,然后就可以对数据库进行完整的冷备份,而主库的对外服务不受影响。这种操作有时候是必须的,因为MySQL不支持从一个空数据库上启动一个从服务器,所以经常对数据库进行完整备份,可以提高下次增加或替换从服务器的速度。
  (4) 如果主服务器失效,MySQL不支持自动失效转移的功能,即不能自动将一台从服务器提升为主服务器。这需要手动更改配置才能生效,而且由于同步的异步性,往往还需要检查将要提升为主服务器的binlog是否是最新的。

  主-主同步拓扑
  针对主服务器挂掉,从服务器不能自动提升为主服务器的情况,有提出主-主同步的架构,两个主服务器是对等的角色,在一台挂掉的情况下应用程序可以快速切换到另外一台服务器进行正常工作。因为MySQL的binlog日志会记录执行的主机信息,所以在主-主同步拓扑的情况下,服务器A发送给服务器B的日志不会再由服务器B发送回服务器A,虽然理论上可以同时向两台服务器进行写入操作,但是为了降低数据冲突的风险,通常也都是向一条服务器进行写入。不仅仅是2,还可以有多个主服务器之间进行同步,总体来说就是在增加可用性的同时,数据的一致性和冲突的问题就越严重,所以现实环境下慎重使用。

  复制的问题和挑战
  虽然使用副本的方法来进行伸缩的时候,只是对读操作进行了伸缩,其写入能力并没有得到改善(主要是处于数据一致性的考虑),这对于读操作多的程序是一种很好的伸缩手段,可以大大增加客户端只读的并发读和QPS指标。增加副本对增加数据规模也没有任何帮助,任何一个主、从服务器都包含了完整的数据副本,其存储能力没有得到任何提升。
  再次强调数据库同步是异步操作,正常情况下主-从的同步延时不会超过500ms,但是也有非正常情况。MySQL的复制是从服务器上单线程独立完成的,而主服务器上的更新操作是多线程并发执行的,很有可能从服务器跟不上主服务器的更新步骤,尤其当执行表结构更新等复杂的操作时,会阻塞之后所有的同步请求。解决这种问题的思路有:a.对数据敏感的请求直接发送到主服务器上面;b.尽量降低复制延迟,比如将服务器上更新表结构的操作binlog关闭,这类操作在从服务器上面手动执行,那么更新表结构和数据同步就可以并行完成了。
  MySQL的复制还可能遇到其他问题导致数据不一致,比如使用生成随机数的功能(还有时间函数?)等,导致主服务器和从服务器的数据不一样,一旦主从服务器同步出现差错,后面的更新操作很可能会得到不一致的结果,这种问题就大发了!
  现在想想,终于知道数据库专家的工资那么高了,数据库对于一个产品和公司的发展至关重要,而其相关技术专业性强,需要大量的经验和积累!

2.2 数据分片

  数据分片是将数据通过某种方式进行切分,以便将他们分散存储在多台服务器上,这也是我们通常听到的数据库分表分库,是除了功能分隔、增加副本之外的第三种伸缩性手段。这里说的数据库分片是针对数据库本身不支持分片,在应用层实现的数据分片。
  数据分片首先需要选择分片键,通过分片键和某种映射算法可以快速定位路由对应记录在哪个服务器上,而不需要获取记录时候同所有的服务器进行通信,分片键和映射算法的选择,最好能让各个分片中的记录能够均匀分布。数据分区是突破数据量、并发链接数、服务器IO限制的有效手段。
  比如根据uuid,可以通过以下方式分割成10个库,每个库100个表,那么所有的数据将分布到1000个单独的表中存储。

1
2
3
4
5
6
7
void getDBIndexByUUID(const std::string &session_id, int &db, int &tb1, int &tb2){
db = order_id[session_id.size()-3] - '0';
tb1 = order_id[session_id.size()-2] - '0';
tb2 = order_id[session_id.size()-1] - '0';
}
...
sprintf(sqlBuff,"update v5kf%d.t_session_%d%d set……", db, tb1, tb2 ... );

  数据分片的问题挑战
  应用层数据分片的主要假定分片键之间是彼此独立没有联系的,这也就制约了无法执行多个分片的联合查询,如果有这种需求只能在各个分片上执行对应操作后再在应用层进行合并后返回结果。而有些情况这种执行甚至是不可能的,比如按照用户ID分片的数据库,如果要执行销售量最多的产品ID,那甚至要对绝大多数记录进行整理,因为那些分布均匀看似普通的产品,可能累计值是最大的。还有,某些以年月为后缀的分表操作,跨月份查询也是个痛点。
  数据分片还导致数据库的ACID特性难以得到满足。MySQL支持单个数据库的ACID,那么上面的例子对某个用户的所有数据执行更新就是可行的,但是如果针对某个产品进行更新就需要横跨多个数据库操作,MySQL是不支持分布式事务的。
  数据分片还可能涉及到一致性HASH类似的问题,绝大多数情况下通过机器数目取模的方式进行定位的算法是极度不推荐的(不过也不是绝对的,比如可以采用逻辑数据库概念分布,然后将逻辑数据库映射到物理主机上面去),因为一旦这样定位就意味着接下来没有第二次伸缩的可能性了。在定位算法中可以简单的在数据库或者配置文件中配置定位关系(比如用户ID在哪个区段定位到哪个数据库),这样不仅不会影响到新增记录,而且数据迁移也十分方便:只需要将对应记录锁住,然后进行实际数据的迁移,接着更改他们的映射关系,最后解锁记录就完成了。映射关系的这种定位还可以增加灵活性,比如把某些活跃用户,或者优质用户映射到配置高、线路好的机器上面去,以实现差异化运营。
  数据分片还有个问题就是生成唯一性键约束。在MySQL有两个变量:auto_increment_offset和auto_increment_increment,数据库在这两个变量上实现的自增的逻辑是:

1
auto_increment_offset + N × auto_increment_increment

  所以分区的数据库通过设置这两个变量就可以产生不重复的唯一性键了;另外的一种方式就是借助外部第三方组件生成唯一性键值,比如Redis。这种唯一性键的特性还常常用在主-主同步的环境中。
  下面这个图是把上面技术都用到的样子。
mysql
  PS:其实分库分表的应用在大型业务之数据库伸缩中是如此的常见,所以透明解决分库分表的中间件也较为的多。如果能够有满足需求的优秀中间件还是推荐使用,或者至少能实现一个数据库访问的代理层也是合适的,否则将分库分表的寻址代码放在业务代码中,总觉得不是那么的别扭。数字公司的Atlas这个项目感兴趣的话还是可以研究一下的。

2.3 其他

  数据的冷热存储
  世界万事万物都是相互制约、相生相克的,在限制成本因素的时候数据存储的性能和容量是相互制约的,因此如果业务模型可以清晰的分离出冷热数据(比如交易类型的数据按照创建日期进行划分),那么冷热数据分开处理也将是个存储上的优化。冷数据一般来说具有数据量大、通常只有简单的查询操作、对性能要求不高的特点,所以可以对低频数据分配性能、线路较差的机器负责(而且由于其访问和操作少,所以低配的机器响应也不一定会差)。现在的云计算厂商也提供不同特性的存储服务类型,自建数据中心的也可以借鉴之。

  当然,上面的水平伸缩虽然在系统设计之初的时候就应当考虑,不过也不要忽视各个独立服务器的性能优化,否则就有一种舍本取末的感觉,毕竟数据分区会在运维、业务处理上带来很多的麻烦,常常感觉有那么一种不得已而为之。在独立服务器本身的性能没有被压榨到极限的时候,你集群的规模可能会成为你的谈资,但是你为此所付出的确是白花花的银子。
  所以,一方面还是要对数据库单表、单库优化做到心中有数,比如索引、数据库引擎、缓存;而对于开发人员来说SQL语句的使用也要养成良好的习惯,多看看数据库的慢查询日志再对应做出优化,不要让宝贵的资源被自己的不成熟代码白白吞噬了。建议查看下面两篇文章:
  《MySQL大表优化方案》
  《Top 20+ MySQL Best Practices》

三、NoSQL数据库伸缩性

  关于NoSQL这里就不多说了,因为没有支持SQL的存储都把自己叫做NoSQL,概念显得比较宽泛和模糊,就像是要有意要和SQL传统数据库划清界限以凸显自己似的。
  觉得大家讲到的NoSQL很多时候都是KV键值类存储(比如Amazon Dynamo),这类存储设计之初就想到高性能和灵活的扩展性,因此两者最大的差异性就体现在可扩展性上。其实两者对比来说也没有绝对的优劣,毕竟SQL是标准性的,这么悠久的历史大家玩起来更有经验,各种优化和扩展方案也十分丰富;NoSQL是伴随着大数据成长起来的一个新概念,因为其横向扩展性优秀,对于数据量大、数据类型简单的存储需求乃是如鱼得水,因此才得以倍受追捧吧。还是那句话,符合业务需求的技术才是好技术!

本文完!

参考