• 分布式锁


    考虑问题:分布式商品秒杀

    在分布式部署中,以nginx提供负载均衡,提供服务主机以集群形式部署。mysql作为db服务。
    比如:现在db中存储了1w+的商品信息,用于提供服务的主机集群里面包含三台高性能主机,预估此次参与秒杀的人有3w+,
    秒杀活动9点中开始,nginx对外暴露的域名为 www.goodms.com

    分析过程:

    1. 在活动开始之后,由于nginx的负载均衡作用,3w+的人员被分流至三台主机上。
    2. 为了实现高并发秒杀商品,在每一台主机上面再启500的线程,从db中获取商品,并进行商品获取之后的信息校验,商品删除的相关操作。
    3. 由于启动的线程可能位于不同的主机上面,而jdk并发包提供的加锁手段此时将失效,因为加锁的实现是针对用一个JVM的。目前而言,db中的
    数据为分布式环境中的共享数据,故引入分布式锁的概念。
    4. 针对分布式锁的实现:目前主要存在三种技术:
    a. 基于数据库的实现
    b. 基于redis缓存的实现
    c. 基于zk的实现

    分布式锁的实现:

    1. 基于数据库
    基于数据库的实现,主要是在数据库中维持一个记录锁信息的表。该表中存在一个字段,且该字段的值时唯一确认,且不能重复,比如值为:lock。
    当不同主机上面的线程需要秒杀商品时,首先去锁表里面插入lock值,如果可以插入成功,说明当前没有其他线程操作db,也就是当前线程获得了锁,比如处理商品相关服务(商品信息校验,商品数据删除等等)需要10s中,在这10s之内进入的线程首先去表里面插入lock值,由于该字段属性是唯一的,那么会出现插入失败,则认为获取锁失败。然后尝试再次获取锁,直到获取成功(前提是上一个锁拥线程删除锁表中的记录),才可以进行商品的相关操作。

    缺点:如果锁拥有线程在执行业务逻辑中,突然宕机,那么锁表中的记录将永远无法清除,或造成死锁现象。可以通过设置过期时间来阻止该现象的发生。

    2. 基于Redis
    实现原理一样,缺点也是容易造成死锁现象,因为必须是在连接存在的情况下才能删除锁记录。可以通过设置过期时间来阻止该现象的发生。

    3. 基于zk实现:
    主要依赖的是临时节点的创建,临时节点会在连接关闭时自动删除,所以以临时节点作为锁,可以避免死锁现象。
    注意:每一次获取锁时,都要进行连接的创建,因为释放锁的时候,节点已经关闭。

    分布式锁可以适用于分布式场景,也可以适用单机部署场景,但是单机场景不建议使用分布式锁,因为分布式锁的实现是依赖连接的建立,会导致性能严重下降。

    依赖:

    1     <dependency>
    2         <groupId>com.101tec</groupId>
    3         <artifactId>zkclient</artifactId>
    4         <version>0.10</version>
    5     </dependency>
    View Code

    分布式锁:

    package com.example.demo.test;
    
    import org.I0Itec.zkclient.IZkDataListener;
    import org.I0Itec.zkclient.ZkClient;
    
    import java.util.concurrent.CountDownLatch;
    
    /**
     * Created by 156 on 2019/2/10.
     */
    public class MyLocak {
    
        // zk连接地址
        private static final String CONNECTSTRING = "192.168.20.1:2181";
        // 创建zk连接
        protected ZkClient zkClient = new ZkClient(CONNECTSTRING);
        //
        protected static final String PATH = "/lock";
        //  信号量
        private CountDownLatch countDownLatch = null;
    
        //获取到锁的资源
        public void getLock(){
            if (tryLock()) {
                System.out.println("##获取lock锁的资源####");
            } else {
                // 等待
                waitLock();
                // 重新获取锁资源
                getLock();
            }
        }
        // 释放锁
        public void unLock(){
            if (zkClient != null) {
                zkClient.close();
                System.out.println("释放锁...");
            }
        }
    
        private boolean tryLock() {
            try {
                // 创建临时节点
                zkClient.createEphemeral(PATH);
                return true;
            } catch (Exception e) {
    //            e.printStackTrace();
                return false;
            }
    
        }
        private void waitLock() {
            IZkDataListener izkDataListener = new IZkDataListener() {
    
                public void handleDataDeleted(String path) throws Exception {
                    // 唤醒被等待的线程
                    if (countDownLatch != null) {
                        countDownLatch.countDown();
                    }
                }
                public void handleDataChange(String path, Object data) throws Exception {
    
                }
            };
            // 注册事件
            zkClient.subscribeDataChanges(PATH, izkDataListener);
            if (zkClient.exists(PATH)) {
                countDownLatch = new CountDownLatch(1);
                try {
                    countDownLatch.await();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            // 删除监听
            zkClient.unsubscribeDataChanges(PATH, izkDataListener);
        }
    }
    View Code

    测试代码:

    public class T {
    
        public static void main(String[] args) {
            for (int i=0;i<100;i++){
                // 每一次获取锁的连接都必须重建
                ThreadDemo demo = new ThreadDemo();
                new Thread(demo).start();
            }
        }
    }
    View Code

    ThreadDemo代码:

    public class ThreadDemo implements Runnable{
     
        //  在分布式场景中,该变量的存在用于标识网站的访问量
        static int count = 0;
     
        private Object lock = new Object();
     
        private MyLocak locak = new MyLocak();
        @Override
        public void run() {
          /*  synchronized (lock){
                System.out.println("当前累计访问人数:"+ ++count +" 人");
            }*/
           locak.getLock();
            System.out.println("当前累计访问人数:"+ ++count +" 人");
            locak.unLock();
        }
    }
    View Code
  • 相关阅读:
    钉钉outgoing机器人小项目开发
    js根据cookie判断,一天之内只弹出一次弹窗
    js倒计时功能
    jquery的$().each,$.each的区别
    VS代码提示自动高亮
    winform当前屏幕大小
    动态增删改控件
    datagridveiw样式
    sql 语句 提取中文的首字母
    按键监听及重写
  • 原文地址:https://www.cnblogs.com/nevegiveup/p/10685156.html
Copyright © 2020-2023  润新知