Redis分布式锁

分布式锁的原理:基于redis的setnx命令,setnx的做用就是设置一个key值,若是在redis中该key值不存在就设置成功,若是存在就会设置失败。在分布式集群环境下,多个服务器的线程同时设置一个key,哪一个服务器的线程设置成功,就表示该服务器的线程得到了锁对象,其余线程必须等待。得到锁的线程须要记得,在某个时刻进行锁的释放(删除那个key)。java

实现思路:redis

(1)获取锁的时候,使用setnx加锁,并使用expire命令为锁添加一个超时时间,超过该时间则自动释放锁,锁的value值为一个随机生成的UUID,经过此在释放锁的时候进行判断。
(2)获取锁的时候还设置一个获取的超时时间,若超过这个时间则放弃获取锁。
(3)释放锁的时候,经过UUID判断是否是该锁,如果该锁,则执行delete进行锁释放。spring

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.UUID;

/**
 * 分布式锁的工具类
 */
@Component
public class RedisLockUtil {

    @Autowired
    private RedisTemplate redisTemplate;

    //redis原始链接对象
    private RedisConnection redisConnection;

    //lua脚本的缓存签名字符串
    private String lockSha;
    private String unlockSha;

    private ThreadLocal<String> threadLocal = new ThreadLocal<>();

    /**
     * 添加分布式锁的Lua脚本(Lua脚本能够保证命令的原子性操做,所以在须要线程安全的操做时,咱们能够考虑Lua脚本)
     */
    private String lockLua = "local key = KEYS[1]\n" +
            "local value = ARGV[1]\n" +
            "local time = ARGV[2]\n" +
            "\n" +
            "local result = redis.call('setnx', key, value)\n" +
            "if result == 1 then\n" +
            "  --当前得到了分布式锁\n" +
            "  --设置锁的过时时间\n" +
            "  redis.call('expire', key, time)\t\n" +
            "  return true\t\n" +
            "end\n" +
            "\n" +
            "--没有得到分布式锁\n" +
            "return false";

    //解锁的lua脚本
    private String unlockLua = "--要删除的是什么锁\n" +
            "local key = KEYS[1]\n" +
            "local uuid = ARGV[1]\n" +
            "\n" +
            "--获取锁中的uuid\n" +
            "local lockUUID = redis.call('get', key)\n" +
            "\n" +
            "--判断是否是本身上的锁\n" +
            "if uuid == lockUUID then\n" +
            "  --是本身上的锁,删除\n" +
            "  redis.call('del', key)\n" +
            "  return true\n" +
            "end\n" +
            "\n" +
            "--不是本身上的锁\n" +
            "return false";

    @PostConstruct
    public void init(){
        //得到原始链接
        redisConnection = redisTemplate.getConnectionFactory().getConnection();

        //缓存lua脚本
        lockSha = redisConnection.scriptLoad(lockLua.getBytes());
        unlockSha = redisConnection.scriptLoad(unlockLua.getBytes());
    }

    /**
     * 加锁的方法
     * @return
     */
    public boolean lock(String key, int timeout){

        String uuid = UUID.randomUUID().toString();
        //线程间的uuid数据隔离
        threadLocal.set(uuid);

        //执行加锁的lua脚本
        boolean flag = redisConnection.evalSha(lockSha, ReturnType.BOOLEAN, 1,
                key.getBytes(), uuid.getBytes(), (timeout + "").getBytes());

        return flag;
    }

    /**
     * 解锁的方法
     * @return
     */
    public boolean unlock(String key){
        //解锁原则:谁加的锁,谁来解锁。
        String uuid = threadLocal.get();

        //执行解锁的lua
        boolean flag = redisConnection.evalSha(unlockSha, ReturnType.BOOLEAN, 1,
                key.getBytes(), uuid.getBytes());

        return flag;
    }

}

 

分布锁的应用(Service层加锁)数据库

import com.qf.stu.student_demo.dao.IStuDao;
import com.qf.stu.student_demo.entity.Student;
import com.qf.stu.student_demo.service.IStuService;
import com.qf.stu.student_demo.until.RedisLockUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.concurrent.TimeUnit;


@Service
public class StuServiceImpl implements IStuService {

