问题描述
我有一个集群,其中一个节点是主节点。通过数据库(通过java/spring/jpa访问Postgres)完成同步/决定谁将成为新的master。
这是我目前拥有的
@Repository
public interface ServiceRepository{
@Transactional
@Modifying
@Query("UPDATE ServiceInstance e SET e.master=true"
+ " where e.id=:serviceId AND NOT"
+ " EXISTS (SELECT master from ServiceInstance where master = true)")
int tryToBecomeMaster(@Param("serviceId") String serviceId);
对于集群中的每个节点,DB 中都有一行 ServiceInstance。每个节点都试图更新自己的行,但前提是没有行已标记为“主”。该查询由每个节点定期运行。
该查询是原子还是存在竞争条件?如果它不是原子的,我在这里需要什么事务隔离级别?
我主要担心的是,如果允许并行执行 2 个查询,它们可能会将 SELECT 子句计算为 false(无主)并更新它们的行。
解决方法
为了确保只能有一个 master,在表上创建一个唯一的部分索引:
CREATE UNIQUE INDEX ON serviceinstance ((1)) WHERE master;
一旦您尝试将表中多于一行的 master
设置为 TRUE
,这将给您一个错误,并且 ACID 保证(一致性)的 C 将确保这总是有效的。您必须在函数中捕获并处理异常。
我要指出,这不是实现高可用性集群的好方法:它具有数据库形式的单点故障。
,如果你想确保你的 SELECT
子句不能被并行读取,你可以使用带有聚合函数的 SELECT FOR UPDATE
来检测 master 而不是 WHERE
子句;这将自动锁定表的所有行,直到 UPDATE
完成。
另一种方法是手动执行 LOCK TABLE EXCLUSIVE
命令。
两者都应该在您的情况下正常工作。