分佈式鎖的原理:基於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