ZooKeeper

   zookeeper是一个为分布式项目提供协调服务的Apache开源项目,是一个基于观察者模式设计的分布式服务管理框架,负责和存储核心数据,接收观察者的注册并通知注册的观察者,是一种主从模式的管理模式

  • zookeeper=文件系统+通知机制
  • 分布式类似不同人做不同工作,集成一体,集群类似不同人做同一份工作
  • zookeeper是一个leader和多个follower来组成的集群,集群只要有半数以上的节点存货就可以正常工作,通常是奇数台集群,每台服务器都保存一份相同的数据副本,全局数据一致,数据更新要么成功要么都失败,在一定时间范围内可以获取最新数据,具有实时性,按照更新的请求顺序执行
  • zookeeper的数据结构(类似于树,每个节点是一个znode)
    • 每一个节点默认存储1MB的元数据,每个节点的路径都是唯一的元数据
    • 元数据描述的是数据属性的信息,用来支持如指示存储位置、历史数据、资源查找、文件记录等功能
  • 常见的应用场景
    • 统一命名服务,同一个域名访问多个服务器
    • 统一配置管理,某一服务器文件修改同步到其他各台
      • 将配置管理交给Zookeeper,写入到Zookeeper的某个节点上,每个客户端监听此节点,一旦节点数据文件被修改,Zookeeper就会通知每个客户端服务器
    • 服务器节点上下线
    • 软负载均衡,Zookeeper会记录每台服务器的访问数,让访问数最少的服务器去处理最新的客户请求

1. 本地模式安装

  • 安装前先安装jdk,拷贝apache-zookeeper-3.6.0-bin.tar.gzopt目录,解压安装包进行重命名
tar -zxvf apache-zookeeper-3.6.0-bin.tar.gz
mv apache-zookeeper-3.6.0-bin zookeepe
  • 配置修改:/opt/zookeeper/这个目录上创建zkDatazkLog目录; 进入/opt/zookeeper/conf这个路径,复制一份 zoo_sample.cfg 文件并命名为 zoo.cfg
dataDir=/opt/zookeeper/zkData
dataLogDir=/opt/zookeeper/zkLog
  • 编辑配置文件 zoo.cfg
dataDir=/usr/zookeeper/zkData
dataLogDir=/usr/zookeeper/zkLog
# the port at which the clients will connect
clientPort=2181
admin.serverPort=8888
  • 启动并查看状态:QuorumPeerMainzookeeper集群的启动入口类,是用来加载配置启动QuorumPeer线程的
 ./zkServer.sh start
jps
./zkServer.sh status
  • 启动或退出客户端
./zkCli.sh

quit
  • 配置文件解读
    • tickTime =2000: 通信心跳数,Zookeeper服务器与客户端心跳时间,毫秒数,是服务器之间或服务器与客户端之间维持通信的时间间隔,每隔一个Zookeeper发送一个心跳
    • initLimit =10:LF初始通信时限,集群中的follower跟随服务器与leader领导者服务器之间,启动时能容忍的最多心跳数10*2000,如果领导和跟随者没有发出心跳通信,就视为失效的连接,领导者和而跟随者彻底断开
    • syncLimit =5:LF同步通信时限,集群启动后,leader和follower的最大响应时间单位,假如响应时间超过syncLimit*tickTime=10秒,leader就认为follower已经死掉,会将follower从服务器列表中删除
    • dataDir:数据文件目录+数据持久化路径,用于保存Zookeeper的数据
    • dataLogDir:日志文件目录
    • clientPort =2181:客户端连接接口,监听客户端连接的窗口
    • admin.serverPort=8888:more占用8080 端口,与Tomcat冲突,需要修改