    @Autowired
    private IStuDao stuDao;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private RedisLockUtil redisLockUtil;
    /**
     * 分布式集群架构-分布式锁
     */
    @Override
    public List<Student> queryAll(){
        //先查询redis是否有缓存该数据
        List<Student> stus = (List<Student>) redisTemplate.opsForValue().get("stus");
        //判断缓存中是否存在
        if (stus == null) {
            //经过lua脚本得到分布式锁
            boolean flag = redisLockUtil.lock("lock", 120);
            if (flag){
                //得到分布式锁,进行缓存重建
                stus = stuDao.queryAll();//查询数据库
                //重建缓存
                redisTemplate.opsForValue().set("stus", stus);
                //设置过时时间
                redisTemplate.expire("stus", 5, TimeUnit.MINUTES);
                //释放锁
                redisLockUtil.unlock("lock");
            }else { //未拿到分布式锁,则休眠50毫秒后,再递归调用 queryAll()
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return this.queryAll();
            }
        }
        return stus;
    }

/*    *//**
     * 单体架构
     * @return
     *//*
    @Override
    public List<Student> queryAll() {
        //先查询缓存
        List<Student> stus = (List<Student>) redisTemplate.opsForValue().get("stus");
        //判断缓存中是否存在
        if(stus == null){
            synchronized (this){
                stus = (List<Student>) redisTemplate.opsForValue().get("stus");
                if (stus == null){
                    System.out.println("查询了数据库");
                    stus = stuDao.queryAll();
                }
            }
            //重建缓存
            redisTemplate.opsForValue().set("stus",stus);
            //设置过时时间
            redisTemplate.expire("stus",5, TimeUnit.MINUTES);
        }

        return stus;
    }*/
}

 

================  SpringBoot提供的操做缓存服务器的API ================缓存

@Cacheable:标记了当前注解的方法,在执行这个方法前,会先去缓存服务中查询数据,若是有就不会调用目标方法,若是没有再调用目标方法,而且重建缓存 - 主要做用于查询方法
@CachePut:该注解做用和@Cacheable差很少,惟一的区别在于被@CachePut注解标记的方法,必定会被执行。被标记方法的返回值会添加到缓存服务中 - 主要做用于添加的方法
@CacheEvict:根据表达式,删除缓存服务器的某个注解 - 主要用于修改和删除的方法
@Caching:能够帮助开发者在同一个方法上标记多个相同的缓存注解安全

import com.qf.stu.student_demo.dao.IStuDao;
import com.qf.stu.student_demo.entity.Student;
import com.qf.stu.student_demo.service.IStuService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
@Primary
public class StuServiceSpringCacheImpl implements IStuService {

    @Autowired
    private IStuDao stuDao;

    @Override
    @Cacheable(cacheNames = "stu",key = "'stulist'")
    public List<Student> queryAll() {
        System.out.println("查询全部学生的方法");
        return stuDao.queryAll();
    }

    @Override
    @CachePut(cacheNames = "stu",key = "'stuone' + #result.id")//从当前方法的返回值中找到id的属性,做为stuone的key值
    @CacheEvict(cacheNames = "stu",key ="'stulist'")
    public Student insert(Student student) {
        System.out.println("添加学生到数据库");
        stuDao.insert(student);
        return student;
    }

    @Override
    @Cacheable(cacheNames = "stu",key = "'stuone' + #id")//从参数中获取id
    public Student queryOne(Integer id) {
        System.out.println("根据id查询学生信息");
        return stuDao.queryOne(id);
    }

    @Override
    @Caching(evict = {
            @CacheEvict(cacheNames = "stu" ,key = "'stulist'"),
            @CacheEvict(cacheNames = "stu" ,key = "'stulist' + #id")
    })
    public int deleteById(Integer id) {
        return stuDao.deleteById(id);
    }
}

注意:使用这些SpringBoot提供的操做缓存服务器的API,启动类上必须加@EnableCaching注解服务器

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;

@SpringBootApplication
@EnableCaching
public class StudentDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(StudentDemoApplication.class, args);
    }

}

 

application.yml配置架构

redis:  host: xx.xx.xx.xx  password: rootcache:  type: redis  redis:    time-to-live: 60000
相关文章
相关标签/搜索