当前位置: 首页 > news >正文

VLL: a lock manager redesign for main memory database systems阅读

为何要有VLL?VLL旨在解决什么问题?

在数据库系统中,锁是广泛使用的并发控制机制。然而对于内存数据库系统,锁管理器却成为了性能瓶颈所在。

一项研究说明内存数据库中有16%~25%的时间用于与锁管理器的交互

在传统的锁管理器中,最常见的实现方式是采用以数据主键为id,请求该数据的锁请求链表为值的hash表,锁请求链表还存在锁头追踪该项的锁状态,以及支持互斥操作。

对于与磁盘相关的数据库,这些hash表查找、锁获取和链表操作都是可以忽略不计的,可对于内存数据库来说,这些额外的内存访问、cache未命中、cpu周期、以及临界区就可能接近或者超过执行实际事务逻辑的成本

VLL在上述锁管理器上做出了两个主要变化

  • 将锁信息从中心hash表中移出到原始数据中
  • 从锁数据结构中删除哪些事务对特定锁未完成请求的信息,取而代之的是原始数据包含未完成的写、读请求的数量(CX记录未完成的写请求数量、CS记录未完成的读请求数量)

在事务获取锁时,会一次请求所有锁,并按照请求锁的顺序对事务排序,按照这种全局事务顺序来确定哪个事务应该先解除堵塞并执行

可是VLL也并非银弹,在高争用负载下,可能会导致并发减少和CPU利用率差。为解决这个问题,又引入了选择性竞争优化(SCA),在需要的时候高效计算最有用的竞争信息子集

VLL算法介绍

锁结构

VLL锁管理结构由

  • 储存到每个原始数据的的未完成锁写请求数量CX和未完成锁读请求数量CS
  • 中心事务请求全局队列TxnQueue

组成

流程

具体流程如下

  1. 当事务到达分区时,会尝试对该分区所有在其生命周期内访问的记录加锁

    每个锁请求都采取相应记录的CX或CS加1的形式

  2. 如果请求后的CX=1且CS=0,则认为独占锁被请求事务抢占

    如果请求后的CX=0,则认为请求事务获取了共享锁

  3. 当事务获取锁之后就被添加到TxnQueue

    请求锁和向队列添加事务都发生在同一临界区中

  4. 在离开临界区时,若事务在请求时获取到所有锁,则该事务为free,否则为堵塞。那么只有free事务才能够立即执行

问题

考虑以下问题

  1. 单线程VLL在需要其他节点协作的事务请求中,如果一直等待,会导致资源的浪费
  2. 在没有中心锁管理结构情况下,如何释放等待中的堵塞事务执行呢?
  3. 如果在CX=0的情况下才能获取锁,而写请求到达之后都能递增CX,那么在存在多个写请求事务堵塞情况下,CX会永远大于0
  4. 随着TxnQueue大小增长,新事务能立刻获取锁的概率大大降低

为解决这些问题

  1. 引入waiting状态,标识事务正在进行中,但需要获取搭配远程结果才能继续执行。因此在进入waiting状态之后就可以挂起,不会导致额外的资源浪费
  2. 让后台线程检查TxnQueue中每个堵塞事务的请求数据CX、CS值,若CX下降到1,CS为0,则说明该事务获取到互斥锁;CX为0,CS大于0,则说明获取到共享锁
  3. 对于队列头的堵塞事务直接解除堵塞并执行。这是因为事务位于队列头,说明在此之前请求锁的事务都已经完结了
  4. 为TxnQueue设立阈值,当超过后,将会停止处理新事务,从TxnQueue中寻找可以被解除堵塞的事务。而在阈值内则会优先处理新请求事务
代码及流程展示

以下是代码展示

