游戏服务器缓存之Redis缓存正确使用
2021-06-07 16:02:22 【

在游戏服务器开发中,为了更快速的获取游戏玩家的数据,一般都会把数据存储在Redis之中,做为一级缓存。数据加载的过程是一般是这样的:

  1. 先从Redis中获取数据库。如果有数据直接返回,不用再查询数据库。

  2. 如果Redis没有数据,再查询数据库,将查询到的数据先缓存到Redis一份,再返回给调用者。

  3. 如果数据库也没有数据,直接返回null。

  4. 我在刚开始使用Redis做游戏服务器缓存的时候,就是这样做的。但是随着工作经验的积累和解决的Bug越来越多,如果代码实现的不够慎密,还是会出现各种问题的,而且现在面试官也喜欢问关于缓存的问题。做为做缓存,一般要解决的就是两个大问题。

缓存穿透

所谓的缓存穿透就是发生在第一步上面:先从缓存查询,如果缓存不存在,再查询数据库。这个时候,如果某些数据是一份公共数据,很多游戏玩家并发来查询,都去redis查了一下,发现没有,就都去数据库查。这个时候,压力就全部在数据库上面了。如果数据库扛着住还好,如果扛不住,就有可能导致数据库超载。

解决缓存穿透的方法也很简单,在收到第一次查询redis时,如果redis查询出来为空,这个时间对从数据库查询的操作加锁,如果从数据库查出来了,并缓存到了redis之中,这时别的查询操作就可以从redis中获取数据了。如果从数据库查出来也是空的,这个时候,可以给redis提供一个默认值,这样,其它查询出来的值就是这个默认值 ,如果判断是默认值,表示不有数据,返回null,这样也不会再查数据库了。

缓存雪崩(缓存击穿)

这种现象也是出现在大并发的情景下。比如Redis缓存了很多玩家的活动数据,但是这一大批玩家很长时间都没有登录了,而在redis中的活跃数据已过期,被redis自动删除了。突然间,运营又搞了一个老玩家拉回的活动,很多长时间不登录的人又都回来登录游戏了,这个时候,一大批用户的活动数据需要会从数据库拉取。极端情况导致数据库超载。

解决这个问题的方法

  1. 缓存的过期时间可以稍微长一些,长时间真正流失的玩家可能也不会回来了。

  2. 对数据库的操作需要添加限流,这个一般的数据库连接池已经做了,可以指定同一时间内,最多有多少连接操作数据库。

提供一个防止缓存穿透的方法

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; } }


】【打印关闭】 【返回顶部
上一篇游戏服务器要选云服务器好还是物.. 下一篇租一个万人游戏服务器每年大概需..