背景
He3DB for PostgreSQL是受Aurora论文启发,基于开源数据库PostgreSQL 改造的数据库产品。架构上实现计算存储分离,并进一步支持数据的冷热分层,大幅提升产品的性价比。
He3DB for PostgreSQL中存在多个会话试图同时访问同一数据的情况,并发控制的目标就是保证所有会话高效地访问,同时维护数据完整性,并发访问控制的常用方式为两种:锁机制和多版本并发控制(MVCC)。因为 MVCC 并不能解决所有的并发控制情况,所以还需要使用传统的锁机制来保证那些通常不需要完整事务隔离并且想要显式管理特定冲突点的应用。
整体概述
按照功能划分,锁管理分为锁功能模块,锁级别管理模块,死锁处理模块。
锁功能模块:针对三种类型的锁功能,自旋锁,轻量级锁,事务锁。
锁级别管理模块:针对四种不同级别的锁管理器,表级别、页级别、元组级别、事务级别。
死锁处理模块:包括死锁检测功能和死锁处理功能。
数据结构
SpinLock 是最底层的锁,使用互斥信号量实现,与操作系统和硬件环境联系紧密。Spinlock分为与机器相关的实现方法(支持TAS)和与机器不相关的实现方法(Semaphore仿真)。
SpinLock的特点是:封锁时间很短,没有等待队列和死锁检测机制,事务结束时不能自动释放SpinLock。
支持TAS(test-and-set)指令集的SpinLock
- 32-bit i386
typedef unsigned char slock_t;
__asm__ __volatile__(
" cmpb $0,%1 \n"
" jne 1f \n"
" lock \n"
" xchgb %0,%1 \n"
"1: \n"
: "+q"(_res), "+m"(*lock)
: /* no inputs */
: "memory", "cc");
- 64-bit x86_64
typedef unsigned char slock_t;
__asm__ __volatile__(
" lock \n"
" xchgb %0,%1 \n"
: "+q"(_res), "+m"(*lock)
: /* no inputs */
: "memory", "cc");
- ARM and ARM64
typedef int slock_t;
__sync_lock_test_and_set(lock, 1)
信号量仿真实现的SpinLock
typedef int slock_t;
SpinLock设计
设计原理
SpinLock 是最底层的锁,使用互斥信号量实现,与操作系统和硬件环境联系紧密。Spinlock分为与机器相关的实现方法(定义在slock,c中)和与机器不相关的实现方法(定义在Spin.c中)。SpinLock的特点是:封锁时间很短,没有等待队列和死锁检测机制,事务结束时不能自动释放SpinLock。
作为一种最底层的锁,一般不直接使用SpinLock,而是利用它来实现其他的功能。毫无疑问,依赖于硬件的SpinLock 机制肯定比不依赖于硬件的SpinLock 机制速度快。因为不依赖于硬件的 SpinLock机制需要使用信号量来仿真SpinLock。如果机器拥有TAS(test-and-set)指令集,那么He3DB会使用s_lock.h和s_lock.c中定义的 SpinLock 实现机制。如果机器没有 TAS指令集,那么不依赖于硬件的 SpinLock 的实现定义在Spin.c中,需要用到He3DB定义的信号量Semaphore。
主要流程
锁通常作为并发访问控制的使用手段,在使用的流程上有三个重要的部分,首先需要对SpinLock进行初始化,初始化分为针对使用信号量的情况和使用TAS的情况。通常情况下自旋锁是独占的,但允许在自旋锁的section中嵌套原子的情况,所以针对信号量自旋锁的初始化会存在启用嵌套和不启用嵌套的两种情况,具体的流程可以参考下图2所示。完成初始化后,会先申请锁SpinLockAcquire,拿锁以后,开始对想要独占的内容进行修改操作等,完成以后去释放锁SpinLockRelease。具体的流程可以参考下图3所示。
主要接口
对外接口函数 | 接口说明 |
---|---|
SpinLockInit(lock) | 自旋锁初始化 |
SpinLockAcquire(lock) | 自旋锁申请锁 |
SpinLockRelease(lock) | 自旋锁释放锁 |
源码流程图
作者介绍
徐元慧,移动云数据库高级系统架构师,负责云原生数据库He3DB的架构设计与研发。