• Redis 分布式锁


    ce分布式锁原理:占用公共访问的地方redis或mysql,先到先得,后到要等。

     * 1.双写模式:修改数据库数据同时,也修改redis缓存数据,存在问题,网络交互或者cpu处理速度的差异,并发修改会产生脏数据

    * 2.失效模式:修改数据同时删除缓存数据,等待下次主动查询更新缓存

    * 思考:如何保证缓存数据与数据库同步,保持一致?要加读写锁;缓存数据本就不应该是实时性,一致性超高的,要求实时性,一致性超高的数据,直接去查数据,简化系统。

    1. package com.hmdp.utils;
    2. import com.fasterxml.jackson.core.type.TypeReference;
    3. import lombok.extern.slf4j.Slf4j;
    4. import org.apache.commons.lang3.StringUtils;
    5. import org.springframework.beans.factory.annotation.Autowired;
    6. import org.springframework.beans.factory.annotation.Value;
    7. import org.springframework.cloud.context.config.annotation.RefreshScope;
    8. import org.springframework.data.redis.core.BoundValueOperations;
    9. import org.springframework.data.redis.core.StringRedisTemplate;
    10. import org.springframework.data.redis.core.script.DefaultRedisScript;
    11. import org.springframework.stereotype.Component;
    12. import java.time.LocalDateTime;
    13. import java.util.Collections;
    14. import java.util.Map;
    15. import java.util.UUID;
    16. import java.util.concurrent.TimeUnit;
    17. import java.util.function.Function;
    18. /**
    19. * User: ldj
    20. * Date: 2022/9/3
    21. * Time: 16:55
    22. * Description: Redis分布锁工具类,目前没有做续机,所以过期时间调大一点,确保业务执行完成
    23. * 定时任务进行锁续命
    24. */
    25. @Slf4j
    26. @Component
    27. @RefreshScope
    28. public class RedisUtil {
    29. private static Integer shortTime;
    30. private static Long expiredTime;
    31. private static StringRedisTemplate stringRedisTemplate;
    32. @Value("${redis.lock.expiredTime:60}")
    33. public void setExpiredTime(Long expiredTime) {
    34. RedisUtil.expiredTime = expiredTime;
    35. }
    36. @Value("${redis.lock.shortTime:10}")
    37. public void setExpiredTime(Integer shortTime) {
    38. RedisUtil.shortTime = shortTime;
    39. }
    40. @Autowired
    41. public void setStringRedisTemplate(StringRedisTemplate stringRedisTemplate) {
    42. RedisUtil.stringRedisTemplate = stringRedisTemplate;
    43. }
    44. //避免因线程1执行业务很长,lock1过期了,线程2进来创建lock2,当线程2执行到一半时,线程1执行完业务释放锁是lock2
    45. private static String uuid;
    46. //获取锁+设置过期时间 原子性
    47. public static Boolean getLock() {
    48. uuid = UUID.randomUUID().toString().replaceAll("-", "");
    49. Boolean isLock = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid, expiredTime, TimeUnit.SECONDS);
    50. if (isLock != null && isLock) {
    51. log.info("[Redis] 添加锁成功 releaseUuid:[{}]", uuid);
    52. return true;
    53. } else {
    54. log.warn("[Redis] 添加锁失败!");
    55. return false;
    56. }
    57. }
    58. //释放锁+获取对比值 原子性
    59. public static void releaseLock() {
    60. String luaScript = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
    61. Long flag = stringRedisTemplate.execute(new DefaultRedisScript(luaScript, Long.class), Collections.singletonList("lock"), uuid);
    62. if (flag != null && flag == 1) {
    63. log.info("[Redis] 释放锁成功 releaseUuid:[{}]", uuid);
    64. } else {
    65. log.warn("[Redis] 释放锁失败!");
    66. }
    67. }
    68. //设置等待重试时间
    69. public static void waite(Long waitTime) {
    70. try {
    71. TimeUnit.MILLISECONDS.sleep(waitTime);
    72. } catch (InterruptedException e) {
    73. log.error("等待异常", e);
    74. }
    75. }
    76. //删除key
    77. public static Boolean removeKey(String key) {
    78. return stringRedisTemplate.delete(key);
    79. }
    80. public static void put(String redisKey, Object object, Integer expireTime) {
    81. if (expireTime != null) {
    82. stringRedisTemplate.opsForValue().set(redisKey, JacksonUtil.writeValueAsString(object), expireTime, TimeUnit.MINUTES);
    83. }
    84. stringRedisTemplate.opsForValue().set(redisKey, JacksonUtil.writeValueAsString(object));
    85. }
    86. public static void putWithLogicalExpire(String redisKey, Object object, Integer time) {
    87. RedisData redisData = new RedisData();
    88. redisData.setData(object);
    89. redisData.setExpireTime(LocalDateTime.now().plusMinutes(time));
    90. stringRedisTemplate.opsForValue().set(redisKey, JacksonUtil.writeValueAsString(redisData));
    91. }
    92. public static T getValue(String key, Class classType) {
    93. String context = stringRedisTemplate.opsForValue().get(key);
    94. if (StringUtils.isBlank(context)) {
    95. return null;
    96. }
    97. return JacksonUtil.readValue(context, classType);
    98. }
    99. public static T getValue(String key, TypeReference valueTypeRef) {
    100. String context = stringRedisTemplate.opsForValue().get(key);
    101. if (StringUtils.isBlank(context)) {
    102. return null;
    103. }
    104. return JacksonUtil.readValue(context, valueTypeRef);
    105. }
    106. public static R queryAndSet(String key, long expiredTime, Map map, TypeReference valueTypeRef, Function, R> callback) {
    107. BoundValueOperations operations = stringRedisTemplate.boundValueOps(key);
    108. String context = operations.get();
    109. if (StringUtils.isNotBlank(context)) {
    110. return JacksonUtil.readValue(context, valueTypeRef);
    111. }
    112. // ""或" "
    113. if (context != null) {
    114. return null;
    115. }
    116. //如果为null
    117. Boolean lock = RedisUtil.getLock();
    118. if (lock) {
    119. try {
    120. R r = callback.apply(map);
    121. if (r == null) {
    122. operations.set("", shortTime, TimeUnit.SECONDS);
    123. return null;
    124. }
    125. operations.set(JacksonUtil.writeValueAsString(r), expiredTime, TimeUnit.MINUTES);
    126. return r;
    127. } catch (Exception e) {
    128. log.error(e.getMessage());
    129. } finally {
    130. RedisUtil.releaseLock();
    131. }
    132. }
    133. //抢不到锁,重试
    134. return queryAndSet(key, expiredTime, map, valueTypeRef, callback);
    135. }
    136. }

     改进 能获取多个分布式锁:

    1. package com.tulin.lock.utils;
    2. import lombok.extern.slf4j.Slf4j;
    3. import org.springframework.beans.factory.annotation.Autowired;
    4. import org.springframework.beans.factory.annotation.Value;
    5. import org.springframework.data.redis.core.StringRedisTemplate;
    6. import org.springframework.data.redis.core.script.DefaultRedisScript;
    7. import org.springframework.stereotype.Component;
    8. import java.time.LocalDateTime;
    9. import java.time.ZoneOffset;
    10. import java.time.format.DateTimeFormatter;
    11. import java.util.Collections;
    12. import java.util.UUID;
    13. import java.util.concurrent.TimeUnit;
    14. /**
    15. * User: ldj
    16. * Date: 2022/10/19
    17. * Time: 14:54
    18. * Description: No Description
    19. */
    20. @Slf4j
    21. @Component
    22. public class RedisDistributedLockUtil {
    23. private static final String LOCK_PREFIX = "look:";
    24. private static final Integer LEFT_SHIFT_BITS = 32;
    25. private static final Long BEGIN_TIMESTAMP = 1672531200L;
    26. private static Long expiredTime;
    27. private static StringRedisTemplate stringRedisTemplate;
    28. @Value("${redis.lock.expiredTime:60}")
    29. public void setExpiredTime(Long expiredTime) {
    30. RedisDistributedLockUtil.expiredTime = expiredTime;
    31. }
    32. @Autowired
    33. public void setStringRedisTemplate(StringRedisTemplate stringRedisTemplate) {
    34. RedisDistributedLockUtil.stringRedisTemplate = stringRedisTemplate;
    35. }
    36. //避免因线程1执行业务很长,lock1过期了,线程2进来创建lock2,当线程2执行到一半时,线程1执行完业务释放锁是lock2
    37. private static String uuid;
    38. //获取锁+设置过期时间 原子性
    39. public static Boolean getLock(String lockName) {
    40. uuid = UUID.randomUUID().toString().replaceAll("-", "");
    41. Boolean isLock = stringRedisTemplate.opsForValue().setIfAbsent(LOCK_PREFIX + lockName, uuid, expiredTime, TimeUnit.SECONDS);
    42. if (isLock != null && isLock) {
    43. log.info("[Redis] 添加锁成功 lockName:[{}],releaseUuid:[{}]", lockName, uuid);
    44. return true;
    45. } else {
    46. log.warn("[Redis] 添加锁失败! lockName:[{}]", lockName);
    47. return false;
    48. }
    49. }
    50. //释放锁+获取对比值 原子性
    51. public static void releaseLock(String lockName) {
    52. String luaScript = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
    53. Long flag = stringRedisTemplate.execute(new DefaultRedisScript(luaScript, Long.class), Collections.singletonList(LOCK_PREFIX + lockName), uuid);
    54. if (flag != null && flag == 1) {
    55. log.info("[Redis] 释放锁成功 lockName:[{}],releaseUuid:[{}]", lockName, uuid);
    56. } else {
    57. log.warn("[Redis] 释放锁失败! lockName:[{}]", lockName);
    58. }
    59. }
    60. //设置等待重试时间
    61. public static void waite(Long waitTime) {
    62. try {
    63. TimeUnit.MILLISECONDS.sleep(waitTime);
    64. } catch (InterruptedException e) {
    65. log.error("等待异常", e);
    66. }
    67. }
    68. public static long getGlobalId(String prefix) {
    69. //生成时间戳
    70. LocalDateTime now = LocalDateTime.now();
    71. long nowSeconds = now.toEpochSecond(ZoneOffset.UTC);
    72. long timestamp = nowSeconds - BEGIN_TIMESTAMP;
    73. //生成32位id
    74. String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));
    75. Long increment = stringRedisTemplate.opsForValue().increment(prefix + ":" + date);
    76. //拼接时间戳 或运算同时为0才是0,有1肯定是1
    77. if (increment == null) {
    78. return 0L;
    79. }
    80. return timestamp << LEFT_SHIFT_BITS | increment;
    81. }
    82. }

    测试类 

    1. @Test
    2. public void test4() {
    3. Boolean lock = RedisUtil.getLock();
    4. System.out.println(lock);
    5. }
    6. @Test
    7. public void test5() {
    8. RedisUtil.releaseLock();
    9. }
    10. @Test
    11. public void test6() {
    12. Boolean lock = RedisUtil.getLock();
    13. System.out.println(lock);
    14. RedisUtil.releaseLock();
    15. }

    1. /**
    2. * 2.从数据库获取数据并封装,加Redis锁、
    3. * 加锁和设置过期时间是原子操作,避免因业务代码出现异常导致死锁
    4. * 获取值对比和删除锁是原子操作,避免因与redis网络交互完数据,key恰好过期,删掉别的线程的锁 使用lua脚本操作解锁
    5. */
    6. public Map> getDataWithRedisLock() {
    7. //1.加锁成功(加过期时间)->执行业务->释放锁
    8. Boolean lock = RedisUtil.getLock();
    9. if (lock != null && lock) {
    10. Map> dataFromDb;
    11. try {
    12. dataFromDb = this.getDataFromDb();
    13. } finally {
    14. RedisUtil.deleteLock();
    15. }
    16. return dataFromDb;
    17. } else {
    18. //2.加锁失败->休眠3秒后重试加锁
    19. RedisUtil.waite(3);
    20. return this.getDataWithRedisLock();
    21. }
    22. }
    1. @GetMapping("/{id}")
    2. public Result queryAndSetTest(@PathVariable("id") Long id) {
    3. String redisKey = RedisConstant.REDIS_SHOP_DOUBLE_PREFIX + id;
    4. Map map = new HashMap<>();
    5. map.put("id", id);
    6. Shop shop = RedisUtil.queryAndSet(redisKey, 30, map, new TypeReference() {
    7. }, invoke -> shopService.lambdaQuery().eq(Shop::getId, invoke.get("id")).one());
    8. if (shop == null) {
    9. return Result.fail("404:店铺不存在!");
    10. }
    11. return Result.ok(shop);
    12. }

  • 相关阅读:
    国产内存强势崛起,光威龙武挑战D5内存24×2新标杆
    互联网公司的组织结构与产品经理岗位职责是什么?
    阿里的三个「价值支点」
    一文能读懂车载与Android的关系
    什么是软件测试?
    我的驾照考试笔记(2)
    MySQL关于between and 和 大于等于>= 小于等于<= 区别
    类EMD的“信号分解方法”及MATLAB实现(第九篇)——小波包变换(WPT)/小波包分解(WPD)
    为什么 C# 访问 null 字段会抛异常?
    Node.js与npm版本比对
  • 原文地址:https://blog.csdn.net/dj1955/article/details/126680609