您的当前位置: 首页>>相城新闻中心>>行业资讯

如何正确处理游戏服务器缓存之Redis缓存

浏览量(106658) 时间:2020-09-09

      在游戏相城服务器开发中,为了更快速的获取游戏玩家的数据,一般都会把数据存储在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;
    }
}


以上文章来源于网络,如有侵权请联系创一网的客服处理。谢谢!

最新文章