当前位置:   article > 正文

基于SpringBoot+Redis解决缓存与数据库一致性、缓存穿透、缓存雪崩、缓存击穿问题_ubuntu如何设置utf-8

ubuntu如何设置utf-8

前言

缓存是一种将数据存储在临时存储器中的技术,以便在需要时能够快速访问该数据。缓存的重要性在于它可以提高系统的性能和响应速度,减轻服务器的负载,节省网络带宽和资源消耗。因此掌握缓存技术是挺重要的哦。

一、缓存之数据库一致性问题

1.删除缓存还是更新缓存?
(1)更新缓存:每次的更新数据库都更新缓存,无效的写操作较多。No
(2)删除缓存:在更新数据库时让缓存失效,查询时再更新缓存。Yes

2.如何保证缓存和数据库的操作同时成功或同时失败?
(1)单体应用:在同一个事务中执行。
(2)分布式系统:使用分布式事务来实现。

3.先操作缓存还是先操作数据库?
先删除缓存,再操作数据库。

二、缓存穿透

1.概念
在缓存中,一个不存在的key被频繁请求,导致每次请求都需要查询数据库,影响系统性能。

2.解决
(1)布隆过滤器:将查询不存在的数据的请求拦截在缓存层之前;或者将查询不存在的数据的请求返回一个默认值,避免直接查询数据库。
(2)使用互斥锁重建缓存:在缓存失效的时候,使用互斥锁来保证只有一个线程去查询数据库并重建缓存,其他线程等待缓存重建完成后再去获取缓存数据。

三、缓存雪崩

1.概念
在缓存中,大量的key在同一时间失效,导致请求直接打到数据库,造成数据库瞬时压力过大,甚至宕机。

2.解决
(1)缓存数据的过期时间随机化:将缓存数据的过期时间进行随机化,避免大量缓存同时失效。
(2)多级缓存:将缓存分为多个层级,如本地缓存、分布式缓存、CDN缓存等,避免单一缓存出现问题导致雪崩。
(2)使用互斥锁重建缓存:在缓存失效的时候,使用互斥锁来保证只有一个线程去查询数据库并重建缓存,其他线程等待缓存重建完成后再去获取缓存数据。

四、缓存击穿

1.概念
在缓存中,某个热点key过期或被删除,导致大量请求直接打到数据库,造成数据库瞬时压力过大,甚至宕机。

2.解决
(1)设置热点数据永不过期:将热点数据设置成永不过期,但是随之而来的就是Redis需要更多的存储空间。
(2)数据预热:在系统启动、请求低峰期,缓存过期前的时候,将常用的数据预先加载到缓存中,避免在高峰期出现大量请求导致缓存失效的情况。
(3)使用互斥锁重建缓存:在缓存失效的时候,使用互斥锁来保证只有一个线程去查询数据库并重建缓存,其他线程等待缓存重建完成后再去获取缓存数据。

五、示例代码

1.控制层

