1  缓存穿透

问题描述

缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,进而给数据库带来压力。

解决方案

缓存空值,即对于不存在的数据,在缓存中放置一个空对象(注意,设置过期时间)

2  缓存击穿

问题描述

缓存击穿是指热点key在某个时间点过期的时候,而恰好在这个时间点对这个Key有大量的并发请求过来,从而大量的请求打到数据库。

解决方案

加互斥锁,在并发的多个请求中,只有第一个请求线程能拿到锁并执行数据库查询操作,其他的线程拿不到锁就阻塞等着,等到第一个线程将数据写入缓存后,直接走缓存。

3  缓存雪崩

问题描述

缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。

解决方案

可以给缓存的过期时间时加上一个随机值时间,使得每个 key 的过期时间分布开来,不会集中在同一时刻失效。

4  缓存服务器宕机

问题描述

并发太高,缓存服务器连接被打满,最后挂了

解决方案

  • 限流:nginx、spring cloud gateway、sentinel等都支持限流
  • 增加本地缓存(JVM内存缓存),减轻redis一部分压力

5  Redis实现分布式锁

问题描述

如果用redis做分布式锁的话,有可能会存在这样一个问题:key丢失。比如,master节点写成功了还没来得及将它复制给slave就挂了,于是slave成为新的master,于是key丢失了,后果就是没锁住,多个线程持有同一把互斥锁。

解决方案

必须等redis把这个key复制给所有的slave并且都持久化完成后,才能返回加锁成功。但是这样的话,对其加锁的性能就会有影响。

zookeeper同样也可以实现分布式锁。在分布式锁的的实现上,zookeeper的重点是CP,redis的重点是AP。因此,要求强一致性就用zookeeper,对性能要求比较高的话就用redis

5  示例代码

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.7</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo426</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo426</name>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.17.1</version>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.1</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>

Product.java

package com.example.demo426.domain;

import lombok.Data;

import java.io.Serializable;
import java.time.LocalDateTime; /**
* @Author ChengJianSheng
* @Date 2022/4/26
*/
@Data
public class Product implements Serializable { private Long productId; private String productName; private Integer stock; private LocalDateTime createTime; private LocalDateTime updateTime; private Integer isDeleted; private Integer version;
}

ProductController.java

package com.example.demo426.controller;

