分布式锁

在分布式场景下,需要同步的进程可能位于不同的节点上,那么就需要使用分布式锁

阻塞锁使用一个互斥量来实现:

可以用一个整数表示,或者也可以用某个数据是否存在来表示

数据库唯一索引

获得锁时向表中插入一条记录,释放锁时删除这条记录

redis setnx

1.获取锁的时候,对某个key执行setnx,加锁(如果设置成功(获得锁)返回1,否则返回0),并使用expire命令为锁添加一个超时时间,超过该时间则自动释放锁,锁的value值为一个随机生成的UUID,通过此在释放锁的时候进行判断。

2.获取锁的时候还设置一个获取的超时时间(防止死锁),若超过这个时间则放弃获取锁。

3.释放锁的时候,通过UUID判断是不是该锁,若是该锁,则执行delete进行锁释放

实现

public class RedisLock {        private StringRedisTemplate template;    private static final String LOCK_KEY = "LOCK";    private String identifyValue;    public RedisLock(StringRedisTemplate template) {this.template = template;}    /**     * @param acquireTimeout 获取锁之前的超时时间     * @param expireTime     锁的过期时间     * @return     */    public boolean lock(long acquireTimeout, long expireTime) {        // 获取锁的时间        long inTime = System.currentTimeMillis();        identifyValue = UUID.randomUUID().toString();        for (; ; ) {            // 判断获取锁是否超时            if (System.currentTimeMillis() - inTime >= acquireTimeout) {                return false;            }            // 通过setnx的方式来获取锁            if (template.opsForValue().setIfAbsent(LOCK_KEY, identifyValue, expireTime, TimeUnit.MILLISECONDS)) {                // 获取锁成功                return true;            }            // 获取锁失败,继续自旋        }    }    public void release() {        if (identifyValue == null){            throw new IllegalStateException("没有获取锁");        }        // 删除的时候验证value,必须确保释放的锁是自己创建的        if (!identifyValue.equals(template.opsForValue().get(LOCK_KEY))){            throw new IllegalStateException("锁的value不一致");        }        template.delete(LOCK_KEY);    }}

与zookeeper比较

相对比来说Redis比Zookeeper性能要好,从可靠性角度分析,Zookeeper可靠性比Redis更好。因为Redis有效期不是很好控制,可能会产生有效期延迟

redis redlock

使用了多个 Redis 实例来实现分布式锁,这是为了保证在发生单点故障时仍然可用

计算获取锁消耗的时间,只有消耗的时间小于锁的过期时间,并且从大多数(N / 2 + 1)实例上获取了锁,才认为获取锁成功如果获取锁失败,就到每个实例上释放锁

zookeeper临时节点

多个进程同时在zookeeper.上创建同一个相同的节点(/lock) , 因为zookeeper节点是唯一的,如果是唯一的话,那么同时如果有多个客户端创建相同的节点/lock的话,最终只有看谁能够快速的抢资源,谁就能创建/lock节点,创建节点不成功的进程,会注册一个监听事件,等节点被删除的时候,重新竞争这个锁这个时候节点类型应该使用临时类型。

当一个进程释放锁后(关闭zk连接或者会话超时),临时节点会被删除,等待锁的其他进程会收到节点被删除的通知,这些等待的进程会重新参与到竞争

需要注意的是,要根据业务设置锁等待时间,避免死锁

实现

public void lock() {    // 尝试获取锁,如果成功,就真的成功了    if (tryLock()) {        System.out.println(Thread.currentThread().getName() + "获取锁成功");    // 否则等待锁    } else {        waitLock();         // 当等待被唤醒后重新去竞争锁        lock();    }}private boolean tryLock() {    try {        // 通过zk创建临时节点的成功与否来表示是否获得锁        zkClient.createEphemeral("/lock");        return true;    } catch (Exception e) {        return false;    }}private void waitLock() {    // 监听节点被删除的事件    zkClient.subscribeDataChanges("/lock", new IZkDataListener() {        @Override        public void handleDataDeleted(String s) throws Exception {            // 如果节点被删除,唤醒latch            if (latch != null) {                latch.countDown();            }        }    });    // 如果zk有lock这个锁    if (zkClient.exists("/lock")) {        // 在这里进行等待,直至被上面的事件监听唤醒        latch = new CountDownLatch(1);        try {            latch.await();        } catch (InterruptedException e) {            throw new RuntimeException(e);        }    }    // 等待完成删除所有监听事件,避免监听器堆积影响性能    zkClient.unsubscribeAll();}
public void release() {    if (zkClient != null) {        // 关闭zk客户端,临时节点也随之被删除,相当于释放锁,让其他人去竞争        zkClient.close();        System.out.println(Thread.currentThread().getName()+"释放锁完成");    }}

zookeeper临时顺序节点

有一把锁,被多个人给竞争,此时多个人会排队,第一个拿到锁的人会执行,然后释放锁;后面的每个人都会去监听排在自己前面的那个人创建的 node 上,一旦某个人释放了锁,排在自己后面的人就会被 zookeeper 给通知,一旦被通知了之后,就 ok 了,自己就获取到了锁

zk锁 vs redis锁

分布式Session

集群产生的问题

服务器集群后,因为session是存放在服务器上,客户端会使用同一个Sessionid在多个不同的服务器上获取对应的Session,从而会导致Session不一致问题

解决方案

SpringSession 使用redis存储session

<dependency>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency>    <groupId>org.springframework.session</groupId>    <artifactId>spring-session-data-redis</artifactId></dependency>
@EnableRedisHttpSession

这时候,Session的存取都是通过redis来了