• Redis分布式锁


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

    实现思路:

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

    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]
    " +
                "local value = ARGV[1]
    " +
                "local time = ARGV[2]
    " +
                "
    " +
                "local result = redis.call('setnx', key, value)
    " +
                "if result == 1 then
    " +
                "  --当前获得了分布式锁
    " +
                "  --设置锁的过期时间
    " +
                "  redis.call('expire', key, time)	
    " +
                "  return true	
    " +
                "end
    " +
                "
    " +
                "--没有获得分布式锁
    " +
                "return false";
    
        //解锁的lua脚本
        private String unlockLua = "--要删除的是什么锁
    " +
                "local key = KEYS[1]
    " +
                "local uuid = ARGV[1]
    " +
                "
    " +
                "--获取锁中的uuid
    " +
                "local lockUUID = redis.call('get', key)
    " +
                "
    " +
                "--判断是不是自己上的锁
    " +
                "if uuid == lockUUID then
    " +
                "  --是自己上的锁,删除
    " +
                "  redis.call('del', key)
    " +
                "  return true
    " +
                "end
    " +
                "
    " +
                "--不是自己上的锁
    " +
                "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: root
    cache:
    type: redis
    redis:
    time-to-live: 60000
  • 相关阅读:
    银行家算法实例(转)
    DNS中的七大资源记录介绍!(转)
    android之存储篇_SQLite数据库_让你彻底学会SQLite的使用(转)
    回顾HTML5的语义化元素
    vueJs2.0学习笔记(六)
    vueJs2.0学习笔记(五)
    vueJs的学习笔记(四)
    vueJs2.0学习笔记(三)
    vueJs的学习笔记(二)
    vueJs 2.0学习笔记(一)
  • 原文地址:https://www.cnblogs.com/wakey/p/10725786.html
Copyright © 2020-2023  润新知