import com.alibaba.fastjson.JSON;
import com.example.demo426.domain.Product;
import com.example.demo426.service.ProductService;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.*; import javax.annotation.Resource;
import java.time.Duration;
import java.util.Random;
import java.util.concurrent.TimeUnit; /**
* @Author ChengJianSheng
* @Date 2022/4/26
*/
@RestController
@RequestMapping("/product")
public class ProductController { @Autowired
private RedissonClient redissonClient;
@Resource
private StringRedisTemplate stringRedisTemplate;
@Autowired
private ProductService productService; private final Cache PRODUCT_LOCAL_CACHE = Caffeine.newBuilder()
.maximumSize(100)
.expireAfterWrite(Duration.ofMinutes(60))
.build(); private final String PRODUCT_CACHE_PREFIX = "cache:product:";
private final String PRODUCT_LOCK_PREFIX = "lock:product:";
private final String PRODUCT_RW_LOCK_PREFIX = "lock:rw:product:"; /**
* 更新
* 写缓存的方式有这么几种:
* 1. 更新完数据库后,直接删除缓存
* 2. 更新完数据库后,主动更新缓存
* 3. 更新完数据库后,发MQ消息,由消费者去刷新缓存
* 4. 利用canal等工具,监听MySQL数据库binlog,然后去刷新缓存
*/
@PostMapping("/update")
public void update(@RequestBody Product productDTO) {
RReadWriteLock readWriteLock = redissonClient.getReadWriteLock(PRODUCT_RW_LOCK_PREFIX + productDTO.getProductId());
RLock wLock = readWriteLock.writeLock();
wLock.lock();
try {
// 写数据库
// update product set name=xxx,...,version=version+1 where id=xx and version=xxx
Product product = productService.update(productDTO);
// 放入缓存
PRODUCT_LOCAL_CACHE.put(product.getProductId(), product);
stringRedisTemplate.opsForValue().set(PRODUCT_CACHE_PREFIX + product.getProductId(), JSON.toJSONString(product), getProductTimeout(60), TimeUnit.MINUTES);
} finally {
wLock.unlock();
}
} /**
* 查询
*/
@GetMapping("/query")
public Product query(@RequestParam("productId") Long productId) {
// 1. 尝试从缓存读取
Product product = getProductFromCache(productId);
if (null != product) {
return product;
}
// 2. 准备从数据库中加载
// 互斥锁
RLock lock = redissonClient.getLock(PRODUCT_LOCK_PREFIX + productId);
lock.lock();
try {
// 再次先查缓存
product = getProductFromCache(productId);
if (null != product) {
return product;
} // 为了避免缓存与数据库双写不一致
// 读写锁
RReadWriteLock readWriteLock = redissonClient.getReadWriteLock(PRODUCT_RW_LOCK_PREFIX + productId);
RLock rLock = readWriteLock.readLock();
rLock.lock();
try {
// 查数据库
product = productService.getById(productId);
if (null == product) {
// 如果数据库中没有,则放置一个空对象,这样做是为了避免”缓存穿透“问题
product = new Product();
} else {
PRODUCT_LOCAL_CACHE.put(productId, product);
}
// 放入缓存
stringRedisTemplate.opsForValue().set(PRODUCT_CACHE_PREFIX + productId, JSON.toJSONString(product), getProductTimeout(60), TimeUnit.MINUTES);
} finally {
rLock.unlock();
}
} finally {
lock.unlock();
} return null;
} /**
* 查缓存
*/
private Product getProductFromCache(Long productId) {
// 1. 尝试从本地缓存读取
Product product = PRODUCT_LOCAL_CACHE.getIfPresent(productId);
if (null != product) {
return product;
}
// 2. 尝试从Redis中读取
String key = PRODUCT_CACHE_PREFIX + productId;
String value = stringRedisTemplate.opsForValue().get(key);
if (StringUtils.isNotBlank(value)) {
product = JSON.parseObject(value, Product.class);
return product;
}
return null;
} /**
* 为了避免缓存集体失效,故而加了随机时间
*/
private int getProductTimeout(int initVal) {
Random random = new Random(10);
return initVal + random.nextInt();
}
}

Redis缓存相关的几个问题的更多相关文章

  1. Redis缓存相关问题总结

    使用缓存是系统性能优化的第一黄金法则. 缓存的设计和使用对一个系统的性能至关重要,平时接触到项目无论多少也都会在某些层面用到缓存,比如用HashMap实现,Ehcache,memcached.redi ...

  2. Redis缓存相关

    Redis缓存服务搭建及实现数据读写 RedisHelper帮助类 /// <summary> /// Redis 帮助类文件 /// </summary> public cl ...

  3. Java缓存相关memcached、redis、guava、Spring Cache的使用

    随笔分类 - Java缓存相关 主要记录memcached.redis.guava.Spring Cache的使用 第十二章 redis-cluster搭建(redis-3.2.5) 摘要: redi ...

  4. ssm+redis 如何更简洁的利用自定义注解+AOP实现redis缓存

    基于 ssm + maven + redis 使用自定义注解 利用aop基于AspectJ方式 实现redis缓存 如何能更简洁的利用aop实现redis缓存,话不多说,上demo 需求: 数据查询时 ...

  5. redis缓存技术

    初学redis缓存技术,如果文章写得不好还请谅解 应用环境:win7 实现环境:cmd,eclipse redis缓存技术的特点就在于高效,因为目前涉及的数据量逐渐增多,在对于数据的存储上面和sql以 ...

  6. Redis缓存项目应用架构设计二

    一.概述 由于架构设计一里面如果多平台公用相同Key的缓存更改配置后需要多平台上传最新的缓存配置文件来更新,比较麻烦,更新了架构设计二实现了缓存配置的集中管理,不过这样有有了过于中心化的问题,后续在看 ...

  7. Redis缓存项目应用架构设计一

    一些项目整理出的项目中引入缓存的架构设计方案,希望能帮助你更好地管理项目缓存,作者水平有限,如有不足还望指点. 一.基础结构介绍 项目中对外提供方法的是CacheProvider和MQProvider ...

  8. Java项目中使用Redis缓存案例

    缓存的目的是为了提高系统的性能,缓存中的数据主要有两种: 1.热点数据.我们将经常访问到的数据放在缓存中,降低数据库I/O,同时因为缓存的数据的高速查询,加快整个系统的响应速度,也在一定程度上提高并发 ...

  9. Redis 缓存失效和回收机制续

    二.Redis Key失效机制 Redis的Key失效机制,主要借助借助EXPIRE命令: EXPIRE key 30 上面的命令即为key设置30秒的过期时间,超过这个时间,我们应该就访问不到这个值 ...

  10. 【原创】详细案例解剖——浅谈Redis缓存的常用5种方式(String,Hash,List,set,SetSorted )

    很多小伙伴没接触过Redis,以至于去学习的时候感觉云里雾里的,就有一种:教程随你出,懂了算我输的感觉. 每次听圈内人在谈论的时候总是插不上话,小编就偷偷去了解了一下,也算是初入门径. 然后就整理了一 ...

