NDB 集群内部结构  /  NDB 模式对象版本

第 6 章 NDB 模式对象版本

NDB 支持在线模式更改。模式对象,例如 索引,都有一个 4 字节的 模式对象版本标识符,这可以在 ndb_desc 工具的输出中观察到(参见 ndb_desc - 描述 NDB 表),如下所示(强调文本)

$> ndb_desc -c 127.0.0.1 -d test t1
-- t1 --
Version: 33554434
Fragment type: HashMapPartition
K Value: 6
Min load factor: 78
Max load factor: 80
Temporary table: no
Number of attributes: 3
Number of primary keys: 1
Length of frm data: 269
Row Checksum: 1
Row GCI: 1
SingleUserMode: 0
ForceVarPart: 1
FragmentCount: 4
ExtraRowGciBits: 0
ExtraRowAuthorBits: 0
TableStatus: Retrieved
HashMap: DEFAULT-HASHMAP-240-4
-- Attributes --
c1 Int PRIMARY KEY DISTRIBUTION KEY AT=FIXED ST=MEMORY AUTO_INCR
c2 Int NULL AT=FIXED ST=MEMORY
c4 Varchar(50;latin1_swedish_ci) NOT NULL AT=SHORT_VAR ST=MEMORY
-- Indexes --
PRIMARY KEY(c1) - UniqueHashIndex
PRIMARY(c1) - OrderedIndex

NDBT_ProgramExit: 0 - OK

模式对象版本标识符(或简称为 模式版本)由主版本和次版本组成;主版本占据模式版本的最低有效字节(单个字节),次版本占据剩余的 3 个最高有效字节。以十六进制表示法查看模式版本时,您可以更轻松地看到这两个组件。在刚刚显示的示例输出中,模式版本显示为 33554434,十六进制(必要时填充前导零)为 0x02000002;这相当于主版本 2,次版本 2。向表 t1 添加索引会导致 ndb_desc 报告的模式版本前进到 50331650,或十六进制的 0x03000002,这相当于主版本 2(3 个最低有效字节 00 00 02),次版本 3(最高有效字节 03)。对于新创建的表,次要架构版本从 0 开始。

此外,每个 NDB API 数据库对象类都有自己的 getObjectVersion() 方法,该方法与 Object::getObjectVersion() 一样,返回对象的模式对象版本。这包括实例,不仅是 Object,还有 TableIndexColumnLogfileGroupTablespaceDatafileUndofile,以及 Event。(但是,NdbBlob::getVersion() 的用途和功能与刚刚列出的方法完全无关。)

被认为是向后兼容的架构更改(例如在表末尾添加 DEFAULTNULL 列)会导致表对象的次要版本增加。不被认为是向后兼容的架构更改(例如从表中删除列)会导致主版本增加。

注意

虽然导致架构主版本更改的操作的实现实际上可能涉及受影响表的 2 个副本(删除和重新创建表),但最终结果可以观察为表的主版本增加。

从 NDB 客户端到达的查询和 DML 操作也有一个关联的架构版本,该版本在数据节点中处理开始时进行检查。如果请求的架构版本与受影响数据库对象的最新架构版本仅在其次要版本组件中不同,则该操作被认为是兼容的,并且允许继续进行。如果架构版本在主要架构版本中不同,则将被拒绝。

此机制允许以各种方式更改数据节点中的架构,而无需在客户端中进行同步架构更改。客户端无需迁移到新的架构版本,直到它们准备好这样做。因此,查询和 DML 操作可以不间断地继续。

NDB API 和架构对象版本。 NDB API 应用程序通常使用与 Ndb 对象关联的 NdbDictionary 对象来检索架构对象。架构对象是根据需要从数据节点检索的;信号用于获取表或索引定义;然后,构建一个本地内存对象,应用程序可以使用该对象。NDB 在内部缓存架构对象,以便对名称相同的表或索引的每个后续请求都不需要信号。

全局架构缓存。 为了避免每次架构对象查找都需要向数据节点发送信号,每个 Ndb_cluster_connection 都使用一个架构缓存。这被称为 全局架构缓存。它在跨越多个 Ndb 对象方面是全局的。实例化的表和索引对象会自动放入此缓存中,以节省未来的信号和实例化成本。缓存维护每个对象的引用计数;此计数用于确定何时可以删除给定的架构对象。可以通过显式 API 方法调用或本地架构缓存操作来修改架构对象的引用计数。

本地架构缓存。 除了每个连接的全局架构缓存之外,每个 Ndb 对象的 NdbDictionary 对象都有一个 本地架构缓存。此缓存包含指向全局架构缓存中保存的对象的指针。每个本地架构缓存都持有对全局架构缓存中架构对象的引用,并将全局架构缓存引用计数增加 1。拥有一个特定于每个 Ndb 对象的架构缓存,可以在不施加任何锁的情况下查找架构对象。本地架构缓存通常在其关联的 Ndb 对象被删除时清空(在此过程中减少全局缓存引用计数)。

