在游戏漾濞服务器开发中,为了更快速的获取游戏玩家的数据,一般都会把数据存储在Redis之中,做为一级缓存。数据加载的过程是一般是这样的:
先从Redis中获取数据库。如果有数据直接返回,不用再查询数据库。
如果Redis没有数据,再查询数据库,将查询到的数据先缓存到Redis一份,再返回给调用者。
如果数据库也没有数据,直接返回null。
我在刚开始使用Redis做游戏漾濞服务器缓存的时候,就是这样做的。但是随着工作经验的积累和解决的Bug越来越多,如果代码实现的不够慎密,还是会出现各种问题的,而且现在面试官也喜欢问关于缓存的问题。做为做缓存,一般要解决的就是两个大问题。
缓存穿透
所谓的缓存穿透就是发生在第一步上面:先从缓存查询,如果缓存不存在,再查询数据库。这个时候,如果某些数据是一份公共数据,很多游戏玩家并发来查询,都去redis查了一下,发现没有,就都去数据库查。这个时候,压力就全部在数据库上面了。如果数据库扛着住还好,如果扛不住,就有可能导致数据库超载。
解决缓存穿透的方法也很简单,在收到第一次查询redis时,如果redis查询出来为空,这个时间对从数据库查询的操作加锁,如果从数据库查出来了,并缓存到了redis之中,这时别的查询操作就可以从redis中获取数据了。如果从数据库查出来也是空的,这个时候,可以给redis提供一个默认值,这样,其它查询出来的值就是这个默认值 ,如果判断是默认值,表示不有数据,返回null,这样也不会再查数据库了。
缓存雪崩(缓存击穿)
这种现象也是出现在大并发的情景下。比如Redis缓存了很多玩家的活动数据,但是这一大批玩家很长时间都没有登录了,而在redis中的活跃数据已过期,被redis自动删除了。突然间,运营又搞了一个老玩家拉回的活动,很多长时间不登录的人又都回来登录游戏了,这个时候,一大批用户的活动数据需要会从数据库拉取。极端情况导致数据库超载。
解决这个问题的方法
缓存的过期时间可以稍微长一些,长时间真正流失的玩家可能也不会回来了。
对数据库的操作需要添加限流,这个一般的数据库连接池已经做了,可以指定同一时间内,最多有多少连接操作数据库。
提供一个防止缓存穿透的方法
package com.mygame.redis;
import java.time.Duration;
import java.util.function.Function;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
/**
*
* @ClassName: RedisCacheTemplate
* @Description: 这是一个redis缓存的模板。在redis做为缓存的时候,需要防止缓存的雪崩,穿透
* @author: wang guang shuai
* @date: 2020年1月9日 下午5:11:53
*/
@Service
public class RedisCacheTemplate {
private static final String DefaultRedisNullValue = "#-#";
@Autowired
private StringRedisTemplate redisTemplate;
/**
*
* <p>
* Description:第一次会从redis中获取,如果redis中没有此值,从db中获取
* </p>
*
* @param redisKey
* @param param
* @param duration
* @param selectFromDB
* @return
* @author wang guang shuai
* @date 2020年1月9日 下午8:07:52
*
*/
public String getValue(String redisKey, String param, Duration duration, Function<String, String> selectFromDB) {
String value = redisTemplate.opsForValue().get(redisKey);
if (value == null) {
// 加锁,防止缓存穿透和击穿
synchronized (redisKey.intern()) {
// 二次检测
value = redisTemplate.opsForValue().get(redisKey);
if (value == null) {// 如果等于空,从数据库取
value = selectFromDB.apply(param);
if (value == null) {// 如果数据库还是没有,说明是真的没有,添加空标记
value = DefaultRedisNullValue;
}
// 将取到的值缓存到redis中。
if (duration != null) {
redisTemplate.opsForValue().set(redisKey, value, duration);
} else {
redisTemplate.opsForValue().set(redisKey, value);
}
}
}
}
if (value.equals(DefaultRedisNullValue)) {
return null;
}
return value;
}
}
以上文章来源于网络,如有侵权请联系创一网的客服处理。谢谢!