背景:
在一个小规模集群当中有十台服务器,我们需要从另外一个集群的消息获取队列到本地的队列(假设有一个外部的ActiveMQ集群),由于另外一个集群当中的消息队列是发布订阅模型,那么如果我们集群中10台服务器都定时任务去访问那么就会有重复的消息放到本地队列,那么这个时候我们只需要其中一台服务器去获取外部集群中的消息放到本地队列就行
解决方案:
1. 通常在我遇到有一个项目中是这么解决的,我们在数据库中插入一条配置记录,里面大致有配置服务ip,端口,那么在服务器来执行定时任务的时候会去根据配置里面的ip端口是否符合,如果不符合就往下执行业务逻辑,获取外部集群的消息,那么问题来了,假设当前的这个服务挂掉了,那么就得等待这个服务重启后再去执行任务,或者人工干预切换服务IP,显得比较不可靠,当然,因为我们之前的项目有一些业务不需要太过于及时,所以这种实现基本满足
2. 通过Curator客户端,利用Zookeeper的特性,来完成选举动作,就是对这个小规模集群中10台服务进行选举,选举出Master,最终执行任务只交给这个Master,也就是其中选出一台服务器执行任务,当这个Master刮掉了,可以立马选举出新的Master继续执行任务,不再需要人工干预,下面直接给出代码:
引入的maven依赖
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.6</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.4.2</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.4.2</version>
</dependency>
package com.test.zookeeper.curator;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.api.BackgroundCallback;
import org.apache.curator.framework.api.CuratorEvent;
import org.apache.curator.framework.recipes.cache.NodeCache;
import org.apache.curator.framework.recipes.cache.NodeCacheListener;
import org.apache.curator.framework.recipes.cache.PathChildrenCache;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheListener;
import org.apache.curator.framework.recipes.leader.LeaderSelector;
import org.apache.curator.framework.recipes.leader.LeaderSelectorListenerAdapter;
import org.apache.curator.framework.recipes.cache.PathChildrenCache.StartMode;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.data.Stat;
public class Curator {
static String connectionString = "192.168.66.138:2181";
static String master_path = "/curator_recipes_master_path";
static CuratorFramework client = CuratorFrameworkFactory
.builder().connectString(connectionString)
.retryPolicy(new ExponentialBackoffRetry(1000, 3))
.sessionTimeoutMs(5000).build();
public static void main(String[] args) throws Exception {
recipes_MasterSelect();
System.in.read();
}
/**
* 选举,一个集群进行Master选举,这里是让一个客户端去进行选举
* 举例,假设集群当中有10台机器,有一个定时任务只需要一台机器去运行
* 那么这个时候我们通常的办法是把一些配置放到库里面,比如指定某台机器某个端口运行
* 这种办法会导致集群所有机器都去运行定时任务判断是否符合配置的ip和端口,办法笨重
* 一旦数据库指定运行的定时任务机器挂掉了,那么定时任务可能要等待后台恢复或者人工干预切换服务
* 但是如果使用curator的选举的办法,利用zk的特性创建节点的特性,进行第一个创建的成为master直到放弃
* Master权利为止,那么其他机器就还有机会参与选举,下面的案例是一个客户端进行参与选举,这里只有一个
* 客户端当然能活的Master权利, 如果多个客户端同时运行就未可知了,在一个客户端获得Master权利之后会调用
* takeLeadership方法,知道结束后才会释放,那么期间其他客户端都在等待Master释放,一旦Master释放
* takeLeadership方法结束,那么其他机器就有机会成为Master
*/
static void recipes_MasterSelect(){
client.start();
@SuppressWarnings("resource")
LeaderSelector selector = new LeaderSelector(client, master_path,
new LeaderSelectorListenerAdapter() {
@Override
public void takeLeadership(CuratorFramework client) throws Exception {
System.out.println("成为Master角色");
Thread.sleep(3000);
System.out.println("完成Master操作,释放Master权利");
}
});
selector.autoRequeue();
selector.start();
}
}
上面代码是一个客户端的简单操作实例,这里假设了一个客户端参与选举Master,如果集群中每台服务器都应该是一个客户端,去进行参与选举,LeaderSelector构造函数需要实现一个监听, 实现takeLeadership方法,这个方法是当成为Master之后就会调用,当执行完会释放Master权利,集群中其他机器都再次有机会成为Master,当一个服务器成为Master之后集群中其他机器都在外面等待这个Master释放
底层实现时每一个参与选举的节点都会在zookeeper的一个节点下注册一个有序的临时节点,尾号是一串数字,最先生成的节点成为Master,其他未抢到锁的会对小于1的节点进行事件监听,然后进行等待,一旦监听到小于1的节点的事件会唤醒所有线程从新争锁。
以上是我总结出来一个实际场景的案例,希望对各位能有帮助!