MySQL源码解读之数据结构-LF_DYNARRAY

MySQL的代码中实现了一个Lock Free的Hash结构,称作LF_Hash。MySQL的不少模块使用了LF_Hash,比如Metadata Lock就依赖于它。但由于使用的方法不正确,导致了bug#98911和bug#98624。理解LF_Hash的实现细节,可以帮助我们用好LF_Hash。

LF_HASH的基本特点动态扩展

初始化时bucket的数量是1. 每个bucket平均拥有的元素(Element)是1个。因此当元素的总数量超过bucket的数量时,就会自动分裂。每次分裂增加一倍的buckets.Lock Free

lf_hash采用Lock Free的方式实现,为了保证多线程操作的安全。lf_hash实现了一个叫做pin的东西来保证多线程操作的安全性。lf_hash的操作都需要通过pin来保护。因此lf_hash提供了获取pin和释放pin的函数。lf_hash自己维护了一个pin的动态数组。

内存管理

lf_hash元素的内存都是lf_hash分配和管理的。用户的数据需要拷贝到lf_hash创建的元素中。

LF_HASH的基本操作

插入元素

// 获取一个LF_PINS对象LF_PINS *pins = lf_hash_get_pins();// 给元素分配内存,并拷贝用户数据到元素中,并插入到Hash链表中lf_hash_insert(lf_hash_object, pins, user_data);// 释放LF_PINS对象lf_hash_put_pins(pins);

删除元素

// 获取一个LF_PINS对象LF_PINS *pins = lf_hash_get_pins();// 删除指定key的元素lf_hash_delete(lf_hash_object, pins, key, key_length);// 释放LF_PINS对象lf_hash_put_pins(pins);

查询元素

// 获取一个LF_PINS对象LF_PINS *pins = lf_hash_get_pins();// 返回指定key的第一个元素,这个元素对象会被pin住,使用完要unpin。// 被pin住的元素不能被其他线程从hash链表中移除el = lf_hash_search(lf_hash_object, pins, key, key_length);// 使用查找到的元素。...// unpin当前元素lf_hash_search_unpin(pins);// 释放LF_PINS对象lf_hash_put_pins(pins);

LF_HASH的基本结构

lf_hash的基本结构如下图所示:所有的元素维护在一个全局排序链表里

同一个bucket的所有元素排在一起

每个bucket有一个指针,指向这个bucket的所有元素的Head

元素排序

为了能够做到将每个bucket的元素排列到一起,lf_hash根据元素hash的反转值进行排序。并且要求bucket的数量必须是2的倍数。

元素Hash的反转值

和其他Hash Table一样, LF_HASH也是通过hash(key)得出一个32bits的整数值(hashnr),这个值决定了元素属于哪一个bucket.

hashnr = hash(key);// size是bucket的数量bucket_id = hashnr % LF_HASH::size; bucket_id从0开始。

Hash的反转值是指将Hash的所有Bits的顺序颠倒过来。例如

// 为了表示方便,这里假设hashnr是8位的,按8位反转// 实际使用是32位的,按32位反转0 -> 00000000 -> 00000001 -> 00000001 -> 10000002 -> 00000010 -> 0100000

排序特点

LF_HASH的全局排序链表看起来是这样的:

为了书写方便,假设hash值的长度是8bit.

这个链表是按hash值的反向bit位排序的,因此最低位为0的排在一起,为1的排在一起。

最低位相同的元素,又按第二低位排序。第二低位相同的,按第三低位排序。

hash值相同的按hash key排序(这个不是重点,这里可以忽略)。

Bucket的数量必须是2的倍数

当bucket的数量是2的倍数时我们会发现当bucket size是1时,所有元素会分到同一个bucket中。

当bucket size是2时,最低1位相同的元素会分到同一个bucket中。

当bucket size是4时,最低2位相同的元素会分到同一个bucket中。

bucket每扩展1倍,多1bit用来分bucket.

这个规律使得每个bucket的元素在全局链表中排列在一起。

如果将bucket id反转,我们会发现全局链表是按照元素的 bucket id的反转值分bucket的。bucket id的反转值就是当前bucket的里的最小值。当bucket size是1时,所有的元素在bucket 0中。

