ce分布式锁原理:占用公共访问的地方redis或mysql,先到先得,后到要等。
* 1.双写模式:修改数据库数据同时,也修改redis缓存数据,存在问题,网络交互或者cpu处理速度的差异,并发修改会产生脏数据
* 2.失效模式:修改数据同时删除缓存数据,等待下次主动查询更新缓存
* 思考:如何保证缓存数据与数据库同步,保持一致?要加读写锁;缓存数据本就不应该是实时性,一致性超高的,要求实时性,一致性超高的数据,直接去查数据,简化系统。
- package com.hmdp.utils;
-
- import com.fasterxml.jackson.core.type.TypeReference;
- import lombok.extern.slf4j.Slf4j;
- import org.apache.commons.lang3.StringUtils;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.beans.factory.annotation.Value;
- import org.springframework.cloud.context.config.annotation.RefreshScope;
- import org.springframework.data.redis.core.BoundValueOperations;
- import org.springframework.data.redis.core.StringRedisTemplate;
- import org.springframework.data.redis.core.script.DefaultRedisScript;
- import org.springframework.stereotype.Component;
-
- import java.time.LocalDateTime;
- import java.util.Collections;
- import java.util.Map;
- import java.util.UUID;
- import java.util.concurrent.TimeUnit;
- import java.util.function.Function;
-
- /**
- * User: ldj
- * Date: 2022/9/3
- * Time: 16:55
- * Description: Redis分布锁工具类,目前没有做续机,所以过期时间调大一点,确保业务执行完成
- * 定时任务进行锁续命
- */
- @Slf4j
- @Component
- @RefreshScope
- public class RedisUtil {
-
- private static Integer shortTime;
- private static Long expiredTime;
-
- private static StringRedisTemplate stringRedisTemplate;
-
- @Value("${redis.lock.expiredTime:60}")
- public void setExpiredTime(Long expiredTime) {
- RedisUtil.expiredTime = expiredTime;
- }
-
- @Value("${redis.lock.shortTime:10}")
- public void setExpiredTime(Integer shortTime) {
- RedisUtil.shortTime = shortTime;
- }
-
- @Autowired
- public void setStringRedisTemplate(StringRedisTemplate stringRedisTemplate) {
- RedisUtil.stringRedisTemplate = stringRedisTemplate;
- }
-
- //避免因线程1执行业务很长,lock1过期了,线程2进来创建lock2,当线程2执行到一半时,线程1执行完业务释放锁是lock2
- private static String uuid;
-
- //获取锁+设置过期时间 原子性
- public static Boolean getLock() {
- uuid = UUID.randomUUID().toString().replaceAll("-", "");
- Boolean isLock = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid, expiredTime, TimeUnit.SECONDS);
- if (isLock != null && isLock) {
- log.info("[Redis] 添加锁成功 releaseUuid:[{}]", uuid);
- return true;
- } else {
- log.warn("[Redis] 添加锁失败!");
- return false;
- }
- }
-
- //释放锁+获取对比值 原子性
- public static void releaseLock() {
- String luaScript = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
- Long flag = stringRedisTemplate.execute(new DefaultRedisScript
(luaScript, Long.class), Collections.singletonList("lock"), uuid); - if (flag != null && flag == 1) {
- log.info("[Redis] 释放锁成功 releaseUuid:[{}]", uuid);
- } else {
- log.warn("[Redis] 释放锁失败!");
- }
- }
-
- //设置等待重试时间
- public static void waite(Long waitTime) {
- try {
- TimeUnit.MILLISECONDS.sleep(waitTime);
- } catch (InterruptedException e) {
- log.error("等待异常", e);
- }
- }
-
- //删除key
- public static Boolean removeKey(String key) {
- return stringRedisTemplate.delete(key);
- }
-
- public static void put(String redisKey, Object object, Integer expireTime) {
- if (expireTime != null) {
- stringRedisTemplate.opsForValue().set(redisKey, JacksonUtil.writeValueAsString(object), expireTime, TimeUnit.MINUTES);
- }
- stringRedisTemplate.opsForValue().set(redisKey, JacksonUtil.writeValueAsString(object));
- }
-
- public static void putWithLogicalExpire(String redisKey, Object object, Integer time) {
- RedisData redisData = new RedisData();
- redisData.setData(object);
- redisData.setExpireTime(LocalDateTime.now().plusMinutes(time));
- stringRedisTemplate.opsForValue().set(redisKey, JacksonUtil.writeValueAsString(redisData));
- }
-
- public static
T getValue(String key, Class classType) { - String context = stringRedisTemplate.opsForValue().get(key);
- if (StringUtils.isBlank(context)) {
- return null;
- }
- return JacksonUtil.readValue(context, classType);
- }
-
- public static
T getValue(String key, TypeReference valueTypeRef) { - String context = stringRedisTemplate.opsForValue().get(key);
- if (StringUtils.isBlank(context)) {
- return null;
- }
- return JacksonUtil.readValue(context, valueTypeRef);
- }
-
- public static
R queryAndSet(String key, long expiredTime, Map map, TypeReference valueTypeRef, Function { - BoundValueOperations
operations = stringRedisTemplate.boundValueOps(key); - String context = operations.get();
- if (StringUtils.isNotBlank(context)) {
- return JacksonUtil.readValue(context, valueTypeRef);
- }
-
- // ""或" "
- if (context != null) {
- return null;
- }
-
- //如果为null
- Boolean lock = RedisUtil.getLock();
- if (lock) {
- try {
- R r = callback.apply(map);
- if (r == null) {
- operations.set("", shortTime, TimeUnit.SECONDS);
- return null;
- }
- operations.set(JacksonUtil.writeValueAsString(r), expiredTime, TimeUnit.MINUTES);
- return r;
- } catch (Exception e) {
- log.error(e.getMessage());
- } finally {
- RedisUtil.releaseLock();
- }
- }
- //抢不到锁,重试
- return queryAndSet(key, expiredTime, map, valueTypeRef, callback);
- }
-
- }
改进 能获取多个分布式锁:
- package com.tulin.lock.utils;
-
- import lombok.extern.slf4j.Slf4j;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.beans.factory.annotation.Value;
- import org.springframework.data.redis.core.StringRedisTemplate;
- import org.springframework.data.redis.core.script.DefaultRedisScript;
- import org.springframework.stereotype.Component;
-
- import java.time.LocalDateTime;
- import java.time.ZoneOffset;
- import java.time.format.DateTimeFormatter;
- import java.util.Collections;
- import java.util.UUID;
- import java.util.concurrent.TimeUnit;
-
- /**
- * User: ldj
- * Date: 2022/10/19
- * Time: 14:54
- * Description: No Description
- */
- @Slf4j
- @Component
- public class RedisDistributedLockUtil {
-
- private static final String LOCK_PREFIX = "look:";
- private static final Integer LEFT_SHIFT_BITS = 32;
- private static final Long BEGIN_TIMESTAMP = 1672531200L;
-
- private static Long expiredTime;
- private static StringRedisTemplate stringRedisTemplate;
-
- @Value("${redis.lock.expiredTime:60}")
- public void setExpiredTime(Long expiredTime) {
- RedisDistributedLockUtil.expiredTime = expiredTime;
- }
-
- @Autowired
- public void setStringRedisTemplate(StringRedisTemplate stringRedisTemplate) {
- RedisDistributedLockUtil.stringRedisTemplate = stringRedisTemplate;
- }
-
- //避免因线程1执行业务很长,lock1过期了,线程2进来创建lock2,当线程2执行到一半时,线程1执行完业务释放锁是lock2
- private static String uuid;
-
- //获取锁+设置过期时间 原子性
- public static Boolean getLock(String lockName) {
- uuid = UUID.randomUUID().toString().replaceAll("-", "");
- Boolean isLock = stringRedisTemplate.opsForValue().setIfAbsent(LOCK_PREFIX + lockName, uuid, expiredTime, TimeUnit.SECONDS);
- if (isLock != null && isLock) {
- log.info("[Redis] 添加锁成功 lockName:[{}],releaseUuid:[{}]", lockName, uuid);
- return true;
- } else {
- log.warn("[Redis] 添加锁失败! lockName:[{}]", lockName);
- return false;
- }
- }
-
- //释放锁+获取对比值 原子性
- public static void releaseLock(String lockName) {
- String luaScript = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
- Long flag = stringRedisTemplate.execute(new DefaultRedisScript
(luaScript, Long.class), Collections.singletonList(LOCK_PREFIX + lockName), uuid); - if (flag != null && flag == 1) {
- log.info("[Redis] 释放锁成功 lockName:[{}],releaseUuid:[{}]", lockName, uuid);
- } else {
- log.warn("[Redis] 释放锁失败! lockName:[{}]", lockName);
- }
- }
-
- //设置等待重试时间
- public static void waite(Long waitTime) {
- try {
- TimeUnit.MILLISECONDS.sleep(waitTime);
- } catch (InterruptedException e) {
- log.error("等待异常", e);
- }
- }
-
- public static long getGlobalId(String prefix) {
- //生成时间戳
- LocalDateTime now = LocalDateTime.now();
- long nowSeconds = now.toEpochSecond(ZoneOffset.UTC);
- long timestamp = nowSeconds - BEGIN_TIMESTAMP;
- //生成32位id
- String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));
- Long increment = stringRedisTemplate.opsForValue().increment(prefix + ":" + date);
- //拼接时间戳 或运算同时为0才是0,有1肯定是1
- if (increment == null) {
- return 0L;
- }
- return timestamp << LEFT_SHIFT_BITS | increment;
- }
-
- }
测试类
- @Test
- public void test4() {
- Boolean lock = RedisUtil.getLock();
- System.out.println(lock);
- }
-
- @Test
- public void test5() {
- RedisUtil.releaseLock();
- }
-
- @Test
- public void test6() {
- Boolean lock = RedisUtil.getLock();
- System.out.println(lock);
- RedisUtil.releaseLock();
- }
- /**
- * 2.从数据库获取数据并封装,加Redis锁、
- * 加锁和设置过期时间是原子操作,避免因业务代码出现异常导致死锁
- * 获取值对比和删除锁是原子操作,避免因与redis网络交互完数据,key恰好过期,删掉别的线程的锁 使用lua脚本操作解锁
- */
- public Map
> getDataWithRedisLock() { - //1.加锁成功(加过期时间)->执行业务->释放锁
- Boolean lock = RedisUtil.getLock();
- if (lock != null && lock) {
- Map
> dataFromDb; - try {
- dataFromDb = this.getDataFromDb();
- } finally {
- RedisUtil.deleteLock();
- }
-
- return dataFromDb;
- } else {
- //2.加锁失败->休眠3秒后重试加锁
- RedisUtil.waite(3);
- return this.getDataWithRedisLock();
- }
- }
- @GetMapping("/{id}")
- public Result queryAndSetTest(@PathVariable("id") Long id) {
- String redisKey = RedisConstant.REDIS_SHOP_DOUBLE_PREFIX + id;
- Map
map = new HashMap<>(); - map.put("id", id);
- Shop shop = RedisUtil.queryAndSet(redisKey, 30, map, new TypeReference
() { - }, invoke -> shopService.lambdaQuery().eq(Shop::getId, invoke.get("id")).one());
-
- if (shop == null) {
- return Result.fail("404:店铺不存在!");
- }
- return Result.ok(shop);
- }