黑马点评05分布式锁 1互斥锁和过期时间

实战篇-09.分布式锁-基本原理和不同实现方式对比_哔哩哔哩_bilibili

1.分布式锁

因为jvm内部的sychonized锁无法在不同jvm之间共享锁监视器,所以需要一个jvm外部的锁来共享。

黑马点评05分布式锁 1互斥锁和过期时间_第1张图片

黑马点评05分布式锁 1互斥锁和过期时间_第2张图片

2.redis setnx互斥锁

加锁解锁即可

黑马点评05分布式锁 1互斥锁和过期时间_第3张图片

黑马点评05分布式锁 1互斥锁和过期时间_第4张图片

黑马点评05分布式锁 1互斥锁和过期时间_第5张图片

2.1不释放锁可能死锁

redis 的setnx不会自动释放锁,要是加锁后服务宕机,锁得不到释放可能死锁。

所以需要给锁加过期时间。

2.2保证加锁和过期时间的原子性

用set + 参数的方式同时设置锁和过期时间,保证不会因为过期时间没来及设置就宕机导致死锁

最终版本 :

黑马点评05分布式锁 1互斥锁和过期时间_第6张图片

黑马点评05分布式锁 1互斥锁和过期时间_第7张图片

到此为止基本完成了分布式锁,但是还可以加以改进

2.3.其他线程失败后是否阻塞?

一般用非阻塞式,阻塞式浪费cpu而且实现麻烦。

阻塞式就是发现别人用锁,就一直等待。

非阻塞式就是别人拿锁我就返回。

黑马点评05分布式锁 1互斥锁和过期时间_第8张图片

3.实现redis set nx分布式锁 

 黑马点评05分布式锁 1互斥锁和过期时间_第9张图片

3.1获取redis分布式锁

private String name; //业务名字
    private StringRedisTemplate stringRedisTemplate;

    private static final String KEY_PREFIX = "lock:"; //规范名字
    private static final String ID_PREFIX = UUID.randomUUID() + "-";
    private static final DefaultRedisScript UNLOCK_SCRIPT;

    public SimpleRedisLock(String name, StringRedisTemplate stringRedisTemplate) {
        this.name = name;
        this.stringRedisTemplate = stringRedisTemplate;
    }

    @Override
    public boolean tryLock(long timeoutSec) {
        //获取线程标示
        String threadId = ID_PREFIX + Thread.currentThread().getId();
        //获取锁 set  key value  NX  EX 过期时间
        Boolean success = stringRedisTemplate.opsForValue()
                .setIfAbsent(KEY_PREFIX + name, threadId, timeoutSec, TimeUnit.SECONDS);
        return Boolean.TRUE.equals(success);  //防拆箱空指针
    }

3.2释放redis分布式锁

黑马点评05分布式锁 1互斥锁和过期时间_第10张图片

4.业务使用redis分布式锁

在订单创建业务那里把sychnoized锁改成自己实现的分布式锁(获取+解锁)

黑马点评05分布式锁 1互斥锁和过期时间_第11张图片

5.服务阻塞导致分布式锁误删问题(判断锁的线程标识)

业务1阻塞时间太长,导致锁过期自动删除,

5.1解决方式:判断线程标识符是否是自己的,需要一个全局唯一线程标识符

黑马点评05分布式锁 1互斥锁和过期时间_第12张图片

黑马点评05分布式锁 1互斥锁和过期时间_第13张图片

黑马点评05分布式锁 1互斥锁和过期时间_第14张图片

 每个jvm内部的线程号是一种递增的数字,但是不同的jvm之间线程号可能冲突,所以需要找一种方法 区分不仅jvm内部而且jvm之间的线程。

uuid是一种唯一识别码,能保证不同的服务(jvm)的uuid一定不一样。

所以用 uuid + jvm内部线程id的方式来唯一标识所有jvm中的线程

小科普:通用唯一标识码UUID的介绍及使用 - 知乎 (zhihu.com)

5.1.1实现(加锁和释放时加上了唯一线程标识判断)

黑马点评05分布式锁 1互斥锁和过期时间_第15张图片

黑马点评05分布式锁 1互斥锁和过期时间_第16张图片

5.2另一种误删问题(判断完之后阻塞丢锁,后面又释放,需要保证线程id和释放锁的原子性)

黑马点评05分布式锁 1互斥锁和过期时间_第17张图片

实战篇-15.分布式锁-Lua脚本解决多条命令原子性问题_哔哩哔哩_bilibili

5.2.1保证原子性--lua脚本

调用redis提供的call函数,传入redis命令参数

黑马点评05分布式锁 1互斥锁和过期时间_第18张图片

黑马点评05分布式锁 1互斥锁和过期时间_第19张图片

黑马点评05分布式锁 1互斥锁和过期时间_第20张图片

为了传参而把参数位留空后:

黑马点评05分布式锁 1互斥锁和过期时间_第21张图片

 5.2.2java调用lua脚本

黑马点评05分布式锁 1互斥锁和过期时间_第22张图片

 提前读取好lua文件,避免频繁读取,等会调用。

黑马点评05分布式锁 1互斥锁和过期时间_第23张图片

为了维持 释放锁时 判断线程id和释放锁操作的原子性,重写unlcok方法

黑马点评05分布式锁 1互斥锁和过期时间_第24张图片

6.最终该类代码

package com.hmdp.utils;

import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;

import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

public class SimpleRedisLock implements ILock {

    private String name; //业务名字
    private StringRedisTemplate stringRedisTemplate;

    private static final String KEY_PREFIX = "lock:"; //规范名字
    private static final String ID_PREFIX = UUID.randomUUID() + "-";
    private static final DefaultRedisScript UNLOCK_SCRIPT;

    static {
        UNLOCK_SCRIPT = new DefaultRedisScript<>();
        UNLOCK_SCRIPT.setLocation(new ClassPathResource("unlock.lua"));
        UNLOCK_SCRIPT.setResultType(Long.class);
    }

    public SimpleRedisLock(String name, StringRedisTemplate stringRedisTemplate) {
        this.name = name;
        this.stringRedisTemplate = stringRedisTemplate;
    }

    @Override
    public boolean tryLock(long timeoutSec) {
        //获取线程标示
        String threadId = ID_PREFIX + Thread.currentThread().getId();
        //获取锁 set  key value  NX  EX 过期时间
        Boolean success = stringRedisTemplate.opsForValue()
                .setIfAbsent(KEY_PREFIX + name, threadId, timeoutSec, TimeUnit.SECONDS);
        return Boolean.TRUE.equals(success);  //防拆箱空指针
    }

    @Override
    public void unlock() {
        //调用lua脚本
        stringRedisTemplate.execute(UNLOCK_SCRIPT,
                Collections.singletonList(KEY_PREFIX + name),
                ID_PREFIX + Thread.currentThread().getId()
        );
    }

    /*
    *
    @Override
    public void unLock() {
        //获取线程标识
        String threadId = ID_PREFIX + Thread.currentThread().getId();
        //获取锁中的标识
        String id = stringRedisTemplate.opsForValue().get(KEY_PREFIX + name);
        //判断标识是否一致
        if (threadId.equals(id)) {
            //释放锁
            stringRedisTemplate.delete(KEY_PREFIX + name);
        }
    }
    * */
}

黑马点评05分布式锁 1互斥锁和过期时间_第25张图片

你可能感兴趣的:(java项目,分布式)