func beginTransaction(t *TxnItem) {// 开始时,事务状态为Freet.TxnStatus = Free// 遍历本地读集合,检查是否存在冲突,若存在冲突,则事务状态为Blockedfor _, readKey := range t.ReadSet {if inLocal(readKey) {data[readKey].cs++if data[readKey].cx > 1 {t.TxnStatus = Blocked}}}// 遍历本地写集合,检查是否存在冲突,若存在冲突,则事务状态为Blockedfor _, writeKey := range t.WriteSet {if inLocal(writeKey) {data[writeKey].cx++if data[writeKey].cs > 0 || data[writeKey].cx > 1 {t.TxnStatus = Blocked}}}// 若事务状态为Free,且事务为分布式事务,则事务状态为Waitingif t.TxnType == TxnTypeDistributed && t.TxnStatus == Free {t.TxnStatus = Waiting}
}func commitTransaction(t *TxnItem) {// 提交事务时,释放读集合的锁for _, readKey := range t.ReadSet {if inLocal(readKey) {data[readKey].cs--}}// 提交事务时,释放写集合的锁for _, writeKey := range t.WriteSet {if inLocal(writeKey) {data[writeKey].cx--}}
}func vll() {for {// 若接收到远程事务消息,则处理该事务的消息,并执行事务、提交事务以及从队列中移除该事务t, ok, message := selectReceivedMessageTx()if ok {handleReadResult(t, message)if isReadyToExecute(t) {execute(t)commitTransaction(t)txnQueue.dequeue(t)}continue}// 若队列头事务堵塞,在分布式事务时,设置状态为等待,并给其他参与节点发送事务请求// 否则,设置状态为空闲,执行事务、提交事务以及从队列中移除该事务if txnQueue.front().TxnStatus == Blocked {t = txnQueue.front()if t.TxnType == TxnTypeDistributed {t.TxnStatus = Waiting// 给其他参与节点发送事务请求} else {// 注意!此处,对于队列头的堵塞事务直接解除堵塞并执行// 这是因为事务位于队列头,说明在此之前请求锁的事务都已经完结了。并且考虑到存在多个事务增加同数据CX值的情况,使得CX总是会大于1,导致死锁t.TxnStatus = Freeexecute(t)commitTransaction(t)txnQueue.dequeue(t)}} else if !txnQueue.isFull() {// 若队列未满,则获取新事务请求// 新事务状态为Free,执行事务、提交事务// 若事务状态为Waiting,给其他参与节点发送事务请求,并入队// 若事务状态为Blocked,入队// 注意!检查未满的缘故在于平衡新旧事务的处理。在阈值内优先处理新事务请求,阈值之外优先处理waiting的事务t = getNewTxnRequest()beginTransaction(t)switch t.TxnStatus {case Free:execute(t)commitTransaction(t)case Waiting:// 给其他参与节点发送事务请求txnQueue.enqueue(t)case Blocked:txnQueue.enqueue(t)}}}
}

考虑下面场景下的演变,表中展示的是各事务请求的数据

txnread setwrite set
Ax, yx
Bx, yx
Cx0
Dyz
E0y
  1. 最开始,所有数据的CX、CS都是0,TxnQueue为空
  2. 事务A发起读取x、y,并写入x请求,此时数据x的CX递增,数据y的CS递增,并且事务A作为free事务加入TxnQueue
  3. 事务B发起x、y读,x写的请求。当由于x.CX>0,所以B无法获取到x的写锁,因此作为堵塞事务加入到TxnQueue
  4. 当A完成执行后,释放锁。此时x.CX–、y.CS–,并将A从TxnQueue移除,B而后标记为free,获取锁开始执行
  5. 事务C发起读取x的请求,因此x.CS++,由于x.CX>0,因此事务C的读请求无法保证,作为堵塞事务加入到TxnQueue
  6. 事务D发起对y读请求,对z写请求。那么y.CS++,z.CX=1,由于y.CX和z.CX此前均为0,事务D所有锁获取成功,并作为free事务加入TxnQueue
  7. 事务E发起对y的写请求,由于y.CS>0,所以E请求失败,作为堵塞事务加入到TxnQueue
  8. 事务B执行完毕后,释放x写锁、y读锁,并从TxnQueue移除,C此时位于TxnQueue队列最前面,因此标记为free,成功获取到x读锁并执行
  9. 最后事务E也能够安全执行,因为不与TxnQueue中在其之前的任何事务冲突。但又由于VLL算法仅允许TxnQueue最前面事务执行,所以还是不能够执行。SCA将解除该限制
array VLL

array VLL采用向量或数组的数据结构来连续储存锁信息,这会使得数据和锁信息是在两次单独请求中进行的,但在单独线程中,由于在核心缓存中,所以也能在性能上有着较好的发挥

一次性获取所有锁的障碍
  • 在运行事务前,事务读写集可能是未知的
  • 由于每个节点都有TxnQueue,并且临界区在本地,那么不同的节点可能处理顺序并不一致,这可能会导致死锁

为解决第一个问题,允许事务在进入临界区前,能给执行所需要的任何读取

为解决节点事务顺序不一致引发的死锁问题,有两种解决方法

  • 允许产生死锁,通过使用死锁检测协议取消死锁事务
  • 协调节点,使各节点事务顺序一致

VLL采用协调节点的方式,可以在增加协调开销的情况下,仍然产生改进的性能

SCA——提升在高争用下的性能

对于高竞争和高百分比的多分区工作负载,VLL引入了SCA,进行模拟标准锁管理器去检测哪些事务应该继承已释放锁的能力

SCA只在真正需要的时候才会激活,比如在CPU处理空闲时。从队列头往尾部进行扫描,分析在事务之前的事务是否存在冲突

func sca(q TxnQueue) *TxnItem {// 初始化冲突检测数组dx := make([]int8, 100<<10)ds := make([]int8, 100<<10)for _, item := range q {// 如果事务被阻塞if item.TxnStatus == Blocked {success := true// 检查读集合,若读集合中的数据已经被写占用,则事务失败for _, read := range item.ReadSet {key := hash(read)if dx[key] == 1 {success = false}// 读集合中的数据标记为已占用ds[key] = 1}// 检查写集合,若写集合中的数据已经被读、写占用,则事务失败for _, write := range item.WriteSet {key := hash(write)if ds[key] == 1 || dx[key] == 1 {success = false}// 写集合中的数据标记为已占用dx[key] = 1}// 若事务成功,则返回该事务if success {return item}}else {// 事务未被阻塞,仅标记读写集合数据被占用for _, read := range item.ReadSet {key := hash(read)ds[key] = 1}for _, write := range item.WriteSet {key := hash(write)dx[key] = 1}}}return nil
}

VLLR——支持范围的VLL

对于频繁读取、写入、删除同一事务中的许多连续行,锁定行范围比锁定多个单独行更有用,可以避免幻读以及在删除一个数据行共享其主键的公共前缀的实体的常见用例中特别有用

VLLR通过锁定位串前缀进行范围锁定。当锁定一个范围主键时,将该范围表示为集合R,然后生成集合P,使得R中任何一个元素都能在P中找到前缀。最简单生成P的方式时选择R中最小和最大位串的最长公共前缀

在得到集合P之后,VLLR采用分层锁的方式进行。即先从数据库获取意图锁,而后从表获取意图锁,接着在页获取意图锁,最后获取行锁

此外,为了更好管理锁状态,VLLR在CX、CS的基础上,引入了P集合的LX、LS计数器,也就是如果当前请求属于集合P的写请求,则LX递增,属于集合P的读请求,则LS递增

func requestLock(item *TxnItem, rg *txnRange, exclusive bool) bool {// 事务加入队列txnQueue.enqueue(item)// 获取锁范围的前缀pfs := rg.toPrefix()// 请求前缀锁success := truefor _, pf := range pfs {success = success && requestPrefixLock(pf, exclusive)}return success
}func requestPrefixLock(key string, exclusive bool) bool {success := truek := hash(key)// 检查当前key数据锁是否不可获取if exclusive {success = success && (cs[k] == 0)}success = success && (cx[k] == 0)// 检查key数据所在前缀锁是否不可获取if exclusive {success = success && (ls[k] == 0)}success = success && (lx[k] == 0)// 检查key所有前缀的数据锁是否不可获取for i := 1; i < len(key); i++ {h := hash(key[:i])success = success && (cx[h] == 0)if exclusive {success = success && (cs[h] == 0)}}// 标记key数据锁if exclusive {cx[k]++} else {cs[k]++}// 标记key数据所有前缀的前缀锁for i := 1; i < len(key); i++ {h := hash(key[:i])if exclusive {lx[h]++} else {ls[h]++}}return success
}func releaseLock(item *TxnItem, rg *txnRange, exclusive bool) {// 释放锁范围的前缀pfs := rg.toPrefix()for _, pf := range pfs {releasePrefixLock(pf, exclusive)}// 事务出队列txnQueue.dequeue(item)
}func releasePrefixLock(key string, exclusive bool) {// 释放key数据锁k := hash(key)if exclusive {cx[k]--} else {cs[k]--}// 释放key数据所有前缀的前缀锁for i := 1; i < len(key); i++ {h := hash(key[:i])if exclusive {lx[h]--} else {ls[h]--}}
}

Ref

  1. https://www.cs.umd.edu/~abadi/papers/vldbj-vll.pdf

相关文章:

VLL: a lock manager redesign for main memory database systems阅读

为何要有VLL&#xff1f;VLL旨在解决什么问题&#xff1f; 在数据库系统中&#xff0c;锁是广泛使用的并发控制机制。然而对于内存数据库系统&#xff0c;锁管理器却成为了性能瓶颈所在。 一项研究说明内存数据库中有16%&#xff5e;25%的时间用于与锁管理器的交互 在传统的锁…...

REST API实战演练之JavaScript使用Rest API

咱们前面讲了一下如何创建REST API 假期别闲着&#xff1a;REST API实战演练之创建Rest API-CSDN博客 又讲了java客户端如何使用REST API 假期别闲着&#xff1a;REST API实战演练之客户端使用Rest API-CSDN博客 接下来咱们看看JavaScript怎么使用REST API。 一、新建一个…...

期货量化交易软件:MQL5 中的范畴论 (第 15 部分)函子与图论

概述 在上一篇文章中&#xff0c;我们目睹了前期文章中涵盖的概念&#xff08;如线性序&#xff09;如何视作范畴&#xff0c;以及为什么它们的“态射”在与其它范畴相关时即构成函子。在本文中&#xff0c;我们赫兹量化软件将阐述来自前期文章中的概括&#xff0c;即通过查看…...

2024妈妈杯数学建模B题思路-甲骨文智能识别中原始拓片单字自动分割与识别研究

# 1 赛题 B 题 甲骨文智能识别中原始拓片单字自动分割与识别研究 甲骨文是我国目前已知的最早成熟的文字系统&#xff0c;它是一种刻在龟甲或 兽骨上的古老文字。甲骨文具有极其重要的研究价值&#xff0c;不仅对中国文明的 起源具有重要意义&#xff0c;也对世界文明的研究有着…...

sql 之 索引

索引是对数据库表中一列或多列的值进行排序的一种结构&#xff0c;使用索引可快速访问数据库表中的特定信息。 1. 什么是索引 官方上面说索引是帮助MySQL高效获取数据的数据结构&#xff0c;通俗点来说&#xff0c;数据库索引就像是是一本书的目录&#xff0c;可以直接根据页码…...

创建基于Node的WebSocket服务

一:安装nodejs与npm apt-get install nodejs npm 但这种方法安装的版本可能偏低,影响后续的 npm install ws wscat。 按照 How to Install Node.js and npm on Ubuntu 18.04 | Linuxize里的步骤安装: 1、curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash …...

Flask快速搭建文件上传服务与接口

说明&#xff1a;仅供学习使用&#xff0c;请勿用于非法用途&#xff0c;若有侵权&#xff0c;请联系博主删除 作者&#xff1a;zhu6201976 一、需求背景 前端通过浏览器&#xff0c;访问后端服务器地址&#xff0c;将目标文件进行上传。 访问地址&#xff1a;http://127.0.0…...

AI算力报告:算力大时代,AI算力产业链全景梳理

今天分享的是AI算力专题系列深度研究报告&#xff1a;《算力大时代&#xff0c;AI算力产业链全景梳理》。 &#xff08;报告出品方&#xff1a;中信建投证券&#xff09; 报告共计&#xff1a;98页 核心观点 生成式 AI取得突破&#xff0c;我们对生成式 A 带来的算力需求做…...

点击上传文件

一、页面样式&#xff1a; &#xff08;1&#xff09;点击前&#xff1a; &#xff08;2&#xff09;点击后&#xff1a; 设计&#xff1a;①自定义elementPlus图标&#xff1b;②使用Tooltip实现鼠标悬浮按钮上出现文字提示&#xff1b;③上传与更换的切换样式&#xff1b;…...

文件上传【2】--靶场通关

1.前端禁用js绕过 上传文件&#xff0c;进行抓包&#xff0c;没有抓到&#xff0c;说明这里的验证是前端js验证跳出的弹窗 禁用js后&#xff0c;php文件上传成功。 2.文件上传.htaccess 上传png木马后连接不上 代码中存在.htaccess&#xff0c;判断此时应该就是需要用到.htac…...

uniapp请求后端接口

新建文件夹utils const request (config) > {// 拼接完整的接口路径config.url http://mm.test.cn config.url;//这里拼接的是访问后端接口的地址&#xff0c;http://mm.test.cn/prod-api/testconsole.log(config.url)//判断是都携带参数if(!config.data){config.data …...

第十三章 OpenGL ES-RGB、HSV、HSL模型介绍

第十三章 OpenGL ES-RGB、HSV、HSL模型详细介绍 第一章 OpenGL ES 基础-屏幕、纹理、顶点坐标 第二章 OpenGL ES 基础-GLSL语法简单总结 第三章 OpenGL ES 基础-GLSL渲染纹理 第四章 OpenGL ES 基础-位移、缩放、旋转原理 第五章 OpenGL ES 基础-透视投影矩阵与正交投影矩阵…...

微软卡内基梅隆大学:无外部干预,GPT4等大语言模型难以自主探索

目录 引言&#xff1a;LLMs在强化学习中的探索能力探究 研究背景&#xff1a;LLMs的在情境中学习能力及其重要性 实验设计&#xff1a;多臂老虎机环境中的LLMs探索行为 实验结果概览&#xff1a;LLMs在探索任务中的普遍失败 成功案例分析&#xff1a;Gpt-4在特定配置下的探…...

探索设计模式的魅力:简单工厂模式

个人主页: danci_ &#x1f525;系列专栏&#xff1a;《设计模式》《MYSQL应用》 &#x1f4aa;&#x1f3fb; 制定明确可量化的目标&#xff0c;坚持默默的做事。 &#x1f680; 转载自热榜文章&#xff1a;探索设计模式的魅力&#xff1a;简单工厂模式 简单工厂模式&#x…...

【数据结构】-----双链表(小白必看!!!)

c语言中的小小白-CSDN博客c语言中的小小白关注算法,c,c语言,贪心算法,链表,mysql,动态规划,后端,线性回归,数据结构,排序算法领域.https://blog.csdn.net/bhbcdxb123?spm1001.2014.3001.5343 给大家分享一句我很喜欢我话&#xff1a; 知不足而奋进&#xff0c;望远山而前行&am…...

【数据结构】考研真题攻克与重点知识点剖析 - 第 8 篇:排序

前言 本文基础知识部分来自于b站&#xff1a;分享笔记的好人儿的思维导图与王道考研课程&#xff0c;感谢大佬的开源精神&#xff0c;习题来自老师划的重点以及考研真题。此前我尝试了完全使用Python或是结合大语言模型对考研真题进行数据清洗与可视化分析&#xff0c;本人技术…...

数字乡村可视化大数据-DIY拖拽式设计

DIY拖拽式大数据自由设计万村乐可视化大数据V1.0 随着万村乐数字乡村系统的广泛使用&#xff0c;我们也接收到了客户的真实反馈&#xff0c;最终在公司的决定下&#xff0c;我们推出了全新的可视化大数据平台V1.0版本&#xff0c;全新的可视化平台是一个通过拖拽配置生成可视化…...

数据集学习

1&#xff0c;CIFAR-10数据集 CIFAR-10数据集由10个类的60000个32x32彩色图像组成&#xff0c;每个类有6000个图像。有50000个训练图像和10000个测试图像。 数据集分为五个训练批次和一个测试批次&#xff0c;每个批次有10000个图像。测试批次包含来自每个类别的恰好1000个随机…...

【解决】npm run dev Syntax Error: TypeError: eslint.CLIEngine is not a constructor

问题&#xff1a; 由于代码语法不符合eslint而照成此错误&#xff0c;可以参照eslint规则修改语法&#xff0c;或者将eslint停掉 以下为停掉eslint的方法。 You may use special comments to disable some warnings. Use // eslint-disable-next-line to ignore the ne…...

Android 如何通过屏幕大小来适配不同大小的图片

可以使用Android中的dp(密度无关像素)单位来设置不同屏幕密度下的图片大小。dp是Android中的一种尺寸单位&#xff0c;它与屏幕密度无关&#xff0c;只与字体大小有关。在开发过程中&#xff0c;可以使用dp来设置布局和控件的大小&#xff0c;以便在不同的屏幕密度下保持一致的…...

JVM垃圾回收机制全解析

Java虚拟机&#xff08;JVM&#xff09;中的垃圾收集器&#xff08;Garbage Collector&#xff0c;简称GC&#xff09;是用于自动管理内存的机制。它负责识别和清除不再被程序使用的对象&#xff0c;从而释放内存空间&#xff0c;避免内存泄漏和内存溢出等问题。垃圾收集器在Ja…...

工程地质软件市场:发展现状、趋势与策略建议

一、引言 在工程建设领域&#xff0c;准确把握地质条件是确保项目顺利推进和安全运营的关键。工程地质软件作为处理、分析、模拟和展示工程地质数据的重要工具&#xff0c;正发挥着日益重要的作用。它凭借强大的数据处理能力、三维建模功能、空间分析工具和可视化展示手段&…...

(二)原型模式

原型的功能是将一个已经存在的对象作为源目标,其余对象都是通过这个源目标创建。发挥复制的作用就是原型模式的核心思想。 一、源型模式的定义 原型模式是指第二次创建对象可以通过复制已经存在的原型对象来实现,忽略对象创建过程中的其它细节。 📌 核心特点: 避免重复初…...

ABAP设计模式之---“简单设计原则(Simple Design)”

“Simple Design”&#xff08;简单设计&#xff09;是软件开发中的一个重要理念&#xff0c;倡导以最简单的方式实现软件功能&#xff0c;以确保代码清晰易懂、易维护&#xff0c;并在项目需求变化时能够快速适应。 其核心目标是避免复杂和过度设计&#xff0c;遵循“让事情保…...

【Android】Android 开发 ADB 常用指令

查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...

Kafka主题运维全指南:从基础配置到故障处理

#作者&#xff1a;张桐瑞 文章目录 主题日常管理1. 修改主题分区。2. 修改主题级别参数。3. 变更副本数。4. 修改主题限速。5.主题分区迁移。6. 常见主题错误处理常见错误1&#xff1a;主题删除失败。常见错误2&#xff1a;__consumer_offsets占用太多的磁盘。 主题日常管理 …...

HybridVLA——让单一LLM同时具备扩散和自回归动作预测能力:训练时既扩散也回归,但推理时则扩散

前言 如上一篇文章《dexcap升级版之DexWild》中的前言部分所说&#xff0c;在叠衣服的过程中&#xff0c;我会带着团队对比各种模型、方法、策略&#xff0c;毕竟针对各个场景始终寻找更优的解决方案&#xff0c;是我个人和我司「七月在线」的职责之一 且个人认为&#xff0c…...

【堆垛策略】设计方法

堆垛策略的设计是积木堆叠系统的核心&#xff0c;直接影响堆叠的稳定性、效率和容错能力。以下是分层次的堆垛策略设计方法&#xff0c;涵盖基础规则、优化算法和容错机制&#xff1a; 1. 基础堆垛规则 (1) 物理稳定性优先 重心原则&#xff1a; 大尺寸/重量积木在下&#xf…...

Kubernetes 节点自动伸缩(Cluster Autoscaler)原理与实践

在 Kubernetes 集群中&#xff0c;如何在保障应用高可用的同时有效地管理资源&#xff0c;一直是运维人员和开发者关注的重点。随着微服务架构的普及&#xff0c;集群内各个服务的负载波动日趋明显&#xff0c;传统的手动扩缩容方式已无法满足实时性和弹性需求。 Cluster Auto…...

STM32标准库-ADC数模转换器

文章目录 一、ADC1.1简介1. 2逐次逼近型ADC1.3ADC框图1.4ADC基本结构1.4.1 信号 “上车点”&#xff1a;输入模块&#xff08;GPIO、温度、V_REFINT&#xff09;1.4.2 信号 “调度站”&#xff1a;多路开关1.4.3 信号 “加工厂”&#xff1a;ADC 转换器&#xff08;规则组 注入…...