(1)ProductController.java

  1. package org.example.controller;
  2. import cn.hutool.core.util.BooleanUtil;
  3. import cn.hutool.core.util.StrUtil;
  4. import cn.hutool.json.JSONUtil;
  5. import org.example.pojo.dto.ProductDTO;
  6. import org.springframework.beans.factory.annotation.Autowired;
  7. import org.springframework.data.redis.core.StringRedisTemplate;
  8. import org.springframework.stereotype.Controller;
  9. import org.springframework.web.bind.annotation.*;
  10. import java.util.ArrayList;
  11. import java.util.HashMap;
  12. import java.util.List;
  13. import java.util.concurrent.ExecutorService;
  14. import java.util.concurrent.Executors;
  15. import java.util.concurrent.TimeUnit;
  16. @Controller
  17. @RequestMapping(value = "api")
  18. public class ProductController {
  19. private static final String PRODUCT_LIST_KEY = "Product-List";
  20. private static final String PRODUCT_KEY = "Product-";
  21. private static final int PRODUCT_CACHE_TTL = 300;
  22. private static final int PRODUCT_NULL_TTL = 60;
  23. private static final String PRODUCT_LOCK_KEY = "Product-Lock-";
  24. @Autowired
  25. private StringRedisTemplate stringRedisTemplate;
  26. // 初始化固定大小为10的线程池
  27. private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);
  28. /**
  29. * 查询商品列表(解决缓存穿透问题,除了返回空对象,还需互斥锁重建缓存)
  30. */
  31. @GetMapping(value = "queryProductList")
  32. @ResponseBody
  33. @CrossOrigin
  34. public <T> T queryProductList() {
  35. HashMap<String, Object> responseObj = new HashMap<>();
  36. // 查询商品缓存列表
  37. List<String> productCacheList = stringRedisTemplate.opsForList().range(PRODUCT_LIST_KEY, 0, -1);
  38. // 是否命中缓存
  39. if (!productCacheList.isEmpty()) {
  40. // 已命中
  41. List<ProductDTO> list = new ArrayList<>();
  42. for (String s : productCacheList) {
  43. ProductDTO productDTO = JSONUtil.toBean(s, ProductDTO.class);
  44. list.add(productDTO);
  45. }
  46. responseObj.put("code", 200);
  47. responseObj.put("success", true);
  48. responseObj.put("data", list);
  49. System.out.println("queryProductList :: 已命中 -> " + list);
  50. return (T) responseObj;
  51. } else {
  52. // 未命中,模拟查询数据库并返回结果集,存入缓存
  53. List<ProductDTO> list = new ArrayList<>();
  54. list.add(new ProductDTO(1L, "面包", 5));
  55. list.add(new ProductDTO(2L, "牛奶", 3));
  56. list.add(new ProductDTO(3L, "苹果", 2));
  57. list.add(new ProductDTO(4L, "香蕉", 2));
  58. for (ProductDTO productDTO : list){
  59. String s = JSONUtil.toJsonStr(productDTO);
  60. productCacheList.add(s);
  61. }
  62. stringRedisTemplate.opsForList().rightPushAll(PRODUCT_LIST_KEY, productCacheList);
  63. responseObj.put("code", 200);
  64. responseObj.put("success", true);
  65. responseObj.put("data", list);
  66. System.out.println("queryProductList :: 未命中 -> " + list);
  67. return (T) responseObj;
  68. }
  69. }
  70. /**
  71. * 根据ID查询商品(解决缓存穿透问题,除了返回空对象,还需互斥锁重建缓存)
  72. * {
  73. * "id": 1
  74. * }
  75. */
  76. @PostMapping(value = "queryProductById")
  77. @ResponseBody
  78. @CrossOrigin
  79. public <T> T queryProductById(@RequestBody HashMap<String, Object> data) {
  80. HashMap<String, Object> responseObj = new HashMap<>();
  81. Long id = 1L;
  82. String key = PRODUCT_KEY + id;
  83. // 查询商品缓存
  84. String productCache = stringRedisTemplate.opsForValue().get(key);
  85. System.out.println("queryProductById :: productCache -> " + productCache);
  86. // 是否命中缓存
  87. if (productCache != null) {
  88. // 已命中
  89. ProductDTO productDTO = null;
  90. if (!productCache.equals("")) {
  91. productDTO = JSONUtil.toBean(productCache, ProductDTO.class);
  92. }
  93. responseObj.put("code", 200);
  94. responseObj.put("success", true);
  95. responseObj.put("data", productDTO);
  96. System.out.println("queryProductById :: 已命中 -> " + productDTO);
  97. return (T) responseObj;
  98. } else {
  99. // 未命中,模拟查询数据库并返回结果集,存入缓存
  100. ProductDTO productDTO = null;
  101. productDTO = new ProductDTO(1L, "面包", 5);
  102. if (productDTO == null) {
  103. // 若数据库查询也是空,则直接缓存空对象
  104. stringRedisTemplate.opsForValue().set(key,"", PRODUCT_NULL_TTL, TimeUnit.MINUTES);
  105. } else {
  106. // 若数据库查询非空,则缓存非空对象
  107. stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(productDTO), PRODUCT_CACHE_TTL, TimeUnit.MINUTES);
  108. }
  109. responseObj.put("code", 200);
  110. responseObj.put("success", true);
  111. responseObj.put("data", productDTO);
  112. System.out.println("queryProductById :: 未命中 -> " + productDTO);
  113. return (T) responseObj;
  114. }
  115. }
  116. /**
  117. * 根据ID更新商品(先更新数据库再删除缓存)
  118. * {
  119. * "id": 1,
  120. * "name": "美味的面包"
  121. * "price": 1
  122. * }
  123. */
  124. @PostMapping(value = "updateProductById")
  125. @ResponseBody
  126. @CrossOrigin
  127. public <T> T updateProductById(@RequestBody HashMap<String, Object> data) {
  128. HashMap<String, Object> responseObj = new HashMap<>();
  129. // 模拟更新数据库
  130. // xxxService.updateProductById(data)
  131. // 删除商品缓存
  132. Long id = 1L;
  133. stringRedisTemplate.delete(PRODUCT_KEY + id);
  134. stringRedisTemplate.delete(PRODUCT_LIST_KEY);
  135. responseObj.put("code", 200);
  136. responseObj.put("success", true);
  137. return (T) responseObj;
  138. }
  139. /**
  140. * 根据ID查询商品(解决缓存击穿问题,互斥锁重建缓存)
  141. * {
  142. * "id": 1
  143. * }
  144. */
  145. @PostMapping(value = "queryProductByIdPlus")
  146. @ResponseBody
  147. @CrossOrigin
  148. public <T> T queryProductByIdPlus(@RequestBody HashMap<String, Object> data) {
  149. HashMap<String, Object> responseObj = new HashMap<>();
  150. String key = PRODUCT_KEY + 1;
  151. Long id = 1L;
  152. // 查询商品缓存
  153. ProductDTO productDTO = null;
  154. String productCache = stringRedisTemplate.opsForValue().get(key); // GET Product-1
  155. if (StrUtil.isBlank(productCache)) {
  156. // 缓存过期或不存在,返回空,开启一个互斥锁的线程进行重建缓存
  157. String lockKey = PRODUCT_LOCK_KEY + id;
  158. boolean isLock = tryLock(lockKey);
  159. if (isLock) {
  160. CACHE_REBUILD_EXECUTOR.submit(() -> {
  161. try {
  162. this.buildProductCache(id, 30L);
  163. System.out.println("queryProductByIdPlus :: 缓存未命中,重建缓存");
  164. } catch (Exception e) {
  165. throw new RuntimeException(e);
  166. } finally {
  167. unLock(lockKey);
  168. }
  169. });
  170. }
  171. System.out.println("queryProductByIdPlus :: 缓存未命中 -> " + productDTO);
  172. responseObj.put("code", 200);
  173. responseObj.put("success", true);
  174. responseObj.put("data", "");
  175. return (T) responseObj;
  176. } else {
  177. // 缓存命中
  178. productDTO = JSONUtil.toBean(productCache, ProductDTO.class);
  179. System.out.println("queryProductByIdPlus :: 缓存已命中 -> " + productDTO);
  180. }
  181. responseObj.put("code", 200);
  182. responseObj.put("success", true);
  183. responseObj.put("data", productDTO);
  184. return (T) responseObj;
  185. }
  186. private boolean tryLock(String lockKey) {
  187. // SETNX Product-1 1 # 在指定的key不存在时,为key设置指定的值,若设置成功则返回1,若设置失败则返回0
  188. // EXPIRE Product-1 10
  189. Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);
  190. // 注意不能直接返回,直接返回存在拆箱操作,可能会有空指针
  191. return BooleanUtil.isTrue(flag);
  192. }
  193. private void unLock(String lockKey) {
  194. stringRedisTemplate.delete(lockKey);
  195. }
  196. private void buildProductCache(Long id, Long expireTime) {
  197. // 模拟查询数据库并返回结果集,存入缓存
  198. ProductDTO productDTO = null;
  199. // productDTO = new ProductDTO(id, "面包", 5);
  200. if (productDTO == null) {
  201. // 缓存兜底数据
  202. productDTO = new ProductDTO();
  203. }
  204. // SET Product-1 {}
  205. // EXPIRE Product-1 30
  206. // TTL Product-1
  207. stringRedisTemplate.opsForValue().set(PRODUCT_KEY + id, JSONUtil.toJsonStr(productDTO), expireTime, TimeUnit.SECONDS);
  208. }
  209. }

2.简单对象

(1)UserDTO.java

  1. package org.example.pojo.dto;
  2. import lombok.AllArgsConstructor;
  3. import lombok.Data;
  4. import lombok.NoArgsConstructor;
  5. import lombok.ToString;
  6. @Data
  7. @ToString
  8. @AllArgsConstructor
  9. @NoArgsConstructor
  10. public class UserDTO {
  11. private String phone;
  12. private String username;
  13. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/菜鸟追梦旅行/article/detail/721946?site
推荐阅读
相关标签
  

闽ICP备14008679号