• Redis-SpringBoot实战与缓存问题


    在xx/config包下配置   Redis默认是JDK序列化,我们要配置成JSON序列化,解决乱码问题

    1. import com.fasterxml.jackson.annotation.JsonAutoDetect;
    2. import com.fasterxml.jackson.annotation.PropertyAccessor;
    3. import com.fasterxml.jackson.databind.ObjectMapper;
    4. import org.springframework.context.annotation.Bean;
    5. import org.springframework.context.annotation.Configuration;
    6. import org.springframework.data.redis.connection.RedisConnectionFactory;
    7. import org.springframework.data.redis.core.RedisTemplate;
    8. import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
    9. import org.springframework.data.redis.serializer.StringRedisSerializer;
    10. @Configuration
    11. public class RedisConfig {
    12. //固定模板
    13. @Bean
    14. @SuppressWarnings("all")
    15. public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
    16. //我们为方便开发,一般直接使用<String, Object>
    17. RedisTemplate<String, Object> template = new RedisTemplate();
    18. template.setConnectionFactory(factory);
    19. //Json序列化配置
    20. Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
    21. ObjectMapper om = new ObjectMapper();
    22. om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
    23. om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
    24. jackson2JsonRedisSerializer.setObjectMapper(om);
    25. //string的序列化
    26. StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
    27. //Key采用String的序列化方式
    28. template.setKeySerializer(stringRedisSerializer);
    29. //hash的key也采用String的序列化方式
    30. template.setHashKeySerializer(stringRedisSerializer);
    31. //value序列化方式采用jackson
    32. template.setValueSerializer(jackson2JsonRedisSerializer);
    33. //hash的value序列化方式采用jackson
    34. template.setHashValueSerializer(jackson2JsonRedisSerializer);
    35. template.afterPropertiesSet();
    36. return template;
    37. }
    38. }

    在xx/utils包下配置工具类,来简化操作命令 

    1. import org.springframework.beans.factory.annotation.Autowired;
    2. import org.springframework.data.redis.core.RedisTemplate;
    3. import org.springframework.stereotype.Component;
    4. import org.springframework.util.CollectionUtils;
    5. import java.util.Collection;
    6. import java.util.List;
    7. import java.util.Map;
    8. import java.util.Set;
    9. import java.util.concurrent.TimeUnit;
    10. @Component
    11. public class RedisUtil {
    12. @Resource
    13. private RedisTemplate<String, Object> redisTemplate;
    14. // =============================common============================
    15. /**
    16. * 指定缓存失效时间
    17. * @param key 键
    18. * @param time 时间(秒)
    19. */
    20. public boolean expire(String key, long time) {
    21. try {
    22. if (time > 0) {
    23. redisTemplate.expire(key, time, TimeUnit.SECONDS);
    24. }
    25. return true;
    26. } catch (Exception e) {
    27. e.printStackTrace();
    28. return false;
    29. }
    30. }
    31. /**
    32. * 根据key 获取过期时间
    33. * @param key 键 不能为null
    34. * @return 时间(秒) 返回0代表为永久有效
    35. */
    36. public long getExpire(String key) {
    37. return redisTemplate.getExpire(key, TimeUnit.SECONDS);
    38. }
    39. /**
    40. * 判断key是否存在
    41. * @param key 键
    42. * @return true 存在 false不存在
    43. */
    44. public boolean hasKey(String key) {
    45. try {
    46. return redisTemplate.hasKey(key);
    47. } catch (Exception e) {
    48. e.printStackTrace();
    49. return false;
    50. }
    51. }
    52. /**
    53. * 删除缓存
    54. * @param key 可以传一个值 或多个
    55. */
    56. @SuppressWarnings("unchecked")
    57. public void del(String... key) {
    58. if (key != null && key.length > 0) {
    59. if (key.length == 1) {
    60. redisTemplate.delete(key[0]);
    61. } else {
    62. redisTemplate.delete((Collection<String>) CollectionUtils.arrayToList(key));
    63. }
    64. }
    65. }
    66. // ============================String=============================
    67. /**
    68. * 普通缓存获取
    69. * @param key 键
    70. * @return
    71. */
    72. public Object get(String key) {
    73. return key == null ? null : redisTemplate.opsForValue().get(key);
    74. }
    75. /**
    76. * 普通缓存放入
    77. * @param key 键
    78. * @param value 值
    79. * @return true成功 false失败
    80. */
    81. public boolean set(String key, Object value) {
    82. try {
    83. redisTemplate.opsForValue().set(key, value);
    84. return true;
    85. } catch (Exception e) {
    86. e.printStackTrace();
    87. return false;
    88. }
    89. }
    90. /**
    91. * 普通缓存放入并设置时间
    92. * @param key 键
    93. * @param value 值
    94. * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
    95. * @return true成功 false 失败
    96. */
    97. public boolean set(String key, Object value, long time) {
    98. try {
    99. if (time > 0) {
    100. redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
    101. } else {
    102. set(key, value);
    103. }
    104. return true;
    105. } catch (Exception e) {
    106. e.printStackTrace();
    107. return false;
    108. }
    109. }
    110. /**
    111. * 递增
    112. * @param key 键
    113. * @param delta 要增加几(大于0)
    114. * @return
    115. */
    116. public long incr(String key, long delta) {
    117. if (delta < 0) {
    118. throw new RuntimeException("递增因子必须大于0");
    119. }
    120. return redisTemplate.opsForValue().increment(key, delta);
    121. }
    122. /**
    123. * 递减
    124. * @param key 键
    125. * @param delta 要减少几(小于0)
    126. * @return
    127. */
    128. public long decr(String key, long delta) {
    129. if (delta < 0) {
    130. throw new RuntimeException("递减因子必须大于0");
    131. }
    132. return redisTemplate.opsForValue().increment(key, -delta);
    133. }
    134. // ================================Map=================================
    135. /**
    136. * HashGet
    137. * @param key 键 不能为null
    138. * @param item 项 不能为null
    139. * @return
    140. */
    141. public Object hget(String key, String item) {
    142. return redisTemplate.opsForHash().get(key, item);
    143. }
    144. /**
    145. * 获取hashKey对应的所有键值
    146. * @param key 键
    147. * @return 对应的多个键值
    148. */
    149. public Map<Object, Object> hmget(String key) {
    150. return redisTemplate.opsForHash().entries(key);
    151. }
    152. /**
    153. * HashSet
    154. * @param key 键
    155. * @param map 对应多个键值
    156. * @return true 成功 false 失败
    157. */
    158. public boolean hmset(String key, Map<String, Object> map) {
    159. try {
    160. redisTemplate.opsForHash().putAll(key, map);
    161. return true;
    162. } catch (Exception e) {
    163. e.printStackTrace();
    164. return false;
    165. }
    166. }
    167. /**
    168. * HashSet 并设置时间
    169. * @param key 键
    170. * @param map 对应多个键值
    171. * @param time 时间(秒)
    172. * @return true成功 false失败
    173. */
    174. public boolean hmset(String key, Map<String, Object> map, long time) {
    175. try {
    176. redisTemplate.opsForHash().putAll(key, map);
    177. if (time > 0) {
    178. expire(key, time);
    179. }
    180. return true;
    181. } catch (Exception e) {
    182. e.printStackTrace();
    183. return false;
    184. }
    185. }
    186. /**
    187. * 向一张hash表中放入数据,如果不存在将创建
    188. * @param key 键
    189. * @param item 项
    190. * @param value 值
    191. * @return true 成功 false失败
    192. */
    193. public boolean hset(String key, String item, Object value) {
    194. try {
    195. redisTemplate.opsForHash().put(key, item, value);
    196. return true;
    197. } catch (Exception e) {
    198. e.printStackTrace();
    199. return false;
    200. }
    201. }
    202. /**
    203. * 向一张hash表中放入数据,如果不存在将创建
    204. * @param key 键
    205. * @param item 项
    206. * @param value 值
    207. * @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
    208. * @return true 成功 false失败
    209. */
    210. public boolean hset(String key, String item, Object value, long time) {
    211. try {
    212. redisTemplate.opsForHash().put(key, item, value);
    213. if (time > 0) {
    214. expire(key, time);
    215. }
    216. return true;
    217. } catch (Exception e) {
    218. e.printStackTrace();
    219. return false;
    220. }
    221. }
    222. /**
    223. * 删除hash表中的值
    224. * @param key 键 不能为null
    225. * @param item 项 可以使多个 不能为null
    226. */
    227. public void hdel(String key, Object... item) {
    228. redisTemplate.opsForHash().delete(key, item);
    229. }
    230. /**
    231. * 判断hash表中是否有该项的值
    232. * @param key 键 不能为null
    233. * @param item 项 不能为null
    234. * @return true 存在 false不存在
    235. */
    236. public boolean hHasKey(String key, String item) {
    237. return redisTemplate.opsForHash().hasKey(key, item);
    238. }
    239. /**
    240. * hash递增 如果不存在,就会创建一个 并把新增后的值返回
    241. * @param key 键
    242. * @param item 项
    243. * @param by 要增加几(大于0)
    244. * @return
    245. */
    246. public double hincr(String key, String item, double by) {
    247. return redisTemplate.opsForHash().increment(key, item, by);
    248. }
    249. /**
    250. * hash递减
    251. * @param key 键
    252. * @param item 项
    253. * @param by 要减少记(小于0)
    254. * @return
    255. */
    256. public double hdecr(String key, String item, double by) {
    257. return redisTemplate.opsForHash().increment(key, item, -by);
    258. }
    259. // ============================set=============================
    260. /**
    261. * 根据key获取Set中的所有值
    262. * @param key 键
    263. * @return
    264. */
    265. public Set<Object> sGet(String key) {
    266. try {
    267. return redisTemplate.opsForSet().members(key);
    268. } catch (Exception e) {
    269. e.printStackTrace();
    270. return null;
    271. }
    272. }
    273. /**
    274. * 根据value从一个set中查询,是否存在
    275. * @param key 键
    276. * @param value 值
    277. * @return true 存在 false不存在
    278. */
    279. public boolean sHasKey(String key, Object value) {
    280. try {
    281. return redisTemplate.opsForSet().isMember(key, value);
    282. } catch (Exception e) {
    283. e.printStackTrace();
    284. return false;
    285. }
    286. }
    287. /**
    288. * 将数据放入set缓存
    289. * @param key 键
    290. * @param values 值 可以是多个
    291. * @return 成功个数
    292. */
    293. public long sSet(String key, Object... values) {
    294. try {
    295. return redisTemplate.opsForSet().add(key, values);
    296. } catch (Exception e) {
    297. e.printStackTrace();
    298. return 0;
    299. }
    300. }
    301. /**
    302. * 将set数据放入缓存
    303. * @param key 键
    304. * @param time 时间(秒)
    305. * @param values 值 可以是多个
    306. * @return 成功个数
    307. */
    308. public long sSetAndTime(String key, long time, Object... values) {
    309. try {
    310. Long count = redisTemplate.opsForSet().add(key, values);
    311. if (time > 0) {
    312. expire(key, time);
    313. }
    314. return count;
    315. } catch (Exception e) {
    316. e.printStackTrace();
    317. return 0;
    318. }
    319. }
    320. /**
    321. * 获取set缓存的长度
    322. * @param key 键
    323. * @return
    324. */
    325. public long sGetSetSize(String key) {
    326. try {
    327. return redisTemplate.opsForSet().size(key);
    328. } catch (Exception e) {
    329. e.printStackTrace();
    330. return 0;
    331. }
    332. }
    333. /**
    334. * 移除值为value的
    335. * @param key 键
    336. * @param values 值 可以是多个
    337. * @return 移除的个数
    338. */
    339. public long setRemove(String key, Object... values) {
    340. try {
    341. Long count = redisTemplate.opsForSet().remove(key, values);
    342. return count;
    343. } catch (Exception e) {
    344. e.printStackTrace();
    345. return 0;
    346. }
    347. }
    348. // ===============================list=================================
    349. /**
    350. * 获取list缓存的内容
    351. * @param key 键
    352. * @param start 开始
    353. * @param end 结束 0 到 -1代表所有值
    354. * @return
    355. */
    356. public List<Object> lGet(String key, long start, long end) {
    357. try {
    358. return redisTemplate.opsForList().range(key, start, end);
    359. } catch (Exception e) {
    360. e.printStackTrace();
    361. return null;
    362. }
    363. }
    364. /**
    365. * 获取list缓存的长度
    366. * @param key 键
    367. * @return
    368. */
    369. public long lGetListSize(String key) {
    370. try {
    371. return redisTemplate.opsForList().size(key);
    372. } catch (Exception e) {
    373. e.printStackTrace();
    374. return 0;
    375. }
    376. }
    377. /**
    378. * 通过索引 获取list中的值
    379. * @param key 键
    380. * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
    381. * @return
    382. */
    383. public Object lGetIndex(String key, long index) {
    384. try {
    385. return redisTemplate.opsForList().index(key, index);
    386. } catch (Exception e) {
    387. e.printStackTrace();
    388. return null;
    389. }
    390. }
    391. /**
    392. * 将list放入缓存
    393. * @param key 键
    394. * @param value 值
    395. * @return
    396. */
    397. public boolean lSet(String key, Object value) {
    398. try {
    399. redisTemplate.opsForList().rightPush(key, value);
    400. return true;
    401. } catch (Exception e) {
    402. e.printStackTrace();
    403. return false;
    404. }
    405. }
    406. /**
    407. * 将list放入缓存
    408. * @param key 键
    409. * @param value 值
    410. * @param time 时间(秒)
    411. * @return
    412. */
    413. public boolean lSet(String key, Object value, long time) {
    414. try {
    415. redisTemplate.opsForList().rightPush(key, value);
    416. if (time > 0) {
    417. expire(key, time);
    418. }
    419. return true;
    420. } catch (Exception e) {
    421. e.printStackTrace();
    422. return false;
    423. }
    424. }
    425. /**
    426. * 将list放入缓存
    427. * @param key 键
    428. * @param value 值
    429. * @return
    430. */
    431. public boolean lSet(String key, List<Object> value) {
    432. try {
    433. redisTemplate.opsForList().rightPushAll(key, value);
    434. return true;
    435. } catch (Exception e) {
    436. e.printStackTrace();
    437. return false;
    438. }
    439. }
    440. /**
    441. * 将list放入缓存
    442. *
    443. * @param key 键
    444. * @param value 值
    445. * @param time 时间(秒)
    446. * @return
    447. */
    448. public boolean lSet(String key, List<Object> value, long time) {
    449. try {
    450. redisTemplate.opsForList().rightPushAll(key, value);
    451. if (time > 0) {
    452. expire(key, time);
    453. }
    454. return true;
    455. } catch (Exception e) {
    456. e.printStackTrace();
    457. return false;
    458. }
    459. }
    460. /**
    461. * 根据索引修改list中的某条数据
    462. * @param key 键
    463. * @param index 索引
    464. * @param value 值
    465. * @return
    466. */
    467. public boolean lUpdateIndex(String key, long index, Object value) {
    468. try {
    469. redisTemplate.opsForList().set(key, index, value);
    470. return true;
    471. } catch (Exception e) {
    472. e.printStackTrace();
    473. return false;
    474. }
    475. }
    476. /**
    477. * 移除N个值为value
    478. * @param key 键
    479. * @param count 移除多少个
    480. * @param value 值
    481. * @return 移除的个数
    482. */
    483. public long lRemove(String key, long count, Object value) {
    484. try {
    485. Long remove = redisTemplate.opsForList().remove(key, count, value);
    486. return remove;
    487. } catch (Exception e) {
    488. e.printStackTrace();
    489. return 0;
    490. }
    491. }
    492. }

    在service层进行应用

    1. @Override
    2. public Product findById(Integer id) {
    3. if (redisUtil.hasKey("productId_" + id)) { //如果有就读取缓存
    4. System.out.println("redis缓存读取了"+" productId_" + id);
    5. return (Product) redisUtil.get("productId_" + id);
    6. } else { //否则存入缓存
    7. // 根据参数id调用私有方法执行查询,获取商品数据
    8. Product product = productMapper.findById(id);
    9. // 判断查询结果是否为null
    10. if (product == null) {
    11. // 是:抛出ProductNotFoundException
    12. throw new ProductNotFoundException("尝试访问的商品数据不存在");
    13. }
    14. // 将查询结果中的部分属性设置为null
    15. product.setPriority(null);
    16. product.setCreatedUser(null);
    17. product.setCreatedTime(null);
    18. product.setModifiedUser(null);
    19. product.setModifiedTime(null);
    20. redisUtil.set("productId_" + id,product,3000);
    21. System.out.println("redis存入缓存"+" productId_" + id+":"+product);
    22. // 返回查询结果
    23. return product;
    24. }
    25. }

    缓存雪崩

    Redis由于某些原因不可用(宕机)或者大量缓存由于超时时间相同在同一时间段失效(大批key失效/热点数据失效),大量请求直接到达数据库(Mysql),数据库压力过大导致系统崩溃。

     

    解决方案:

    • 搭建集群

             这个思想的含义是,既然redis有可能挂掉,那我多增设几台redis,这样一台挂掉之后其他的还可以继续工作,其实就是搭建的集群。(异地多活!)

    • 限流降级

          这个解决方案的思想是,在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。

    •  数据预热
             预先读取一遍数据。缓存的过期时间用随机值,尽量让不同的key的过期时间不同(例如:定时任务新建大批量key,设置的过期时间不同)

    set(Key,value,time + Math.random() * 10000);
     

    缓存击穿 (量太大,缓存过期)

    缓存击穿,跟缓存雪崩有点像,但是又有一点不一样,缓存雪崩是因为大面积的缓存失效,打崩了DB,而缓存击穿不同的是缓存击穿是指一个Key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个Key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个完好无损的桶上凿开了一个洞。

    • 分布式互斥锁:这种方案思路比较简单,但是存在一定的隐患,如果在查询数据库 + 和 重建缓存(key失效后进行了大量的计算)时间过长,也可能会存在死锁和线程池阻塞的风险,高并发情景下吞吐量会大大降低!但是这种方法能够较好地降低后端存储负载,并在一致性上做得比较好。

    • 热点数据永不过期:这种方案由于没有设置真正的过期时间,实际上已经不存在热点key产生的一系列危害,但是会存在数据不一致的情况,同时代码复杂度会增大。

    缓存穿透

       缓存穿透是指查询一个根本不存在的数据,缓存层和持久层都不会命中。在日常工作中出于容错的考虑,如果从持久层查不到数据则不写入缓存层,缓存穿透将导致不存在的数据每次请求都要到持久层去查询,失去了缓存保护后端持久的意义

      造成缓存穿透的基本原因有两个。第一,自身业务代码或者数据出现问题(例如:set 和 get 的key不一致),第二,一些恶意攻击、爬虫等造成大量空命中(爬取线上商城商品数据,超大循环递增商品的ID)

    • 缓存空对象:从缓存取不到的数据,在数据库中也没有取到,这时也可以将对应Key的Value对写为null、位置错误、稍后重试这样的值具体取啥问产品,或者看具体的场景,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。

      这样可以防止攻击用户反复用同一个id暴力攻击,但是我们要知道正常用户是不会在单秒内发起这么多次请求的,那网关层Nginx有配置项,可以让运维对单个IP每秒访问次数超出阈值的IP都拉黑。
       

    • 布隆过滤器拦截:

    •  

    Redis 缓存穿透 + 缓存雪崩 + 缓存击穿的原因和解决方案_骑驴的小牧童的博客-CSDN博客_redis的缓存穿透和缓存雪崩

    阿里面试Redis最常问的三个问题:缓存雪崩、击穿、穿透(带答案)_敖 丙的博客-CSDN博客 

  • 相关阅读:
    MySQL-常用数据库操作SQL汇总
    了解一下,我是如何用Python在业余时间赚5千外快的
    618京东到家APP-门详页反爬实战
    高科技电子行业采购供应链管理
    MySQL高级:(二)存储引擎
    基于FPGA的超声波测距
    Thread的常用方法
    在C#开发中使用第三方组件LambdaParser、DynamicExpresso、Z.Expressions,实现动态解析/求值字符串表达式
    【Java】DDD领域驱动设计理解
    轻量级的VsCode为何越用越大?为什么吃了我C盘10G?如何无痛清理VsCode缓存?手把手教你为C盘瘦身
  • 原文地址:https://blog.csdn.net/m0_46845579/article/details/125452632