分布式协调服务zookeeper的应用场景(分布式笔记)

数据发布订阅/配置中心

  实现配置信息的集中式管理和数据的动态更新,实现配置中心有两种模式:push(推送)、pull(主动拉取,长轮询)。
  zookeeper采用的是推拉相结合的方式,客户端向服务器端注册自己需要关注的节点。一旦节点数据发生变化,那么服务器端就会向客户端发送watcher事件通知。客户端收到通知后,主动到服务器端获取更新后的数据。其特点如下:

  1. 数据量比较小
  2. 数据内容在运行时会发生动态变更
  3. 集群中的各个机器共享配置

Zookeeper配置管理

  程序总是需要配置的,如果程序分散部署在多台服务器上,要逐个改变配置就变得很麻烦。而如果把这些配置全部放到zookeeper上去,保存在zookeeper的某个目录节点中,然后所有相关应用程序对这个目录节点进行监听,一旦配置信息发生变化,每个应用程序就会收到zookeeper的通知,然后从zookeeper获取新的配置信息应用到系统中。
在这里插入图片描述

实现分布式锁的几种常用方式

1. 数据库实现思路

  在数据库中创建一个表,表中包含方法名等字段,并在方法名字段上创建唯一索引,想要执行某个方法,就使用这个方法名向表中插入数据,成功插入则获取锁,执行完成后删除对应的行数据释放锁。

2. Redis实现思路

主要用到的命令如下:
(1)SETNX key val:当且仅当key不存在时,set一个key为val的字符串,返回1;若key存在,则什么都不做,返回0。
(2)expire key timeout:为key设置一个超时时间,单位为second,超过这个时间锁会自动释放,避免死锁。
(3)delete key:删除key
实现思想:
(1)获取锁的时候,使用setnx加锁,并使用expire命令为锁添加一个超时时间,超过该时间则自动释放锁,锁的value值为一个随机生成的UUID,通过此在释放锁的时候进行判断。
(2)获取锁的时候还设置一个获取的超时时间,若超过这个时间则放弃获取锁。
(3)释放锁的时候,通过UUID判断是不是该锁,若是该锁,则执行delete进行锁释放。

3. zookeeper实现思路及代码

  ZooKeeper的内部是一个分层的文件系统目录树结构,规定同一个目录下只能有一个唯一文件名。基于ZooKeeper实现分布式锁的步骤如下:
(1)创建一个目录mylock;
(2)线程A想获取锁就在mylock目录下创建临时顺序节点;
(3)获取mylock目录下所有的子节点,然后获取比自己小的兄弟节点,如果不存在,则说明当前线程顺序号最小,获得锁;
(4)线程B获取所有节点,判断自己不是最小节点,设置监听比自己次小的节点;
(5)线程A处理完,删除自己的节点,线程B监听到变更事件,判断自己是不是最小的节点,如果是则获得锁。
Tips:MySQL中的共享锁与排他锁
  共享锁又称为读锁,简称S锁,顾名思义,共享锁就是多个事务对于同一数据可以共享一把锁,都能访问到数据,但是只能读不能修改。
  排他锁又称为写锁,简称X锁,顾名思义,排他锁就是不能与其他所并存,如一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的其他锁,包括共享锁和排他锁,但是获取排他锁的事务是可以对数据就行读取和修改。
  对于共享锁大家可能很好理解,就是多个事务只能读数据不能改数据,对于排他锁大家的理解可能就有些差别,我当初就犯了一个错误,以为排他锁锁住一行数据后,其他事务就不能读取和修改该行数据,其实不是这样的。排他锁指的是一个事务在一行数据加上排他锁后,其他事务不能再在其上加其他的锁。mysql InnoDB引擎默认的修改数据语句,update,delete,insert都会自动给涉及到的数据加上排他锁,select语句默认不会加任何锁类型,如果加排他锁可以使用select …for update语句,加共享锁可以使用select … lock in share mode语句。所以加过排他锁的数据行在其他事务种是不能修改数据的,也不能通过for update和lock in share mode锁的方式查询数据,但可以直接通过select …from…查询数据,因为普通查询没有任何锁机制。
用zookeeper的Java API实现分布式共享锁:

public class DistributeLock {

    private static final String ROOT_LOCKS = "/LOCKS";//根节点

    private ZooKeeper zooKeeper;

    private int sessionTimeout; //会话超时时间

    private String lockID; //记录锁节点id

    private final static byte[] data = {1, 2}; //节点的数据

    private CountDownLatch countDownLatch = new CountDownLatch(1);

    public DistributeLock() throws IOException, InterruptedException {
        this.zooKeeper = ZookeeperClient.getInstance();
        this.sessionTimeout = ZookeeperClient.getSessionTimeout();
    }

