穿透和雪崩效应的解决方案

缓存穿透

概念

       缓存穿透是指用户查询数据,在数据库没有,自然在缓存中也不会有。这样就导致用户查询的时候,在缓存中找不到,每次都要发生jdbc请求,去数据库再查询一遍,然后返回空。这样请求就绕过缓存直接查数据库,这也是经常提的缓存命中率问题。

解决方案

       如果查询数据库也为空,直接在Redis中设置一个为null的结果,这样第二次到缓冲中获取就有值了,而不会继续访问数据库,这种办法最简单粗暴。当真正存入该数据时,清空相应的缓存即可。

缓存雪崩

概念

       由于原有缓存失效(或者数据未加载到缓存中),新缓存未到期间(缓存正常从Redis中获取)所有原本应该访问缓存的请求都去查询数据库了,而对数据库CPU和内存造成巨大压力,严重的会造成数据库宕机,造成系统的崩溃。

如果Redis中所有的key在同一实际失效的话,如果有大量的请求调用数据库,灰指甲导致整套系统瘫痪,这就是雪崩效应。

解决方案

  • 加锁或者队列的方式

        这样可以保证来保证不会有大量的线程对数据库一次性进行读写,避免缓存失效时对数据库造成太大的压力,虽然能够在一定的程度上缓解了数据库的压力。但是与此同时又降低了系统的吞吐量。

  • 分析用户的行为,尽量让缓存失效的时间均匀分布(也可以均摊失效时间)
  • 使用二级缓存(EhCache+Redis)
  • 使用消息中间件方式

        如果Redis查不到结果的情况下,这个时间直接扔给消息中间件,等到生产者生产了该消息后,消费者拿到消息就可以进行反馈了。

现在着重讲一下二级缓存(EhCache+Redis)

二级缓存(EhCache+Redis)解决雪崩效应

现在用EhCache作为一级缓存,Redis作为二级缓存,先走本地内存查询,本地缓存如果没有,再走网络连接,这样效率会更高。

1、pom文件引入

<dependencies>
		<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>fastjson</artifactId>
			<version>1.2.47</version>
		</dependency>
		<dependency>
	<groupId>org.apache.commons</groupId>
			<artifactId>commons-lang3</artifactId>
		</dependency>
		<!-- SpringBoot web 核心组件 -->
		<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>

		<!--开启 cache 缓存 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-cache</artifactId>
		</dependency>
		<!-- ehcache缓存 -->
		<dependency>
			<groupId>net.sf.ehcache</groupId>
			<artifactId>ehcache</artifactId>
			<version>2.9.1</version><!--$NO-MVN-MAN-VER$ -->
		</dependency>

		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>1.1.1</version>
		</dependency>
		<!-- mysql 依赖 -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
		</dependency>
</dependencies>

2、后端相关代码

首先封装对EhCache的基本操作的工具类

//封装对EhCache的基本操作
@Component
public class EhCacheUtils {

	@Autowired
	private EhCacheCacheManager ehCacheCacheManager;

	// cacheName 和 key 区别 就是 redis中的db库 组
	// 添加本地缓存 (相同的key 会直接覆盖)
	public void put(String cacheName, String key, Object value) {
		Cache cache = ehCacheCacheManager.getCacheManager().getCache(cacheName);
		Element element = new Element(key, value);
		cache.put(element);
	}
	// 获取本地缓存
	public Object get(String cacheName, String key) {
		Cache cache = ehCacheCacheManager.getCacheManager().getCache(cacheName);
		Element element = cache.get(key);
		return element == null ? null : element.getObjectValue();
	}
	//清除缓存
	public void remove(String cacheName, String key) {
		Cache cache = ehCacheCacheManager.getCacheManager().getCache(cacheName);
		cache.remove(key);
	}
}

.添加Redis的Service

@Component
public class RedisService {
	
	@Autowired
	private StringRedisTemplate stringRedisTemplate;

	public void setString(String key, Object object) {
		String value = (String) object;
		stringRedisTemplate.opsForValue().set(key, value);
	}

	public void setSet(String key, Object object) {
		Set<String> value = (Set<String>) object;
		for (String oj : value) {
			stringRedisTemplate.opsForSet().add(key, oj);
		}
	}

	public String getString(String key) {
		return stringRedisTemplate.opsForValue().get(key);
	}
}

整合二级缓存

需要注意的是,Redis存放的是Json格式,而EhCache存放的是对象,所以需要格式转换。

@Service
public class UserService {
	@Autowired
	private EhCacheUtils ehCacheUtils;
	@Autowired
	private RedisService redisService;
	@Autowired
	private UserMapper userMapper;
	private String cacheName = "userCache";

	/**
	 * 1.先查询一级缓存
	 * 	  1.1如果一级缓存有对应的值,则直接返回
	 * 	  1.2如果没有,则查询二级缓存
	 * 	  	 1.2.1如果二级缓存有对应的值,则修改一级缓存,并返回
	 * 	  	 1.2.2如果没有,查询数据库,修改一级二级缓存
	 * @param id
	 * @return
	 */
	public Users getUser(Long id) {
		// 1.先查询一级缓存 key 以 当前的类名+方法名称+id +参数值FD
		String key = this.getClass().getName() + "-" + Thread.currentThread().getStackTrace()[1].getMethodName()
				+ "-id:" + id;
		// 1.1 查询一级缓存数据有对应的值存在,如果存在直接返回
		Users users = (Users) ehCacheUtils.get(cacheName, key);
		if (users != null) {
			System.out.println("key:" + key + ",从一级缓存获取数据:users:" + users.toString());
			return users;
		}
		// 1.1 查询一级缓存数据没有对应的值存在,直接查询二级缓存redis redis 中如何存放对象呢? json格式
		// 2.查询二级缓存
		String userJSON = redisService.getString(key);
		// 如果redis缓存中有这个对应的 值,修改一级缓存
		if (!StringUtils.isEmpty(userJSON)) {
			JSONObject jsonObject = new JSONObject();
			Users resultUser = jsonObject.parseObject(userJSON, Users.class);
			// 存放在一级缓存
			ehCacheUtils.put(cacheName, key, resultUser);
			return resultUser;
		}
		// 3.查询db 数据库
		Users user = userMapper.getUser(id);
		if (user == null) {
			return null;
		}
		// 存放二级缓存redis
		redisService.setString(key, new JSONObject().toJSONString(user));
		// 存放在一级缓存
		ehCacheUtils.put(cacheName, key, user);
		return user;
	}
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值