zoukankan      html  css  js  c++  java
  • 结对编程作业服务器端总结

    博客和GitHub

    博客1
    博客2
    GitHub

    分工

    杨明哲负责制定规则和部分牌型判定,我负责API文档,部分牌型,服务器端逻辑实现,服务器端AI实现

    思路和实现

    架构图

    说明

    服务器和客户端采用HTTP方式交互。交互数据类型(MIME)采用application/json。考虑到每个人出牌的独立性,因此采用异步比赛,异步结算的方式。每个人通过开局接口拿到牌,出牌,服务器端保存,并使用一个周期性运行的程序对牌进行判定和积分结算。然后,客户端可以使用历史记录接口查询结果。为了认证身份,要求每个玩家绑定教务处帐号。
    异步比赛的设计让玩家可以独立开局出牌,不必等待其他玩家;同时,这样结算程序、服务器端AI可以单独抽离出来,做成独立的程序或微服务(虽然最后还是采用了大内核的形式),这样可以降低服务器负担,也降低了系统耦合。

    代码组织

    代码采用Java和Kotlin混合编写,其中,牌型判定和计算部分主要使用Java实现,其余部分使用Kotlin

    Java代码组织

    主要有两个包:

    1. compare: 主要是牌型判定,排序,服务器端AI和常数定义.
    2. logic: 主要是工具类,缓存,牌的数据结构定义
      这些代码主要是从一个叫[NeatlyServer]嫖来的,因此代码规范和风格比较奇怪。

    Kotlin代码组织

    主要分为8个包:

    1. config: 配置包,主要存储配置类,用于配置服务器程序的全局选项。
    2. controller: 控制器包,实现了最前端的逻辑,主要是接收请求,参数检查,并调用对应的Service
    3. exception: 异常包,主要用于自定义异常。
    4. model: 数据模型包,主要放置领域对象和数据传输对象
    5. obsolete: 弃用的代码
    6. repository: 数据仓库,都是接口,由Spring Data JPA实现
    7. service: 服务包,主要的业务逻辑代码
    8. util: 工具类

    关键代码

    都挺关键的

    性能改进

    问题

    在测试时发现提交时程序和数据库CPU占用都很高

    分析

    由于数据库CPU占用高,因此推测问题主要和数据库交互有关,进一步的性能剖析发现调用栈在CombatRepository::save()花费了大量时间,此方法由Spring Data JPA在运行时动态实现,无法查看代码。但查阅资料发现,Spring Data JPA在Many-to-One关系的One一方修改有大量的性能开销,因为它需要去检查Many的其他部分,而我的代码就是像这样

    combat.users!!.add(userCombat)
    combatRepository.save(combat)
    

    在One一方进行修改,造成了较大开销

    解决

    修改代码为在Many一方,也就是关系的持有者一方修改

    val userCombat = UserCombat(
        ...
        combat = combat
    )
    userCombatRepoistory.save(userCombat)
    

    问题解决,CPU占用正常

    单元测试

    由于时间不足没有自动化测试程序,但使用Kotlin REPL对工具函数进行了手动测试,编写了客户端程序对服务端其他逻辑进行测试

    GitHub记录

    问题

    描述

    并发程序的数据一致性问题,主要表现为多人加入导致战局人数超限

    尝试

    首先是加锁,查找可用战局时加悲观锁,保证一次只有一个线程在访问战局。

        @Lock(LockModeType.PESSIMISTIC_WRITE)
        @Query("select c from CombatDO c where id in (select uc.id.combatId from UserCombatDO uc where not exists (select uc2 from UserCombatDO uc2 where uc2.id.combatId = uc.id.combatId and uc2.id.userId = :playerId) group by uc.id.combatId having count(uc.id.userId) < 4)")
        fun findAllAvailableRoomForPlayer(@Param("playerId") playerId: Int): List<CombatDO>
    

    但是这样仍然没有解决问题,原因是数据库是先查找,后加锁,虽然保证了只有一个线程访问,但数据可能已经无效了,而且对大量数据加悲观锁导致性能降低。
    同时,我观察到尽管我在开局加入了重试机制,但在发生异常时仍然无法开局。原因是GameController::open()开启了事务,GameService::joinCombat()也被注解了@Transactional,默认继承了GameController::open()开启的事务,然后GameService::joinCombat()异常返回时将事务标记为rollbackOnly,导致事务回滚。
    因此我选择去除GameController::open()的事务注解。不加锁查找战局,然后重试GameService::joinCombat(),在其中加锁和校验。如无法加入再开新局。

    是否解决

    收获

    并发程序设计真nm神奇学习了一堆数据库和并发程序设计的知识

    评价队友

    值得学习

    1. 十分有想法

    需要改进

    1. 十分有想法

    PSP

    我觉得这个辣鸡表格没什么作用,谁tm写代码的时候还会特意去记个时,写一半想起来计时早就不知道什么时候开始的了

    PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
    Planning 计划 20 20
    Estimate 估计这个任务需要多少时间 20 20
    Development 开发 1065 2520
    Analysis 需求分析(包括学习新技术) 75 180
    Design Spec 生成设计文档 60 480
    Design Review 设计复审 60 60
    Coding Standard 代码规范(为开发制定合适的规范) 60 60
    Design 具体设计 120 300
    Coding 具体编码 600 1200
    Code Review 代码复审 60 120
    Test 测试(自我测试,修改,提交修改) 30 120
    Reporting 报告 60 120
    Test Report 测试报告 20 20
    Size Measurement 计算工作量 10 10
    Postmortem & Process Improvement Plan 事后总结并提出过程改进计划 30 90
    合计 1145 2660
  • 相关阅读:
    paraview添加vector
    origin横纵坐标颠倒
    [转] python提取计算结果的最大最小值及其坐标
    康奈尔大学CFD课程
    anaconda多环境配置
    mfix的Negative gas density报错解决
    python基础补漏-01
    ssh 公钥登陆的问题
    多进程
    关于GIL
  • 原文地址:https://www.cnblogs.com/rtxux/p/11678776.html
Copyright © 2011-2022 走看看