    //获取锁的方法
    public boolean lock(){
        try {
            //LOCKS/00000001
            lockID = zooKeeper.create(ROOT_LOCKS+"/", data, ZooDefs.Ids.
                    OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);

            System.out.println(Thread.currentThread().getName() + "->成功创建了lock节点[" + lockID + "],开始去竞争锁");

            //获取根节点下的所有子节点
            List<String> childrenNodes = zooKeeper.getChildren(ROOT_LOCKS, true);
            //排序,从小到大
            SortedSet<String> sortedSet = new TreeSet<String>();
            for(String children : childrenNodes){
                sortedSet.add(ROOT_LOCKS + "/" + children);
            }
            String first = sortedSet.first(); //拿到最小的节点
            if(lockID.equals(first)){
                //表示当前就是最小的节点
                System.out.println(Thread.currentThread().getName() + "->成功获得锁,lock节点为:[" + lockID + "]");
                return true;
            }
            //获取所有比当前lockID小的节点集合
            SortedSet<String> lessThanLockId = sortedSet.headSet(lockID);
            if(!lessThanLockId.isEmpty()){
            	//拿到比当前lockID小的上一节点
                String prevLockID = lessThanLockId.last();
                //监听前一个节点是否被释放,然后设置监听的过期时间,如果监听超时则放弃获取锁,若节点被释放则立刻去获取锁
                zooKeeper.exists(prevLockID, new LockWatcher(countDownLatch));
                boolean flag = countDownLatch.await(sessionTimeout, TimeUnit.MILLISECONDS);
                if(!flag)
                	return false;
                System.out.println(Thread.currentThread().getName() + " 成功获取锁:[" + lockID + "]");
            }
            return true;
        } catch (KeeperException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return false;
    }

    public boolean unlock(){
        System.out.println(Thread.currentThread().getName() + "->开始释放锁:[" + lockID + "]");
        try {
            zooKeeper.delete(lockID, -1);
            System.out.println("节点[" + lockID + "]成功被删除");
            return true;
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (KeeperException e) {
            e.printStackTrace();
        }
        return false;
    }

    public static void main(String[] args) {
        final CountDownLatch latch = new CountDownLatch(10);
        Random random = new Random();
        for(int i=0; i<10; i++){
            new Thread(()->{
                DistributeLock lock = null;
                try {
                    lock = new DistributeLock();
                    //使用闭锁,当10个线程都countDown()后,释放闭锁,使10个线程同时去争夺锁
                    latch.countDown();
                    latch.await();
                    lock.lock();
                    Thread.sleep(random.nextInt(500));
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    if(lock != null){
                        lock.unlock();
                    }
                }
            }).start();
        }
    }
}
/**
* @author 全恒
* 创建会话
*/
public class ZookeeperClient {

	private final static String CONNECTSTRING = "192.168.123.38:2181,192.168.123.55:2181," +
            "192.168.123.45:2181,192.168.123.174:2181";

    private static int sessionTimeout = 5000;

    //获取连接
    public static ZooKeeper getInstance() throws IOException, InterruptedException {
        final CountDownLatch conectStatus = new CountDownLatch(1);
        ZooKeeper zooKeeper = new ZooKeeper(CONNECTSTRING, sessionTimeout, new Watcher() {
            public void process(WatchedEvent event) {
                if(event.getState() == Event.KeeperState.SyncConnected){
                    conectStatus.countDown();
                }
            }
        });
        conectStatus.await();
        return zooKeeper;
    }

    public static int getSessionTimeout() {
        return sessionTimeout;
    }
}
public class LockWatcher implements Watcher{

    private CountDownLatch latch;

    public LockWatcher(CountDownLatch latch) {
        this.latch = latch;
    }

    public void process(WatchedEvent event) {
    	//如果节点被删除,则释放 闭锁
        if(event.getType() == Event.EventType.NodeDeleted){
            latch.countDown();
        }
    }
}

Zookeeper实现master选举

  随着分布式系统的流行,现在许多服务都需要24小时工作,如果机器挂了的话,我们希望能有其他机器顶替他继续工作。这种问题通常采用master-slave模式,即是正常情况下主机提供服务,备用机器监听主机状态,当主机发生异常时,会选取一个备用机器代替主机器提供服务,这个选举过程即是master选举。
  由于zookeeper能保证同一时刻只能具有一个主节点,即使有网络问题,zookeeper的集群也完全可以解决,而且zookeeper的通知机制,客户端可以监听节点的变化,所以可以考虑采用zookeeper实现master选举。其实现原理图如下:
在这里插入图片描述
  上图中,左侧是zookeeper节点,master节点保存当前master的机器,server节点保存可用的备用机器,每个服务器在创建的时候会进行选举,即每个服务器同时去创建master节点,只有一个机器能创建成功,创建成功的机器则被选为master,其他机器会同时监听master节点的变化,当主机器挂了时,master节点会被删除,其他机器会重新进行master选举。
用zkclient实现master选举

/**
* @author 全恒
* 用户中心,用来模拟 争抢master节点的机器
*/
public class UserCenter implements Serializable{

    private static final long serialVersionUID = -1776114173857775665L;
    
    private int mc_id; //机器信息

    private String mc_name;//机器名称

    public int getMc_id() {
        return mc_id;
    }

    public void setMc_id(int mc_id) {
        this.mc_id = mc_id;
    }

    public String getMc_name() {
        return mc_name;
    }

    public void setMc_name(String mc_name) {
        this.mc_name = mc_name;
    }

    @Override
    public String toString() {
        return "UserCenter{" + "mc_id=" + mc_id + ", mc_name='" + mc_name + '\'' + '}';
    }
}
/**
* @author 全恒
* 提供master选举的服务
*/
public class MasterSelector {

    private ZkClient zkClient;

    private final static String MASTER_PATH = "/master"; //需要争抢的master节点

    private IZkDataListener dataListener; //注册节点内容变化

    private UserCenter server;  //其他服务器

    private UserCenter master;  //master节点

    private boolean isRunning = false;

    ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);

    public MasterSelector(UserCenter server, ZkClient zkClient) {
        System.out.println("[" + server + "] 去争抢master权限");
        this.server = server;
        this.zkClient = zkClient;

        this.dataListener = new IZkDataListener() {
            @Override
            public void handleDataChange(String s, Object o) throws Exception {

            }

            @Override
            public void handleDataDeleted(String s) throws Exception {
                //节点如果被删除, 重新进行master选举
                chooseMaster();
            }
        };
    }

    public void start(){
        //开始选举,如果当前机器已经启动,则不进行任何处理
        if(!isRunning){
            isRunning = true;
            //注册节点事件,使当前客户端机器监听master节点的删除动作
            zkClient.subscribeDataChanges(MASTER_PATH, dataListener); 
            chooseMaster();
        }
    }

    public void stop(){
        //停止,如果当前机器已经停止,则不进行任何处理
        if(isRunning){
            isRunning = false;
            //关闭定时器
            scheduledExecutorService.shutdown();
            //取消订阅
            zkClient.unsubscribeDataChanges(MASTER_PATH, dataListener);
            releaseMaster();
        }
    }

    //具体选master的实现逻辑
    private void chooseMaster(){
        if(!isRunning){
            System.out.println("当前服务没有启动");
            return ;
        }
        try {
            zkClient.createEphemeral(MASTER_PATH, server);
            master=server; //把server节点赋值给master
            System.out.println(master + "->我现在已经是master,你们要听我的");

            //使用定时器模拟 主服务器出现故障,每2秒释放一次
            scheduledExecutorService.schedule(()->{
                releaseMaster();//释放锁(模拟故障的发生)
            }, 2, TimeUnit.SECONDS);
        } catch (ZkNodeExistsException e){
            //表示master已经存在
            UserCenter userCenter = zkClient.readData(MASTER_PATH, true);
            if(userCenter == null) {
                System.out.println("启动操作:");
                chooseMaster(); //再次获取master
            } else {
                master = userCenter;
            }
        }
    }

    //释放锁(模拟故障的发生)
    private void releaseMaster(){
        //判断当前是不是master,只有master才需要释放
        if(checkMaster()){
            zkClient.delete(MASTER_PATH); //删除
        }
    }

    //判断当前的server是不是master
    private boolean checkMaster(){
        UserCenter userCenter = zkClient.readData(MASTER_PATH);
        if(userCenter.getMc_name().equals(server.getMc_name())){
            master = userCenter;
            return true;
        }
        return false;
    }
}
/**
* @author 全恒
* 模拟多个机器抢夺master角色
*/
public class MasterChooseTest {

