游戏服务器缓存之Redis缓存正确使用

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

  1. 先从Redis中获取数据库。如果有数据直接返回,不用再查询数据库。
  2. 如果Redis没有数据,再查询数据库,将查询到的数据先缓存到Redis一份,再返回给调用者。
  3. 如果数据库也没有数据,直接返回null。
    我在刚开始使用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;
    }
}

 

版权申明:本站文章均来自网络,如有侵权,请联系01056159998 邮箱:itboby@foxmail.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有

猜你还会喜欢下面的内容

    无相关信息

中国领先的互联网域名及云服务提供商

为您提供域名,比特币,P2P,大数据,云计算,虚拟主机,域名交易最新资讯报道

域名注册云服务器