Redis在游戏服务器的使用,看看战力排行榜的实现

香菜聊游戏 发表于 2022/06/26 21:53:46 2022/06/26
【摘要】 今天主要回顾一下redis 在我们游戏服务器上的应用。1、redis 介绍Redis 是完全开源的,是一个高性能的 key-value 数据库。redis 的优势性能高 – Redis能读的速度是110000次/s,写的速度是81000次/s 。丰富的数据类型 – Redis支持二进制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets 数据类型操作...

今天主要回顾一下redis 在我们游戏服务器上的应用。

1、redis 介绍

Redis 是完全开源的,是一个高性能的 key-value 数据库。

redis 的优势

  • 性能高 – Redis能读的速度是110000次/s,写的速度是81000次/s 。
  • 丰富的数据类型 – Redis支持二进制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets 数据类型操作。
  • 原子 – Redis的所有操作都是原子性的,意思就是要么成功执行要么失败完全不执行。单个操作是原子性的。多个操作也支持事务,即原子性,通过MULTI和EXEC指令包起来。
  • 丰富的特性 – Redis还支持 publish/subscribe, 通知, key 过期等等特性。。

2、在项目中的应用

在游戏服务器中的应用主要有下面几个场景:

基于redis 的高性能,所以我们当做跨服的数据存储使用

基于redis的发布订阅功能,我们当做服务器之间的消息发布使用,统领所有的服务器

基于redis 的排序功能,我们当做跨服的排行榜使用。

看下我们的服务器架构:

image.png

注:

MS:跨服服务器

G:游戏逻辑服务器

这里仅仅画出了和redis的链接,并没有标注出游戏的链接

3、代码展示

在项目中加入下面的依赖

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
</dependency>

如果你使用springboot搭建的项目,可以加入下面到pom

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

1、数据存储

数据的存储比较简单,常规的使用就行

@Component
public class RedisUtil {
 
    public static RedisUtil util;
 
    public RedisUtil(@Autowired  JedisPool jedisPool) {
        this.jedisPool = jedisPool;
        RedisUtil.util = this;
    }
 
    public static RedisUtil getInstance(){
        return util;
    }
 
 
    @Autowired
    private JedisPool jedisPool;
 
    /**
     * 向Redis中存值,永久有效
     */
    public String set(String key, String value) {
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            return jedis.set(key, value);
        } catch (Exception e) {
            return "0";
        } finally {
            jedis.close();
        }
    }
 
    /**
     * 根据传入Key获取指定Value
     */
    public String get(String key) {
        Jedis jedis = null;
        String value;
        try {
            jedis = jedisPool.getResource();
            value = jedis.get(key);
        } catch (Exception e) {
            return "0";
        } finally {
            jedis.close();
        }
        return value;
    }
 
    /**
     * 校验Key值是否存在
     */
    public Boolean exists(String key) {
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            return jedis.exists(key);
        } catch (Exception e) {
            return false;
        } finally {
            jedis.close();
        }
    }
 
    /**
     * 删除指定Key-Value
     */
    public Long del(String key) {
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            return jedis.del(key);
        } catch (Exception e) {
            return 0L;
        } finally {
            jedis.close();
        }
    }
 
    /**
     * 分布式锁
     * @param key
     * @param value
     * @param time 锁的超时时间,单位:秒
     *
     * @return 获取锁成功返回"OK",失败返回null
     */
    public String getDistributedLock(String key,String value,int time){
        Jedis jedis = null;
        String ret = "";
        try {
            jedis = jedisPool.getResource();
 
            ret = jedis.set(key, value, new SetParams().nx().ex(time));
            return ret;
        } catch (Exception e) {
            return null;
        } finally {
            jedis.close();
        }
    }
 
    public void pub(){
        jedisPool.getResource().publish("test","msg");
    }
 
}

2、排行榜排序功能

排行榜的功能,主要是使用sortset的数据结构,但是这里有一个比较要注意的情况就是时间的问题。

比如相同战力,先上榜的玩家肯定排名靠前,这是毫无疑问的,但是总有些同学会忘记这件事情,

同时,在更新的时候,要把原来值的小数去掉,再加上当前的时间运算之后的值

把玩家id 当做key,把玩家战力当做score,把时间当做小数进行累加

/**
     * 计算score,通过一个基准时间,可以是2100或2050年,减去lastOrderTime再除以基准时间,可以获得一个小于1的小数,
     * 在获取真正score的时候,只要舍去小数位即可
     * @param orderNum
     * @param lastOrderTime
     * @return
     */
    private double getOrderNum(int orderNum, long lastOrderTime) {
        return orderNum + (BASE_TIME - lastOrderTime) * 1.0 / BASE_TIME;
    }
 
        /**
     * 向redis中存入数据
     * 保存的是经过处理之后的数据
     * @param key
     * @param value
     */
    public long put(String key, int value) {
        long time = System.currentTimeMillis();
        double dValue = value + 1 - time / Math.pow(10, (int) Math.log10(time) + 1d);
        dbData.put(key, dValue);
        return time;
    }

3、发布订阅

@Component
public class JedisPubListener extends JedisPubSub implements ApplicationRunner {
 
    @Override
    public void onMessage(String channel, String message) {
        System.out.println(String.format("receive redis published message, channel %s, message %s", channel, message));
    }
 
    @Override
    public void onSubscribe(String channel, int subscribedChannels) {
        System.out.println(String.format("subscribe redis channel success, channel %s, subscribedChannels %d",
                channel, subscribedChannels));
    }
 
    @Override
    public void onUnsubscribe(String channel, int subscribedChannels) {
        System.out.println(String.format("unsubscribe redis channel, channel %s, subscribedChannels %d",
                channel, subscribedChannels));
 
    }
 
    @Override
    public void run(ApplicationArguments args) throws Exception {
 
    }
}

因为Jedis的订阅是阻塞的,所以单独开一个线程

@Component
public class ListenerThread extends Thread{
    @Autowired
    public JedisPubListener listener;
    @Autowired
    private JedisPool jedisPool;
    @PostConstruct
    public void init(){
        this.start();
        System.err.println("thread start");
    }
    @Override
    public void run() {
        Jedis resource = jedisPool.getResource();
        resource.subscribe(listener,"test");
    }
}

4、总结

redis的高性能贴合了游戏行业的的要求,所以现在很多游戏服务器都开始使用redis。

redis 可以作为数据存储,可以快速实现排行榜,可以作为缓存使用,是真的好用。



【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区),文章链接,文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件至:cloudbbs@huaweicloud.com进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容。
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。