可插拔存储锁
从 ActiveMQ Classic 5.7.0 版本开始,持久化适配器使用的存储锁定机制的选择已变得可插拔。此功能仅对在共享存储主/从拓扑中配置的代理有意义。在 5.7.0 版本之前,存储锁定机制(以及主选举)由持久化适配器的选择决定。例如,使用 KahaDB 持久化适配器时,存储锁定机制基于共享文件锁。类似地,JDBC 持久化适配器使用数据库支持的存储锁。
现在,存储锁的选择与持久化适配器的选择分离,可以混合匹配这两种选择。存储锁的可插拔性由 Locker 接口实现,所有可插拔锁都必须实现此接口。此接口使实现满足本地需求的自定义存储锁变得容易。
但是,每个持久化适配器都有自己的默认锁,其工作方式与以前相同。
锁
每个锁都必须实现 Locker 接口。锁接口具有以下属性
属性名称 | 默认值 | 描述 |
---|---|---|
lockAcquireSleepInterval |
10000 |
获取锁尝试之间轮询间隔(以毫秒为单位)。 |
failIfLocked |
false |
如果锁不可立即使用,代理是否应启动失败。当 true 时,从代理将无法启动。 |
持久化适配器
每个持久化适配器(或希望使用锁的任何其他代理服务)都必须实现 Lockable 接口。此接口具有以下属性
属性名称 | 默认值 | 描述 |
---|---|---|
useLock |
true |
持久化适配器是否应使用配置的锁。主要用于开发期间,暂时禁用锁的使用,而无需删除其配置。 |
lockKeepAlivePeriod |
0 |
保持锁处于活动状态的持续时间(以毫秒为单位),当大于 0 时。 |
现有锁
共享文件锁
共享文件锁是 KahaDB 持久化适配器的默认锁。它锁定一个文件以确保只有持有锁的代理(主代理)被授予访问消息存储的权限。
示例
<persistenceAdapter>
<kahaDB directory="target/activemq-data" lockKeepAlivePeriod="10000">
<locker>
<shared-file-locker lockAcquireSleepInterval="5000"/>
</locker>
</kahaDB>
</persistenceAdapter>
该 lockKeepAlivePeriod
属性不适用于早于 5.9.0 版本的 ActiveMQ Classic。
lockKeepAlivePeriod = 0 的后果
对于此锁,
lockKeepAlivePeriod
应大于0
。此时间段是主代理进行锁保持活动调用频率。当
lockKeepAlivePeriod = 0
时,从代理仍然无法获得文件锁。但是,如果某个第三方修改了锁文件(修改或删除),主代理将检测不到更改。因此,从代理的下次尝试(根据其配置的lockAcquireSleepInterval
)获取文件锁将成功。发生这种情况时,集群中将存在两个主代理。这种情况会导致消息存储损坏!当
lockKeepAlivePeriod
大于0
时,主代理将在每lockKeepAlivePeriod
毫秒进行一次锁保持活动调用。因此,主代理将在进行下次保持活动调用时检测到任何锁文件更改。检测到此更改后,主代理将降级为从代理。
请注意,从 ActiveMQ Classic 5.9.0 版本开始,KahaDB 持久化适配器也可以使用租赁数据库锁(见下文)。
数据库锁
数据库锁是 JDBC 持久化适配器的默认锁。它在一个事务中锁定一个数据库表,以确保只使用单个资源。
示例
<persistenceAdapter>
<jdbcPersistenceAdapter dataDirectory="${activemq.data}" dataSource="#mysql-ds" lockKeepAlivePeriod="10000">
<locker>
<database-locker lockAcquireSleepInterval="5000"/>
</locker>
</jdbcPersistenceAdapter>
</persistenceAdapter>
数据库锁使用其 keepAlive
方法确保代理仍然持有锁。可以使用 lockKeepAlivePeriod
属性设置保持活动时间段。默认时间段为 30000 毫秒。如果代理未能获取数据库上的锁,它将在每 lockAcquireSleepInterval
毫秒重试一次。
此锁针对数据库表(activemq_lock
)打开一个 JDBC 事务,该事务持续到代理保持活动状态。这会锁定整个表,并阻止其他代理访问存储。在大多数情况下,这将是一个相当长时间运行的 JDBC 事务,随着时间的推移占用数据库上的资源。
当主代理崩溃或失去与数据库的连接时,此锁会出现问题,导致锁保留在数据库中,直到数据库通过 TCP 超时响应半关闭的套接字连接。数据库锁过期要求可能会阻止从代理在一段时间内启动。此外,如果数据库支持故障转移,并且在副本故障转移事件中连接断开,则该 JDBC 事务将回滚。代理将其视为失败。主代理和从代理将再次争夺锁。
租赁数据库锁
租赁数据库锁是为了解决数据库锁的不足而创建的。租赁数据库锁不会打开长时间运行的 JDBC 事务。相反,它允许主代理获取一个有效的锁定,该锁定在固定(通常是短的)持续时间后过期。为了保留锁,主代理必须定期在锁过期之前延长其租约。同时,从代理会定期检查租约是否过期。如果由于任何原因,主代理未能更新其锁租约,从代理将获得锁的所有权,并在此过程中成为新的主代理。租赁锁可以经受住 DB 副本故障转移。
示例
<persistenceAdapter>
<jdbcPersistenceAdapter dataDirectory="${activemq.data}" dataSource="#mysql-ds" lockKeepAlivePeriod="5000">
<locker>
<lease-database-locker lockAcquireSleepInterval="10000"/>
</locker>
</jdbcPersistenceAdapter>
</persistenceAdapter>
为了使此机制正常工作,主/从集群中的每个代理都必须具有 brokerName
属性的唯一值,如 <broker/>
标签中定义的。或者,在 <lease-database-locker/>
标签上使用 leaseHolderId
属性的唯一值,因为此值用于创建租赁锁定义。
基于租赁的锁是通过在启动时阻塞来获取的。然后,它保留的时间段由 lockKeepAlivePeriod
属性(以毫秒为单位)指定。为了保留锁,主代理会定期将自己的租约延长 lockAcquireSleepInterval
毫秒。因此,理论上,就租约而言,主代理始终领先于从代理 (lockAcquireSleepInterval - lockKeepAlivePeriod
)。必须确保 lockAcquireSleepInterval > lockKeepAlivePeriod
,以确保租赁始终是当前的。从 ActiveMQ Classic 5.9.0 版本开始,如果未满足此条件,将记录一条警告消息。
在最简单的情况下,主代理和从代理之间的时钟必须同步,才能使此解决方案正常工作。如果时钟无法同步,锁可以从数据库当前时间使用系统时间,并根据它们与 DB 系统时间的本地偏差调整超时。如果 maxAllowableDiffFromDBTime
大于零,则本地时间段将根据超过 maxAllowableDiffFromDBTime
的任何增量进行调整。
重要的是要知道,您的 JDBC 驱动程序用于转换
TIME
值的默认规则是否符合 JDBC 标准。例如,如果您使用的是 MySQL,则驱动程序的 JDBC URL 应包含useJDBCCompliantTimezoneShift=true
,以确保TIME
值转换符合 JDBC 标准。如果不符合,锁在将检索到的租约过期时间与当前系统时间进行比较时可能会报告很大的时间差。有关更多详细信息,请咨询您的 JDBC 驱动程序的文档。
从 ActiveMQ Classic 5.9.0 版本开始,租赁数据库锁可以与 KahaDB 持久化适配器一起使用。但是,这种特定组合要求租赁数据库锁元素包含 <statements/>
子元素。在下面的示例中,还配置了 lockTableName
,尽管这样做不是强制性的。
<persistenceAdapter>
<kahaDB directory="target/activemq-data" lockKeepAlivePeriod="5000">
<locker>
<!\-\- When used with the KahaDB persistence adapter the 'dataSource' attribute must be defined on the locker itself: -->
<lease-database-locker lockAcquireSleepInterval="10000" dataSource="#mysql-ds">
<statements>
<!\-\- Default locker attributes and SQL statements may be overridden here
using one or more <statements attribute\_or\_statement="value"/> entries: -->
<statements lockTableName="activemq_lock"/>
</statements>
</lease-database-locker>
</locker>
</kahaDB>
</persistenceAdapter>
要查看可以覆盖的属性和 SQL 语句的完整列表,请参阅 Statements 类。
当 KahaDB 持久化适配器配置为使用 lease-database-locker
时,您必须将代理配置为使用您自己的 IO 异常处理程序,因为 DefaultIOExceptionHandler
或 JDBCIOExceptionHandler
均无法与此组合正常工作。有关如何编写处理程序的详细信息,请参阅 可配置 IO 异常处理程序。
但是,从 ActiveMQ Classic 5.11 版本开始,
JDBCIOExceptionHandler
已被弃用。它已被org.apache.activemq.util.LeaseLockerIOExceptionHandler
取代,该处理程序将与支持可插拔存储锁的任何持久化适配器一起工作,无论是否配置了锁。