当bucket size是2时,按照hash值的最低位(反转值的最高位)分bucket,0的分在bucket 0中,1分在bucket 中。排序规律符合要求,bucket 0和1的元素分别排列在一起。

当bucket size是4时,按照最低2位的值分bucket,00的分在bucket 0, 01分在bucket 2中。10排在bucket 1中,11排在bucket 3中。排序规律要求,每个bucket的元素仍然是排列在一起的。

78b8456677ddea6471c532a5abd8887a.png

因此以2的倍数来扩展lf_hash的bucket时全局链表不需要任何变动

原有的buckets不需要变动

只需要将新的buckets指向自己的第一个元素。

94dc17044b9452abe8b1d3d6fa56e123.png

Bucket Parent

你可能已经注意到了,按2的倍数扩展。实际上就是将原bucket能容纳的排序值的范围分成两半。前一半保留在原来的bucket中,后一半放到一个新bucket中。lf_hash中称这个被分裂的bucket为parent。Parent bucket是固定的,根据bucket id可以算出parent. 对于bucket id的反转值来说,是将低位的1清零。

27110d949bfac3ad6e94bfdebb5029bb.png

对bucket id来说,就是将高位的1清零。

uint parent = my_clear_highest_bit(bucket);

Dummy 元素

每个Bucket中都是一个指针,指向全局链表中这个bucket的最小元素,即head。为了避免这个指针随着head的变化而变化:初始化一个bucket时会生成一个dummy元素,把dummy元素插入到全局链表中。

dummy元素的hash指定为bucket id。

bucket id的反转值是bucket中所有元素的最小值。所以dummy元素始终是这个bucket的链表的head。bucket的指针将始终指向这个dummy元素。

区分用户元素和Dummy元素

用户元素的hash值可能会等于bucket id,为了避免将这个元素插到dummy元素的前面(lf_hash中用的是前插)。lf_hash会将用户元素的的hash反转值的最低位变为1。这样就保证了dummy元素的hash反转值最小且唯一。

元素管理

为了Lock Free, lf_hash自己管理元素的内存分配。

元素结构

lf_hash的元素使用一块连续的内存,包含两部分信息:LF_SLIST 链表和hash相关的信息

用户数据。放在LF_SLIST之后,

LF_SLISTlink:        指向链表中的下一个元素

hashnr    hash的反转值

key         指针指向key值

LF_ALLOCATOR

LF_ALLOCATOR负责元素的管理。

LF_ALLOCATOR::top

Hash链表中的元素被删除后,并不会被释放(free)掉。它们会被放到一个链表中(lf_hash中称作栈),top指向链表中的第一个元素(栈顶)。当向Hash链表中插入一个元素时,会从这个链表中取一个元素使用。如果没有可供使用的元素,才会通过my_malloc分配一个新的。

用LF_SLIST::key指向下一个元素

这里要注意的一点是,这个链表是使用LF_SLIST::key连在一起的。为什么不使用LF_SLIST::link呢?那是因为,是因为lf_hash lock free的设计。

问题

除非Destroy整个Hash,LF_ALLOCATOR中未使用的元素是不会释放的。如果这个HASH链表在某个时刻特别大,占用内存特别多。这些内存就会一直被占用,直到整个Hash被释放掉。

PIN的机制

Lock Free意味着多个线程可能同时在使用一个元素。一个元素从全局链表中移除后,不能被立刻放入到LF_ALLOCATOR::top 指向的Free元素链表中。别的线程可能正在使用这个元素。如果此时放到free链表中,又被别的线程重用了,就可能会造成错误。lf_hash用LF_PINS来保护一个正在使用的元素不被删除或者重用。我们可以将PIN想象成一个锁。

LF_PINS::pin

std::atomic pin[LF_PINBOX_PINS];

pin包含4个指针,可以同时引用4个元素,看代码中最多用了3个。这是因为lf_hash链表在操作的过程中最多可以使用到连续的三个元素previous, current, next。这3个元素要同时pin住。

线程在将一个元素放入Free元素链表之前,要检查所有的pin。如果有任何pin引用了这个元素,则要等待这个元素的引用被取消后才能继续操作。

LF_PINS::purgatory

