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
相關文章
相關標籤/搜索