java--游戏后端--项目开发总结--服务端
- 功能
- 客户端交互
- 客户端获取服务器列表客户端
- 获取公告
- CDN服务
- 资源包上传
- 更新资源
- 服务端交互
- 服务端验证登录
- 支付分发给服务端
- 数据配置
- GM功能
- 数据统计
- 客户端交互
- 技术
- 高级语言--Java8
- 框架--SpringBoot2.0
- 项目管理--Gradle
- 缓存--redis
- 数据库Mysql5.6
- 通信框架--Netty
- 传输框架--Protostuff
- 启动流程
- 不占用端口启动
- 通过注解获取协议进行初始化
- 初始化线程池用于有序处理客户端消息
- 初始化游戏数据
- 向后台获取游戏配置数据
- 初始化定时任务用于定时更新数据
- 启动Netty
- 关闭流程
- 实现接口ApplicationListener<ContextClosedEvent>
- 配置中注册监听
- 服务器关闭前处理临时数据落地到数据库
- 开发总结
- 关于缓存
- 缓存使用的redis
- 使用Jackson2JsonRedisSerializer替换了value的序列化与反序列化,但是对应map的序列化如果有排序要求,即使使用ConcurrentSkipListMap有序的集合,依然在反序列化的时候回出现顺序错误,在redis可视化工具看到的数据顺序是对的
- 关于成就设计
- 成就采用的是spring自带的事件系统
- 业务处理完回到数据完成后可以提交事件
- 在成就或任务系统中对事件进行处理
- 关于线程池
- 重写了拒绝策略
-
/** * 在线程池提交任务的最后一步——被线程池拒绝的任务,可以在拒绝后调用队列的put()方法,让任务的提交者阻塞,直到队列中任务被被线程池执行后,队列有了多余空间,调用方才返回 */ private static class BlockCallerPolicy implements RejectedExecutionHandler { @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { try { executor.getQueue().put(r); } catch (InterruptedException e) { e.printStackTrace(); } } }
- 关于业务初始化
- 使用了接口
-
/** * 初始化接口 * * @date :2019/3/14 17:15 */ public interface InitBaseHandler { /** * 实现此方法将在服务器启动时进行初始化操作 */ void init(); }
- 此接口可以在应用初始化时调用,将调用实现此接口的所有实现类
-
LogHandler.info("no2.初始化游戏数据......"); applicationContext.getBeansOfType(InitBaseHandler.class).values().forEach(InitBaseHandler::init);
- 关于定时器
- 先使用的每分钟一次的定时器
-
/** * 定时更新 */ @Override public void init() { LogHandler.info("初始化定时任务"); int second = Calendar.getInstance().get(Calendar.SECOND); ExecutorHandler.scheduledExecutorService.scheduleAtFixedRate(() -> { Calendar calendar = Calendar.getInstance(); int minute = calendar.get(Calendar.MINUTE); if (minute % INTERVAL == 0) { playerService.updatePlayer(); playerService.updatePlayerData(); globalService.updateGlobalData(); } }, 60 - second, 60, TimeUnit.SECONDS); }
- 后期会优化为扩展性更高的类似linux的cron月日时分定时器
- 没有考虑年
- 如果需要可以增加季度
- 没有考虑秒
- 聊天队列会单独使用秒定时器
- 数据解码
- 协议ID
- 参数长度
- 是否压缩
- 参数
- 关于缓存