2. Zookeeper内部原理

  • 选举机制:半数机制,集群中半数以上机器存货,集群可用,Zookeeper工作时,有一个节点为leader,其他为follower,leader通过内部机制产生。
  • 节点类型
    • 持久型persistent
      • 持久化目录节点:客户端与zookeeper断开连接后,该节点依旧存在
      • 持久化顺序编号目录节点:持久化顺序编号目录节点persistent_sequential)客户端与zookeeper断开连接后,该节点依旧存在,创建znode时设置顺序标识,znode名称后会附加一个值,顺序号是一个单调递增的计数器,由父节点维护,例如:Znode001Znode002...
    • 短暂型ephemeral
      • 临时目录节点:客户端与服务器断开连接后,创建的节点自动删除
      • 临时顺序编号目录节点:临时顺序编号目录节点ephemeral_sequential)客户端与zookeeper断开连接后,该节点被删除,创建znode时设置顺序标识,znode名称后会附加一个值,顺序号是一个单调递增的计数器,由父节点维护,例如:Znode001Znode002...
  • 监听器原理
    • main方法中创建Zookeeper客户端的同时就会创建两个线程,一个负责网络连接通信,一个负责监听
    • 监听事件就会通过网络通信发送给zookeeper
    • zookeeper获得注册的监听事件后,立刻将监听事件添加到监听列表里
    • zookeeper监听到 数据变化 或 路径变化,就会将这个消息发送给监听线程
    • 常见的监听
      • 监听节点数据的变化:get path [watch]
      • 监听子节点增减的变化:ls path [watch]
    • 监听线程就会在内部调用process方法(需要我们实现process方法内容)

  • 写数据流程
    • Client 想向 ZooKeeper Server1 上写数据,必须的先发送一个写的请求
    • 如果Server1不是Leader,那么Server1 会把接收到的请求进一步转发给Leader
    • 这个Leader 会将写请求广播给各个Server,各个Server写成功后就会通知Leader
    •  Leader收到半数以上的 Server 数据写成功了,那么就说明数据写成功了
    • 随后,Leader会告诉Server1数据写成功了
    • Server1会反馈通知 Client 数据写成功了,整个流程结束

    

3. 分布式安装zookeeper

  • 安装zookeeper
  • /opt/zookeeper/zkData创建myid文件, 添加与server对应的编号:1
  • 配置参数解读 server.A=B:C:D
    • A:一个数字,表示第几号服务器,集群模式下配置的/opt/zookeeper/zkData/myid文件里面的数据就是A的值
    • B:服务器的ip地址
    • C:与集群中Leader服务器交换信息的端口2888
    • D:选举时专用端口3888,万一集群中的Leader服务器挂了,需要一个端口来重新进行选举,选出一个新的Leader,而这个端口就是用来执行选举时服务器相互通信的端口。
  • 配置其余两台服务器
    •  在虚拟机数据目录vms下,创建zk02
    • 将本台服务器数据目录下的.vmx文件和所有的.vmdk文件分别拷贝zk02
    • 虚拟机->文件->打开 (选择zk02下的.vmx文件)
    • 开启此虚拟机,弹出对话框,选择我已复制该虚拟机
    • 进入系统后,修改linux中的ip,修改/opt/zookeeper/zkData/myid中的数值为2
  • 启动服务端和客户端

4. 客户端命令操作

  • 显示所有操作命令 help
  • 查看当前znode中所包含的内容  ls /
  • 查看当前节点详细数据  ls -s /
    • cZxid:创建节点的事务,每次修改ZooKeeper状态都会收到一个zxid形式的时间戳,也就是ZooKeeper事务ID。事务IDZooKeeper中所有修改总的次序。每个修改都有唯一的zxid,如果zxid1小于zxid2,那么zxid1zxid2之前发生
    • ctime:被创建的毫秒数(1970年开始)
    • mZxid:最后更新的事务zxid
    • mtime:最后修改的毫秒数(1970年开始)
    • pZxid:最后更新的子节点zxid
    • cversion:创建版本号,子节点修改次数
    • dataVersion:数据变化版本号
    • aclVersion:权限版本号
    • ephemeralOwner:如果是临时节点,这个是znode拥有者的session id。如果不是临时节点则是0
    • dataLength:数据长度
    • numChildren:子节点数
  • 创建普通节点
  • 多级创建节点,必须保证父级节点存在,create /japan/Tokyo "hot"
  • 获得节点的值  get /japan/Tokyo
  • 创建短暂节点:创建成功之后,quit退出客户端,重新连接,短暂的节点消失,create -e /uk
  • 创建带序号的节点:如果原来没有序号节点,序号从0开始递增;如果原节点下已有2个节点,则再排序时从2开始,以此类推 create -s /ru/city # 执行三次
  • 修改节点数据值set /japan/Tokyo "too hot"
  • 监听 节点的值变化 子节点变化(路径变化) addWatch /usa
    •  server3主机上注册监听/usa节点的数据变化,Server1主机上修改/usa的数据,Server3会立刻响应:WatchedEvent state:SyncConnected type:NodeDataChanged path:/usa
    • 如果在Server1/usa下面创建子节点NewYork,Server3会立刻响应:WatchedEvent state:SyncConnected type:NodeCreatedpath:/usa/NewYork
  • 删除节点 delete /ru
  • 递归删除节点 (非空节点,节点下有子节点)deleteall /ru/city

