在使用版本标记之前,请按照 第 7.6.6.2 节,“安装或卸载版本标记” 中提供的说明进行安装。
版本标记可能在以下情况下很有用:一个系统访问 MySQL 服务器集合,但需要通过监控它们和根据负载变化调整服务器分配来管理它们以实现负载平衡。这样的系统包含以下元素
要管理的 MySQL 服务器集合。
一个与服务器通信并将它们组织到高可用性组中的管理或管理应用程序。组用于不同的目的,每个组内的服务器可能具有不同的分配。组中特定服务器的分配随时可能更改。
访问服务器以检索和更新数据的客户端应用程序,根据分配给它们的目的选择服务器。例如,客户端不应该将更新发送到只读服务器。
版本标记允许根据分配管理服务器访问,而无需客户端重复查询服务器以了解其分配
管理应用程序执行服务器分配并在每个服务器上建立版本标记以反映其分配。该应用程序缓存此信息以提供对其的集中访问点。
如果管理应用程序需要更改服务器分配(例如,将其从允许写入更改为只读),则它会更改服务器的版本标记列表并更新其缓存。
为了提高性能,客户端应用程序从管理应用程序获取缓存信息,从而避免它们需要为每个语句检索有关服务器分配的信息。根据其发出的语句类型(例如,读取与写入),客户端选择适当的服务器并连接到该服务器。
此外,客户端将自己的客户端特定版本标记发送到服务器以注册其对服务器的要求。对于客户端发送到服务器的每个语句,服务器会将自己的标记列表与客户端标记列表进行比较。如果服务器标记列表包含客户端标记列表中存在的所有标记且具有相同的值,则匹配,并且服务器执行该语句。
另一方面,管理应用程序可能已更改服务器分配及其版本标记列表。在这种情况下,新的服务器分配现在可能与客户端要求不兼容。服务器和客户端标记列表之间会发生标记不匹配,并且服务器会返回错误以响应该语句。这表示客户端需要从管理应用程序缓存中刷新其版本标记信息,并选择一个新的服务器进行通信。
客户端逻辑用于检测版本标记错误并选择新的服务器可以以不同的方式实现
客户端可以自行处理所有版本标记注册、不匹配检测和连接切换。
这些操作的逻辑可以在连接器中实现,该连接器管理客户端和 MySQL 服务器之间的连接。此类连接器可能会自行处理不匹配错误检测和语句重新发送,或者它可能会将错误传递给应用程序并让应用程序重新发送语句。
以下示例以更具体的形式说明了前面的讨论。
当版本标记在给定服务器上初始化时,服务器的版本标记列表为空。标记列表维护通过调用函数来执行。需要 VERSION_TOKEN_ADMIN
权限(或已弃用的 SUPER
权限)才能调用任何版本标记函数,因此预计标记列表修改将由具有该权限的管理或管理应用程序执行。
假设管理应用程序与一组服务器通信,这些服务器被客户端查询以访问员工和产品数据库(分别命名为 emp
和 prod
)。所有服务器都允许处理数据检索语句,但其中一些服务器允许进行数据库更新。为了在数据库特定的基础上处理这个问题,管理应用程序在每个服务器上建立了一个版本标记列表。在给定服务器的标记列表中,标记名称表示数据库名称,标记值为 read
或 write
,具体取决于数据库是否必须以只读方式使用,或者它是否可以进行读取和写入。
客户端应用程序通过设置系统变量来注册服务器需要匹配的一系列版本令牌。变量设置是针对特定客户端的,因此不同的客户端可以注册不同的需求。默认情况下,客户端令牌列表为空,这意味着它可以匹配任何服务器令牌列表。当客户端将令牌列表设置为非空值时,匹配可能会成功或失败,具体取决于服务器版本令牌列表。
为了为服务器定义版本令牌列表,管理应用程序会调用 version_tokens_set()
函数。(还有用于修改和显示令牌列表的函数,将在后面介绍。)例如,应用程序可能会向三台服务器组发送以下语句
服务器 1
mysql> SELECT version_tokens_set('emp=read;prod=read');
+------------------------------------------+
| version_tokens_set('emp=read;prod=read') |
+------------------------------------------+
| 2 version tokens set. |
+------------------------------------------+
服务器 2
mysql> SELECT version_tokens_set('emp=write;prod=read');
+-------------------------------------------+
| version_tokens_set('emp=write;prod=read') |
+-------------------------------------------+
| 2 version tokens set. |
+-------------------------------------------+
服务器 3
mysql> SELECT version_tokens_set('emp=read;prod=write');
+-------------------------------------------+
| version_tokens_set('emp=read;prod=write') |
+-------------------------------------------+
| 2 version tokens set. |
+-------------------------------------------+
在每种情况下,令牌列表都以分号分隔的
对的形式指定。生成的令牌列表值会导致以下服务器分配name
=value
任何服务器都接受对任何数据库的读取操作。
只有服务器 2 接受对
emp
数据库的更新操作。只有服务器 3 接受对
prod
数据库的更新操作。
除了为每个服务器分配版本令牌列表之外,管理应用程序还维护一个缓存,该缓存反映了服务器分配情况。
在与服务器通信之前,客户端应用程序会联系管理应用程序并检索有关服务器分配的信息。然后,客户端根据这些分配选择一台服务器。假设一个客户端想要对 emp
数据库执行读取和写入操作。根据前面的分配,只有服务器 2 符合条件。客户端连接到服务器 2,并通过设置其 version_tokens_session
系统变量在服务器 2 上注册其服务器需求
mysql> SET @@SESSION.version_tokens_session = 'emp=write';
对于客户端随后发送到服务器 2 的语句,服务器会将其自己的版本令牌列表与客户端列表进行比较,以检查它们是否匹配。如果匹配,则语句会正常执行
mysql> UPDATE emp.employee SET salary = salary * 1.1 WHERE id = 4981;
Query OK, 1 row affected (0.07 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> SELECT last_name, first_name FROM emp.employee WHERE id = 4981;
+-----------+------------+
| last_name | first_name |
+-----------+------------+
| Smith | Abe |
+-----------+------------+
1 row in set (0.01 sec)
服务器和客户端版本令牌列表之间存在差异的方式有两种
version_tokens_session
值中的令牌名称不存在于服务器令牌列表中。在这种情况下,会发生ER_VTOKEN_PLUGIN_TOKEN_NOT_FOUND
错误。version_tokens_session
值中的令牌值与服务器令牌列表中对应令牌的值不同。在这种情况下,会发生ER_VTOKEN_PLUGIN_TOKEN_MISMATCH
错误。
只要服务器 2 的分配没有改变,客户端就会继续使用它进行读取和写入操作。但是,假设管理应用程序想要更改服务器分配,以便对 emp
数据库的写入必须发送到服务器 1 而不是服务器 2。为此,它使用 version_tokens_edit()
修改两台服务器上的 emp
令牌值(并更新其服务器分配缓存)
服务器 1
mysql> SELECT version_tokens_edit('emp=write');
+----------------------------------+
| version_tokens_edit('emp=write') |
+----------------------------------+
| 1 version tokens updated. |
+----------------------------------+
服务器 2
mysql> SELECT version_tokens_edit('emp=read');
+---------------------------------+
| version_tokens_edit('emp=read') |
+---------------------------------+
| 1 version tokens updated. |
+---------------------------------+
version_tokens_edit()
修改服务器令牌列表中指定的令牌,而其他令牌保持不变。
下次客户端向服务器 2 发送语句时,其自己的令牌列表将不再与服务器令牌列表匹配,并会发生错误
mysql> UPDATE emp.employee SET salary = salary * 1.1 WHERE id = 4982;
ERROR 3136 (42000): Version token mismatch for emp. Correct value read
在这种情况下,客户端应该联系管理应用程序以获取有关服务器分配的更新信息,选择一个新的服务器,并将失败的语句发送到新的服务器。
每个客户端都必须与版本令牌合作,只发送符合其在给定服务器上注册的令牌列表的语句。例如,如果客户端注册了一个 'emp=read'
的令牌列表,那么版本令牌中没有任何内容可以阻止客户端发送对 emp
数据库的更新操作。客户端本身必须避免这样做。
对于从客户端接收到的每个语句,服务器会隐式地使用锁定,如下所示
为客户端令牌列表中(即
version_tokens_session
值中)命名的每个令牌获取共享锁执行服务器和客户端令牌列表之间的比较
根据比较结果执行语句或产生错误
释放锁
服务器使用共享锁,以便多个会话的比较可以不阻塞地发生,同时防止在任何试图在操作服务器令牌列表中相同名称的令牌之前获取排他锁的会话中更改令牌。
前面的示例只使用了版本令牌插件库中包含的一些函数,但还有其他函数。一组函数允许操作和检查服务器的版本令牌列表。另一组函数允许锁定和解锁版本令牌。
这些函数允许创建、更改、删除和检查服务器的版本令牌列表
version_tokens_set()
将完全替换当前列表并分配一个新的列表。参数是一个分号分隔的
对列表。name
=value
version_tokens_edit()
允许对当前列表进行部分修改。它可以添加新的令牌或更改现有令牌的值。参数是一个分号分隔的
对列表。name
=value
version_tokens_delete()
从当前列表中删除令牌。参数是一个分号分隔的令牌名称列表。version_tokens_show()
显示当前令牌列表。它不接受任何参数。
如果这些函数成功,它们会返回一个二进制字符串,指示发生了什么操作。以下示例建立了服务器令牌列表,通过添加新令牌修改它,删除一些令牌,并显示生成的令牌列表
mysql> SELECT version_tokens_set('tok1=a;tok2=b');
+-------------------------------------+
| version_tokens_set('tok1=a;tok2=b') |
+-------------------------------------+
| 2 version tokens set. |
+-------------------------------------+
mysql> SELECT version_tokens_edit('tok3=c');
+-------------------------------+
| version_tokens_edit('tok3=c') |
+-------------------------------+
| 1 version tokens updated. |
+-------------------------------+
mysql> SELECT version_tokens_delete('tok2;tok1');
+------------------------------------+
| version_tokens_delete('tok2;tok1') |
+------------------------------------+
| 2 version tokens deleted. |
+------------------------------------+
mysql> SELECT version_tokens_show();
+-----------------------+
| version_tokens_show() |
+-----------------------+
| tok3=c; |
+-----------------------+
如果令牌列表格式错误,则会发生警告
mysql> SELECT version_tokens_set('tok1=a; =c');
+----------------------------------+
| version_tokens_set('tok1=a; =c') |
+----------------------------------+
| 1 version tokens set. |
+----------------------------------+
1 row in set, 1 warning (0.00 sec)
mysql> SHOW WARNINGS\G
*************************** 1. row ***************************
Level: Warning
Code: 42000
Message: Invalid version token pair encountered. The list provided
is only partially updated.
1 row in set (0.00 sec)
如前所述,版本令牌使用分号分隔的
对列表进行定义。考虑对 name
=value
version_tokens_set()
的以下调用
mysql> SELECT version_tokens_set('tok1=b;;; tok2= a = b ; tok1 = 1\'2 3"4')
+---------------------------------------------------------------+
| version_tokens_set('tok1=b;;; tok2= a = b ; tok1 = 1\'2 3"4') |
+---------------------------------------------------------------+
| 3 version tokens set. |
+---------------------------------------------------------------+
版本令牌会按如下方式解释参数
名称和值周围的空格将被忽略。名称和值内部允许使用空格。(对于
version_tokens_delete()
,它接受一个不带值的名称列表,名称周围的空格将被忽略。)没有引号机制。
令牌的顺序无关紧要,但如果令牌列表包含给定令牌名称的多个实例,则最后一个值优先于之前的值。
根据这些规则,前面的 version_tokens_set()
调用会生成一个包含两个令牌的令牌列表:tok1
的值为 1'2 3"4
,tok2
的值为 a = b
。要验证这一点,可以调用 version_tokens_show()
mysql> SELECT version_tokens_show();
+--------------------------+
| version_tokens_show() |
+--------------------------+
| tok2=a = b;tok1=1'2 3"4; |
+--------------------------+
如果令牌列表包含两个令牌,为什么 version_tokens_set()
会返回 3 version tokens set
值?这是因为原始令牌列表包含 tok1
的两个定义,第二个定义替换了第一个定义。
版本令牌令牌操作函数对令牌名称和值施加了以下约束
令牌名称不能包含
=
或;
字符,并且最大长度为 64 个字符。令牌值不能包含
;
字符。值的长度受max_allowed_packet
系统变量的值限制。版本令牌将令牌名称和值视为二进制字符串,因此比较区分大小写。
版本令牌还包含一组函数,允许锁定和解锁令牌
version_tokens_lock_exclusive()
获取排他版本令牌锁。它接受一个或多个锁名称列表和一个超时值。version_tokens_lock_shared()
获取共享版本令牌锁。它接受一个或多个锁名称列表和一个超时值。version_tokens_unlock()
释放版本令牌锁(排他锁和共享锁)。它不接受任何参数。
每个锁定函数在成功时返回非零值。否则,会发生错误
mysql> SELECT version_tokens_lock_shared('lock1', 'lock2', 0);
+-------------------------------------------------+
| version_tokens_lock_shared('lock1', 'lock2', 0) |
+-------------------------------------------------+
| 1 |
+-------------------------------------------------+
mysql> SELECT version_tokens_lock_shared(NULL, 0);
ERROR 3131 (42000): Incorrect locking service lock name '(null)'.
使用版本令牌锁定函数的锁定是建议性的;应用程序必须同意合作。
可以锁定不存在的令牌名称。这不会创建令牌。
版本令牌锁定函数基于在 Section 7.6.9.1, “The Locking Service” 中描述的锁定服务,因此对于共享锁和排他锁具有相同的语义。(版本令牌使用内置于服务器中的锁定服务例程,而不是锁定服务函数接口,因此不需要安装这些函数才能使用版本令牌。)版本令牌获取的锁使用 version_token_locks
的锁定服务命名空间。可以使用性能模式监控锁定服务锁,因此对于版本令牌锁也是如此。有关详细信息,请参见 Locking Service Monitoring。
对于版本令牌锁定函数,令牌名称参数的使用与指定的一致。周围的空格不会被忽略,并且允许使用 =
和 ;
字符。这是因为版本令牌只是将要锁定的令牌名称原封不动地传递给锁定服务。