DLM分布式锁管理
RAC首先是一个数据库,必须要解决并发问题。通过锁,在每个进程访问修改数据之前,必须对数据枷锁。锁机制,保护自己访问的数据不被别的进程破坏,自己也不去破坏别的进程的数据
其次RAC是运行在多台计算机上的数据库。一个进程是否可以修改一条数据,取决于是否有其他进程(本地和RAC环境中其他机器中的进程)是否访问该数据 ,为了解决 在多台计算机的环境下感知并发的存在,Oracle引入了 分布式锁管理(Distributed Lock Management,DLM) 。 可以把DLM 想象成一个“仲裁”,它记录着哪个节点操作哪个数据,并负责协调解决节点间的竞争。
DLM的作用举例:
1、一个2节点的RAC;
2、节点1想要修改数据1
3、 节点1向DLM请求,DLM发现数据1还没有被任何节点使用,DLM就授权给节点1;并且DLM登记节点1对数据1的使用
4、节点2也想修改数据1
5、节点2向DLM请求,DLM发现数据1被节点1使用,DLM就会请求节点1“先给节点2用吧”,节点1接到请求后释放其对数据1的占用,节点2能够操作数据1
6、DLM记录这个过程
需要强调的是DLM负责的是节点的协调,而节点内的协调不是DLM负责。继续上面的例子
1、现在节点2的进程1修改数据1;
2、节点2的进程2也想修改数据1
3、节点2仍然请求DLM,DLM发现节点2现在已经具有权限,无须授权
4、进程2对DLM的请求被通过,但是进程2是否能够修改数据1,还需要进一步检查
5、通过传统的锁模式,比如“行级锁”,进程2发现数据1正被进程2修改,所以进程2只能等待。
DLM协调资源是以数据块为单位进行协调的。也就是说,向DLM踢出的申请是“数据块1的操作权限”,而不是“记录1的操作权限”, 这也是从性能方面权衡的结果 。
RAC比单实例多的就是分布式锁管理器。DLM的作用就是协调实例之间对资源的竞争访问。实例内部的竞争RAC和单实例是没有任何区别的。
DLM中资源和锁
DLM协调集群各个节点对资源使用的功能叫做同步。所有资源的访问都需要同步。集群间的同步功能是一把双刃剑。保护数据一致性是它的有利的一面,反过来数 据的同步会影响集群的同步。如果集群同步活动非常密集,那么对集群性能的影响是非常巨大的。因此要实现集群环境的理想性能,关键是如何在节点间分布资源和 协调任务,把节点间的同步活动降低到最低但有正好满足需求。
同步活动的数量取决于资源的数量和资源上活动的密集程度,并发活动少,需要进行的同步就少,如果并发活动密集,需要的同步活动也就多。 在DLM中,根据同步活动的数量取决于资源的数量和资源上活动的密集程度把 资源分成两类:
Cache Fusion资源(也叫 PCM资源,9i术语) , 指的是数据块资源:普通数据块,索引数据块,段头块(Segenment Header),Undo块
Non-Cache Fusion资源( Non-PCM资源) 非数据块资源都:数据文件,控制文件,数据字典视图,Library Cache,Row Cache等
Non-Cache Fusion 资源
典型的Non-Cache Fusion资源是 Shared Pool中的 Row Cache 和 Library Cache中的内容。
以Library Cache为例, Library Cache 中存放的是所有SQL语句、执行计划、包等对象,以及这些对象所引用的对象。当一个语句或包编译时,这个语句引用的所有对象都会被加一个Library Cache Lock;执行时,所有被引用的对象都会加上一个Library Cache Pin,以保证在语句执行过程中,应用对象的结构不会改变。编译完成以后,加在引用对象上的Library Cache Lock会由原来的Share或Exclusive模式变成Null模式。Null也是一种锁,相当于一个触发器,当这些对象的定义被修改以后,引用这个 对象的所有的对象都变成无效,必须进行重新编译。例如select * from a,编译以后,这个执行计划会在a对象上加一个NULL模式的Library Cache Lock,以后对a修改结构以后,这个“触发器”就会导致“select * from a”这个语句执行计划失效。再次执行这个语句的时候,需要重新编译新的执行计划。
RAC环境中,每个节点都可能有引用对象a的SQL语句,无论在任何节点上对a进行结构修改,都需要把所有节点上的引用a的SQL语句置为失效。
除了传统的Library Cache Lock之外,每个节点的LOCK0进程会对本实例Library Cache中的对象加一个Shared-mode的IV(Invalidation)Instance Lock。
用户想修改对象定义,必须获得一个Exclusive模式的IV锁,这会通知本地的LCK0释放Shared-mode锁,本地LCK0 在释放这个锁之前,会通知其他的节点上的LCK0,其他节点的LCK0收到这个消息后,就会将本地Library Cache中所有相关对象置为失效,这种机制是一种广播机制,通过实例的LMD进程完成。
Raw Cache中存放的是数据字典,目的是减少编译过程中对磁盘的访问。内容也需要在所有实例之间同步,同步机制同Library Cache,也是LCK0实现。
Non-Cache Fusion锁实质就是:某个节点更 改了以后,广播通知所有节点。当然, 资源上 加了 额外的锁(IV Instance Lock),只有这些锁才会触发广播。
Non-Cache Fusion的特点:
资源数量有限
Raw Cache中存放的是对象定义、Library Cache中存放的是SQL代码、执行计划等。表、视图、存储过程、包是有限的,SQL语句虽多,但有限。
资源被修改的频率很低
因为这些特点,Oracle采用了广播这种机制,每个节点的变化都通知给所有节点。
Cache Fusion资源
和Shared Pool中的资源比起来,buffer cache的数据块数量更多,修改更密集。如果每次数据块修改都在集群内发送广播,效率太低。因此对于这种资源,Oracle采用的是Cache Fusion机制,使用的锁是PCM Lock。 PCM Lock有三种模式:Shared、Exclusive、Null
Oracle用另外一个术语来描述加在数据块上的锁类型-数据块状态(Buffer States)
PCM锁模式 |
数据块状态 |
说明 |
X | XCUR | 如果数据块的状态是XCUR,这个状态描述了两方面的内容。首先在这个数据块上的锁是X模式的,而且这个数据块的版本是最新版本,也就是CURRENT,因此eXclusive+CURrent构成了上面的缩写。 |
S | SCUR | 这个数据块的状态是SCUR,也是描述了两方面的内容,首先这个数据块的锁是S模式的锁,而且这个数据块的版本是最新版本,即Current版本,SCUR实际上是Shared+CURrent的缩写。 |
NULL | CR | 实例没有对数据块加PCM锁。 |
还是以RAC两个节点 为例
1、开始两个实例都读取某 个数据块内容,因此两个实例对这个数据块的锁是S锁,数据块的版本是CURRENT,所以数据块状态是SCUR。
2、实例1要修改这个数据块内容,实例1必须获得这个数据块的X模式的锁:因为X模式的锁和S模式的锁不兼容,因此实例2必须释放其S模式的锁。
3、最终实例1拥有X模式的PCM Lock,而数据块状态是XCUR,而实例2的锁模式是Null,数据块状态是CR。
关于PCM Lock所的mode属性,下文另有说明。
GCS/GES
Global Cache Service
Global Cache Service (GCS)是Oracle Cache Fusion的主要组件。该服务由LMSn进程执行,每个实例最多可以有10个LSM进程。GCS的功能是跟踪数据块位置和状态(数据块的mode和 role,下文会详述),负责协调不同实例间对数据块的访问。
Global Enqueue Service
全局队列服务( Global Enqueue Service,GES):主要负责维护字典缓存和库缓存内的一致性。字典缓存是实例的SGA内所存储的对数据字典信息的缓存,用于高速访问。由于该字典 信息存储在内存中,因而在某个节点上对字典进行的修改(如DDL)必须立即被传播至所有节点上的字典缓存。GES负责处理上述情况,并消除实例间出现的差 异。出于同样的原因,为了分析影响这些对象的SQL语句,数据库内对象上的库缓存锁会被去掉。这些锁必须在实例间进行维护,而全局队列服务必须确保请求访 问相同对象的多个实例间不会出现死锁。LMON、LCK和LMD进程联合工作来实现全局队列服务的功能。GES是除了数据块本身的维护和管理(由GCS完 成)之外,在RAC环境中调节节点间其他资源的重要服务。
GCS和GES的工作原理
被GCS 和GES 管理的块和锁叫做资源。对这些资源的访问必须在群集的多个实例中进行协调。这个协调在实例层面和数据库层面都有发生。实例层次的资源协调叫做本地资源协调;数据库层次的协调叫做全局资源协调。
支 持GCS 和GES的后台进程使用私网心跳来做实例之间的通讯。这个网络也被Oracle的群集组件使用,也有可能被群集文件系统(比如OCFS)所使用。GCS 和GES 独立于 Oracle 群集组件而运行。但是,GCS 和GES依靠这些群集组件获得群集中每个实例的状态。如果这些信息不能从某个实例获得,这个实例将被关闭。这个关闭操作的目的是保护数据库的完整性,因为 每个实例需要知道其他实例的情况,这样可以更好的确定对数据库的协调访问。
Global Resource Dictionary(GRD)
根据上文,Cache Fusion要对数据块进行管理,首要解决的问题是, 数据块拷贝在集群节点间的状态分布图,而这是通过GRD来实现的。
GRD 是一个内存结构,在所有的实例中分配。GRD里面记录着每个数据块在集群间的分布图,位于每个实例的SGA中。每个实例都是部分GRD,所有实例的GRD 汇总在一起才是一个完整的GRD。RAC会给每一个资源(数据块)选择一个节点作为它的Master Node,而其他节点作为Shadow Node。 可以通过以下sql查询数据块的master node
select b.dbablk, r.kjblmaster master_node
from x$lel, x$kjblr, x$bhb
where b.obj = <DataObjectId>
and b.le_addr= l.le_addr
and l.le_kjbl = r.kjbllockp
GRD的目的是提供优化的表现。每个实例负责对SGA中部分的GRD信息进行管理, 因此,访问GRD的开销在RAC的所有实例中共享。GRD中的信息对于所有的实例都是可以访问的,如果这个信息是在本地,可以通过直接访问;如果不在本 地,可以通过和远程节点的后台进程通信来访问。GRD 也被设计来提供容错。如果发生一个节点失败事件,GRD将被剩下来的实例所重构。在恢复之后,只要还有一个活动的实例。这个共享的数据库还是可以被访问 的。GCS和GES 被设计来在多个并行节点的失败情况下恢复。一个节点加入或离开群集 都会导致GRD被重建。GRD的动态执行将 方便RAC的任何实例都可以在任何时候以任何顺序启停。节点关系的任何改动都将导致群集信息的重构。每个资源初始时候都是通过hash 算法来指派给某一个实例的。这个实例叫做资源属主(resource master)。某个特定资源的 master 实例可能在每次群集信息重构(cluster reconfiguration)的时候改变,这种改动方式叫做静态资源管理。在oracle 10.1以及以上,资源能够通过使用模式来被重新指定属主,目的是降低网络访问和随之的CPU资源损耗。这种叫做动态资源管理。在ORACLE 10.1上,GCS 将定时的评估资源的管理情况。如果它发先某个实例和某个数据文件上的数据块资源之间有密切的关系,那么这个文件上的所有块的master 属主将被动态分配给这个实例。在ORACLE 10.2以及以上,动态资源属主管理(dynamic resource mastering)是在段级别来实施的,GCS 发现了某个实例和一个segment上的数据有密切的联系后,将启动重新指派属主(initiate remastering)的动作。每个实例保存了GRD 的一部分,它包含了全局资源的某个子集的当前状态。这个信息,在实例进行失败恢复的时候或群集信息重配的时候都被使用,包含有数据块的识别符号,数据块的 当前版本的位置,该数据块被任何实例持有的模式,模式可以是null(N),shared(S),或exclusive(X).而每个持有数据块的角色可 以是本地的或者全局的。
Master Node的GRD中记录了该资源在所有节点上的使用信息,而shadow node的GRD只需要记录资源在该节点上的使用信息,这些信息就是PCM Lock信息。这些信息由如下属性组成
Data Block Addresses (DBA). 被修改的数据块位置
Location: 最新版本的数据块位置,只有当多个节点共享数据块时才存在。
Mode:数据块的模式,(N)Null, (S)Shared, (X)Exclusive。
Role:数据块的角色,有local和global两个值
SCN:数据块的SCN
数据块的镜像,包括当前镜像和过去的镜像
MODE
模式名 | 含义 |
Exclusive | 持有这种锁的实例对数据块进行读、写操作。而其他的实例不能再获得X、S模式的锁,不能进行任何操作,集群中最多只能有一个实例拥有这个模式的锁。这个只是数据块级别的写权限,获得这个锁以后,是否可以修改其中的记录,还要看记录上的行级锁,这一点和单实例的机制一样。 |
Shared | 拥有这个模式的实例可以对数据块进行只读操作,而其他的实例可以获得S模式的锁,进行读操作。但是其他实例不能获得X模式的锁,可以同时有多个实例拥有这个模式的锁。 |
Null | 这个模式代表对应的内存空间可以被重用。 |
Role
Oracle修改数据块时,要先把数据块读到内存,在内存中修改,但是提交以后并不立即写回 到磁盘。而是采用延迟写的技术,在被写回磁盘以前,内存数据块和磁盘数据块内容不一致,这时内存中的这个数据块是“脏数据块”。“脏”是用来描述数据块的 内存版本和磁盘版本是否一致,和事务没有关系。事务提交时,只会把日志写到联机重做日志文件,但是数据块不会立即写回到磁盘,这是内存中的就是“脏数据块”。
每个数据块可以被多个节点修改,role这个属性就是用来描述这种“脏数据块”在集群间的分布状况的,其有local,global两个值。
Local Role: 数据块只被单实例访问,则无论是否是脏数据块,role值为local。多实例拥有干净的数据块, role值同样为local。当role值为local的数据块持有S锁时,代表这个内存数据块是和磁盘上的内容完全一致的 。持有 X锁代表这个内存数据块做过修改,如果修改没有写回磁盘,这是一个脏数据块。如果需要把修改写回磁盘,不再需要联系GRD,本实例就可以完成。如果拥有 X+Local的实例要给其他实例发送这个数据块,如果发送的是和磁盘一致的版本,也就是说接收方收到的也是和磁盘一致的版本,那么本实例就仍然保持 Local Role。如果发送的是和磁盘不一致的版本,那么角色就要转变成Global,同时接收方的角色也是Global,代表同时有多个实例拥有“脏数据块”版 本
Global Role:有多个实例拥有和磁盘不一致的版本,这时如果想要把这个数据块写回到磁盘,必须联系GRD,由拥有数据块Current版本的实例完成写动作。
Past Image
举例说明什么是Past Image :假设一个2节点的RAC集群,某个数据块在磁盘上的SCN=100
1、实例1要修改这个数据块,从磁盘读入SGA进行修改,修改后内存中的SCN=110
2、实例2也要修改这个数据块,实例1就会通过Cache Fusion把这个数据块传送给实例2,发送的版本是SCN=110,即Current Copy的数据块。这时实例1还会保留这个SCN=110的数据块在SGA中,但是不能在进行任何修改操作,这时实例1拥有的这个拷贝就是Past Image,其SCN=110.在实例1发送这个数据块以前,会先把log buffer内容写回到redo log中。
3、实例2修改这个数据块,修改后SCN=120,磁盘上的版本仍然是100.
4、假设实例1现在因为日志切换,触发了检查点动作,因为实例1上这个数据块是个脏数据块(不是最新的脏),所以需要同步到磁盘
5、实例1会查找GRD,发现实例2拥有的是这个数据块的Current版本,GRD会通知实例2把这个数据块写入磁盘。
6、实例2完成写以后,会通知其他实例(所有拥有PI版本的实例)释放它们拥有的PI内存,实例1就会在Log Buffer中记录一条(Block Write Record)记录,然后释放PI内存。
7、假设实例2没有完成写操作就异常down机了(假设已经提交事务,但是数据块没有写回到磁盘中),这时就会触发实例1进行Crash Recovery(不同于单实例的Instance Recovery)。因为实例1拥有最近的PI,所以只需要利用实例1的PI及实例2的联机重做日志就可以完成恢复。 PI代表着这个实例的SGA是否拥有和磁盘内容不一致的版本,以及版本顺序。PI主要能够加速Crash Recovery的恢复过程。最新的PI+日志记录,可以大大的缩短数据块的恢复速度。
实例讲解Cache Fusion
针对一个4个节点的RAC,某 个数据块的master node是实例2,PCM Lock使用Mode、Role、Pasting格式描述。SL0代表Shared Mode、Local Role、0个Past Image。
场景1: 并发读操作
1、实例1想要读取这个数据块,计算该数据块的Master node是实例2,于是向实例2发出请求,请求数据块以及PCM锁(SL)
2、实例2查找自己的GRD,发现数据块没有被任何实例使用,同意实例1的请求。
3、实例1从本地读取数据块到Buffer Cache中,SCN是1000,获得数据块的 PCM Lock(share-mode、local mode)。
4、更新实例2的GRD
5、实例3要读取这个数据块,向实例2发出请求
6、实例2向实例1发出通知,让实例1发送数据
7、实例1向实例3发送数据块,强调实例3必须以shared mode方式访问数据(如果实例1释放了该数据块,也会通知实例3,实例3会自己从磁盘读取数据块)
8、实例3通知实例2,更新GRD
场景2: 读并发写(继续上一个场景)
1、实例3要修改数据块,向实例2请求Exclusive mode PCM锁请求
2、实例2检查GRD,发现实例1以Shared-mode持有数据块,于是向实例1发出请求,请求其Mode转换为Null Mode,意味着实例1可以释放这块数据所占有的内存。
3、实例1释放自己的PCM锁
4、实例3获得Exclusive-mode PCM锁,通知实例2,更新GRD
5、实例2更新GRD,删除了实例1持有这个数据块的条目
6、实例3修改数据块,数据块的SCN变成2000
为了减少网络交互次数,Oracle对信息的发送做了捆绑处理,比如实例1收到实例2的请求后,并不给实例2返回响应,而是在实例3给实例2的响应中,隐含了实例1对实例2的响应。 场景3: 写并发写(继续上一个场景)
1、实例4想修改数据,向实例2发出Exclusive-mode PCM锁请求。
2、实例2查看GRD,确定实例3持有的是“当前版本”(Current Version),于是向实例3转发请求
3、实例3发送数据块到实例4,把自己的锁降低到null-mode
4、实例4收到数据块,获得exclusive-mode锁,通知实例2更新GRD
5、实例4对数据块进行了修改,SCN变成2100
注意:实例3必须完成下面的工作才能发送数据块
1、 把所有的log写入到redo log file中
2、把buffer标识为null mode,PI=1,表示持有的这个数据块的PI版本,必须等到Current Version写到磁盘后才能清空
3、发送的数据块是current state,也就是带着修改内容发送过去的Cache Fusion是跨事务边界的,也就是不必等待实例3上的事务完成,就可以发送这个PI数据块。
场景4: 写入磁盘
1、假设实例3因为log switch触发了检查点,要把所有dirtey buffer内容写入磁盘
2、实例3通知实例2写入磁盘
3、实例2查看GRD,确定实例4持有的是Current Version,向实例4发出写请求
4、实例4将数据块写入到磁盘,同时在日志中记录一条BWR记录记录,并把锁降级为XL0
5、实例4通知实例2写完成,更新GRD
6、实例2通知所有拥有PI的实例,可以清空PI空间,所有拥有PI的实例都会在logfile中记录一条BWR记录
7、实例3会清空内存空间,在logfile中记录BWR记录,同时释放锁
在集群环境中,只有当需要清空的数据块是current或者PI的时候,才需要发出写请求,也就是实例曾经改变过数据块,才要发出写请求。
上面的例子中,实例1要清空这个数据块,可以直接清空,因为没有改变过数据块,但是实例3和实例4需要清空数据块的话,就需要发出写请求。
真正的执行写入操作的是拥有Current版本的实例完成的,在集群中,只有一个实例拥有Current Version版本,所有持有PI版本的实例,必须等到持有Current版本的实例完成写操作以后,才能清空这块内存。
场景5: 写并发读
1、实例1想要读取数据块,向实例2请求
2、实例2查找GRD,实例4持有Current Version,向实例4转发请求
3、实例4发送数据块,模式将为SG1
4、实例1获得数据块,获得share模式的锁
(注:Oracle 10g的算法中,实例4在第一次发送数据块时并不会立即降级为share mode,仍然继续持有exclusive模式,同时实例4会计算发给实例1的次数,如果次数超过了默认参数_fairness_threashold, 就把自己降级为share mode)
AST
下面我们来详细的研究一下分布式锁管理器是如何工作的?数据块在实例之间是如何传递的。
DLM使用两个队列跟踪所有的Lock请求,并用两个ASTs(Asynchronous Traps)来完成请求的发送和相应,实际就是异步中断(interrupt)或者陷阱(trap)。下图显示了队列和资源的关系
Granted Queue中记录的是所有已经获得Lock的进程,而Convert Queue记录的是所有等等Lock的进程。
1、进程1和进程2拥有数据块的S锁模式,因此在Granted Queue中有记录。假设现在进程2要获得X模式的锁,进程2必须先向DLM提出请求
2、请求提交给DLM后,DLM就要把进程2放入到Converte Queue中,向拥有不兼容模式锁的进程1发送一个Blocking ASTs(BAST),这是一个异步请求,所以DLM不必等待响应。
3、进程1接收到这个BAST以后,就会把lock降级为Null模式,DLM把进程2的锁模式转化为X锁模式
4、然后DLM发送一个Acquisition ASTs(AAST)给进程2,并把进程2放到Granted Queue中
RAC并发控制总结
1、针对资源性质不同,分为Cache Fusion和Non-Cache Fusion。针对不同的资源,使用不同的锁机制。之所以使用不同的方式,主要是考虑性能问题,并不是资源并发需求不同。
2、在Cache Fusion中,每一个数据块都被映射为一个Cache Fusion资源,或者说是一个PCM资源,PCM资源实际上是一个数据结构,资源的名称就是数据块的地址(DBA)。每个数据请求动作是分步完成的,首 先把数据块地址X转化为PCM资源名称,然后把这个资源请求提交给DLM,DLM进行Global Lock的申请和释放活动。只有进程获得了PCM Lock,才能继续下一步,也就是说第一步“实例要获得数据块的使用权”。
3、除了获得数据块的使用权,还要考虑数据块状态,在单实例中,进程要想修改数据块,必须在数据块的当前版本(Current Copy)上进行修改。在RAC环境中也是一样,如果实例要修改数据块,必须获得这个数据块的当前版本拷贝,这就涉及一系列问题:如何获得数据块拷贝在集群节点间的分布图,如何知道哪个节点拥有当前拷贝,如何完成传递过程,使用的技术就是内存融合技术(Cache Fusion),一旦获得了访问权限,并且得到了正确的版本,进程就可以访问资源。进程间仍然使用Lock和latch,和单实例一样。
参考至:《大话Oracle RAC》张晓明著
http://www.jiagulun.com/home.php?mod=space&uid=338&
http://blog.csdn.net/csucxcc/article/details/5842634do=blog&id=514
http://blog.csdn.net/tianlesoftware/article/details/6534239
http://blog.csdn.net/csucxcc/article/details/5805957
http://avdeo.com/2008/07/21/oracle-rac-10g-cache-fusion/
本文原创,转载请注明出处、作者
如有错误,欢迎指正
邮箱:czmcj@163.com
作者:czmmiao 文章出处:http://czmmiao.iteye.com/blog/1741126