• 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();
        }
    }

     

  • 相关阅读:
    Java 之SpringBoot+Vue实现后台管理系统的开发
    保证接口数据安全的10种方式
    连八股文都不懂还指望在前端混下去么
    2020年2月面试题100+大全(合适各级Java人员)
    Java8 Stream源码精讲(一):从一个简单的例子入手
    Java8新特性Lambda表达式
    Java高级面试题及答案
    切片 零值 浅拷贝 泄露 扩容
    @ConfigurationProperties使用及与@Value对比
    JSON中的JSON.parseArray()方法
  • 原文地址:https://www.cnblogs.com/alimayun/p/12663762.html
Copyright © 2020-2023  润新知