如果并发的线程很多,遍历所有的pin就会消耗较长的时间。因此lf_hash并不是每删除一个元素做一次遍历操作。而是对多个要删除的元素一起做遍历操作。这些要删除的元素会临时的放入LF_PINS::purgatory链表。只有当purgatory的元素数量到达LF_PURGATORY_SIZE(10个)时或这个pin被释放时,才做一次遍历。没有被引用的元素会被放到LF_ALLOCATOR::top指向的Free 元素链表中去。

24ba42724519e3c0f9b9d0776bb1b3ba.png

当将一个元素放入purgatory时,其他的线程可能正在读取这个元素,也可能正在读取这个元素的LF_SLIST::link。因此puragory链表使用LF_SLIST::key将要purge的元素链接到一起的。难道并发的线程不访问这个元素的LF_SLIST::key吗?会访问,为了能够访问到正确的值,lf_hash有下面这个设计。

删除标记

每个元素都有一个DELETED的标记位,在将元素从全局链表中移除之前,首先要将元素标记为DELETED。看代码时,你可能会迷惑。因为LF_SLIST中,并没有一个DELETED标记位。那是因为DELETED标记位共享了link的最低位。

a70675c37021b916d45b974e66317211.png

之所以能够和link共享最低位,是因为link是一个指针指向一个内存地址。内存地址总是4/8字节对齐的,最低位一定是0。

删除的过程找到元素

标记为DELETED

从全局链表中移除

加入purgatory链表,会修改元素的LF_SLIST::key

执行purge过程,如果purgatory链表有10个元素。

查找元素的过程pin当前元素

拷贝元素的hash key指针到临时变量,会读取LF_SLIST::key

检查元素是否是DELETED,如果是则移动到下一个元素。

比较元素的hashnr和key,如果hashnr和key都小于要查找的hashnr和key则,移动到下一个元素。

可以看到,删除的过程中是先标记DELETED,然后修改LF_SLIST::key。而在查找元素时,是先拷贝LF_SLIST::key,然后检查DELETED标记。这就保证了查找中使用的key是正确的key。

LF_PINBOX

Pinbox是pin的管理器,所有的pin放在一个动态数据里。pinarray pin的动态数组

LF_PINBOX::pinstack_top_ver

和LF_ALLOCATOR::top类似,pinstack_top_ver指向free pin的链表(栈)。但它存储的不是指针,而是第一个元素在pinarray中的index. LF_PINS::link用来指向下一个pin在pinarray中的index。

e2b0e8b0ce6b8bec1567c9a1d4c0f140.png

当用户调用lf_hash_put_pins()时,会将pin放入这个链表。当调用lf_hash_get_pins()时,会从pinstack_top_ver取出一个free pin。如果free pin的链表是空的(top是0),则会给pinarray中增加一个元素。

top version

LF_ALLOCATOR::top上的lock free操作是通过Pin来保护。那么LF_PINBOX::pinstack_top_ver上的lock free操作又是做到的呢? 为了做到lock free, LF_PINBOX::pinstack_top_ver上使用了version的方法。

a3c0fe781445055a82656f793bd043ba.png

每次操作free pin链表时,都会将version加1。在做atomic_compare_exchange操作时,pinstack_top_ver作为一个整数,整体进行操作。

由于top只有16位,这就限制了pinarray最多只能有LF_PINBOX_MAX_PINS(65535)个元素。

PIN使用上的问题

从pin的设计可以看出,pin的使用原则是保护lf_hash操作本身的。一个操作完成后,pin就可以释放了。MySQL中有些lf_hash的pin是长期持有的。如MDL_context::m_pins,这个pin是在session第一次调用时获取,session退出时才释放。它会导致:session的数量最多只能有65535个

session的数量很大时,导致pinarray很大。因此元素的purge操作效率很低。

前面说过purgatory中的元素到达LF_PURGATORY_SIZE(10个)时或者释放pin时,才会释放。由于这些pin到session结束时才释放,就会导致元素的释放不及时。分配的元素更多,占用内存更多。

动态数组

lf_hash中的bucket和pin都使用了动态数组。为了实现lock free,在动态扩展时不拷贝内存,它做了特殊的设计。

