zookeeper 主备切换方案实现分布式锁

zookeeper 主备切换方案实现分布式锁

   点关注不迷路,欢迎再访!	

精简博客内容,尽量已行业术语来分享。
努力做到对每一位认可自己的读者负责。
帮助别人的同时更是丰富自己的良机。

一.原理介绍

Zookeeper 是一个开源的分布式协调服务框架,利用其强一致性,能够很好地保证在分布式高并发情况下节点的创建一定能够保证全局唯一性,即 ZooKeeper 将会保证客户端无法创建一个已经存在的 ZNode。也就是说,如果同时有多个客户端请求创建同一个临时节点,那么最终一定只有一个客户端请求能够创建成功。利用这个特性,就能很容易地在分布式环境中进行 Master 选举了。

成功创建该节点的客户端所在的机器就成为了 Master。同时,其他没有成功创建该节点的客户端,都会在该节点上注册一个节点变更的 Watcher,用于监控当前 Master 机器是否存活,一旦发现当前的 Master 挂了,那么其他客户端将会重新进行 Master 选举。

在这里插入图片描述

二.引入依赖
<dependency>
	<groupId>org.apache.zookeeper</groupId>
	<artifactId>zookeeper</artifactId>
	<version>3.4.9</version>
</dependency>

<dependency>
	<groupId>org.apache.curator</groupId>
	<artifactId>curator-recipes</artifactId>
	<version>4.2.0</version>
</dependency>
三.实现代码
3.1封装zookeeper参数
public class ZKConfig implements Serializable{
	
	private static final long serialVersionUID = -5837486392158655001L;
	private  String connectString;
    private  int sessionTimeout;
    private  String znodeName;
    private  int connectionTimeout;

	//Setter/Getter
}
3.2定义Zookeeper客户端基本配置
@Configuration
public class LeaderSelectorConfig {
	private final static Logger logger = LoggerFactory.getLogger(LeaderSelectorConfig.class);
	
	@Value("${zk.connectString}")
    String connectString;
	
	@Value("${zk.sessionTimeout}")
    int sessionTimeout;
	
	@Value("${zk.connectionTimeout}")
    int connectionTimeout;
	
	@Value("${disconf.env}")
    String env;
	
	@Value("${disconf.appName}")
    String appName;
    
	@Bean
    public ZKConfig zkConfig() throws IOException {
		ZKConfig zkConfig = new ZKConfig();
		zkConfig.setConnectionTimeout(connectionTimeout);
		zkConfig.setSessionTimeout(sessionTimeout);
		zkConfig.setConnectString(connectString);
		logger.info("当前应用名:"+appName);
		logger.info("当前环境:"+env);
        //ZNode路径为"应用名称"+"环境",保证不同环境下独立选主
		zkConfig.setZnodeName("/ZKLeaderSelector/zk/master/"+appName+"/"+env);
		return zkConfig;
    }
    
}
3.3 启动 LeaderSelector 选主监听

Zookeeper的节点监听机制,可以保障占有锁的方式有序而且高效。每个线程抢占锁之前,先抢号创建自己的ZNode。同样,释放锁的时候,就需要删除抢号的Znode。抢号成功后,如果不是排号最小的节点,就处于等待通知的状态。等谁的通知呢?不需要其他人,只需要等前一个Znode 的通知就可以了。当前一个Znode 删除的时候,就是轮到了自己占有锁的时候,击鼓传花似的依次向后。

说Zookeeper的节点监听机制,是非常完美的?

Zookeeper这种首尾相接,后面监听前面的方式,可以避免羊群效应。所谓羊群效应就是每个节点挂掉,所有节点都去监听,然后做出反映,这样会给服务器带来巨大压力,所以有了临时顺序节点,当一个节点挂掉,只有它后面的那一个节点才做出反映。

@Component("leaderSelector")
public class LeaderSelector {
	
	private final static Logger logger = LoggerFactory.getLogger(LeaderSelector.class);

	@Autowired
	private ZKConfig zkConfig;
	
    private CuratorFramework client;

	private LeaderLatch latch;
	
    //全局变量标识-当前节点是否为master
    private boolean isMaster = false;
    
    @PostConstruct
    public void startSelector() {
        this.start();
    }
    
  
    public void start() {
        client = CuratorFrameworkFactory.builder()
                .connectString(zkConfig.getConnectString())
                .connectionTimeoutMs(zkConfig.getConnectionTimeout())
                .sessionTimeoutMs(zkConfig.getSessionTimeout())
                .retryPolicy(new ExponentialBackoffRetry(1000, 3))
                .connectionStateErrorPolicy(new SessionConnectionStateErrorPolicy())
                .build();

        latch = new LeaderLatch(client, zkConfig.getZnodeName());

        try {
            client.start();
            //添加监听程序,通过心跳检测判断选主
            //LeaderLatchListener 是LeaderLatch客户端节点成为Leader后的回调方法,有isLeader(),notLeader()两个方法
            latch.addListener(new LeaderLatchListener() {
                public void isLeader() {
                	isMaster = true;
                	logger.info("[ZKLeaderSelector]Current node is  a master.");
                }

                public void notLeader() {
                	isMaster = false;
                	logger.info("[ZKLeaderSelector]Current node is a slaver.");
                }
            });
            latch.start();
        } catch (Exception e) {
            logger.error("启动ZK选主监听程序失败!"+e.getMessage());
        }
    }

    public void stop() {
        CloseableUtils.closeQuietly(latch);
        CloseableUtils.closeQuietly(client);
    }

	public boolean getIsMaster() {
		return isMaster;
	}

	public void setIsMaster(boolean isMaster) {
		this.isMaster = isMaster;
	}
  
}
3.4编写自动任务测试
@Component
@Configuration
@EnableScheduling
public class ZKConsumerTask {
	
	private final Log logger = LogFactory.getLog(getClass());

	@Autowired
	private LeaderSelector leaderSelector;
	
	@Scheduled(cron = "${time.task}")
	public void consumerFileTask() {
			
		if(leaderSelector.getIsMaster()) {
			logger.info("当前节点为master! Date:" + LocalDate.now());
			//执行业务逻辑
		}else {
			logger.info("当前节点为salve! Date:" + LocalDate.now());
			//退出当前任务
		}
	}
}
3.5 application.propertiesZK 客户端参数配置
time.task = 0 0/01 * * * ?
zk.connectString = X.X.X.X:2181
zk.sessionTimeout = 5000
zk.connectionTimeout = 3000
disconf.env = sit
disconf.appName = andy
四.测试APP1/APP2两个进程
4.1 启动APP1/APP2

启动APP1
在这里插入图片描述
启动APP2
在这里插入图片描述
观察结果:发现 APP1 注册成为 master 节点,可以启动zookeeper可视化工具来观察节点变化
在这里插入图片描述

4.2 暂停APP1

观察发现 APP2 立刻成为 master 节点,接管批处理任务继续运行,丢失会话,临时节点也会随着销毁。
在这里插入图片描述

4.3 模拟网络抖动、断网。

人工干预APP2失去 ZK 连接,下线
在这里插入图片描述
恢复网络连接后,APP2 自动连接 ZK 注册成为 master。
在这里插入图片描述

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值