system desing 系统设计(五):Big Table原理

   1、计算机硬件系统存储数据有两个地方:memory和hard disk! 这两者各有各的优缺点,互为补充,所以能共存至今!理论上讲:memory的读写速度是hard disk的million倍,但memory很贵,普通服务器能有256G已经很不错了,并且memory掉电就丢数据,而hard disk价格便宜,单机几十上百TB的都很常见,而且掉电不丢数据,所以长远看,大量数据最终还是会存放在硬盘。就存数据的原理上讲,hard disk和memory都一样: 都是0101形式的二进制字符串存储。为了方便查看,人为把二进制数据通过一些特定的编码规则转成string或char,所以有可以理解为:数据都是以string或char形式存放在memory和hard disk的,比如下面这种:

  

   这种形式很容易理解吧!数据按照特定的格式存放,字段和字段之间用逗号、分号、空格、tab等隔开,每条数据都用{}包起来,从形式上看就转成了结构化数据!至此,写数据的问题解决了,那读了?往磁盘写数据的最终目的就是为了后续有用的时候读出来,像这种“一行一行”的数据,怎么才能快速找到自己想要的“那一行”了?最简单粗暴的莫过于写个for循环,逐行匹配了,数据量很少的时候还行,但是行数一旦达到了million、10 million甚至billion级别时还是逐行扫描,等找到数据时黄花菜都凉了!怎么提升查找效率了?

    2、逐行扫描的方式效率低,根因是数据无序disorder,所以只能挨个去匹配!所以为了提升read的效率(理论上讲read的QPS远比write高),在write后肯定是要找个合适的时机order的,对于ordered数据,就可以通过binary search查找了!那么问题来了,这么多行数据,memory完全放不下(如果能放下,就没hard disk啥事了),该怎么排序??什么时候排序?

  (1)几千年前,老祖宗就留下了这类问题的处理思路:分而治之!既然数据量累积到这么多,大到内存都放不下了,那就按照一定大小切分块呗!这里按照一块256M的规模切分!当数据往disk写的时候,同时需要在内存保留。当内存写入的数据累计达到256M的时候sort一次!由于数据本来就在内存,所以排序速度肯定快!等排完序,就可以整体写入hard disk了!为了后续快速检索,在memory里面肯定是要保留disk中数据块位置信息的,所以就有了(file0,Address0)这种映射关系!整个写入的流程如下:

         

       这里采用append的模式写入数据,而不是在原基础上改,原因简单:

  •   找到原始的数据要花时间,导致写入效率比读还低【随机写】
  •        万一更改数据长度,原来的数据都要挪动,效率更低

  所以采用append的方式,最新的数据永远都在最后,读的效率反而更高!

  (2)因为采用的是append,所以查找key首先从内存的最后一个块开始,如果有还好,那肯定是最新的,并且速度还杠杠的,可万一没有了,咋整?那就只能去磁盘找各个File查了,核心流程如下:

       

  随着时间的推移,disk的数据块会越来越多。由于数据块内部有序,但数据块之间是无序的,为了提升从磁盘File整体的查询的效率,需要定期使用K-way merger sort算法把数个小的数据块合并成一个大的数据块!为了快速找到K路中最小的数,还可以采用小根堆的结构,如下:

      

  (3)把K个数据块合并排好序后,就可以采用binary search查数据了!理论上讲,binary search的时间复杂度是O(lgN),这就是read的最高速度了?还有改进空间么?

  查找数据时,肯定是先根据key来匹配。如果我能确定key不在数据块中,是不是连数据块的binary search都省了?这本质上就是个deduplication问题,此时就要借助一种极为重要的数据结构了:bloomFilter(最常见的就是crawler url去重),其原理很简单:bolomFilter本质就是个bitMap,对key求hash(key),比如这里的结果是5,然后看看bitMap第5位是不是1。如果是,说明整个key已经存在了!为了提高准确率,hash函数通常会有好几个!

    

     假设有15和hash function,bitMap有2million,key数量是100thousand,判断20million字符串的误判率大概在3%~4%。在查找key的场景下,足够了!加入bloomFilter后,整个查询的过程如下:现在内存的sorted list查询,如果没有,再去磁盘的File查询!

       

   最大的变化在于:正式去File查询前,会先在bloom Filter查一次。如果结果是true,说明key在File里面,再去File用binary search查!相应的,往File写数据前,也要先在bloomFilter里面写入!

  3、解决了查询效率的问题,接下来就要考虑sharding了!这次还是用consistent hash算法,如是如下:

  

   通过这种方式把读写的QPS分散到不同的节点!整个read过程如下:

  

  •   client把key发给Master,通过consistent hash得到具体存放key的slave
  •      client把key发给slave
  •      slave通过key查找data:
    •  先在内存查最后一个块,也就是skip List,看看key在不在
    •    如果不再,就要去disk的File查找了,此时先去bloomFilter查查key在不在;
    •    如果key在bloomFilter,说明key在disk的File里面。继续通过binary search在File里面查找!

   理解了read的过程,再来看write的过程:

         

  •   client把key提交给Master,通过consistent hash得到slave的id;
  •     client把(key,value)发给slave
  •     slave的操作:
    •   先是write ahead log:主要是避免断电导致数据丢失,所以先在disk写入
    •        再在内存sorted List里面排序。如果sorted List达到256M,那么一次性写入disk,并在memory保留(file, address)的映射关系,便于后续查找

    4、以hadoop为例,大家都知道HDFS是分布式文件系统,hbase是基于HDFS的KV数据库,这两者的关系和GFS、BigTable的关系如出一辙,展示如下:

           

    从物理上讲,数据最终都是存放在GFS的!为了方便查询和更改,人为设置了key、value的形式,构成了Big Table

  5、还是以hadoop为例:分布式系统同时可供多个用户读写数据,为了避免race condition,势必需要分布式锁distributed lock!目前业界常用的分布式锁有chubby和zookeeper,hadoo体系用的是zookeeper!以读数据为例,整个过程如下:

  

  •  client先去lock server拿锁,同时让locker server 提供slave的id;注意:这里的lock server同时也充当了consistenct hash map的角色,可以节约服务器资源
  •  client发送key给slave读数据
  •  slave拿到key后重复之前提到的流程根据key查数据:找memory的skip List;找bloomFilter; 在File块通过binary search查找等!
  • slave返回数据给client
  • client通知lock server解锁unlock!

  6、回到在slave上根据key检索的场景:经过memory中的sorted List、bloomFilter等步骤,最终会到disk的File里面做binary search!这里就用到了大家耳熟能详的BST树了:

    

    这种树相比逐行查找,确实有所改进和提升,但也不是100%,还是有缺陷的,比如这种场景:数据一共有10亿个,按照每个数 4byte计算,所需空间>=4GB; 长远来看只能存hard disk,但hard disl的读写速度很慢,一次寻址大约耗时10ms/次(机械硬盘要查找盘面、柱面等,整个过程都是机械式的移动,效率比内存的“电子化”低多了);顺序读取的速度是80MB/s,4GB的数据全部读到内存,耗时4000/80=500s;再加上二分查找的耗时,用户还能忍受么?所以为了提升binary search的效率,现在面临两个问题:

  •   不可能把所有数据都加载到内存后排序,需要在disk就是排好序的,这样可以节约磁盘寻址disk addressing的时间和读取disk数据的时间!
  •        继续优化bianry search:上述的案例因为数据少,所以只有3 level;如果有billion级别的数据,如果用二叉树组织,树的height有30 level,这样依赖至少读取30次磁盘才能找到key;每次寻址耗时10ms,30次寻址耗时300ms,用户早就跑路了!

  为了解决这个问题,B+树诞生了,mysql用的就是B+树!用一张都爆浆的老图来说明问题,比如下面的InnoDB(支持transaction):

  •  数据在disk里面也是排好序的(memory关机掉电就没了,所以辛苦排好序的数据只能存disk file,用的就是tree形状的组织,所以查询的时候可以按图索骥index,不用每个节点都去遍历,极大节约时间
  •  为了减少disk的寻址次数,就必须降低tree的height!10亿数据,如果用二叉,最深的地方有30层,意味着要寻址30次;如果把数据的height控制在3层,那么树上非叶子节点的分岔要达到1000;
  • 每个磁盘块的大小是有限的,这里以16KB为例,point和key分别都是8byte,那么每个磁盘块能容纳16KB/(8byte+8byte)=1000个,刚好能分成1000叉,能讲height控制在3层
  • 还是因为磁盘块大小受限,非叶子节点只存储point和key;叶子leaf节点存储key和data!
  • 以上整个tree都是存储在disk的,因为磁盘也和内存一样可以寻址,方式有CHS、LBA等

  InnoDB类型的B+树还可以优化一下,增加leaf data node 的顺序访问指针,提高区间的访问性能:比如查询18到30之间的data

        

  B+树的核心思路:hard disk寻址很耗时,所以一旦成功找到目标地址的数据,一定要好好利用,多读一些数据出来处理,才能减少后续寻址的次数!多读出来的数据该怎么利用了?就是把这些数据尽量多塞一些point和key等index数据,才能更大范围的检索key!

 

参考:

1、https://www.bilibili.com/read/cv11314297/  磁盘CHS\LBA寻址

2、https://www.bilibili.com/video/BV1Za411Y7rz?p=65&vd_source=241a5bcb1c13e6828e519dd1f78f35b2  Big Table教程

3、https://blog.csdn.net/jinking01/article/details/105192830  磁盘寻址

4、https://www.zhihu.com/question/38573286 bloomFilter误判率

posted @ 2022-08-17 23:26  第七子007  阅读(198)  评论(0编辑  收藏  举报