多级数组

这个数组LF_DYNARRAY_LEVELS(4).

LevelIndex范围

00 到 255

1256 到 256*256-1

2256*256 到 256*256*256-1

3256*256*256 到 256*256*256*256-1

0级

0级包含256个指针,指向index 0到255的元素。这些元素初始化时不分配,用到时才分配。

88e576b183d4099542ca68f33506ecd9.png

1级

1级包含256个指针,每个指针指向一个0级数组。

ff15c40f5f8c5fa665f5e0364c85ac30.png

2级

2级包含256个指针,每个指针指向一个1级数组。

5688825fb2ab1c394795cf2031601dce.png

3级

3级包含256个指针,每个指针指向一个2级数组。

64d7cf5e140f85b100da20f56b470bf7.png

相关资源:mysql错误以及处理方式_mysql语法错误怎么办-MySQL文档类资源...

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/462750.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何运行一个Vue项目

一开始很多刚入手vue.js的人,会扒GitHub上的开源项目,但是发现不知如何运行GitHub上的开源项目,很尴尬。通过查阅网上教程,成功搭建好项目环境,同时对前段工程化有了朦朦胧胧的认知,因此将环境搭建过程分享…

Hibernate初学之CURD

Hibernate初学之CURD 以前学过JDBC的增删改查,感觉挺方便的,用hibernate框架则会更加简便, 关于hibernate的增删改查模块,我感觉用代码解释再合适不过了; 首先是导包问题:应用hibernate需要导十个包&#x…

计算机网络读书笔记(1)

