为您推荐:
精华内容
最热下载
问答
  • 5星
    29.62MB weixin_45750628 2021-03-27 16:33:32
  • 静态数据简单点可以理解为不会在变化的数据,那你就可以用于历史归档性的业务。比如你去年酷狗歌单、每上月的支付记录等,这类不会再修改的数据。 接下来上场的是层次不齐的树结构 树结构基础就是普通二叉树,其它...

    01

    为啥BAT大厂,在数据库上都喜欢深入的问索引呢?

    一线大厂,是很多人梦寐以求的盛典天堂。因为存在的无限的可能,可以帮你实现自己的远大抱负。大平台机会、视野、格局往往都比小厂多很多。但随之而来也是那高挑的技术门槛需等你迈过。好事物大家都喜欢,但毕竟僧多粥少。外加任务有难度,如果你没过硬的本领,那很难踏入平台,领会一览众山小的风采。不知你心里有没有小九九?


    大厂产品大多数都成型很久,数据库里面存储的数据都以海量计算,如何在这种规模下的数据中做到快速筛选呢?那就需要你来答。

    大家思路肯定和我一样,话不多说,加索引再说!索引为的就是提高数据的检索效率,进而减少请求的响应时间。

    这时,有内涵的人可能会反问你啦?

    那你说说索引有哪些类型?索引底层实现是什么结构?B+Tree的优点?聚簇索引和非聚簇的区别?索引一次读读取多少数据合适?为什么说索引会降低插入、删除、修改等维护任务的速度?

    这一套组合拳,可能虐的你是体无完肤。让人招教不住,心理一万个xxx省略。送他一个微笑,然后再尴尬而不失优雅的离去。

    大家可能都知道查询慢了加索引,那为啥加?在哪些字段上加?以及索引的数据结构特点。索引优化、优点啥的都比较模糊或者不知道。

    今天将是对索引来一次灵魂的拷问,在进一步对索引优化、常见大厂面试问题、SQL优化等内容进行分享。这是个大工程,大家得关注再看。深,那就得深出高度。MOG!太深啦

    02

    用索引,那你得知道索引是什么?

    百度百科定义:索引是数据表中一列或多列的值进行排序的一种数据结构

    故此,索引本质就是数据结构。这也是为什么每次数据表建立索引都需要设置在列字段上的原因。那常见数据结构有哪些? 

    常见数据结构大致可分为三大类,如下所示

    • 线性表:顺序表、链表、栈和队列;

    • 树结构:二叉树,堆、线索二叉树、红黑树、B-Tree等;

    • 图存储结构

    但在数据库中常用数据结构为B+Tree、Hash索引

    对于此,有人可能觉得有了Hash和那么多树结构(红黑树、B树、完全平衡二叉(AVL)树、B+树),为啥Mysql唯独喜欢B+树?

    请听如下分解:

    首先上场的是顽固不变Hash索引,这Hash索引又是什么?

    哈希索引(hash index)基于哈希表实现,只有精确匹配索引所有列的查询才能生效额。切记!切记!切记!

    哈希的思路很简单,以键-值( key-value )存储数据的结构,对于待查找每一行的数据值,用一个哈希函数把数据值换算成一个确定的位置即  key,位置就是哈希码,并且不同键值的行计算出来的哈希码也不一样。然后在 value 上存放每个数据行的指针。

    对这样的索引结构,执行如下sql语句的过程是什么呢?

    select * from nezha where name='lianhua'
    

    MySQL首先计算'lianhua'的哈希值,并使用该值寻找对应的记录指针。然后根据指针寻找对应的数据,最后一步是比较读取的值是否为'lianhua', 从而来确保就是要查找的行。

    那如果改变为范围性查找就会存在问题。还记得上面的切记吗?因为它不支持范围匹配,只支持等值匹配。例如:

    select * from nezha where name like '%lianhua'
    

    那像Hash这种等值查询还有哪些场景?

    Hash故名思议体现的就是(key-value)结构。所以像 Redis、Memcached 及其他一些 NoSQL 引擎(如 Memory)。

    那有没有既能快速查找,又可以支持范围型查找呢?

    自然有,有序数组在等值查询和范围查询场景中的性能就都非常OK,足以满足你的口味。

    那它就好的没天理啦?不,世上没得十全十美的!

    有序数组索引只适用于静态存储引擎,因为数组的空间必须是连续的,这就造成数组在内存中分配空间时必须找到一块连续的内存空间。所以新增、删除、修改数据时就会改变它的结构。

    一下掉入无底洞,这在业务场景上怎样使用?

    静态数据简单点可以理解为不会在变化的数据,那你就可以用于历史归档性的业务。比如你去年酷狗歌单、每上月的支付记录等,这类不会再修改的数据。

    接下来上场的是层次不齐的树结构

    树结构基础就是普通二叉树,其它树结构都是基于它演进产生。二叉树会根据元素值的大小来创建树形结构。所以它是有序的,并支持范围查找。具体可查看数据结构相关书籍。

    但普通二叉树,有个问题,就是当元素是递增或递减时,它就会退化为线性表。

    为了解决这个问题,就出现了我们的完全平衡二叉树。可为何数据库没选择它呢

    数据库操作都是在内存里面完成的,但最终还是要落地到磁盘。如果数据多了,树会变得很高。然而查询数据时,那都是从磁盘里面把数据读取出来放入到内存中。这样I/O操作成本就会随着树的高度而增加。这也是常说完全平衡二叉树具有高瘦特点。

    好像女孩子都喜欢这样的吧!

    一般为节约成本,很多公司服务器采用的还是机械硬盘,这样一次千万级别的查询差不多就要10秒,这还不算网络传输、业务处理、CPU的执行时间,一但汇总那谁顶得住?

    那这怎么解决呢?不可能一直让让它变高吧! 可使用B-Tree。

    B-Tree的特征就是矮胖,也称为多叉树,就是在树的同一高度上开辟多个分叉来容纳元素。从而树在横向上面变宽了。这样减少了磁盘I/O的查询查找次数,从而提升了效率。

    B-Tree的特点简写:

    • 每个节点中的元素(关键字)从小到大排列。

    • 每个节点都保存有数据

    那为什么最终数据库选择了B+Tree,而不是B-Tree呢?

    B+Tree自然保持了B-Tree的矮胖特征,但它还做了升级的处理。就是让叶子节点保存数据,而非叶子节点保存关键字即可,并且会有指针指向下一个叶子节点。这样的好处是为了提高范围查找的效率。找到数据后直接根据指针向后读取即可,而B-Tree就不行,当它读取下一个数据,还需要再一次的进行索引树的查找。

    B+Tree特点:

    • 所有的非叶子节点只存储关键字信息  

    • 只有叶子节点存储数据

    • 所有叶子节点之间都有一个链指针

    小结:最终MySQL选用B+Tree作为索引,从而提高检索索引时的磁盘IO效率,并且提高范围查询的效率,整个B+树里的元素也是有序的。因为B+Tree默认就是按照主键索引来构建的树结构。那你说呢?

            03

         索引是怎么构建的?

    开发过程中,MySQL都首先B+Tree。在MySQL下还拥有Hash索引,也就是它拥有2大索引类型。具体选择用什么,可在创建表时进行选择。

    那这索引到底是怎么创建出来的?

    那还得分情况而定,分为以下2种

    建表建索引

    创建表的时候,先把索引字段建立好。如:

     create table nezha(
    id int unsigned   AUTO_INCREMENT PRIMARY KEY, 
    phone int not null,
    name varchar(16),
    index (phone))engine=InnoDB;
    

    当添加数据时,数据库就会自动先去创建好索引结构,然后创建数据。最终在落地到磁盘上。

    先建表,后添索引

    这种情况需要注意,因为先建表,那可能你数据表已经拥有了大量数据,这时候你在添加索引,那你的整个数据库肯定会阻塞,因为数据库需要根据表中数据建立索引,这都是由数据库后台线程来完成。

    这也是为什么线上数据库不要轻易变动索引,需根据用户低峰时间来操作。所以索引创建过多,那也算是需要耗费资源的。

    一般还需要维护表和索引,你这里有什么建议吗?不妨留言说说你的提议,优化就留到下次。

    所以当你的大表需要导入到其它数据库时,需在新数据库上先关闭索引,然后再添上索引,要不然效率就太低了。

    04

    索引的表现类型代表作有哪些?

    乖乖,索引还有表现种类,这神马情况?

    大家都知道B+Tree、Hash索引,但这些都底层实现的数据结构,而表现种类在明面上,我们常说的,例如:聚簇索引、非聚簇索引等,都包含了对应的数据结构。

    问最多算聚簇索引、非聚簇索引,那它们是什么呢?

    聚簇索引:索引和数据都存储在一起,代表作Innodb

    非聚簇索引:索引和数据分开存储,代表MyIASM

    上述的特性,也和它们的物理存储文件有关系。文件放在数据库安装目录下的data目录中

    /mysql-57/data/mysql
    

    MyISAM结构如下:

    .frm为表结构文件,存储像create alter等语句    .MYD为存储数据文件   .MYI为存储索引文件

    InnoDb结构如下:

    .frm为表结构文件,.ibd为数据+索引文件

    在InnoDB存储引擎中,就一定都是聚簇索引吗?

    并不是,只有主键索引被称为聚簇索引( clusteredindex )。除开主键以外的字段上创建的索引被称为非主键索引,非主键索引也被称为二级索引( secondary index )。

    注:现在你该知道,为啥面试都不问你什么唯一、普通、联合索引了吧,那都是属于二级索引呢

    那这两者之间有什么区别吗?区别在非主键索引的叶子节点内容是主键,当找到主键后,还需要根据主键再一次的进行索引树的查找,这个过程称之为回表。

    例如:

    如果语句是 select * from nezha  where ID=7 ,即主键查询方式,那它只需搜索 ID 这棵 B+ 树;

    如果语句是 select * from nezha  where name = '哪吒' ,即普通索引查询方式,则需要先搜索 name 索引树,得到 ID的值为 7,再到 ID 索引树搜索一次。这就是所谓的回表

    那这个问题怎么解决呀!

    内心独白:哎呀!咋这么多问题,烦不烦。 

    这个好办,刚才我们是 select * ,查询所有记录,如果查询字段上只出现主键索引与创建索引的字段,那就不需要回表了。因为走二级索引时,就已经包含了你需要的字段列啦,那就不需要在回表了。这就被称之为索引覆盖,即索引已经包含了查询操作的值。

    这也是为什么,当有多个字段需创建索引时,会创建联合索引,也是为了更好支持索引覆盖。

    瞬间飞过,"我怎么这么好看,这么好看怎么办"

    05 

    数据库内部利用索引是如何读取数据的?

    搞了这么久,那这个索引查找数据的时候,是怎么个读取原理又是什么?

    那这首先得说数据库中的读取数据单位,数据库中的数据是按照页读取的。默认一页的数据为16KB。而磁盘块(OS)默认为4KB

     show global variables like 'innodb_page%';
    

    那索引和数据都保存在节点里面,这个数据怎么个读法?

    上面说到,数据库读取数据是根据页为单位,并且读的数据不满足1页或超过1页,那么也会读满1页。这也叫做预读

    也就是说节点读取数据的大小应该控制在1页、2页、3页、4页等倍数页大小最为合适。

    那你说说这个页吧!

    每个数据页中的数据,采用单向链表的形式进行连接。

    各个页之间采用双向链表链接

    查找数据时是根据页内分组定义的。首先在插入数据时就会根据主键大小做好排序结构,并按照最大和最小进行分组。

    最小虚拟数据独自一组,它拥有一条数据,就是最小数据。然后剩下的数据再分成一组,即最大数据为另一组。当进行数据插入的时,都是先插入到最大数据组,当最大数据组装满后在进行分裂。 

    分组确立后,在进行数据查找的时就是根据二分查找法确定对应数据所在的槽位置,然后在使用记录头信息的next_record一条条进行查找。

    当以(非主键)作为搜索条件:只能从最小虚拟数据记录,开始依次遍历单链表中的每条记录

    所以,当写
     select * from nezha where name='nezha'
    
     这样没有进行任何优化的sql语句,默认会这样做:
    
    • 读取记录所在页的范围

    • 根据双向链表,找到所在页

    • 从所在页中查找相应的记录

    • 由于不是主键查询,就遍历所在页的单链表

    06

    索引就不命中?前提你得知道规则

    使用索引当中,最核心的就是最左匹配原则,索引命中都是根据它来定义的。

    最左匹配原则

    • 索引可以的简单如单列 (a),也可以复杂如多列 (a,b,c,d),即联合索引

    • 如果是联合索引,那么key也由多个列组成,同时,索引只能用于查找key是否存在(相等),遇到范围查询 (>、<、BETWEEN、LIKE)等就不能进一步匹配了,后续退化为线性查找。因此,列的排列顺序决定了可命中索引的列数

    • 索引列不能是表达式的一部分,那样无法命中索引,例如

    • :SELECT  *   FROM  nezha  WHERE  id + 1 = 5;   date(create_time)='2020-03-05'
      

    例如:

    • 如有索引 (a,b,c,d),查询条件 a=7 and b=8 and c>15 and d=32,则会在每个节点依次命中a、b、c,无法命中d。(c已经是范围查询了,d就没办法进行对比查找了)

    总结:

    索引在数据库中是一个非常重要的内容!

    • 最左前缀匹配原则。这是非常重要的原则,SQL查询都是基于它来。MySQL会一直向右匹配,直到遇到范围查询 (>,<,BETWEEN,LIKE)就停止匹配。

    • 也需要了解下,这个是数据库在内部的工作机制。

    • 索引的表现形式针对于不同的存储引擎,表现也不一样,并且2者之间的存储引擎区别也要掌握了解

    • 索引创建方式来自于建表前还是建表后。重点都是数据库再用后台线程创建与维护索引

    • B+Tree和Hash这2个特点还是需要注意,并且它们之间区别还未细讲。后面会针对面试问题,给大家补上来。

    特别推荐一个分享架构+算法的优质内容,还没关注的小伙伴,可以长按关注一下:

    
    
    
    
    长按订阅更多精彩▼
    
    如有收获,点个在看,诚挚感谢
    
    展开全文
    g6U8W7p06dCO99fQ3 2021-01-16 11:00:00
  • 统一声明:关于原创博客内容,可能会有部分内容参考自互联网,如有原创链接会声明引用;如找不到原创链接,在此声明如有侵权请联系删除哈。关于转载博客,如有原创链接会声明;如找不到原创链接,在此声明如有侵权请...

    0.range() 函数,其功能是创建一个整数列表,一般用在 for 循环中

         语法格式:range(start, stop, step),参数使用参考如下:

    • start: 计数从 start 开始。默认是从 0 开始。例如range(4)等价于range(0, 4);结果:(0,1,2,3)
    • stop: 计数到 stop 结束,但不包括 stop。例如:range(0, 5) 是[0, 1, 2, 3, 4]没有5
    • step:步长,默认为1。例如:range(0, 5) 等价于 range(0, 5, 1)
      #使用range函数建立列表
      ls =[x*2 for x in range(10)]
      print(ls)#[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
      
      ls1 = [x for x in range(0,10,2)]   #步长是2.
      print(ls1) #[0, 2, 4, 6, 8]
      
      ls2 = [x for x in range(3,10,2)]  #开始从3开始,步长是2.
      print(ls2) # [3, 5, 7, 9]
      
      ls3 =[x for x in range(0, -10, -1)] #负数的使用
      print(ls3)  #[0, -1, -2, -3, -4, -5, -6, -7, -8, -9]
        
      print(range(0))  #range(0, 0)
      print(range(1,0)) #range(1, 0)
      

    1.生成器的创建与元素迭代遍历

    1.1创建生成器方法1:只要把一个列表生成式的 [ ] 改成 ( )

      生成器(generator)其实是一类特殊的迭代器前面博客我们每次迭代获取数据(通过next()方法)时按照特定的规律进行生成。但是我们在实现一个迭代器时,关于当前迭代到的状态需要我们自己记录,进而才能根据当前状态生成下一个数据。为了达到记录当前状态,并配合next()函数进行迭代使用,python就搞了个生成器。所以说生成器(generator)其实是一类特殊的迭代器

    #1.创建生成器
    ls = [x*2 for x in range(10)]
    generator1 =(x*2 for x in range(10)) #这是一个生成器generator
    print(ls)
    print(generator1) #注意,打印生成器,不会像列表一样打印他的值,而是地址。
    '''
    [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
    <generator object <genexpr> at 0x00000239FE00A620>
    '''
    
    
    

      1.1遍历生成器内容 

    遍历生成器对象中的内容:
    1.方法1.使用for循环遍历
    for i in generator1:
        print(i)
    
    #方法2:命令行使用next()函数:调用next(G) ,就计算出 G 的下一个元素的值,直到计算到最后一个元素
    没有更多的元素时,抛出 StopIteration 的异常。
    >>> generator1 =(x*2 for x in range(5))
    >>> next(generator1)
    0
    >>> next(generator1)
    2
    >>> next(generator1)
    4
    >>> next(generator1)
    6
    >>> next(generator1)
    8
    >>> next(generator1)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    StopIteration
    2.方法2.python脚本使用next()方法,实际开发中是通过for循环来实现遍历,这种next()方法太麻烦。
    g1 =(x*2 for x in range(5))
    while True:
        try:
            x = next(g1)
            print(x)
        except StopIteration as  e :
            print("values=%s"%e.value)
            break #注意这里要加break,否则会死循环。
    '''结果如下:
    0
    2
    4
    6
    8
    values=None
    '''
    3.方法3:使用对象自带的__next__()方法,效果等同于next(g1)函数
    >>> g1 =(x*2 for x in range(5))
    >>> g1.__next__()
    0
    >>> g1.__next__()
    2
    >>> g1.__next__()
    4
    >>> g1.__next__()
    6
    >>> g1.__next__()
    8
    >>> g1.__next__()
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    StopIteration
    >>>

    1.2创建生成器方法2:使用yield函数创建生成器。

      generator非常强大。如果推算的算法比较复杂,用类似列表生成式的 for 循环无法实现的时候,还可以用函数来实现。简单来说:只要在def中有yield关键字的 就称为 生成器

    #著名的斐波拉契数列(Fibonacci):除第一个和第二个数外,任意一个数都可由前两个数相加得到
    #1.举例:1, 1, 2, 3, 5, 8, 13, 21, 34, ...使用函数实现打印数列的任意前n项。
    
    def fib(times): #times表示打印斐波拉契数列的前times位。
        n = 0
        a,b = 0,1
        while n<times:
            print(b)
            a,b = b,a+b
            n+=1
        return 'done'
    
    fib(10)  #前10位:1 1 2 3 5 8 13 21 34 55
    
    #2.将print(b)换成yield b,则函数会变成generator生成器。
    #yield b功能是:每次执行到有yield的时候,会返回yield后面b的值给函数并且函数会暂停,直到下次调用或迭代终止;
    def fib(times): #times表示打印斐波拉契数列的前times位。
        n = 0
        a,b = 0,1
        while n<times:
            yield b  
            a,b = b,a+b
            n+=1
        return 'done'
    
    print(fib(10))  #<generator object fib at 0x000001659333A3B8>
    
    3.对生成器进行迭代遍历元素
    方法1:使用for循环
    for x in fib(6):
        print(x)
    ''''结果如下,发现如何生成器是函数的话,使用for遍历,无法获取函数的返回值。
    1
    1
    2
    3
    5
    8
    '''
    方法2:使用next()函数来遍历迭代,可以获取生成器函数的返回值。同理也可以使用自带的__next__()函数,效果一样
    f = fib(6)
    while True:
        try:  #因为不停调用next会报异常,所以要捕捉处理异常。
            x = next(f)  #注意这里不能直接写next(fib(6)),否则每次都是重复调用1
            print(x)
        except StopIteration as e:
            print("生成器返回值:%s"%e.value)
            break
    '''结果如下:
    1
    1
    2
    3
    5
    8
    生成器返回值:done
    '''

    生成器使用总结:

    1.生成器的好处是可以一边循环一边进行计算,不用一下子就生成一个很大的集合,占用内存空间。生成器的使用节省内存空间。

    2.生成器保存的是算法,而列表保存的计算后的内容,所以同样内容的话生成器占用内存小,而列表占用内存大。每次调用 next(G) ,就计算出 G 的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出 StopIteration 的异常。

    3.使用for 循环来遍历生成器内容,因为生成器也是可迭代对象。通过 for 循环来迭代它,不需要关心 StopIteration 异常。但是用for循环调用generator时,得不到generator的return语句的返回值。如果想要拿到返回值,必须用next()方法,且捕获StopIteration错误,返回值包含在StopIteration的value中。

    4.在 Python 中,使用了 yield 的函数都可被称为生成器(generator)。生成器是一个返回迭代器的函数,只能用于迭代操作。更简单点理解生成器就是一个迭代器。

    5.一个带有 yield 的函数就是一个 generator,它和普通函数不同,生成一个 generator 看起来像函数调用,但不会执行任何函数代码,直到对其调用 next()(在 for 循环中会自动调用 next())才开始执行。虽然执行流程仍按函数的流程执行,但每执行到一个 yield 语句就会中断,保存当前所有的运行信息,并返回一个迭代值,下次执行next() 方法时从 yield 的下一个语句继续执行。看起来就好像一个函数在正常执行的过程中被 yield 中断了数次,每次中断都会通过 yield 返回当前的迭代值。生成器不仅“记住”了它数据状态;生成器还“记住”了它在流控制构造中的位置。

     

    统一声明:关于原创博客内容,可能会有部分内容参考自互联网,如有原创链接会声明引用;如找不到原创链接,在此声明如有侵权请联系删除哈。关于转载博客,如有原创链接会声明;如找不到原创链接,在此声明如有侵权请联系删除哈。

    展开全文
    qq_26442553 2018-09-05 14:36:38
  • 简单点说就是相同PG内的对象都会放到相同的硬盘上; PG是 ceph的核心概念, 服务端数据均衡和恢复的最小粒度就是PG; OSD是负责物理存储的进程,一般配置成和磁盘一一对应,一块磁盘启动一个OSD进程; 下面这...

    我们在上篇文章已经对比了不同的存储系统之间的区别,本章开始逐步深入记录Ceph的学习和运用。

    开源分布式存储系统的对比

    Ceph简介

    Ceph是一个分布式存储系统,提供对象,块和文件存储,是一个免费开源软件的存储解决方案,可以部署于普通的x86兼容服务器上,可用于解决统一存储的io问题。Ceph诞生于2004年,最早是SageWeil一项关于存储系统的PhD研究项目,致力于开发下一代高性能分布式文件系统的项目。随着云计算的发展,ceph乘上了OpenStack的春风,进而成为了开源社区受关注较高的项目之一。
    该系统被设计成自动修复和智能管理,希望减低管理员和预算开销。
    想达到的目标:没有单点故障的完全分布式存储系统,使数据能容错和无缝的复制,可扩展EB水平(EB,PB,TB,GB)

    Ceph同时支持块、文件、对象接口,支持PB级别扩展,规格上可部署到上千台通用服务器。对象S3和Swift写入的数据是相互可读取的。

    官网
    https://ceph.com/
    文档
    http://docs.ceph.com/docs/master/#how-ceph-clients-stripe-data
    ceph线下会议
    https://ceph.com/cephdays/

    Ceph的优点

    CRUSH算法
    Crush算法是ceph的两大创新之一,简单来说,ceph摒弃了传统的集中式存储元数据寻址的方案,转而使用CRUSH算法完成数据的寻址操作。CRUSH在一致性哈希基础上很好的考虑了容灾域的隔离,能够实现各类负载的副本放置规则,例如跨机房、机架感知等。Crush算法有相当强大的扩展性,理论上支持数千个存储节点。

    高可用
    Ceph中的数据副本数量可以由管理员自行定义,并可以通过CRUSH算法指定副本的物理存储位置以分隔故障域,支持数据强一致性; ceph可以忍受多种故障场景并自动尝试并行修复。

    高扩展性
    Ceph不同于swift,客户端所有的读写操作都要经过代理节点。一旦集群并发量增大时,代理节点很容易成为单点瓶颈。Ceph本身并没有主控节点,扩展起来比较容易,并且理论上,它的性能会随着磁盘数量的增加而线性增长。

    特性丰富
    Ceph支持三种调用接口:对象存储,块存储,文件系统挂载。三种方式可以一同使用。在国内一些公司的云环境中,通常会采用ceph作为openstack的唯一后端存储来提升数据转发效率。

    Ceph的存储实现架构

    Ceph系统可以大致划分为两大部分,客户端和服务端,客户端包含了四种接口,服务端包含了元数据服务器,对象存储集群和集群监视器:

    客户端

    面向用户的使用提供接口,目前有三种存储方式接口提供,对象存储 RGW(rados gateway)、块存储 RBD(rados block device) 和文件存储 CephFS。
    块存储和文件存储都是基于对象存储来进行封装实现的,块存储和文件存储的底层还是对象存储。

    对象存储(RGW:RADOS gateway)

    Ceph 对象存储服务提供了 REST 风格的 API ,它有与 Amazon S3 和 OpenStack Swift 兼容的接口。也就是通常意义的键值存储,其接口就是简单的GET、PUT、DEL和其他扩展;
    RADOSGW是一套基于当前流行的RESTFUL协议的网关,并且兼容S3和Swift。

    块存储(RBD:RADOS block device)

    RBD通过Linux内核客户端和QEMU/KVM驱动来提供一个分布式的块设备。
    RBD 是通过librbd库对应用提供块存储,主要面向云平台的虚拟机提供虚拟磁盘;RBD类似传统的SAN存储,提供数据块级别的访问;
    目前 RBD 提供了两个接口,一种是直接在用户态实现, 通过 QEMU Driver 供 KVM 虚拟机使用。 另一种是在操作系统内核态实现了一个内核模块。通过该模块可以把块设备映射给物理主机,由物理主机直接访问。

    文件存储 (CEPH FS)

    CEPH FS通过Linux内核客户端和FUSE来提供一个兼容POSIX的文件系统。
    Ceph 文件系统服务提供了兼容 POSIX 的文件系统,可以直接挂载为用户空间文件系统。它跟传统的文件系统如Ext4是一个类型,区别在于分布式存储提供了并行化的能力;

    原生接口

    除了以上3种存储接口, 还可以直接使用 librados 的原生接口,直接和RADOS通信;
    原生接口的优点是是它直接和和应用代码集成,操作文件很方便;但它的问题是它不会主动为上传的数据分片;一个1G的大对象上传,落到 Ceph 的存储磁盘上就是1G的文件;
    而以上三个接口是具有分片功能(即:条带化 file-striping)

    服务端

    元数据服务器

    主要是实现集群元数据的分布式管理

    对象存储集群

    因为ceph的三种存储接口都是通过对象存储实现的,对象存储集群将数据和元数据作为对象存储,执行其他关键职能。
    对象存储集群的核心组件是RADOS (Reliable, AutonomicDistributed Object Store)。

    集群监视器

    执行监视功能,保证集群的健康运行和告警

    客户端和服务端交互

    它们之间的结构和交互如图:


    从架构图中可以看到最底层的是RADOS,RADOS自身是一个完整的分布式对象存储系统,它具有可靠、智能、分布式等特性,Ceph所有的存储功能都是基于RADOS实现,所以Ceph的高可靠、高可拓展、高性能、高自动化都是由这一层来提供的,用户数据的存储最终也都是通过这一层来进行存储的,RADOS可以说就是Ceph的核心。

    RADOS系统主要由两部分组成,分别是OSD和Monitor。

    RADOS采用C++开发,所提供的原生Librados API包括C和C++两种。Ceph的上层应用调用本机上的librados API,再由后者通过socket与RADOS集群中的其他节点通信并完成各种操作。

    基于RADOS层的上一层是LIBRADOS,LIBRADOS是一个库,它允许应用程序通过访问该库来与RADOS系统进行交互,支持多种编程语言,比如C、C++、Python等。

    基于LIBRADOS层开发的又可以看到有三层,分别是RADOSGW、RBD和CEPH FS。

    RADOS GateWay、RBD其作用是在librados库的基础上提供抽象层次更高、更便于应用或客户端使用的上层接口。
    其中,RADOS GW是一个提供与Amazon S3和Swift兼容的RESTful API的gateway,以供相应的对象存储应用开发使用。RBD则提供了一个标准的块设备接口,常用于在虚拟化的场景下为虚拟机创建volume。
    目前,Red Hat已经将RBD驱动集成在KVM/QEMU中,以提高虚拟机访问性能。
    这两种方式目前在云计算中应用的比较多。
    CEPHFS则提供了POSIX接口,用户可直接通过客户端挂载使用。它是内核态的程序,所以无需调用用户空间的librados库。它通过内核中的net模块来与Rados进行交互。通过FUSE挂载到客户端的存储系统使用起来跟本地硬盘的使用方式一致,使用挂载路径即可访问。

    Ceph的物理部署

    服务端 RADOS 集群主要由两种节点组成:一种是为数众多的、负责完成数据存储和维护功能的OSD(Object Storage Device),另一种则是若干个负责完成系统状态检测和维护的monitor。

    Monitor

    Monitor 集群提供了整个存储系统的节点信息等全局的配置信息,通过 Paxos 算法保持数据的一致性。

    OSD

    Pool是存储对象的逻辑分区,它规定了数据冗余的类型和对应的副本分布策略;支持两种类型:副本(replicated)和 纠删码( Erasure Code);目前我们公司内部使用的Pool都是副本类型(3副本);

    PG( placement group)是一个放置策略组,它是对象的集合,该集合里的所有对象都具有相同的放置策略;简单点说就是相同PG内的对象都会放到相同的硬盘上; PG是 ceph的核心概念, 服务端数据均衡和恢复的最小粒度就是PG;

    OSD是负责物理存储的进程,一般配置成和磁盘一一对应,一块磁盘启动一个OSD进程;

    下面这张图形象的描绘了它们之间的关系:

    一个Pool里有很多PG,
    一个PG里包含一堆对象;一个对象只能属于一个PG;
    PG有主从之分,一个PG分布在不同的OSD上(针对三副本类型)

    Ceph的组件详解

    Ceph的核心组件包括Ceph OSD、Ceph Monitor和Ceph MDS。

    Ceph OSD

    OSD的英文全称是Object Storage Device,它的主要功能是存储数据、复制数据、平衡数据、恢复数据等,与其它OSD间进行心跳检查等,并将一些变化情况上报给Ceph Monitor。一般情况下一块硬盘对应一个OSD,由OSD来对硬盘存储进行管理,当然一个分区也可以成为一个OSD。

    Ceph OSD的架构实现由物理磁盘驱动器、Linux文件系统和Ceph OSD服务组成,对于Ceph OSD Deamon而言,Linux文件系统显性的支持了其拓展性,一般Linux文件系统有好几种,比如有BTRFS、XFS、Ext4等,BTRFS虽然有很多优点特性,但现在还没达到生产环境所需的稳定性,一般比较推荐使用XFS。

    OSD是强一致性的分布式存储,它的读写流程如下图

     Ceph的读写操作采用主从模型,客户端要读写数据时,只能向对象所对应的主osd节点发起请求。主节点在接受到写请求时,会同步的向从OSD中写入数据。当所有的OSD节点都写入完成后,主节点才会向客户端报告写入完成的信息。因此保证了主从节点数据的高度一致性。而读取的时候,客户端也只会向主osd节点发起读请求,并不会有类似于数据库中的读写分离的情况出现,这也是出于强一致性的考虑。由于所有写操作都要交给主osd节点来处理,所以在数据量很大时,性能可能会比较慢,为了克服这个问题以及让ceph能支持事物,每个osd节点都包含了一个journal文件。

    伴随OSD的还有一个概念叫做Journal盘,一般写数据到Ceph集群时,都是先将数据写入到Journal盘中,然后每隔一段时间比如5秒再将Journal盘中的数据刷新到文件系统中。一般为了使读写时延更小,Journal盘都是采用SSD,一般分配10G以上,当然分配多点那是更好,Ceph中引入Journal盘的概念是因为Journal允许Ceph OSD功能很快做小的写操作;一个随机写入首先写入在上一个连续类型的journal,然后刷新到文件系统,这给了文件系统足够的时间来合并写入磁盘,一般情况下使用SSD作为OSD的journal可以有效缓冲突发负载。

    在ceph中,每一个osd进程都可称作是一个osd节点,也就是说,每台存储服务器上可能包含了众多的osd节点,每个osd节点监听不同的端口,类似于在同一台服务器上跑多个mysql或redis。每个osd节点可以设置一个目录作为实际存储区域,也可以是一个分区,一整块硬盘。如下图,当前这台机器上跑了两个osd进程,每个osd监听4个端口,分别用于接收客户请求、传输数据、发送心跳、同步数据等操作。

    如上图所示,osd节点默认监听tcp的6800到6803端口,如果同一台服务器上有多个osd节点,则依次往后排序。

      在生产环境中的osd最少可能都有上百个,所以每个osd都有一个全局的编号,类似osd0,osd1,osd2……..序号根据osd诞生的顺序排列,并且是全局唯一的。存储了相同PG的osd节点除了向mon节点发送心跳外,还会互相发送心跳信息以检测pg数据副本是否正常。

    之前在介绍数据流向时说过,每个osd节点都包含一个journal文件,如下图:

    默认大小为5G,也就说每创建一个osd节点,还没使用就要被journal占走5G的空间。这个值是可以调整的,具体大小要依osd的总大小而定。

      Journal的作用类似于mysql innodb引擎中的事物日志系统。当有突发的大量写入操作时,ceph可以先把一些零散的,随机的IO请求保存到缓存中进行合并,然后再统一向内核发起IO请求。这样做效率会比较高,但是一旦osd节点崩溃,缓存中的数据就会丢失,所以数据在还未写进硬盘中时,都会记录到journal中,当osd崩溃后重新启动时,会自动尝试从journal恢复因崩溃丢失的缓存数据。因此journal的io是非常密集的,而且由于一个数据要io两次,很大程度上也损耗了硬件的io性能,所以通常在生产环境中,使用ssd来单独存储journal文件以提高ceph读写性能。

    Ceph Monitor

    由该英文名字我们可以知道它是一个监视器,负责监视Ceph集群,维护Ceph集群的健康状态,同时维护着Ceph集群中的各种Map图,比如OSD Map、Monitor Map、PG Map和CRUSH Map,这些Map统称为Cluster Map,Cluster Map是RADOS的关键数据结构,管理集群中的所有成员、关系、属性等信息以及数据的分发,比如当用户需要存储数据到Ceph集群时,OSD需要先通过Monitor获取最新的Map图,然后根据Map图和object id等计算出数据最终存储的位置。

    Mon节点监控着整个ceph集群的状态信息,监听于tcp的6789端口。每一个ceph集群中至少要有一个Mon节点,官方推荐每个集群至少部署三台。Mon节点中保存了最新的版本集群数据分布图(cluster map)的主副本。客户端在使用时,需要挂载mon节点的6789端口,下载最新的cluster map,通过crush算法获得集群中各osd的IP地址,然后再与osd节点直接建立连接来传输数据。所以对于ceph来说,并不需要有集中式的主节点用于计算与寻址,客户端分摊了这部分工作。而且客户端也可以直接和osd通信,省去了中间代理服务器的额外开销。

      Mon节点之间使用Paxos算法来保持各节点cluster map的一致性;各mon节点的功能总体上是一样的,相互间的关系可以被简单理解为主备关系。如果主mon节点损坏,其他mon存活节点超过半数时,集群还可以正常运行。当故障mon节点恢复时,会主动向其他mon节点拉取最新的cluster map。

      Mon节点并不会主动轮询各个osd的当前状态,相反,osd只有在一些特殊情况才会上报自己的信息,平常只会简单的发送心跳。特殊情况包括:1、新的OSD被加入集群;2、某个OSD发现自身或其他OSD发生异常。Mon节点在收到这些上报信息时,则会更新cluster map信息并加以扩散。

      cluster map信息是以异步且lazy的形式扩散的。monitor并不会在每一次cluster map版本更新后都将新版本广播至全体OSD,而是在有OSD向自己上报信息时,将更新回复给对方。类似的,各个OSD也是在和其他OSD通信时,如果发现对方的osd中持有的cluster map版本较低,则把自己更新的版本发送给对方。

    推荐使用以下的架构

    这里的ceph除了管理网段外,设了两个网段,一个用于客户端读写传输数据。另一个用于各OSD节点之间同步数据和发送心跳信息等。这样做的好处是可以分担网卡的IO压力。否则在数据清洗时,客户端的读写速度会变得极为缓慢。

    Ceph MDS

    全称是Ceph MetaData Server,Mds是ceph集群中的元数据服务器,而通常它都不是必须的,因为只有在使用cephfs的时候才需要它,对象存储和块存储设备是不需要使用该服务的,而目前云计算中用的更广泛的是另外两种存储方式。

    Mds虽然是元数据服务器,但是它不负责存储元数据,元数据也是被切成对象存在各个osd节点中的,如下图:

    在创建CEPHFS时,要至少创建两个POOL,一个用于存放数据,另一个用于存放元数据。Mds只是负责接受用户的元数据查询请求,然后从osd中把数据取出来映射进自己的内存中供客户访问。所以mds其实类似一个代理缓存服务器,替osd分担了用户的访问压力,如下图:

    Ceph与云平台的关系

    Ceph已经成为OpenStack后端存储标配,OpenStack作为IaaS系统,涉及到存储的部分主要是块存储服务模块、对象存储服务模块、镜像管理模块和计算服务模块,对应为其中的Cinder、Swift、Glance和Nova四个项目。

    Ceph RBD块存储是以独立卷的方式挂接到OpenStcak Cinder模块,主要用作数据盘,这种方式主要通过Cinder Driver实现,删除虚拟机时卷依然存在。Nova对接Ceph时,Ceph RBD块存储卷需要与虚拟机绑定,所以删除虚拟机时卷也删除,一般用作启动盘。Ceph也可以和Glance对接用于镜像卷。Keystone作为OpenStack对象Swift的认证模块,支持Ceph通过RADOSGW网关认证,给OpenStcak提供Swift存储服务。

    Ceph社区已经把Ceph 的RBD块存储镜像支持功能扩展到Docker中。在Docker中Ceph的RBD镜像功能主要是负责把RBD镜像通过异步通信的方式从一个Ceph集群复制到另一个Ceph集群,用于对Docker镜像容灾保护和恢复。

    Ceph内部数据存储视图

    在Ceph存储系统中,Cehp的基础服务架构主要包括了Object Storage Device(OSD),Monitor和MDS。
    提供了Librados原生对象基础库、Librbd块存储库、基于S3 和Swift兼容的Librgw对象库和Libceph文件系统库。
    搭建一台Ceph系统至少需要1个Ceph Monitor和2个Ceph OSD。
    一个Cluster可逻辑上划分为多个Pool。
    一个 Pool由若干个逻辑 PG( Placement Group)组成,Pool内的副本数量也是可以设置的。

    Ceph底层是对象系统,所以一个文件会被切分为多个Object,每个Object会被映射到一个PG,每个 PG 会映射到一组 OSD(Object Storage Device),其中第一个OSD 是主,其余的是备,OSD间通过心跳来相互监控存活状态。引入PG概念后,OSD只和PG相关,不但简化了OSD的数据存储,而且实现了Object到OSD的动态映射,OSD的添加和故障不影响Object的映射。

    Ceph数据如何存储

    在Ceph存储系统中,数据存储分三个映射过程
    首先要将用户要操作的file,映射为RADOS能够处理的object。就是简单的按照object的size对file进行切分,相当于RAID中的条带化过程。
    接着把Object映射到PG,在file被映射为一个或多个object之后,就需要将每个object独立地映射到一个PG中去。
    第三次映射就是将作为object的逻辑组织单元的PG映射到数据的实际存储单元OSD。

    文件存入时,首先把File切分为RADOS层面的Object,每个Object一般为2MB或4MB(大小可设置)。每个Object通过哈希算法映射到唯一的PG。每个PG通过Crush算法映射到实际存储单元OSD,PG和OSD间是多对多的映射关系。OSD在物理上可划分到多个故障域中,故障域可以跨机柜和服务器,通过策略配置使PG的不同副本位于不同的故障域中。

    Ceph数据分布算法

    在分布式存储系统中比较关注的一点是如何使得数据能够分布得更加均衡,常见的数据分布算法有一致性Hash和Ceph的Crush算法。Crush是一种伪随机的控制数据分布、复制的算法,Ceph是为大规模分布式存储而设计的,数据分布算法必须能够满足在大规模的集群下数据依然能够快速的准确的计算存放位置,同时能够在硬件故障或扩展硬件设备时做到尽可能小的数据迁移,Ceph的CRUSH算法就是精心为这些特性设计的,可以说CRUSH算法也是Ceph的核心之一。

    Ceph以私有Client方式对外提供服务,支持Linux用户态(Fuse)和内核态(VFS)方式,Clinet还实现数据切片,通过Crush算法定位对象位置,并进行数据的读写。但在测试中,通常在Ceph服务器端将Ceph配置成NFS服务的ExportFS,通过标准NFS接口导出目录。 CephFS 支持POSIX、HDFS、NFS、CIFS服务接口,其中NFS和CIFS通过外置网关实现(通过Clinet导出)。

    在PG通过Crush算法映射到数据的实际存储单元OSD时,需求通过Crush Map、Crush Rules和Crush算法配合才能完成。

    Cluster Map用来记录全局系统状态记数据结构,由Crush Map和OSD Map两部分组成。 Crush Map包含当前磁盘、服务器、机架的层级结构,OSD Map包含当前所有Pool的状态和所有OSD的状态。

    Crush Rules就是数据映射的策略,决定了每个数据对象有多少个副本,这些副本如何存储。

    Crush算法是一种伪随机算法,通过权重决定数据存放(如跨机房、机架感知等),通常采用基于容量的权重。Crush算法支持副本和EC两种数据冗余方式,还提供了四种不同类型的Bucket(Uniform、List、Tree、Straw),大多数情况下的都采用Straw。

    在说明CRUSH算法的基本原理之前,先介绍几个概念和它们之间的关系。

    存储数据与object的关系:当用户要将数据存储到Ceph集群时,存储数据都会被分割成多个object,每个object都有一个object id,每个object的大小是可以设置的,默认是4MB,object可以看成是Ceph存储的最小存储单元。

    object与pg的关系:由于object的数量很多,所以Ceph引入了pg的概念用于管理object,每个object最后都会通过CRUSH计算映射到某个pg中,一个pg可以包含多个object。

    pg与osd的关系:pg也需要通过CRUSH计算映射到osd中去存储,如果是二副本的,则每个pg都会映射到二个osd,比如[osd.1,osd.2],那么osd.1是存放该pg的主副本,osd.2是存放该pg的从副本,保证了数据的冗余。

    pg和pgp的关系:pg是用来存放object的,pgp相当于是pg存放osd的一种排列组合,我举个例子,比如有3个osd,osd.1、osd.2和osd.3,副本数是2,如果pgp的数目为1,那么pg存放的osd组合就只有一种,可能是[osd.1,osd.2],那么所有的pg主从副本分别存放到osd.1和osd.2,如果pgp设为2,那么其osd组合可以两种,可能是[osd.1,osd.2]和[osd.1,osd.3],是不是很像我们高中数学学过的排列组合,pgp就是代表这个意思。一般来说应该将pg和pgp的数量设置为相等。这样说可能不够明显,我们通过一组实验来体会下:

    先创建一个名为testpool包含6个PG和6个PGP的存储池
    ceph osd pool create testpool 6 6
    通过写数据后我们查看下pg的分布情况,使用以下命令:

    ceph pg dump pgs | grep ^1 | awk ‘{print 1, 1 , 2,$15}’
    dumped pgs in format plain
    1.1 75 [3,6,0]
    1.0 83 [7,0,6]
    1.3 144 [4,1,2]
    1.2 146 [7,4,1]
    1.5 86 [4,6,3]
    1.4 80 [3,0,4]
    第1列为pg的id,第2列为该pg所存储的对象数目,第3列为该pg所在的osd

    我们扩大PG再看看
    ceph osd pool set testpool pg_num 12
    再次用上面的命令查询分布情况:
    1.1 37 [3,6,0]
    1.9 38 [3,6,0]
    1.0 41 [7,0,6]
    1.8 42 [7,0,6]
    1.3 48 [4,1,2]
    1.b 48 [4,1,2]
    1.7 48 [4,1,2]
    1.2 48 [7,4,1]
    1.6 49 [7,4,1]
    1.a 49 [7,4,1]
    1.5 86 [4,6,3]
    1.4 80 [3,0,4]
    我们可以看到pg的数量增加到12个了,pg1.1的对象数量本来是75的,现在是37个,可以看到它把对象数分给新增的pg1.9了,刚好是38,加起来是75,而且可以看到pg1.1和pg1.9的osd盘是一样的。
    而且可以看到osd盘的组合还是那6种。

    我们增加pgp的数量来看下,使用命令:
    ceph osd pool set testpool pgp_num 12
    再看下
    1.a 49 [1,2,6]
    1.b 48 [1,6,2]
    1.1 37 [3,6,0]
    1.0 41 [7,0,6]
    1.3 48 [4,1,2]
    1.2 48 [7,4,1]
    1.5 86 [4,6,3]
    1.4 80 [3,0,4]
    1.7 48 [1,6,0]
    1.6 49 [3,6,7]
    1.9 38 [1,4,2]
    1.8 42 [1,2,3]
    再看pg1.1和pg1.9,可以看到pg1.9不在[3,6,0]上,而在[1,4,2]上了,该组合是新加的,可以知道增加pgp_num其实是增加了osd盘的组合。

    通过实验总结:
    (1)PG是指定存储池存储对象的目录有多少个,PGP是存储池PG的OSD分布组合个数
    (2)PG的增加会引起PG内的数据进行分裂,分裂相同的OSD上新生成的PG当中
    (3)PGP的增加会引起部分PG的分布进行变化,但是不会引起PG内对象的变动

    pg和pool的关系:pool也是一个逻辑存储概念,我们创建存储池pool的时候,都需要指定pg和pgp的数量,逻辑上来说pg是属于某个存储池的,就有点像object是属于某个pg的。

    以下这个图表明了存储数据,object、pg、pool、osd、存储磁盘的关系

    本质上CRUSH算法是根据存储设备的权重来计算数据对象的分布的,权重的设计可以根据该磁盘的容量和读写速度来设置,比如根据容量大小可以将1T的硬盘设备权重设为1,2T的就设为2,在计算过程中,CRUSH是根据Cluster Map、数据分布策略和一个随机数共同决定数组最终的存储位置的。

    Cluster Map里的内容信息包括存储集群中可用的存储资源及其相互之间的空间层次关系,比如集群中有多少个支架,每个支架中有多少个服务器,每个服务器有多少块磁盘用以OSD等。

    数据分布策略是指可以通过Ceph管理者通过配置信息指定数据分布的一些特点,比如管理者配置的故障域是Host,也就意味着当有一台Host起不来时,数据能够不丢失,CRUSH可以通过将每个pg的主从副本分别存放在不同Host的OSD上即可达到,不单单可以指定Host,还可以指定机架等故障域,除了故障域,还有选择数据冗余的方式,比如副本数或纠删码。

    下面这个式子简单的表明CRUSH的计算表达式:

    CRUSH(X) -> (osd.1,osd.2…..osd.n)

    式子中的X就是一个随机数。

    下面通过一个计算PG ID的示例来看CRUSH的一个计算过程:

    (1)Client输入Pool ID和对象ID;

    (2)CRUSH获得对象ID并对其进行Hash运算;

    (3)CRUSH计算OSD的个数,Hash取模获得PG的ID,比如0x48;

    (4)CRUSH取得该Pool的ID,比如是1;

    (5)CRUSH预先考虑到Pool ID相同的PG ID,比如1.48。

    对象的寻址过程

    查找对象在集群中的存储的位置,具体分为两步:
    第一步,对象到PG的映射;将对象的id 通过hash映射,然后用PG总数对hash值取模得到pg id:

    pg_ id = hash( object_ id ) % pg_num

    第二步,PG到osd列表映射; 通过crush算法计算PG上的对象分布到哪些OSD硬盘上;

    CRUSH(PG_ID) =⇒ OSD

    CRUSH算法是 ceph的精华所在;

    crush的目标

    先看看crush算法的希望达成的目标:
    数据均匀的分布到集群中;
    需要考虑各个OSD权重的不同(根据读写性能的差异,磁盘的容量的大小差异等设置不同的权重)
    当有OSD损坏需要数据迁移时,数据的迁移量尽可能的少;

    crush算法过程

    简单说下crush算法的过程:
    第一步输入PG id、可供选择的OSD id 列表,和一个常量,通过一个伪随机算法,得到一个随机数,伪随机算法保证了同一个key总是得到相同的随机数,从而保证每次计算的存储位置不会改变;

    CRUSH_HASH( PG_ID, OSD_ID, r ) = draw

    第二步将上面得到的随机数和每个OSD的权重相乘,然后挑出乘积最大的那个OSD;

     ( draw &0xffff ) * osd_weight = osd_straw

    在样本容量足够大之后,这个随机数对挑中的结果不再有影响,起决定性影响的是OSD的权重,也就是说,OSD的权重越大,被挑中的概率越大。
    到这里了我们再看看crush算法如何达成的目标:
    通过随机算法让数据均衡分布,乘以权重让挑选的结果考虑了权重;而如果出现故障OSD,只需要恢复这个OSD上的数据,不在这个节点上的数据不需移动;

    crush优缺点

    聊到这里,crush算法的优缺点就明显了:
    优点:
    输入元数据( cluster map、 placement rule) 较少, 可以应对大规模集群。
    可以应对集群的扩容和缩容。
    采用以概率为基础的统计上的均衡,在大规模集群中可以实现数据均衡

    缺点
    在小规模集群中, 会有一定的数据不均衡现象(权重的影响低,主要起作用的是伪随机算法)。
    看清楚了寻址的过程,就明白为啥PG不能轻易变更了;PG是寻址第一步中的取模参数,变更PG会导致对象的PG id 都发生变化,从而导致整个集群的数据迁移;

    Ceph 是Sega本人的博士论文作品, 其博士论文被整理成三篇短论文,其中一篇就是 CRUSH
    CRUSH论文标题为《CRUSH: Controlled, Scalable, Decentralized Placement of Replicated Data》,介绍了CRUSH的设计与实现细节。

    (PS:另外两篇是 RADOS和 CephFS, 分别讲 Ceph 的服务器实现和 Ceph 文件系统的细节实现)

    错误检测和恢复

    错误检测
    利用心跳
    上报monitor
    更新map

    错误恢复
    主osd主持恢复工作
    若主osd挂掉,二级osd选择一个顶上

    数据条带化

    由于存储设备吞吐量的限制,影响性能和可伸缩性。
    跨多个存储设备的连续块条带化存储信息,以提高吞吐量和性能
    Ceph条带化相似于RAID0
    注意:ceph条带化属于client端,不在RADOS范畴

    注意:条带化是独立于对象副本的。由于CRUSH副本对象跨越OSDs,所以条带自动的被复制。

    条带化参数

    Object Size:
    足够大可以容纳条带单元,必须容纳一个或者多个条带单元。(如2MB,4MB)
    Stripe Width:
    一个条带单元的大小,除了最后一个,其他必须一样大(如64K)
    Stripe Count:
    连续写入一系列的对象的个数(如4个)
    注意:
    参数一旦设置不可改变,提前做好性能测试

    Ceph的高级功能

    Ceph支持丰富的存储功能,从分布式系统最基本的横向扩展、动态伸缩、冗余容灾、负载平衡等,到生产环境滚动升级、多存储池、延迟删除等,再到高大上的Ceph集群、快照、EC纠删码、跨存储池缓存等,下面我们简单介绍几个关键特性。

    Ceph基于统一存储系统设计,支持三种接口。File文件系统支持POSIX、HDFS、NFS、CIFS服务接口,Block块服务支持精简配置、COW快照、克隆,对象服务支持原生的Object API、也兼容Swift和S3的API。

    在Ceph storage 2中,提供全球对象存储集群,支持单个命名空间,并支持在多Region地区运行的集群之间提供了数据同步,包含Region内主Zone到从Zone数据同步(可同步数据和元数据)和不同Region间数据同步(只能同步元数据,包含网关用户和桶信息、但不包含桶内的对象)。

    哪些公司在使用Ceph

    红帽
    美国预测分析公司FICO
    澳大利亚的莫纳什大学 500PB
    乐视,一点资讯,今日头条,滴滴,青云等

    Ceph仅仅是OpenStack后端存储标配,目前很多存储厂商、大企业都基于Ceph技术开发或搭建存储系统,我们首先看看几家存储厂商的产品,如HopeBay和SanDisk。

    Hope Bay科技是一家专注于云平台的科技公司,拥有ArkEase Pro存储服务平台、ArkFlex数据存储平台、Ark Express存储网关和ArkVoice企业云端语音录制平台。在ArkFlex数据存储平台中,Hope Bay对Ceph文件系统进行改良,将CIFS、NFS、iSCSI建构在Ceph RBD之上。

    SanDisk收购Fusion-io之后相继推出ioControl混合式存储阵列和InfiniFlash系列闪存。剥离相关业务到新成立新NextGen公司,SanDisk通过InfiniFlash系列闪存主攻闪存市场,其中就有一款机型InfiniFlash System IF500采用Ceph技术(IF100硬件和InfiniFlash OS Ceph横向扩展软件),同时提供对象存储与块存储服务。SanDisk的存储策略是比较开放,低端存储IF100(纯硬件形态)整合了Nexenta的基于ZFS文件系统开源NexentaStor软件(支持NAS和iSCSI),而高端的IF700则使用了Fusion-io时期的 ION Accelerator数据库加速软件。

    此外,很多大型企业也采用Ceph构建构建云平台和分布式存储解决方案,也正是因为Ceph和OpenStack的深度集成,使得Ceph和OpenStack配合被互联网公司用来搭建云平台。

    乐视基于OpenStack 和Ceph(RBD块存储和RADOSGW对象)搭建乐视云平台;宝德云也基于OpenStack、Ceph(RBD块存储和CephFS) 和Docker构建。电商eBay也采用Ceph和 OpenStack 建设私有云,每个Ceph集群容量都高达数 PB 级别,这些集群主要为 OpenStack 服务。同时,eBay 团队在NAS云化投入逐渐加大,CephFS有可能作为NAS 云化的不二之选。

    携程基于Ceph搭建PB级云对象存储,浪潮AS13000系列存储也是基于Ceph开发,思科UCS流媒体服务存储也是基于Ceph对象存储,雅虎基于Ceph搭建云对象存储。联通研究院、CERN实验室、United Stack等也基于Ceph搭建了开发环境。

    Ceph已经支持云Ready: 随着云计算的发展,首先Ceph搭上了OpenStack这只大船,预示着Ceph已经完全云Ready。接着Ceph受到Intel、SanDisk、思科、Yahoo等公司支持,尤其是RedHat以重金收购Inktank公司,将其作为发展的主方向。通过多年发展,RadHat也明确了Ceph和Gluster侧重点和发展方向,Gluster更专注于文件,Ceph更专注于块和对象。

    Ceph社区力量支持:Ceph社区现在已经有很多厂商参入进来,从Intel、思科、SanDisk等这样的巨头,到United Stack这样的Startup公司,再到电信、大学、研究所这类非存储领域的公司或单位,Ceph的参与队伍越来越庞大。

    Ceph功能的不断完善: Ceph的性能不断得到提升,存储特性也不断丰富,甚至可以与传统专业存储媲美,完备的存储服务和低廉的投资成本,使得越来越多的企业和单位选用Ceph提供存储服务。

    SDS和分布式架构: Ceph软件与硬件平台之间完全解耦,对企业来说搭建Ceph存储系统的门槛是逐渐变低,部署简单基于Linux Ubuntu和标准X86平台。Ceph与存储Sandisk、宝德,云计算United Stack、携程和乐视等公司的成功实践,也为Ceph的广泛应用打下参考基础。

    更多详细方案可参考:
    其他公司应用Ceph的具体方案

    相关使用经验

    预先设置PG不更改

    一个Pool里设置的PG数量是预先设置的,PG的数量不是随意设置,需要根据OSD的个数及副本策略来确定:

    Total PGs = ((Total_number_of_OSD * 100) / max_replication_count) / pool_count

    线上尽量不要更改PG的数量,PG的数量的变更将导致整个集群动起来(各个OSD之间copy数据),大量数据均衡期间读写性能下降严重;

    良好的工程实践建议(掉坑后的教训):
    预先规划Pool的规模,设置PG数量;一旦设置之后就不再变更;后续需要扩容就以 Pool 为维度为扩容,通过新增Pool来实现(Pool通过 crushmap实现故障域隔离);

    故障域的划分

    刚开始接触 Ceph,通常会忽略 crushmap,因为即使对它不做任何设置,也不影响我们的正常使用;
    一旦集群大了,没有它集群就处于一个危险的运行状态中;
    没有故障域的划分,整个集群就处于一个未隔离的资源池中;
    一个对象存过去,可能落在 500个OSD硬盘的任意三个上;
    如果一块硬盘坏了,可能带来的是全局影响(副本copy,这个硬盘上丢失的PG副本可能分布在全局各个硬盘上);

    使用crushmap 将整个集群的OSD 划分为一个个故障域,类似将一个集群按业务划分成为了多个小集群;每个Pool 只会用到特定的 OSD,这样,一旦某个OSD 损坏,影响的只是某个业务的某个Pool,将故障的范围控制在一个很小的范围内。

    良好的工程实践建议:
    使用crushmap 划分故障域,将pool限制在特定的osd list上,osd的损坏只会引起这个pool内的数据均衡,不会造成全局影响;

    服务端对象的存储

    对象是数据存储的基本单元, 一般默认 4MB 大小(这里指的是RADOS的底层存储的对象,非RGW接口的对象)。

    对象的组成分为3部分:key 、value、元数据;

    元数据可直接存在文件的扩展属性中(必须是标准的文件属性),也可存到levelDb中;
    value 就是对象数据,在本地文件系统中用一个文件存储;
    对于大文件的存储,Ceph 提供的客户端接口会对大文件分片(条带化)后存储到服务端;这个条带化操作是在客户端接口程序完成的,在 Ceph 存储集群内存储的那些对象是没条带化的。客户端通过 librados 直接写入 Ceph 存储的数据不会分片。

    良好的工程实践建议:
    对于对象存储,只使用 Ceph 提供的 RGW 接口, 不使用 librados原生接口;不仅有分片功能,扩展也更容易(RGW是无状态的,可水平扩展);大量大对象直接存放到 Ceph中会影响 Ceph 稳定性(存储容量达到60%后);

    Ceph二次开发可优化的地方

    内网传输的加密安全问题
    优化Ceph对levelDB迭代器的使用

    参考链接:
    https://www.cnblogs.com/me115/p/6366374.html
    https://www.cnblogs.com/luohaixian/p/8087591.html
    https://blog.csdn.net/i_chips/article/details/53487719

    展开全文
    q383965374 2018-04-26 18:54:45
  • MVCC 底层原理 案例一 下图是准备的素材,这里应该都理解 select 返回的结果为 niuniu,即事务 102 修改后的结果: 在上图中可以看到有三个事务在进行。事务 ID 为 100、101 是修改的其他表,只有事务 ID 为 102 ...

    MVCC 到底是什么?

    MVCC 即多版本控制器,其特点就是在同一时间,不同事务可以读取到不同版本的数据,从而去解决脏读和不可重复读的问题。

    这样的解释你看了不下几十遍了吧!但是你真的理解什么是多版本控制器吗?

    ①生活案例:搬家

    最近小 Q 跟自己的女朋友需要搬到新家,由于出小区的的时候需要支付当月的物业费。于是小 Q 跟自己的女朋友同时登陆了小区提供的物业缴费系统。

    ②悲观并发控制

    假设小 Q 正在查当月需要缴纳的费用是多少,然后进行支付的时候,此时小 Q 查询的这条数据是已经被锁定的。

    那么小 Q 女朋友是无法访问该数据的,直至小 Q 支付完成或者退出系统将悲观锁释放,小 Q 的女朋友才可以查询到数据。

    悲观锁保证在同一时间只能有一个线程访问,默认数据在访问的时候会产生冲突,然后在整个过程都加上了锁。

    这样的系统对于站在程序员的角度看就是毫无用户体验感,如果多个人需要同时访问一条信息,只能在一台设备上看喽!

    ③乐观并发控制

    在小 Q 查看物业费欠费情况,并且支付的同时,小 Q 的女朋友也可以访问到该数据。

    乐观锁认为即使在并发环境下,也不会产生冲突问题,所以不会去做加锁操作。而是在数据提交的时候进行检测,如果发现有冲突则返回冲突信息。

    小结:Innodb 的 MVCC 机制就是乐观锁的一种体现,读不加锁,读写不冲突,在不加锁的情况下能让多个事务进行并发读写,并且解决读写冲突问题,极大的提高系统的并发性。

    悲观锁、乐观锁

    锁按照粒度分为表锁、行锁、页锁;按照使用方式分为共享锁、排它锁;根据思想分为乐观锁、悲观锁。

    无论是乐观锁、悲观锁都只是一种思想而已,并不是实际的锁机制,这点一定要清楚。

    ①悲观锁(悲观并发控制)

    悲观锁实际为悲观并发控制,缩写 PCC。

    悲观锁持消极态度,认为每一次访问数据时,总是会发生冲突,因此,每次访问必须先锁住数据,完成访问后在释放锁。

    保证在同一时间只有单个线程可以访问,实现数据的排它性。同时悲观锁使用数据库自身的锁机制实现,可以解决读-写,写-写的冲突。

    那么在什么场景下可以使用悲观锁呢?悲观锁适用于在写多读少的并发环境下使用,虽然并发效率不高,但是保证了数据的安全性。

    ②乐观锁(乐观并发控制)

    跟悲观锁一样,乐观锁实际为乐观并发控制,缩写为OCC。

    乐观锁相对于悲观锁而言,认为即使在并发环境下,外界对数据的操作不会产生冲突,所以不会去加锁,而是会在提交更新的时候才会正式的对数据的冲突与否进行检测。

    如果发现冲突,要么再重试一次,要么切换为悲观的策略。乐观并发控制要解决的是数据库并发场景下的写-写冲突,指用无锁的方式去解决。

    MVCC 解决了哪些问题

    在事务并发的情况下会产生以下问题:

    • 脏读:读取其他事务未提交的数据。

    • 不可重复读:一个事务在读取一条数据时,由于另一个事务修改了这条数据,导致前后读取的数据不一致。

    • 幻读:一个事务先读取了某个范围的数量,同时另一个事务新增了这个范围的数据,再次读取发现俩次得到的结果不一致。

    MVCC 在 Innodb 存储引擎的实现主要是为了提高数据库并发能力,用更好的方式去处理读–写冲突,同时做到不加锁、非阻塞并发读写。

    但是 MVCC 可以解决脏读,不可重复读,MVCC 使用快照读解决了部分幻读问题,但是在修改时还是使用的当前读,所以还是存在幻读问题,幻读的问题最终就是使用间隙锁解决。

    当前读、快照读

    在了解 MVCC 是如何解决事务并发带来的问题之前,需要先明白俩个概念,当前读、快照读。

    ①当前读

    给读操作加上共享锁、排它锁,DML 操作加上排它锁,这些操作就是当前读。

    共享锁、排它锁也被称之为读锁、写锁。共享锁与共享锁是共存的,但是如果要修改、添加、删除时,必须等到共享锁释放才可进行操作。

    因为在 Innodb 存储引擎中,DML 操作都会隐式添加排它锁。所以说当前读所读取的记录就是最新的记录,读取数据时加上锁,保证其他事务不能修改当前记录。

    ②快照读

    如果你看到这里就默认你对隔离级别有一定的了解哈!快照读的前提是隔离级别不是串行级别,串行级别的快照读会退化成当前读。

    快照读的出现旨在提高事务并发性,其实现基于本文的主角 MVCC 即多版本控制器。

    MVCC 可以认为就是行锁的一个变种,但是它在很多情况下避免了加锁操作。所以说快照读读取的数据有可能不是最新的,而是之前版本的数据。

    为什么要提到快照读呢!因为在 read-view 就是通过快照读生成的,为了防止后文概念模糊,所以在这里进行说明。

    ③如何区分当前读、快照读

    不加锁的简单的 select 都属于快照读:

    select id name user where id = 1;
    

    与之对应的则是当前读,给 select 加上共享锁、排它锁:

    select id name from user where id = 1 lock in share mode;
    
    select id name from user where id = 1 for update;
    

    MVCC 实现三大要素

    终于来到本文最终要的部分了,前边的叙述都是为了原理这一块做的铺垫。

    在这之前需要知道 MVCC 只在 REPEATABLE READ(可重复读) 和  READ COMMITTED(已读提交)这俩种隔离级别下适用。

    MVCC 实现原理是由俩个隐式字段、undo 日志、Read view 来实现的。

    ①隐式字段

    在 Innodb 存储引擎中,在有聚簇索引的情况下每一行记录中都会隐藏俩个字段,如果没有聚簇索引则还有一个 6byte 的隐藏主键。

    这俩个隐藏列一个记录的是何时被创建的,一个记录的是什么时候被删除。这里不要理解为是记录的是时间,存储的是事务 ID。

    俩个隐式字段为 DB_TRX_ID,DB_ROLL_PTR,没有聚簇索引还会有 DB_ROW_ID 这个字段:

    • DB_TRX_ID:记录创建这条记录上次修改他的事务 ID。

    • DB_ROLL_PTR:回滚指针,指向这条记录的上一个版本。

    隐式字段实际还有一个 delete flag 隐藏字段,即记录被更新或删除,这里的删除并不代表真的删除,而是将这条记录的 delete flag 改为 true(这里埋下一个伏笔,数据库的删除是真的删除吗?)

    ②undo log(回滚日志)

    之前对 undo log 的作用只提到了回滚操作实现原子性,现在需要知道的另一个作用就是实现 MVCC 多版本控制器。

    undo log 细分为俩种:

    • insert 时产生的 undo log、update

    • delete 时产生的 undo log

    在 Innodb 中 insert 产生的 undo log 在提交事务之后就会被删除,因为新插入的数据没有历史版本,所以无需维护 undo log。

    update 和 delete 操作产生的 undo log 都属于一种类型,在事务回滚时需要,而且在快照读时也需要,则需要维护多个版本信息。

    只有在快照读和事务回滚不涉及该日志时,对应的日志才会被 purge 线程统一删除。

    purge 线程会清理 undo log 的历史版本,同样也会清理 del flag 标记的记录。

    写到这里关于 undo log 在 MVCC 中的作用估计还是蒙圈的。

    undo log 在 MVCC 中的作用:undo log 保存的是一个版本链,也就是使用 DB_ROLL_PTR 这个字段来连接的。当数据库执行一个 select 语句时会产生一致性视图 read view。

    那么这个 read view 是由在查询时所有未提交事务 ID 组成的数组,数组中最小的事务 ID 为 min_id 和已创建的最大事务 ID 为 max_id 组成,查询的数据结果需要跟 read-view 做比较从而得到快照结果。

    所以说 undo log 在 MVCC 中的作用就是为了根据存储的事务 ID 做比较,从而得到快照结果。

    ③undo log 底层实现

    假设一开始的数据为下图:

    此时执行了一条更新的 SQL 语句:

    update user set name = 'niuniu where id = 1'
    

    那么 undo log 的记录就会发生变化,也就是说当执行一条更新语句时会把之前的原有数据拷贝到 undo log 日志中。

    同时你可以看见最新的一条记录在末尾处连接了一条线,也就是说 DB_ROLL_PTR 记录的就是存放在 undo log 日志的指针地址。

    最终有可能需要通过指针来找到历史数据:

    ④read-view

    当执行 SQL 语句查询时会产生一致性视图,也就是 read-view,它是由查询的那一时间所有未提交事务 ID 组成的数组,和已经创建的最大事务 ID 组成的。

    在这个数组中最小的事务 ID 被称之为 min_id,最大事务 ID 被称之为 max_id,查询的数据结果要根据 read-view 做对比从而得到快照结果。

    于是就产生了以下的对比规则,这个规则就是使用当前的记录的 trx_id 跟 read-view 进行对比,对比规则如下。

    ⑤版本链对比规则

    如果落在 trx_id<min_id,表示此版本是已经提交的事务生成的,由于事务已经提交所以数据是可见的。

    如果落在 trx_id>max_id,表示此版本是由将来启动的事务生成的,是肯定不可见的。

    如果落在 min_id<=trx_id<=max_id 会存在俩种情况

    • 如果 row 的 trx_id 在数组中,表示此版本是由还没提交的事务生成的,不可见,但是当前自己的事务是可见的。

    • 如果 row 的 trx_id 不在数组中,表明是提交的事务生成了该版本,可见。

    在这里还有一个特殊情况那就是对于已经删除的数据,在之前的 undo log 日志讲述时说了 update 和 delete 是同一种类型的 undo log,同样也可以认为 delete 就是 update 的特殊情况。

    当删除一条数据时会将版本链上最新的数据复制一份,然后将 trx_id 修改为删除时的 trx_id,同时在该记录的头信息中存在一个 delete flag 标记,将这个标记写上 true,用来表示当前记录已经删除。

    在查询时按照版本链的规则查到对应的记录,如果 delete flag 标记位为 true,意味着数据已经被删除,则不返回数据。

    如果你对这里的 read-view 的生成和版本链对比规则不懂,不要着急,也不要在这里浪费时间,请继续往下看,我会使用一个简单的案例和一个复杂的案例给大家重现上述的规则。

    MVCC 底层原理

    案例一

    下图是准备的素材,这里应该都理解 select 返回的结果为 niuniu,即事务 102 修改后的结果:

    在上图中可以看到有三个事务在进行。事务 ID 为 100、101 是修改的其他表,只有事务 ID 为 102 修改的需要查询的这张表。

    接下来看看 select 这一列查询返回的结果是不是就是事务 ID 为 102 修改的结果。

    此时生成的 read-view 为 [100,101],102,那么现在就可以返回去看一下 read-view 规则,在这里事务 ID100 就是 min_id,事务 ID102 就是 max_id。

    这个 select 语句返回结果肯定是 niuniu。那么接下来看一下在 MVCC 中是如何查找数据的。

    当前版本链:

    那么就会拿着这个 trx_id 为 102 进行比对,会发现这个 102 就是 max_id,然后你再看一下版本链的对比规则中第三种情况。

    如果落在 min_id<=trx_id<=max_id 会存在俩种情况。此时信息就已经非常明确了,事务 ID102 是没有在数组中的,所以表示这个版本是已经提交的事务生成的,那么就是可见的呗!

    毫无疑问查询会返回 niuniu 这个值,先通过这个简单的案例让你对版本链有一个简单的理解,接下来将使用一个比较繁琐的案例再来跟大家演示一遍。

    案例二

    本例要求知道 select 的第二次查询结果。深黑色字体。同样是在 kaka 那一条记录的基础上。

    当事务 ID100 俩次更新后,版本链也会改变,此时的版本链如下图。红色部分为最新数据,蓝色数据为 undo log 的版本链数据。

    对于此时生成的 read-view 你会有什么疑问,在 RR 级别也就是可重复读的隔离级别下。

    当在一个事务下执行查询时,所有的 read-view 都是沿用的第一条查询语句生成的。

    那此时的 read-view 也就是[100,101],102。

    看一下底层查找步骤:

    • 当前数据的事务 ID 为 100

    • 根据规则会落在 min_id<=trx_id<=max_id 这个区间

    • 并且当前行的事务 ID100 是在 read-view 的数组中的,表示此时的事务还没有提交则不可见

    • 继续在版本链中往下寻找,此时找到的事务 ID 还是 100,跟上述流程一致

    • 通过查找版本链,将发现事务 ID 为 102

    • 102 是 read-view 的 max_id,同样也会落在 min_id<=trx_id<=max_id 这个区间,但是跟之前不同的是,事务 102 是没有在数组中的,表示这个版本事务已经提交了所以是可见的

    • 最后返回的是 niuniu

    案例三

    为了让大家体会一下可重复读级别生成的 read-view 是根据在同一事务中第一条快照读产生的,再来看一个案例。

    此时的事务 ID101 也再对数据更新两次,然后在进行查询看一下会返回什么值:

    经过案例一、案例二的熟悉,现在对 undo log 的版本链和对比规则已经有了一定的了解了吧!

    案例三就不在那么详细的说明了,此时的版本链如下:

    此时的 read-view 依然为[100,101],102。

    那么首先会根据事务 101 去版本链对比,事务 101 和事务 100 都会落在 min_id<=trx_id<=max_id 这个区间,并且还都在数组中,所以数据是不可见的。

    那么继续往版本链中寻找就会到事务 102,这个是最大的事务 ID 并且不在数组中,所以是可见的。

    于是最终的返回结果还是 niuniu。

    案例四

    可以看到个案例三的图不同的是新增了一个查询语句,那么假设这两条语句执行的时间都是一致的,它们返回的结果会相同吗?

    案例三查询到的值为 niuniu:

    其实现在版本链跟案例三也是一致的:

    那么来理一下寻找过程:

    • 首先这里的 read-view 发生了变化,此时的 read-view 为[101],102。

    • 拿着当前的事务 ID101 跟版本链规则进行对比,落盘在 min_id<=trx_id<=max_id,并且在数组中,则数据不可见。

    • 然后进入版本链,找到下一个数据的事务 ID,还是 101,与上一个一致。

    • 接下来是事务 ID100。

    • 事务 ID100 是落在 trx_id<min_id,表示此版本是已经提交的事务生成的,由于事务已经提交,所以数据是可见的。

    • 所以最终返回结果为 niuniu2。

    小结:在同一个事务中进行查询,会沿用第一次查询语句生成的 read-view(前提是隔离级别是在可重复读)。

    通过以上的四个案例,在版本链寻找过程中,可以总结出一个小技巧:

    根据这个小技巧你可以很快的得知此版本是否可见:

    • 如果当前的事务 ID 在绿色部分,是已经提交事务,则数据可见。

    • 如果当前的事务 ID 在蓝色部分,会有两种情况,如果当前事务 ID 在 read-view 数组内,是没有提交的事务不可见,如果不在数组内数据可见。

    • 如果落在红色部分,则不考虑,对于未来的事情不去想即可。

    总结

    阅读本文后,在面试过程中极大可能会遇到的问题就是聊聊你对 MVCC 的认识。

    本文内容从浅到深,从什么是 MVCC 到 MVCC 的底层实现,一步一步地陈述了 MVCC 的实现原理。

    本文简单总结:

    • MVCC 在不加锁的情况下解决了脏读、不可重复读和快照读下的幻读问题,一定不要认为幻读完全是 MVCC 解决的。

    • 对当前读、快照读理解,简单点说加锁就是当前读,不加锁的就是快照读。

    • MVCC 实现的三大要素:两个隐式字段、回滚日志、read-view。

    • 两个隐式字段:DB_TRX_ID:记录创建这条记录最后一次修改该记录的事务ID;DB_ROLL_PTR:回滚指针,指向这条记录的上一个版本。

    • undo log 在更新数据时会产生版本链,是 read-view 获取数据的前提。

    • read-view 当 SQL 执行查询语句时产生的,是由为提交的事务 ID 组成的数组和创建的最大事务 ID 组成的。

    • 版本链规则看第六节的小结即可。

    作者:咔咔

    简介:坚持学习、坚持写博、坚持分享是咔咔从业以来一直所秉持的信念。希望在偌大互联网中咔咔的文章能带给你一丝丝帮助。我是咔咔,下期见。

    编辑:陶家龙

    展开全文
    w397090770 2021-04-07 00:47:32
  • dog250 2018-03-31 11:03:38
  • weixin_39957835 2020-12-03 02:41:52
  • valada 2018-04-12 10:41:44
  • weixin_43692844 2019-08-22 10:29:19
  • jacke121 2018-04-15 12:46:37
  • weixin_43395985 2020-11-11 12:53:34
  • raogeeg 2018-12-04 11:48:55
  • m0_57286472 2021-05-20 12:52:38
  • qq_45714272 2020-04-05 21:30:56
  • weixin_43992935 2020-05-28 16:33:57
  • weixin_33181159 2021-02-28 11:29:31
  • Fly_hps 2018-06-12 08:54:53
  • u010852160 2016-06-24 18:20:59
  • asdzheng 2016-06-28 21:45:39
  • qq36846776 2020-11-26 10:21:23
  • u014352080 2019-10-24 09:48:23
  • weixin_40706420 2021-02-18 19:20:33
  • weixin_41452575 2022-01-05 16:52:38
  • xiaokang123456kao 2017-08-11 17:14:46
  • qq_41011072 2018-02-05 21:06:58
  • chanbishan1955 2019-09-18 07:17:52
  • alex_yangchuansheng 2021-10-23 00:24:40
  • qq_31960623 2021-09-30 09:16:59
  • qq_28165595 2018-07-25 22:51:57
  • minemi 2020-09-25 23:15:03

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 2,254
精华内容 901
关键字:

互联网的原理简单点