zookeeper专题学习(四)-----zookeeper应用

服务注册与发现

场景

一个产品服务productService,一个订单服务orderService。下订单的时候要获取产品信息。

代码示例 

项目总结构

productService

结构:

1、ProductApp

package cn.enjoy.product;

import cn.enjoy.product.listener.InitListener;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class ProductApp {
    public static void main(String[] args) {
        SpringApplication.run(ProductApp.class,args);
    }

    @Bean
    public ServletListenerRegistrationBean servletListenerRegistrationBean() {
        ServletListenerRegistrationBean servletListenerRegistrationBean = new ServletListenerRegistrationBean();
        servletListenerRegistrationBean.setListener(new InitListener());
        return  servletListenerRegistrationBean;
    }
}

2、InitListener

package cn.enjoy.product.listener;

import cn.enjoy.product.zk.ServiceRegister;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.context.support.WebApplicationContextUtils;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import java.net.InetAddress;
public class InitListener implements ServletContextListener {

    @Value("${server.port}")
    private int port;

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        WebApplicationContextUtils.getRequiredWebApplicationContext(sce.getServletContext()).getAutowireCapableBeanFactory().autowireBean(this);
        try {
            String hostAddress = InetAddress.getLocalHost().getHostAddress();
            ServiceRegister.register(hostAddress,port);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {

    }
}

3、ServiceRegister

package cn.enjoy.product.zk;

import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;

public class ServiceRegister {

    private  static final String BASE_SERVICES = "/services";
    private static final String  SERVICE_NAME="/products";