传输控制协议TCP最主要特点: 1、 面向连接的传输层协议,类似于打电话 2、 点对点(一对一),每一条TCP只能有两个端点 3、 提供可靠交付的服务 4、 全双工通信 5、 面向字节流(所谓流:流入到…

网卡驱动收发包过程图解

网卡 网卡工作在物理层和数据链路层,主要由PHY/MAC芯片、Tx/Rx FIFO、DMA等组成,其中网线通过变压器接PHY芯片、PHY芯片通过MII接MAC芯片、MAC芯片接PCI总线 PHY芯片主要负责:CSMA/CD、模数转换、编解码、串并转换 MAC芯片主要负责&#x…

在c++中,如果派生类没有重写基类中对应virtual函数会怎样?

在c中&#xff0c;如果一个派生类没有重写基类中对应的虚函数&#xff0c;那么在派生类的构造函数中依然会创建虚指针&#xff0c;但是该虚指针指向的是基类的虚表。 #include <iostream> #include <string>class Base { public:virtual void foo() {std::cout &l…

哪些类继承了Collection接口

Collection集合的基本结构&#xff1a; 1、Collection接口 Collection是最基本集合接口&#xff0c;它定义了一组允许重复的对象。Collection接口派生了两个子接口Set和List&#xff0c;分别定义了两种不同的存储方式&#xff0c;如下&#xff1a; 2、 Set接口 Set接口继承于Co…

在c++中一个空类占用的字节数

结论&#xff1a; 在c中一个空类占用&#xff1a;1Byte。 有的同学认为一个空类占0Byte&#xff0c;那么定义一个空类时&#xff0c;他存放到哪里呢&#xff1f;为此&#xff0c;在c编译器中&#xff0c;为空类设置了占用1Byte内存。 #include <iostream> // std::c…

使用Mule ESB与Groovy编排RESTful服务【转】很适合我们当前的架构

http://www.infoq.com/cn/articles/restful-services-mule 在过去几年中&#xff0c;REST风格的软件架构获得了越来越多的认可&#xff0c;这主要是因为它减少了系统对动件的需求、同时使系统耦合性更低&#xff0c;弹性更好。 目前越来越多的REST资源出现在企业应用中&#xf…

Starling框架帮助手册中文版(PDF下载)

什么是Statling&#xff1f;Starling 是一个基于Stage3D&#xff08;这是Flash Player11及Adobe AIR 3中新增的为3D加速功能所提供的API&#xff09;所开发的一个能够使用GPU来加速的2D Flash应用程序的ActionScript3框架。Starling主要是为游戏开发而设计的, 但是它的用途不仅…

园区网VLAN应用实例

园区环境&#xff1a;一台核心路由器&#xff0c;一台核心交换机&#xff1b;四台汇聚层交换机&#xff1b;四台接入层交换机&#xff08;可根据实际需求和带宽的大小增加接入层的数量&#xff09;——接入层下接入大量的PC机。技术要求&#xff1a;通过VLAN的划分及VTP的设置实…

GMF 教程 Mindmap 5

2019独角兽企业重金招聘Python工程师标准>>> 这一部分主要实现的是&#xff1a; 使用扩展的Plug-in为我们的Diagram添加一个自定义动作 Creating a Customization Plug-in 尽管可以给生成的代码添加备注generated NOT来避免下次重新生成代码时覆盖我们修改的代码…

Jmeter(四十二)_控制器下遍历一组参数

概述 在接口自动化的过程中&#xff0c;经常遇到需要遍历的参数组。jmeter在中&#xff0c;foreach控制器可以实现遍历参数&#xff0c;但是只能有一个入参。一旦遇到数组&#xff0c;foreach控制器表示我也无能为力。。。 为了解决这个问题&#xff0c;今天教大家一个实现数组…

JS 停留几秒后返回上一页

2019独角兽企业重金招聘Python工程师标准>>> <script type"text/javascript" language"javascript"> var i 5; intervalid setInterval("fun()", 1000); function fun(){if(i0){history.go(-1);clearInterval(intervalid);}i…

spring boot 分布式锁组件 spring-boot-klock-starter

基于redis的分布式锁spring-boot starter组件&#xff0c;使得项目拥有分布式锁能力变得异常简单&#xff0c;支持spring boot&#xff0c;和spirng mvc等spring相关项目 快速开始 spring boot项目接入 1.添加lock starter组件依赖&#xff0c;目前还没上传到公共仓库&#xff…

std::string中的find_first_of()和find_last_of()函数

编程语言: c/linux 在std::string中&#xff0c;有时需要找到一个string中最后一个或者第一个以某个特定的字符开始的位置或者下标&#xff0c;这时就需要使用find_first_of()和find_last_of()函数。 find_first_of() &#xff1a; 找到一个string中第一个以 某个 字符开始的…

【学习笔记4】Action名称的搜索顺序

2019独角兽企业重金招聘Python工程师标准>>> 1&#xff0e;获得请求路径的URI&#xff0c;例如url是&#xff1a;http://server/struts2/path1/path2/path3/test.action 2&#xff0e;首先寻找namespace为/path1/path2/path3的package&#xff0c;如果不存在这个pac…

std::map中的lower_bound与upper_bound

最近在工作中遇到了std::map中的lower_bound与upper_bound&#xff0c;再次记录下其功能和使用方式。 std::map<char, int> mp; mp.lower_bound<key> &#xff1a; 返回的是第一个大于、等于key的iterator&#xff0c;如果没有则返回空。 mp.upper_bound<key…

IoC容器Autofac(3) - 理解Autofac原理,我实现的部分Autofac功能(附源码)

上篇文章中&#xff0c;举了一个Autofac的简单例子&#xff0c;园友eflay反应: “虽然对autofac架构原理还是完全不懂&#xff0c;不过基本是明白ioc的用途了&#xff0c;会从已注册的类中找出符合接口的类来调用构造函数进行构造” 所以这篇文章&#xff0c;还是针对上次的例子…

[置顶] 我的iOS作品

我的iOS作品 罗朝辉 ( http://blog.csdn.net/kesalin)CC 许可&#xff0c;转载请注明出处前言 做了好几年的 iOS 开发了&#xff0c;业余也零零散散地写了不少代码和博文教程。可惜一直都没有整理下&#xff0c;上次过年回家在张江广兰路把笔记本给丢了&#xff0c;损失惨重&am…

SSM框架搭建

SSM&#xff08;SpringSpringMvcMybatis&#xff09;项目环境搭建&#xff1a; 1、项目环境&#xff1a; jdk-1.8 tomcat-9.0 mysql-5.1.44 spring 5.1.6 mybatis 3.5.1 maven 3.5.42、项目目录结构&#xff1a; 3、pom.xml中引入的依赖&#xf…