从零开始搭建游戏服务器 第五节 Redis引入并实现生成账号Id

本文介绍了在项目中引入Redis缓存数据库的过程,包括Redis的安装、依赖管理、配置设置以及Redisson客户端的使用。重点讲述了如何利用Redis生成唯一的账号ID,确保在高并发情况下线程安全。
摘要由CSDN通过智能技术生成

前言

上一节我们在项目中引入了MongoDB作为持久化数据库,然后用了注册和登录两个例子分别展示了如何使用spring-data-mongo库进行数据插入与查询。
持久化数据库将数据写入硬盘,存储安全性较高,并且硬盘成本便宜,可以长久存放大量数据并按需扩容。
但是他有个缺点在于硬盘的io速度,较于内存底了不是一个数量级。而游戏玩家对于延迟是很敏感的,因此经常会使用缓存来进行热数据的存放,减少数据库的压力。

当然,现在市面上还有大量的游戏没有使用缓存数据库,不是不会用,而是没必要。比如滚服制的游戏,一个服务器同时在线可能就百多人,在登录时直接将玩家从数据库取出,存放在程序内存中进行读取修改完全是支撑得住,特意使用缓存数据库反而徒增成本。不仅是服务器硬件成本,也有技术成本,越复杂的架构,开发时候黑盒的地方越多,越容易出现不可预期的错误。

但是如果同时在线玩家量上去,就非常有必要引入缓存数据库了。

正文

redis安装

一样我这边不给出redis安装相关流程,读者可以自己选择本地安装、云redis、或者用docker快速创建。
安装完毕后给redis设置密码。

redis-cli
config set requirepass 123456

导入依赖

这里我用Redission,读者们也可以选择自己喜欢的Redis客户端库。

implementation 'org.redisson:redisson:3.27.2'

添加Redis配置

修改common.conf, 添加redis相关的配置

redis.host=redis://localhost:6379
redis.password=123456

修改CommonConfig.java,自动装载redis配置进来

@Getter
@Component
@PropertySource("classpath:common.conf")
public class CommonConfig {
    ...
    
    @Value("${redis.host}")
    String redisHost;
    @Value("${redis.password}")
    String redisPassword;
}

添加RedisService类

创建一个RedisService类,用来进行Redis相关操作。

@Slf4j
@Component
public class RedisService {

    private RedissonClient redissonClient;

    public void initRedisService(String url, String password) {
        Config config = new Config();
        config.useSingleServer()
                .setAddress(url)
                .setPassword(password);
        redissonClient = Redisson.create(config);

        log.info("redis service ok!");
    }

    public void set(String key, Object value) {
        RBucket<Object> bucket = redissonClient.getBucket(key);
        bucket.set(value);
    }

    public <T> T get(String key, Class<T> clz) {
        RBucket<T> bucket = redissonClient.getBucket(key);
        return bucket.get();
    }

    public Long increase(String key) {
        return increase(key, 1L);
    }
    public Long increase(String key, long addValue) {
        RAtomicLong atomicLong = redissonClient.getAtomicLong(key);
        return atomicLong.addAndGet(addValue);
    }
}

这里我们提供了一个初始化方法,服务器启动时传入redis的地址和密码,创建一个单点的redis服务连接。
同时我们下面简单提供了redis的get、set方法,以及一个递增接口,用于递增生成账号Id。

接下来我们修改一下LoginMain的服务器启动流程。

    @Override
    protected void initServer() {
		...
        // redis服务启动
        RedisService redisService = SpringUtils.getBean(RedisService.class);
        redisService.initRedisService(commonConfig.getRedisHost(), commonConfig.getRedisPassword());
        redisService.set("test", 10);
        int test = redisService.get("test", Integer.class);
        log.info("value = {}", test);
        Long testIncrease = redisService.increase("test_increase", 1L);
        log.info("test increase = {}", testIncrease);
        log.info("LoginServer start!");
    }

这里加了一点测试代码,等下记得删掉。

测试redis连接

启动LoginServer,查看打印日志:
redis测试

使用redis生成唯一id

我们在org.common.uitls包下创建GenIdUtils.java,用于生成Id

package org.common.utils;
import ...
/**
 * 生成Id相关工具
 */
public class GenIdUtils {

    /**
     * 生成ConnectId
     */
    public static long genConnectId() {
        // 先只用uuid的64位来做connectId,只要不超过64位理论上不会重复
        return UUID.randomUUID().getLeastSignificantBits();
    }

    /**
     * 生成账号Id
     */
    public static long genAccountId() {
        // 玩家的账号Id从1000000开始, 1000000之前的id做预留,万一有什么后续功能要用到
        long baseAccountId = 1000000L;
        RedisService redisService = SpringUtils.getBean(RedisService.class);
        return baseAccountId + redisService.increase("genAccountId");
    }

}

我们顺便把ConnectId的生成也放进来,这边不展示了大家自己改一下就好。

修改LoginProtoHandler的注册逻辑, 将accountId = 1L修改掉.

    public static void onPlayerRegisterMsg(ConnectActor actor, PlayerMsg.C2SPlayerRegister up) {
        ...
        long accountId = GenIdUtils.genAccountId();
        ...
    }

测试注册生成accountId

修改ClientMain.java 中的登录注册测试逻辑

    @Override
    protected void handleBackGroundCmd(String cmd) {
        if (cmd.equals("test")) {
            channel.writeAndFlush("test".getBytes());
        } else if (cmd.startsWith("register_")) {
            String[] s = cmd.split("_");
            if (s.length != 3) {
                return;
            }
            String accountName = s[1];
            String password = s[2];
            PlayerMsg.C2SPlayerRegister.Builder builder = PlayerMsg.C2SPlayerRegister.newBuilder();
            builder.setAccountName(accountName);
            builder.setPassword(password);
            Pack pack = new Pack(ProtoEnumMsg.CMD.ID.PLAYER_REGISTER_VALUE, builder.build().toByteArray());
            byte[] data = PackCodec.encode(pack);
            channel.writeAndFlush(data);
        } else if (cmd.equals("login_")) {
            String[] s = cmd.split("_");
            if (s.length != 3) {
                return;
            }
            String accountName = s[1];
            String password = s[2];
            PlayerMsg.C2SPlayerLogin.Builder builder = PlayerMsg.C2SPlayerLogin.newBuilder();
            builder.setAccountName(accountName);
            builder.setPassword(password);
            Pack pack = new Pack(ProtoEnumMsg.CMD.ID.PLAYER_LOGIN_VALUE, builder.build().toByteArray());
            byte[] data = PackCodec.encode(pack);
            channel.writeAndFlush(data);
        }
    }

改为手动输入账号密码。
起服测试一下:
自增id
可以看到id已经是从1000000开始的自增id了。

总结

本节我们往项目中添加了缓存数据库redis,并使用redisson对其进行了操作,代码量较少但是很重要。
将accountId的生成改为由redis进行递增,因为redis的单线程特性,使用原子操作increase可以线程安全地获得一个accountId,未来若是多个注册请求同时到达,也不会出现重复id的问题。

至此我们已经将所有关键的外部组件都给装配到我们的项目中,包括持久化数据库MongoDB、缓存数据库Redis、协议序列化工具Protobuf。

接下来要做的就是对服务器架构进行开发,下一节开始笔者将对截至目前开发的所有代码进行一次整理,包括但不限于:

  1. 包名修改——规范包名,修改代码结构
  2. 优化代码——使用自定义注解优化代码,减少重复工作量
  3. 补全MongoService的增删改查接口
  • 13
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值