随机推荐

  1. 基于java+springboot的宠物商店、宠物管理系统

    该系统是基于java+springboot开发的宠物商城,用户可以登录该网站购买宠物.该系统是给师弟开发的课程作业.运行过程中的问题,可以咨询github或留言. 演示地址 前台地址: http:// ...

  2. Pgsql之查询一个月份的天数

    前几天干活儿的时候,项目中有这么个需求,需要用pgsql查询某个月份有多少天,下面贴代码: select date_part('days', date_trunc('month', to_timest ...

  3. Java基础篇(05):函数式编程概念和应用

    目录 一.函数式概念 二.函数与方法 三.JDK函数基础 1.Lambda表达式 2.函数式接口 四.Optional类 1.Null判断 2.Optional应用 五.Stream流 一.函数式概念 ...

  4. [转帖]tidb 搭建私有镜像库

    https://docs.pingcap.com/zh/tidb/stable/tiup-mirror 在构建私有云时,通常会使用隔离的网络环境,此时无法访问 TiUP 的官方镜像.因此,TiUP 提 ...

  5. [转帖]jmeter线程组与循环次数的区别

    在压测的时候,有些接口需要携带登录信息,但是我们只想登录一次,然后其他接口进行多用户压测,此时你会怎么办?用仅一次控制器实现吗?下面我们来看看用仅一次控制器能不能实现 压测时jmeter中的线程数是模 ...

  6. [转帖]CentOS 7 下用 firewall-cmd / iptables 实现 NAT 转发供内网服务器联网

    https://www.cnblogs.com/hope250/p/8033818.html 自从用 HAProxy 对服务器做了负载均衡以后,感觉后端服务器真的没必要再配置并占用公网IP资源.而且由 ...

  7. Nginx拆分配置文件的办法

    Nginx拆分配置文件的办法 摘要 最近公司使用Nginx进行微服务的路由处理 但是发现随着业务发展, 配置文件越来越复杂. 修改起来也很容易出现错误. 基于此. 想通过拆分配置文件的方式来提高修改效 ...

  8. buildkit的简单学习与使用

    下载 需要注意本文学习了很多如下网站的内容: https://zhuanlan.zhihu.com/p/366671300 # 第一步下载资源 https://github.com/moby/buil ...

  9. CentOS 下载RPM包的方法

    有时候linux安装rpm包总是各种各样的错误提示, 很不好友 公司网路有不好 很难下载下来rpm包 这个时候可以使用如下网站进行获取rpm包 www.rpmfind.nethttps://cento ...

  10. Arthas 超简单离线使用教程

    简介 Arthas 是阿里巴巴开源的一套监控java应用的工具 官网的文档地址: https://arthas.aliyun.com/doc/ 官方的简介说明: Arthas 是Alibaba开源的J ...