5. API应用

  • IDEA 环境搭建
    • 创建maven工程
    • 添加pom文件
      <dependencies>
      <dependency>
      <groupId>org.apache.logging.log4j</groupId>
      <artifactId>log4j-core</artifactId>
      <version>2.8.2</version>
      </dependency>
      <dependency>
      <groupId>org.apache.zookeeper</groupId>
      <artifactId>zookeeper</artifactId>
      <version>3.6.0</version>
      </dependency>
      <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      </dependency>
      </dep
    • 在resource下创建log4j.properties
    log4j.rootLogger=INFO, stdout
    log4j.appender.stdout=org.apache.log4j.ConsoleAppender
    log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
    log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n
    log4j.appender.logfile=org.apache.log4j.FileAppender
    log4j.appender.logfile.File=target/zk.log
    log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
    log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - %m%n
  • 创建zookeeper客户端
      @Before
    //    初始化客户端
        public void init()throws Exception {
        // 创建监听器
            Watcher watcher = new Watcher() {
                @Override
                public void process(WatchedEvent watchedEvent) {
                    System.out.println("正在监听...");
                }
            };
    
            // 创建zookeeper客户端
             zooKeeper = new ZooKeeper(conStr,sessionTimeOut,watcher);
        }
  • 创建节点,一个ACL对象就是一个ID和permission对,表示谁或者哪些范围的ID在通过了怎样的鉴权后运行进行哪些操作,permission是一个int表示的位码,每一位表示一个对应操作的允许状态
    • OPEN_ACL_UNSAFE:创建开放节点,允许任意操作 (用的最多,其余的权限用的很少)
    • READ_ACL_UNSAFE:创建只读节点
    • CREATOR_ALL_ACL:创建者才有全部权限
     @Test
    //    创建节点
        public void createNode ()  throws  Exception{
            // 参数1:要创建的节点的路径
            // 参数2:节点数据
            // 参数3:节点权限
            // 参数4:节点的类型
           String node= zooKeeper.create("/rf", "pc".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
            System.out.println("nodeCreated = " + node);
        }
  • 查询节点值
       @Test
    //    查询节点的值
        public void getNode() throws Exception{
    //        不监听
            byte[] bytes = zooKeeper.getData("/rf", false, new Stat());// 路径不存在时会报错
            System.out.println("rf的数据是"+new String(bytes));
        }
  • 修改节点值
       @Test
    //    修改节点的值
        public void setNode() throws Exception{
        //        不监听
            Stat stat = zooKeeper.setData("/rf", "rf is great".getBytes(), 0);// 路径不存在时会报错
            System.out.println("rf的数据是"+stat);
        }
  • 删除节点
    //删除节点
        @Test
        public void delete() throws Exception{
            zooKeeper.delete("/rf",1);
            System.out.println("删除成功");
        }
  • 获取子节点,监听子节点变化
        @Test
    //    获取子节点,若是监听则监听器watch设置为true
        public void getchildren() throws Exception{
            List<String> children = zooKeeper.getChildren("/japan", true);
            System.out.println("japan的数据是");
            for (String child : children) {
                System.out.println(child);
            }
            System.in.read();
        }
  • 判断子节点是否存在
//    判断子节点是否存在
    @Test
public void exists()  throws Exception{
    Stat stat = zooKeeper.exists("/rf", false);
    System.out.println(stat==null?"不存在":"存在");
}

6.模拟案例

  • 美团商家上下线
    package test;
    
    import org.apache.zookeeper.WatchedEvent;
    import org.apache.zookeeper.Watcher;
    import org.apache.zookeeper.ZooDefs;
    import org.apache.zookeeper.ZooKeeper;
    import org.apache.zookeeper.data.Stat;
    
    import java.io.IOException;
    import java.util.ArrayList;
    import java.util.List;
    
    import static org.apache.zookeeper.CreateMode.EPHEMERAL_SEQUENTIAL;
    
    public class Consumer {
        private  String connStr="192.168.80.128:2181,192.168.80.129:2181,192.168.80.130:2181";
        private int timeOut=60*1000;
        private ZooKeeper zooKeeper=null;
        // 创建到zk的客户端连接
        public  void init() throws Exception {
            zooKeeper = new ZooKeeper(connStr, timeOut, new Watcher() {
                @Override
                public void process(WatchedEvent watchedEvent) {
                    try {
                        getList();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    
        // 获取服务器列表信息
        public void getList() throws Exception{
            // 1获取服务器子节点信息,并且对父节点进行监听
            List<String> children = zooKeeper.getChildren("/meituan", true);
            // 2存储服务器信息列表
            ArrayList<String> list = new ArrayList<>();
            // 3遍历所有节点,获取节点中的主机名称信息
            for (String child : children) {
                byte[] data = zooKeeper.getData("/meituan/" + child, false, new Stat());
                list.add(new String(data));
            }
            // 4打印服务器列表信息
            System.out.println(list);
    
        }
        // 业务功能
        public void business() throws  Exception{
            System.out.println("正在浏览商家点餐......");
            System.in.read();
        }
    
        public static void main(String[] args) throws Exception {
    
            Consumer consumer = new Consumer();
            // 1.获取zk连接 (客户打开美团)
            consumer.init();
            // 2.获取/meituan的子节点信息,从中获取服务器信息列表(从美团中获取商家列表
            consumer.getList();
            // 3.业务进程启动 (对比商家,点餐)
            consumer.business();
        }
    
    }
    package test;
    
    import org.apache.zookeeper.*;
    import org.apache.zookeeper.data.Id;
    
    import java.io.IOException;
    
    import static org.apache.zookeeper.CreateMode.EPHEMERAL_SEQUENTIAL;
    
    //商家服务类
    public class ShopServer {
        private  String connStr="192.168.80.128:2181,192.168.80.129:2181,192.168.80.130:2181";
        private int timeOut=60*1000;
        private ZooKeeper zooKeeper=null;
    
    
        // 创建到zk的客户端连接
        public  void init() throws IOException {
       zooKeeper =  new  ZooKeeper(connStr, timeOut, new Watcher() {
             @Override
             public void process(WatchedEvent watchedEvent) {
    
             }
         });
        }
    
        // 注册到集群
        public void register(String name) throws Exception{
            String shop = zooKeeper.create("/meituan/shop" , name.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, EPHEMERAL_SEQUENTIAL);
        }
       // 业务功能
        public void business(String name) throws  Exception{
            System.out.println(name+"正在营业中......");
            System.in.read();
        }
    
    
        public static void main(String[] args) throws Exception {
            ShopServer shopServer = new ShopServer();
            // 1.连接zookeeper集群(和美团取得联系)
            shopServer.init();
            // 2.将服务器节点注册(入住美团)
            shopServer.register(args[0]);
            // 3.业务逻辑处理(做生意)
            shopServer.business(args[0]);
        }
    }

     

  • 分布式锁,商品秒杀
  • zookeeper采用分布式锁,避免羊群效应
    • 所有请求进来,在/lock下创建 临时顺序节点 zookeeper自动编号排序
    • 判断自己是不是/lock最小的节点
      • 是,获得锁(创建节点)
      • 否,对前面小我一级的节点进行监听
    • 获得锁请求,处理完业务逻辑,释放锁(删除节点),后一个节点得到通知
    • 重复步骤

 

  •  初始化数据库
    -- 商品表
    CREATE TABLE product(
    id INT PRIMARY KEY AUTO_INCREMENT, -- 商品编号
    product_name VARCHAR(20) NOT NULL, -- 商品名称
    stock INT NOT NULL, -- 库存
    VERSION INT NOT NULL -- 版本
    ) ;
    INSERT INTO product (product_name,stock,VERSION) VALUES('锦鲤-清空购物车-大奖',5,0);
    
    -- 订单表
    CREATE TABLE `order`(
    id VARCHAR(100) PRIMARY KEY, -- 订单编号
    pid INT NOT NULL, -- 商品编号
    userid INT NOT NULL -- 用户编号
    );
  • 搭建工程
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>org.example</groupId>
        <artifactId>zookeeper-lock</artifactId>
        <version>1.0-SNAPSHOT</version>
    
        <packaging>war</packaging>
        <properties>
            <spring.version>5.2.7.RELEASE</spring.version>
        </properties>
        <dependencies>
            <!-- Spring -->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context</artifactId>
                <version>${spring.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-beans</artifactId>
                <version>${spring.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-webmvc</artifactId>
                <version>${spring.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-jdbc</artifactId>
                <version>${spring.version}</version>
            </dependency>
            <!-- Mybatis -->
            <dependency>
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis</artifactId>
                <version>3.5.5</version>
            </dependency>
            <dependency>
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis-spring</artifactId>
                <version>2.0.5</version>
            </dependency>
            <!-- 连接池 -->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid</artifactId>
                <version>1.1.10</version>
            </dependency>
            <!-- 数据库 -->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>8.0.20</version>
            </dependency>
            <!-- junit -->
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.12</version>
                <scope>test</scope>
            </dependency></dependencies>
        <build>
            <plugins>
                <!-- maven内嵌的tomcat插件 -->
                <plugin>
                    <groupId>org.apache.tomcat.maven</groupId>
                    <!-- 目前apache只提供了tomcat6和tomcat7两个插件 -->
                    <artifactId>tomcat7-maven-plugin</artifactId>
                    <version>2.2</version>
                    <configuration>
                        <port>8001</port>
                        <path>/</path>
                    </configuration>
                    <executions>
                        <execution>
                            <!-- 打包完成后,运行服务 -->
                            <phase>package</phase>
                            <goals>
                                <goal>run</goal>
                            </goals>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </build>
    </project>
    package domain;
    
    public class Order {
    
    //    id VARCHAR(100) PRIMARY KEY, -- 订单编号
        private String id;
    //pid INT NOT NULL, -- 商品编号
        private Integer pid;
    //userid INT NOT NULL -- 用户编号
        private Integer userid;
    
        public Order(Integer pid, Integer userid) {
            this.pid = pid;
            this.userid = userid;
        }
    
        public Order() {
        }
    
        public String getId() {
            return id;
        }
    
        public void setId(String id) {
            this.id = id;
        }
    
        public Integer getPid() {
            return pid;
        }
    
        public void setPid(Integer pid) {
            this.pid = pid;
        }
    
        public Integer getUserid() {
            return userid;
        }
    
        public void setUserid(Integer userid) {
            this.userid = userid;
        }
    }
    
    
    package domain;
    
    public class Product {
    
    //    id INT PRIMARY KEY AUTO_INCREMENT, -- 商品编号
        private Integer id;
    //product_name VARCHAR(20) NOT NULL, -- 商品名称
        private String product_name;
    //stock INT NOT NULL, -- 库存
        private Integer stock;
    //VERSION INT NOT NULL -- 版本
    private Integer version;
    
        @Override
        public String toString() {
            return "Product{" +
                    "id=" + id +
                    ", product_name='" + product_name + '\'' +
                    ", stock=" + stock +
                    ", version=" + version +
                    '}';
        }
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public String getProduct_name() {
            return product_name;
        }
    
        public void setProduct_name(String product_name) {
            this.product_name = product_name;
        }
    
        public Integer getStock() {
            return stock;
        }
    
        public void setStock(Integer stock) {
            this.stock = stock;
        }
    
        public Integer getVersion() {
            return version;
        }
    
        public void setVersion(Integer version) {
            this.version = version;
        }
    }
    package dao;
    
    
    import domain.Order;
    import org.apache.ibatis.annotations.Insert;
    import org.apache.ibatis.annotations.Mapper;
    import org.apache.ibatis.annotations.Param;
    
    @Mapper
    public interface OrderDao {
        //添订单
        @Insert("INSERT INTO `order` VALUES(#{id},#{pid},#{userid})")
        int addOrder(Order order);
    
    }
    
    
    
    package dao;
    
    
    import domain.Product;
    import org.apache.ibatis.annotations.Mapper;
    import org.apache.ibatis.annotations.Param;
    import org.apache.ibatis.annotations.Select;
    import org.apache.ibatis.annotations.Update;
    import org.springframework.stereotype.Repository;
    
    import java.util.List;
    
    @Mapper
    public interface ProductDao {
    
        // 查询商品(目的查库存)
        @Select("select * from product where id = #{id}")
        Product getProduct(@Param("id") int id);
    //减库存
        @Update("update product set stock=stock-1 where id=#{id}")
        int updatePro(@Param("id") Integer id);
    
    }
    package service.impl;
    
    import dao.OrderDao;
    import dao.ProductDao;
    import domain.Order;
    import domain.Product;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import service.OrderService;
    
    import java.util.UUID;
    
    @Service
    public class OrderServiceImp implements OrderService {
    
    @Autowired
    private OrderDao orderDao;
    
    @Autowired
    private ProductDao productDao;
    
           public void addOrder(Integer pid) throws Exception{
    
            Product product = productDao.getProduct(pid);
    
                Thread.sleep(5000);
                if(product!=null){
                    Integer stock = product.getStock();
                    if(stock<=0){
                        throw new RuntimeException("已抢光!");
    
                    }else {
                        int i = productDao.updatePro(pid);
                        if(i==1)
                        {
                            Order order = new Order();
                            order.setId(UUID.randomUUID().toString());
                            order.setPid(pid);
                            order.setUserid(101);
                            orderDao.addOrder(order);}
                        else
                            throw new RuntimeException("减库存失败,请重试!");
                    }
                }else   throw new RuntimeException("无此类商品!");
            }
    
    }
    package controller;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.ResponseBody;
    import service.OrderService;
    
    @Controller
    public class PoductService {
        @Autowired
        private OrderService orderService;
    
        @RequestMapping("/product/reduce")
        @ResponseBody
        public String reduce(Integer pid) throws Exception {
            orderService.addOrder(pid);
            return "ok";
        }
    }
  • 启动测试
    upstream sga{
    server idea项目本机地址:8001;
    server idea项目本机地址:8002;
    } 
    server {
    listen 80;
    server_name localhost;
    #charset koi8-r;
    #access_log logs/host.access.log main;
    location / {
    proxy_pass http://sga;
    root html;
    index index.html index.htm;
    }
  • Apache提供的zookeeper客户端
    基于zookeeper原生态的客户端类实现分布式是非常麻烦的,我们使用apahce提供了一个zookeeper客户端来实现
<!-- recipes是curator族谱大全,里面包含zookeeper和framework -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>4.2.0</version> <!-- 网友投票最牛逼版本 -->
</dependency>
  • 加分布式锁
    package controller;
    
    import org.apache.curator.RetryPolicy;
    import org.apache.curator.framework.CuratorFramework;
    import org.apache.curator.framework.CuratorFrameworkFactory;
    import org.apache.curator.framework.recipes.locks.InterProcessMutex;
    import org.apache.curator.retry.ExponentialBackoffRetry;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.ResponseBody;
    import service.OrderService;
    
    @Controller
    public class PoductController {
        @Autowired
        private OrderService orderService;
    
        private String connStr = "192.168.80.128:2181,192.168.80.129:2181,192.168.80.130:2181";
    
        @RequestMapping("/product/reduce")
        @ResponseBody
        public String reduce(Integer pid) throws Exception {
            // 重试策略 (1000毫秒试1次,最多试3次)
            RetryPolicy retry = new ExponentialBackoffRetry(1000, 3);
            //1.创建curator工具对象
            CuratorFramework client = CuratorFrameworkFactory.newClient(connStr, retry);
            //2.根据工具对象创建“内部互斥锁”
            InterProcessMutex lock= new InterProcessMutex(client, "/product_" + pid);
            client.start();
            try {
                lock.acquire();
                orderService.addOrder(pid);
            }catch(Exception e){
                if(e instanceof RuntimeException){
                    throw e;
                }
            }finally {
                //4.释放锁
                lock.release();
            }
            return "ok";
    
        }
    }
  • 再次测试,并发解决

 

posted @ 2021-07-28 21:50  forever_fate  阅读(47)  评论(0)    收藏  举报