    public static void register(String address,int port) {
        try {
            //产品服务最终会注册到的地址
            String path = BASE_SERVICES + SERVICE_NAME;
            ZooKeeper zooKeeper = new ZooKeeper("127.0.0.1:2181",5000,(watchedEvent)->{});
            Stat exists = zooKeeper.exists(BASE_SERVICES + SERVICE_NAME, false);
            //先判断服务根路径是否存在
            if(exists==null) {
                zooKeeper.create(path,"".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
            }
            //拼接ip和端口
            String server_path = address+":"+port;
            //注册的类型,EPHEMERAL_SEQUENTIAL临时并且带序号。当客户端与服务端的连接关闭时,临时节点会自动删除。当服务挂了,节点应该删除
            zooKeeper.create(BASE_SERVICES + SERVICE_NAME+"/child",server_path.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.EPHEMERAL_SEQUENTIAL);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

4、Product

package cn.enjoy.product.pojo;

public class Product {
    private  String id;

    private String name;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Product(String id, String name) {
        this.id = id;
        this.name = name;
    }

    public Product() {
    }
}

5、ProductController

package cn.enjoy.product.controller;

import cn.enjoy.product.pojo.Product;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
@RestController
@RequestMapping("/product")
public class ProductController {

    @RequestMapping("/getProduct/{id}")
    public Object getProduct(HttpServletRequest request, @PathVariable("id") String id) {
        return new Product(id,"name:"+request.getLocalPort());
    }
}

6、application.properties

server.port=8081

orderService

结构:

1、OrderApp

package cn.enjoy.order;

import cn.enjoy.order.listener.InitListener;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
public class OrderApp {
    public static void main(String[] args) {
        SpringApplication.run(OrderApp.class,args);
    }


    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

    @Bean
    public ServletListenerRegistrationBean servletListenerRegistrationBean() {
        ServletListenerRegistrationBean servletListenerRegistrationBean = new ServletListenerRegistrationBean();
        servletListenerRegistrationBean.setListener(new InitListener());
        return servletListenerRegistrationBean;
    }
}

2、InitListener

package cn.enjoy.order.listener;

import cn.enjoy.order.utils.LoadBalance;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import java.util.ArrayList;
import java.util.List;
public class InitListener implements ServletContextListener {

    private  static final String BASE_SERVICES = "/services";
    private static final String  SERVICE_NAME="/products";

    private ZooKeeper zooKeeper;
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        try {
            //连接上zk,获得列表信息
             zooKeeper = new ZooKeeper("localhost:2181",5000,(watchedEvent)->{
                 //当有节点变更的时候通知到orderService
                if(watchedEvent.getType() == Watcher.Event.EventType.NodeChildrenChanged  && watchedEvent.getPath().equals(BASE_SERVICES+SERVICE_NAME)) {
                    updateServiceList();
                }
            });
            //第一次连接的时候要获得列表
            updateServiceList();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void updateServiceList() {
       try{
           List<String> children = zooKeeper.getChildren(BASE_SERVICES  + SERVICE_NAME, true);
           List<String> newServerList = new ArrayList<String>();
           for(String subNode:children) {
               byte[] data = zooKeeper.getData(BASE_SERVICES  + SERVICE_NAME + "/" + subNode, false, null);
               String host = new String(data, "utf-8");
               System.out.println("host:"+host);
               newServerList.add(host);
           }
           LoadBalance.SERVICE_LIST = newServerList;
       }catch (Exception e) {
           e.printStackTrace();
       }
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {

    }
}

3、LoadBalance

package cn.enjoy.order.utils;

import java.util.List;

public abstract class LoadBalance {
    public volatile static List<String> SERVICE_LIST;

    public abstract String choseServiceHost();

}

4、RamdomLoadBalance

package cn.enjoy.order.utils;

import org.springframework.util.CollectionUtils;

import java.util.Random;
public class RamdomLoadBalance extends LoadBalance {
    @Override
    public String choseServiceHost() {
        String result = "";
        if(!CollectionUtils.isEmpty(SERVICE_LIST)) {
            int index = new Random().nextInt(SERVICE_LIST.size());
            result = SERVICE_LIST.get(index);
        }
        return result ;
    }
}

5、Product

package cn.enjoy.order.pojo;

public class Product {
    private  String id;

    private String name;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Product(String id, String name) {
        this.id = id;
        this.name = name;
    }

    public Product() {
    }
}

6、Order

package cn.enjoy.order.pojo;

public class Order {
    private String id;
    private  String name;
    private Product product;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Product getProduct() {
        return product;
    }

    public void setProduct(Product product) {
        this.product = product;
    }

    public Order(String id, String name, Product product) {
        this.id = id;
        this.name = name;
        this.product = product;
    }

    public Order() {
    }
}

7、OrderController

package cn.enjoy.order.controller;

import cn.enjoy.order.pojo.Order;
import cn.enjoy.order.pojo.Product;
import cn.enjoy.order.utils.LoadBalance;
import cn.enjoy.order.utils.RamdomLoadBalance;
import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;

@RequestMapping("/order")
@RestController
public class OrderController {

    @Resource
    private RestTemplate restTemplate;

    private LoadBalance loadBalance = new RamdomLoadBalance();

    @RequestMapping("/getOrder/{id}")
    public Object getOrder(@PathVariable("id") String id ) {
        Product product = restTemplate.getForObject("http://"+loadBalance.choseServiceHost()+"/product/getProduct/1", Product.class);
        return new Order(id,"orderName",product);
    }
}

zookeeper分布式锁 

基于同名节点的分布式锁

原理图

说明:所有客户端尝试创建临时节点,创建成功则说明成功获取锁,若创建失败,则说明创建失败,然后一直自旋,等待创建成功的client删除临时节点,再尝试获取锁资源。 

代码示例

1、Lock

package com.ty;

public interface Lock {
    //获取到锁的资源
    void getLock();
    //释放锁的资源
    void unLock();
}

2、AbstractLock

package com.ty;

public abstract class AbstractLock implements Lock {
    public void getLock() {
        if(tryLock()) {
            System.out.println("##获取lock锁的资源###");
            unLock();
        }else {
            //未获取到锁资源,等待
            waitLock();
            //继续准备获取锁资源
            getLock();
        }
    }

    public abstract boolean tryLock();

    public abstract void waitLock();
}

3、ZookeeperAbstractLock

package com.ty;

import org.I0Itec.zkclient.ZkClient;

public abstract class ZookeeperAbstractLock extends AbstractLock {
    //zk连接地址
    private static final String CONNECTSTRING = "127.0.0.1:2181";
    //创建zk连接
    protected ZkClient zkClient = new ZkClient(CONNECTSTRING);
    protected static final String PATH = "/lock";

    protected static final String PATH2 = "/lock2";
}

4、ZookeeperDistributeLock

package com.ty;

import org.I0Itec.zkclient.IZkDataListener;

import java.util.concurrent.CountDownLatch;

public class ZookeeperDistributeLock extends ZookeeperAbstractLock {
    private CountDownLatch countDownLatch = null;

    public boolean tryLock() {
        try {
            zkClient.createEphemeral(PATH);
            return true;
        }catch (Exception ex) {
            return false;
        }
    }

    public void waitLock() {
        //如果节点存在
        if(zkClient.exists(PATH)) {
            countDownLatch = new CountDownLatch(1);

            IZkDataListener izkDataListener = new IZkDataListener() {
                public void handleDataChange(String s, Object o) throws Exception {
                    //唤醒被等待的线程
                    if(countDownLatch != null) {
                        countDownLatch.countDown();
                    }
                }

                public void handleDataDeleted(String s) throws Exception {
                }
            };

            //注册事件
            zkClient.subscribeDataChanges(PATH, izkDataListener);

            try{
                //等待,一直等到接收到事件通知
                countDownLatch.await();
            }catch (Exception ex) {

            }

            //删除监听
            zkClient.subscribeDataChanges(PATH, izkDataListener);
        }
    }

    public void unLock() {
        //释放锁
        if(zkClient != null) {
            zkClient.delete(PATH);
            zkClient.close();
            System.out.println("释放锁资源......");
        }
    }
}

优缺点

优点:实现简单

缺点:性能不好,当一个client释放锁之后,有很多client竞争锁资源,出现羊群效应。

高性能分布式锁

原理图

节点图

代码示例

Lock等代码与上面一种都是相同的,区别在于ZookeeperDistributeLock类上

1、HignPerformanceZookeeperDistributeLock

package com.ty;

import org.I0Itec.zkclient.IZkDataListener;

import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;

public class HignPerformanceZookeeperDistributeLock extends ZookeeperAbstractLock {
    private CountDownLatch countDownLatch = null;
    //当前请求的节点前的一个节点
    private String beforePath;
    //当前请求的节点
    private String currentPath;

    public HignPerformanceZookeeperDistributeLock() {
        if(!this.zkClient.exists(PATH2)) {
            this.zkClient.createPersistent(PATH2);
        }
    }

    public boolean tryLock() {
        //如果currentPath为空则为第一次尝试加锁,第一次加锁赋值currentpath
        if(currentPath == null || currentPath.length() == 0) {
            //创建一个临时顺序节点
            currentPath = this.zkClient.createEphemeralSequential(PATH2 + "/", "lock");
        }
        //获取所有临时节点并排序,临时节点名称为自增长的字符串,如00000001
        List<String> children = this.zkClient.getChildren(PATH2);
        Collections.sort(children);

        //如果当前节点在所有节点中排名第一则获取锁成功
        if(currentPath.equals(PATH2 + "/" + children.get(0))) {
            return true;
        }else {
            //如果当前节点在所有节点排名中不是排名第一,则获取前面的节点名称,并赋值给beforePath
            int wz = Collections.binarySearch(children, currentPath.substring(7));
            beforePath = PATH2 + "/" + children.get(wz - 1);
        }
        return false;
    }

    public void waitLock() {
        if(this.zkClient.exists(beforePath)) {
            countDownLatch = new CountDownLatch(1);

            IZkDataListener listener = new IZkDataListener() {
                public void handleDataChange(String s, Object o) throws Exception {
                    if(countDownLatch != null) {
                        countDownLatch.countDown();
                    }
                }

                public void handleDataDeleted(String s) throws Exception {

                }
            };
            //给排在前面的节点增加数据删除的watcher,本质是启动另外一个线程去监听前置节点
            this.zkClient.subscribeDataChanges(beforePath, listener);

            try {
                countDownLatch.await();
            }catch(Exception ex) {
                ex.printStackTrace();
            }

            this.zkClient.unsubscribeDataChanges(beforePath, listener);
        }
    }

    public void unLock() {
        //删除当前临时节点
        zkClient.delete(currentPath);
        zkClient.close();
    }
}

 

猜你喜欢

转载自www.cnblogs.com/alimayun/p/12663762.html