	private final static String CONNECTSTRING = "192.168.123.38:2181,192.168.123.55:2181," +
            "192.168.123.45:2181,192.168.123.174:2181";

    public static void main(String[] args) throws IOException {
        List<MasterSelector> selectorLists = new ArrayList<>();
        try {
            for(int i=0; i<10; i++) {
                ZkClient zkClient = new ZkClient(CONNECTSTRING, 5000,
                        5000, new SerializableSerializer());
                UserCenter userCenter = new UserCenter();
                userCenter.setMc_id(i);
                userCenter.setMc_name("客户端:" + i);

                MasterSelector selector = new MasterSelector(userCenter, zkClient);
                selectorLists.add(selector);
                selector.start();//触发选举操作
                TimeUnit.SECONDS.sleep(1);//睡眠1秒
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            for(MasterSelector selector:selectorLists){
                selector.stop();
            }
        }
    }
}

用curator实现master选举

public class MasterSelector {

	private final static String CONNECTSTRING = "192.168.123.38:2181,192.168.123.55:2181," +
            "192.168.123.45:2181,192.168.123.174:2181";

    private final static String MASTER_PATH = "/curator_master_path";
    
    public static void main(String[] args) {

        CuratorFramework curatorFramework = CuratorFrameworkFactory
        		.builder()
        		.connectString(CONNECTSTRING)
        		.retryPolicy(new ExponentialBackoffRetry(1000, 3))
        		.build();

        @SuppressWarnings("resource")
		LeaderSelector leaderSelector = new LeaderSelector(curatorFramework, MASTER_PATH, new LeaderSelectorListenerAdapter() {
            @Override
            public void takeLeadership(CuratorFramework client) throws Exception {
                System.out.println("获得leader成功");
                TimeUnit.SECONDS.sleep(2);
            }
        });
        leaderSelector.autoRequeue();//自动争抢leader
        leaderSelector.start();//开始选举
    }
}
发布了51 篇原创文章 · 获赞 1 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_38038396/article/details/91793335
今日推荐