欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

【Zookeeper】编程实战之Zookeeper分布式锁实现秒杀

程序员文章站 2022-03-27 10:40:22
1. Zookeeper简述 我们要了解一样技术,首先应该要到它的官网,因为官网的信息一般都是最准确的,如下图是Zookeeper官网对它的介绍。 从官网的介绍中,可以总结出,Zookeeper是一个集中式服务,它能够实现高度可靠的分布式协调,可用于开发和维护开源服务器。 除了官网的解释外,我的观点 ......

1. Zookeeper简述

    我们要了解一样技术,首先应该要到它的官网,因为官网的信息一般都是最准确的,如下图是Zookeeper官网对它的介绍。

【Zookeeper】编程实战之Zookeeper分布式锁实现秒杀

    从官网的介绍中,可以总结出,Zookeeper是一个集中式服务,它能够实现高度可靠的分布式协调,可用于开发和维护开源服务器。

    除了官网的解释外,我的观点是还可以这样理解。它也相当于是一个数据库,具有数据同步和选举功能,能够用来存储一些信息,可用于解决大数据集群的单点故障问题。Zookeeper有leader和follow两种角色,当leader的节点宕掉之后,会自动选举出新的leader,如果只剩一个节点活着,就是standalone状态。Zookeeper各个节点之间的数据会自动同步,比如在Zookeeper集群的A节点存储数据,那么这份数据也会自动拷贝到集群中另外的节点上。在Hadoop、Storm、Spark集群都可以使用Zookeeper实现高可用(HA),防止出现单点故障。

2. 为什么要加锁

    在多线程编程中,必须要考虑到线程安全问题,当共享数据被高并发访问时,会破坏数据的一致性。比如抢购商品,商品数量为1,有两个用户(线程)同时对它进行访问,当第一个线程拿到数据,还没有对数量执行减1操作的这段时间,第二个线程在这个时间段也拿到了数据,两个线程都对商品数量进行减1操作的话,就会出现商品数量是 -1 的数据,就违背了实际原则。

    因此,在程序中引入了锁,在线程访问共享数据之前,首先要请求锁,当得到这把锁的时候,才能够访问共享数据,使用完以后再归还这把锁。如果锁已经被一个线程获取,其它线程就请求不到锁,就执行重试策略,进入等待状态,不会访问共享数据,也就保证了数据的一致性。

3. 编程实战

 3.1 原理

【Zookeeper】编程实战之Zookeeper分布式锁实现秒杀

  3.2 实现

    3.2.1 代码

        (1) 创建Maven项目,并在pom文件中加入以下依赖

<dependencies>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>3.8.1</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.apache.curator</groupId>
        <artifactId>curator-framework</artifactId>
        <version>4.0.0</version>
    </dependency>
    <dependency>
        <groupId>org.apache.curator</groupId>
        <artifactId>curator-recipes</artifactId>
        <version>4.0.0</version>
    </dependency>
    <dependency>
        <groupId>org.apache.curator</groupId>
        <artifactId>curator-client</artifactId>
        <version>4.0.0</version>
    </dependency>
    <dependency>
        <groupId>org.apache.zookeeper</groupId>
        <artifactId>zookeeper</artifactId>
        <version>3.4.6</version>
    </dependency>
    <dependency>
        <groupId>com.google.guava</groupId>
        <artifactId>guava</artifactId>
        <version>16.0.1</version>
    </dependency>
</dependencies>

 

        (2) Product.java

package com.nova;
/**
 * 
 * @author Supernova
 * @date 2018/06/16
 *
 */
public class Product {
    // 商品数量,这里默认共有8件商品
    private static int number = 5;

    public static int getNumber() {
        return number;
    }

    public static void setNumber(int number) {
        Product.number = number;
    }
    
}

 

        (2) Client.java

package com.nova;

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;
/**
 * 
 * @author Supernova
 * @date 2018/06/16
 *
 */
public class Client {
    /*
     * 抢购商品的方法 
     * 作用:访问共享资源,获取并更新商品数量
     */
    public static void buy() {
        System.out.println("--------【"+Thread.currentThread().getName()+"】开始购买-------");
        //获取商品数量
        int currentNumber = Product.getNumber();
        /*
         * 如果商品数量为0,则不能购买
         * 如果还有商品,则执行购买操作
         */
        if(currentNumber == 0 ) {
            System.out.println("商品已被抢空!!!");
        }else {
            System.out.println("当前商品数量:"+currentNumber);
            
            //购买后商品数量减1
            currentNumber--;
            Product.setNumber(currentNumber);
            
            //为了便于观察程序的运行结果,这里使线程在执行购买操作后,停顿3秒
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("--------【"+Thread.currentThread().getName()+"】  购买结束-------");
    }

    public static void main(String[] args) {
        /*
         * 定义重试策略:等待2秒,重试10次
         * 第一个参数:等待时间
         * 第二个参数:重试次数
         */
        RetryPolicy policy = new ExponentialBackoffRetry(2000, 10);
        
        /*
         * 创建客户端向zookeeper请求锁
         * connectString() : zookeeper地址
         * retryPolicy() : 重试策略
         */
        CuratorFramework curatorFramework = CuratorFrameworkFactory.builder().connectString("192.168.243.11").retryPolicy(policy).build();
        //启用
        curatorFramework.start();

        //获取zookeeper锁的信息
        final InterProcessMutex mutex = new InterProcessMutex(curatorFramework, "/myMutex");

        /*
         * 创建8个线程模拟8个客户端并发访问
         * 
         */
        for (int i = 0; i < 8; i++) {
            new Thread(new Runnable() {
                public void run() {
                    try {
                        //请求锁资源,如果没有得到锁资源,就会执行重试策略
                        mutex.acquire();
                        //开始访问共享资源,这里是访问商品信息
                        buy();
                        
                    } catch (Exception e) {
                        e.printStackTrace();
                    }finally {
                        try {
                            //将锁归还
                            mutex.release();
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }
            }).start();
        }

    }
}

 

 

    3.2.2 运行测试

        (1) 启动Zookeeper。通过Putty连接VM的Linux系统,我是在Hadoop伪分布的机器上启动的Zookeeper,只启动一个Zookeeper节点,因此是standalone的状态。 

        【Zookeeper】编程实战之Zookeeper分布式锁实现秒杀

        (2) 运行Java程序,程序运行结果如下图

        【Zookeeper】编程实战之Zookeeper分布式锁实现秒杀

    可以看到运行的结果是线程安全的,只有在一个线程购买商品操作结束后,另一个线程才能接着购买。保证了数据的一致性。那么,如果去掉锁的情况是如何的呢?

    (3) 将请求锁的代码mutex.acquire();和mutex.release();注释掉之后。运行结果如下:

        【Zookeeper】编程实战之Zookeeper分布式锁实现秒杀

    从运行结果可以看出,如果没有锁的限制,程序运行的结果将会混乱。