• 分布式锁实现


    在微服务开发中常用的分布式有redis,zookeeper实现,下面就基于这两种分布式锁实现做一个总结:

    1,zookeeper的分布式锁实现

    基于ZooKeeper分布式锁的流程
    在zookeeper指定节点(locks)下创建临时顺序节点node_n
    获取locks下所有子节点children
    对子节点按节点自增序号从小到大排序
    判断本节点是不是第一个子节点,若是,则获取锁;若不是,则监听比该节点小的那个节点的删除事件
    若监听事件生效,则回到第二步重新进行判断,直到获取到锁

    具体实现
    下面就具体使用java和zookeeper实现分布式锁,操作zookeeper使用的是apache提供的zookeeper的包。
    通过实现Watch接口,实现process(WatchedEvent event)方法来实施监控,使CountDownLatch来完成监控,
    在等待锁的时候使用CountDownLatch来计数,等到后进行countDown,停止等待,继续运行。
    以下整体流程基本与上述描述流程一致,只是在监听的时候使用的是CountDownLatch来监听前一个节点。

    (1) pom.xml

    <dependency>
                <groupId>org.apache.zookeeper</groupId>
                <artifactId>zookeeper</artifactId>
                <version>3.4.11</version>
            </dependency>

    (2)DistributedLock.java

    package com.test.lock;
    
    import org.apache.zookeeper.*;
    import org.apache.zookeeper.data.Stat;
    
    import java.io.IOException;
    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.List;
    import java.util.concurrent.CountDownLatch;
    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.Lock;
    
    
    public class DistributedLock implements Lock, Watcher {
        private ZooKeeper zk = null;
        // 根节点
        private String ROOT_LOCK = "/locks";
        // 竞争的资源
        private String lockName;
        // 等待的前一个锁
        private String WAIT_LOCK;
        // 当前锁
        private String CURRENT_LOCK;
        // 计数器
        private CountDownLatch countDownLatch;
        private int sessionTimeout = 30000;
        private List<Exception> exceptionList = new ArrayList<Exception>();
    
        /**
         * 配置分布式锁
         * @param config 连接的url
         * @param lockName 竞争资源
         */
        public DistributedLock(String config, String lockName) {
            this.lockName = lockName;
            try {
                // 连接zookeeper
                zk = new ZooKeeper(config, sessionTimeout, this);
                Stat stat = zk.exists(ROOT_LOCK, false);
                if (stat == null) {
                    // 如果根节点不存在,则创建根节点
                    zk.create(ROOT_LOCK, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
                }
            } catch (IOException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (KeeperException e) {
                e.printStackTrace();
            }
        }
    
        // 节点监视器
        public void process(WatchedEvent event) {
            if (this.countDownLatch != null) {
                this.countDownLatch.countDown();
            }
        }
    
        public void lock() {
            if (exceptionList.size() > 0) {
                throw new LockException(exceptionList.get(0));
            }
            try {
                if (this.tryLock()) {
                    System.out.println(Thread.currentThread().getName() + " " + lockName + "获得了锁");
                    return;
                } else {
                    // 等待锁
                    waitForLock(WAIT_LOCK, sessionTimeout);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (KeeperException e) {
                e.printStackTrace();
            }
        }
    
        public boolean tryLock() {
            try {
                String splitStr = "_lock_";
                if (lockName.contains(splitStr)) {
                    throw new LockException("锁名有误");
                }
                // 创建临时有序节点
                CURRENT_LOCK = zk.create(ROOT_LOCK + "/" + lockName + splitStr, new byte[0],
                        ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
                System.out.println(CURRENT_LOCK + " 已经创建");
                // 取所有子节点
                List<String> subNodes = zk.getChildren(ROOT_LOCK, false);
                // 取出所有lockName的锁
                List<String> lockObjects = new ArrayList<String>();
                for (String node : subNodes) {
                    String _node = node.split(splitStr)[0];
                    if (_node.equals(lockName)) {
                        lockObjects.add(node);
                    }
                }
                Collections.sort(lockObjects);
                System.out.println(Thread.currentThread().getName() + " 的锁是 " + CURRENT_LOCK);
                // 若当前节点为最小节点,则获取锁成功
                if (CURRENT_LOCK.equals(ROOT_LOCK + "/" + lockObjects.get(0))) {
                    return true;
                }
    
                // 若不是最小节点,则找到自己的前一个节点
                String prevNode = CURRENT_LOCK.substring(CURRENT_LOCK.lastIndexOf("/") + 1);
                WAIT_LOCK = lockObjects.get(Collections.binarySearch(lockObjects, prevNode) - 1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (KeeperException e) {
                e.printStackTrace();
            }
            return false;
        }
    
        public boolean tryLock(long timeout, TimeUnit unit) {
            try {
                if (this.tryLock()) {
                    return true;
                }
                return waitForLock(WAIT_LOCK, timeout);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return false;
        }
    
        // 等待锁
        private boolean waitForLock(String prev, long waitTime) throws KeeperException, InterruptedException {
            Stat stat = zk.exists(ROOT_LOCK + "/" + prev, true);
    
            if (stat != null) {
                System.out.println(Thread.currentThread().getName() + "等待锁 " + ROOT_LOCK + "/" + prev);
                this.countDownLatch = new CountDownLatch(1);
                // 计数等待,若等到前一个节点消失,则precess中进行countDown,停止等待,获取锁
                this.countDownLatch.await(waitTime, TimeUnit.MILLISECONDS);
                this.countDownLatch = null;
                System.out.println(Thread.currentThread().getName() + " 等到了锁");
            }
            return true;
        }
    
        public void unlock() {
            try {
                System.out.println("释放锁 " + CURRENT_LOCK);
                zk.delete(CURRENT_LOCK, -1);
                CURRENT_LOCK = null;
                zk.close();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (KeeperException e) {
                e.printStackTrace();
            }
        }
    
        public Condition newCondition() {
            return null;
        }
    
        public void lockInterruptibly() throws InterruptedException {
            this.lock();
        }
    
    
        public class LockException extends RuntimeException {
            private static final long serialVersionUID = 1L;
            public LockException(String e){
                super(e);
            }
            public LockException(Exception e){
                super(e);
            }
        }
    }

    (3)测试类

    package com.test.lock;
    
    public class Test {
        static int n = 500;
    
        public static void secskill() {
            System.out.println(--n);
        }
    
        public static void main(String[] args) {
            
            Runnable runnable = new Runnable() {
                public void run() {
                    DistributedLock lock = null;
                    try {
                        lock = new DistributedLock("192.168.0.105:2181", "test1");
                        lock.lock();
                        secskill();
                        System.out.println(Thread.currentThread().getName() + "正在运行");
                    } finally {
                        if (lock != null) {
                            lock.unlock();
                        }
                    }
                }
            };
    
            for (int i = 0; i < 10; i++) {
                Thread t = new Thread(runnable);
                t.start();
            }
        }
    }

     总体来说,如果了解到整个实现流程,使用zookeeper实现分布式锁并不是很困难,不过这也只是一个简单的实现,与前面实现Redis实现相比,本实现的稳定性更强,这是因为zookeeper的特性所致,在外界看来,zookeeper集群中每一个节点都是一致的。

    原文:https://www.cnblogs.com/liuyang0/p/6800538.html

    Redis实现分布式锁原因

    • Redis有很高的性能
    • Redis命令对此支持较好,实现起来比较方便

    使用命令介绍

    SETNX

    SETNX key val
    当且仅当key不存在时,set一个key为val的字符串,返回1;若key存在,则什么都不做,返回0。

    expire

    expire key timeout
    为key设置一个超时时间,单位为second,超过这个时间锁会自动释放,避免死锁。

    delete

    delete key
    删除key

    在使用Redis实现分布式锁的时候,主要就会使用到这三个命令。

    实现

    使用的是jedis来连接Redis。

    实现思想

    • 获取锁的时候,使用setnx加锁,并使用expire命令为锁添加一个超时时间,超过该时间则自动释放锁,锁的value值为一个随机生成的UUID,通过此在释放锁的时候进行判断。
    • 获取锁的时候还设置一个获取的超时时间,若超过这个时间则放弃获取锁。
    • 释放锁的时候,通过UUID判断是不是该锁,若是该锁,则执行delete进行锁释放。
    package redis;
    
    import redis.clients.jedis.Jedis;
    import redis.clients.jedis.JedisPool;
    import redis.clients.jedis.Transaction;
    import redis.clients.jedis.exceptions.JedisException;
    
    import java.util.List;
    import java.util.UUID;
    
    public class DistributedLock {
        private final JedisPool jedisPool;
    
        public DistributedLock(JedisPool jedisPool) {
            this.jedisPool = jedisPool;
        }
    
        /**
         * 加锁
         * @param locaName  锁的key
         * @param acquireTimeout  获取超时时间
         * @param timeout   锁的超时时间
         * @return 锁标识
         */
        public String lockWithTimeout(String locaName,
                                      long acquireTimeout, long timeout) {
            Jedis conn = null;
            String retIdentifier = null;
            try {
                // 获取连接
                conn = jedisPool.getResource();
                // 随机生成一个value
                String identifier = UUID.randomUUID().toString();
                // 锁名,即key值
                String lockKey = "lock:" + locaName;
                // 超时时间,上锁后超过此时间则自动释放锁
                int lockExpire = (int)(timeout / 1000);
    
                // 获取锁的超时时间,超过这个时间则放弃获取锁
                long end = System.currentTimeMillis() + acquireTimeout;
                while (System.currentTimeMillis() < end) {
                    if (conn.setnx(lockKey, identifier) == 1) {
                        conn.expire(lockKey, lockExpire);
                        // 返回value值,用于释放锁时间确认
                        retIdentifier = identifier;
                        return retIdentifier;
                    }
                    // 返回-1代表key没有设置超时时间,为key设置一个超时时间
                    if (conn.ttl(lockKey) == -1) {
                        conn.expire(lockKey, lockExpire);
                    }
    
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                    }
                }
            } catch (JedisException e) {
                e.printStackTrace();
            } finally {
                if (conn != null) {
                    conn.close();
                }
            }
            return retIdentifier;
        }
    
        /**
         * 释放锁
         * @param lockName 锁的key
         * @param identifier    释放锁的标识
         * @return
         */
        public boolean releaseLock(String lockName, String identifier) {
            Jedis conn = null;
            String lockKey = "lock:" + lockName;
            boolean retFlag = false;
            try {
                conn = jedisPool.getResource();
                while (true) {
                    // 监视lock,准备开始事务
                    conn.watch(lockKey);
                    // 通过前面返回的value值判断是不是该锁,若是该锁,则删除,释放锁
                    if (identifier.equals(conn.get(lockKey))) {
                        Transaction transaction = conn.multi();
                        transaction.del(lockKey);
                        List<Object> results = transaction.exec();
                        if (results == null) {
                            continue;
                        }
                        retFlag = true;
                    }
                    conn.unwatch();
                    break;
                }
            } catch (JedisException e) {
                e.printStackTrace();
            } finally {
                if (conn != null) {
                    conn.close();
                }
            }
            return retFlag;
        }
    }

    测试

    下面就用一个简单的例子测试刚才实现的分布式锁。
    例子中使用50个线程模拟秒杀一个商品,使用--运算符来实现商品减少,从结果有序性就可以看出是否为加锁状态。

    模拟秒杀服务,在其中配置了jedis线程池,在初始化的时候传给分布式锁,供其使用。

    package redis;
    
    import redis.clients.jedis.JedisPool;
    import redis.clients.jedis.JedisPoolConfig;
    
    
    public class Service {
        private static JedisPool pool = null;
    
        static {
            JedisPoolConfig config = new JedisPoolConfig();
            // 设置最大连接数
            config.setMaxTotal(200);
            // 设置最大空闲数
            config.setMaxIdle(8);
            // 设置最大等待时间
            config.setMaxWaitMillis(1000 * 100);
            // 在borrow一个jedis实例时,是否需要验证,若为true,则所有jedis实例均是可用的
            config.setTestOnBorrow(true);
            pool = new JedisPool(config, "127.0.0.1", 6379, 3000);
        }
    
        DistributedLock lock = new DistributedLock(pool);
    
        int n = 500;
    
        public void seckill() {
            // 返回锁的value值,供释放锁时候进行判断
            String indentifier = lock.lockWithTimeout("resource", 5000, 1000);
            System.out.println(Thread.currentThread().getName() + "获得了锁");
            System.out.println(--n);
            lock.releaseLock("resource", indentifier);
        }
    }

     模拟线程进行秒杀服务

    package redis;
    
    public class ThreadA extends Thread {
        private Service service;
    
        public ThreadA(Service service) {
            this.service = service;
        }
    
        @Override
        public void run() {
            service.seckill();
        }
    }
    public class Test {
        public static void main(String[] args) {
            Service service = new Service();
            for (int i = 0; i < 50; i++) {
                ThreadA threadA = new ThreadA(service);
                threadA.start();
            }
        }
    }

    zookeeper实现分布式锁,使用zookeeper的可靠性是要大于使用redis实现的分布式锁的,但是相比而言,redis的性能更好。

     原文:https://www.cnblogs.com/liuyang0/p/6744076.html

    好记性不如烂笔头
  • 相关阅读:
    转:全面理解Javascript闭包和闭包的几种写法及用途
    VS2010 AnkhSvn
    silverlight 子UserControl获取父UserControl
    IIS相关
    (转)Javascript中console.log()用法
    (转)Sql Server 保留几位小数的两种做法
    asp.net Web API
    (转)C#中数组、ArrayList和List三者的区别
    PowerDesigner
    (转)jQuery.fn.extend与jQuery.extend到底区别在哪?
  • 原文地址:https://www.cnblogs.com/codehello/p/11410791.html
Copyright © 2020-2023  润新知