在没有架构更改的情况下运行。 在以下列出的情况下,正常操作如下进行

  1. 某个客户端(Ndb 对象)首次请求表。 检查本地缓存;尝试结果为未命中。然后还检查全局缓存(使用锁),结果是另一个未命中。

    由于没有缓存命中,因此向数据节点发送信号;节点的响应用于实例化表对象。指向实例化数据对象的指针被添加到全局缓存中;另一个这样的指针被添加到本地缓存中,并且引用计数设置为 1。指向表的指针被返回给客户端。

  2. 第二个客户端(不同的 Ndb 对象)请求访问同一个表,也是通过名称。 本地缓存的检查导致未命中,但全局缓存的检查产生命中。

    结果,对象指针被添加到本地缓存中,全局引用计数递增(因此其值现在为 2),并且对象指针被返回给客户端。没有新的指针被添加到全局缓存中。

  3. 第二次,第二个客户端通过名称请求访问同一个表。 检查本地缓存,产生命中。对象指针立即返回给客户端。没有指针被添加到本地或全局缓存中,并且对象的引用计数没有递增(因此引用计数保持在 2 不变)。

  4. 第二个客户端删除 Ndb 对象。 此客户端的本地架构缓存中的对象的引用计数在全局缓存中递减。

    这会将全局缓存引用计数设置为 1。由于它尚未为 0,因此尚未采取任何操作来删除父 Ndb 对象。

架构更改。 假设对象的架构永远不会更改,则首次检索到的架构版本将在应用程序进程的整个生命周期内使用,并且仅当所有本地缓存引用(即对 Ndb 对象的所有引用)都被删除时才会删除内存中的对象。除了在关闭或集群连接重置期间,这不太可能发生。

如果对象的架构在应用程序运行时以向后兼容的方式更改,则会产生以下影响

  • 数据节点上的次要版本递增。(使用旧架构版本进行的正在进行的 DML 操作仍然会成功。)

  • 随后检索架构对象最新版本的 NDB API 客户端将获取新的架构版本。

  • 具有缓存的旧版本的 NDB API 客户端不会使用新的架构版本,除非并且直到它们的本地和全局缓存失效。

  • 订阅事件的 NDB API 客户端可以观察到相关表的 TE_ALTER 事件,并且可以使用它来触发架构对象缓存失效。

  • 可以通过调用 removeCachedTable()removeCachedIndex() 来删除每个本地缓存条目。这会从本地缓存中删除该条目,并减少全局缓存中的引用计数。当(以及如果)全局缓存引用计数达到零时,就可以删除旧的缓存对象。

  • 或者,可以通过调用 invalidateTable()invalidateIndex() 来删除本地缓存条目,并使全局缓存条目失效。随后对此和其他客户端调用 getTable()getIndex() 将通过向数据节点发送信号并实例化新对象来返回新的架构对象版本。

  • 新的 Ndb 对象按需从全局表缓存中填充其本地表缓存,就像往常一样。这意味着,一旦旧的架构对象在全局缓存中失效,此类对象就会检索在表对象首次缓存时已知的最新表对象。

当进行不兼容的模式更改时(即模式主要版本更改),一旦提交新版本,使用旧版本的 NDB API 请求就会失败。这也可以用作检索新模式对象版本的触发器。

以下列表总结了有关处理模式版本更改的规则:

  • 在线模式更改(次要版本更改)不会影响现有客户端(Ndb 对象);客户端可以继续使用旧的模式对象版本。

  • 当且仅当客户端通过进行 API 调用自愿删除缓存的对象时,它才能观察到新的模式对象版本。

  • 随着 Ndb 对象删除缓存的对象并被删除,旧模式对象版本上的引用计数会减少。

  • 当此引用计数达到 0 时,就可以删除该对象。

模式对象生命周期的含义。  模式对象(例如 TableIndex)的生命周期受限于从中获取它的 Ndb 对象的生命周期。当删除模式对象的父 Ndb 对象时,保持 Ndb 对象活动的引用计数会减少。如果此 Ndb 对象持有对给定模式对象版本的最后一个剩余引用,则删除 Ndb 对象也可能导致删除模式对象。因此,此时不能有其他线程使用该对象。

在应用程序中保存指向模式对象的指针并在多个 Ndb 对象之间使用它们时必须小心。不应在创建模式对象的 Ndb 对象的生命周期之外使用它。

应用程序可以异步且彼此独立地响应向后兼容的模式更改,仅在必要时才迁移到新模式。不同的线程可以同时对不同的模式对象版本进行操作。

因此,确保模式对象的生存期不超过用于创建它们的 Ndb 对象的生存期非常重要。为了防止这种情况发生,您可以采取以下任何操作来使旧模式对象无效:

  • 要在需要时触发失效,请使用 NDB API TE_ALTER 事件(请参阅 Event::TableEvent)。

  • 使用外部触发器来启动失效。

  • 定期显式执行失效。

以任何这些方式使缓存无效,应用程序就可以根据需要获取新版本的模式对象。

还值得注意的是,并非所有 NDB API Table getter 方法都返回指针;除了 Table::getName() 之外,还有很多方法返回表名。这些方法包括 Index::getTable()NdbOperation::getTableName()Event::getTableName()NdbDictionary::getRecordTableName()