第三章 开发环境搭建:Git版本控制、Docker容器化部署、CI/CD流水线

说实话,很多新手项目死在第一步——环境不一致。你在本地跑得好好的,部署到服务器就崩了。我见过太多次了。所以这一章,咱们把地基打牢。

3.1 Git版本控制:不只是git add和git commit

Git这东西,很多人用了好几年还停留在「三板斧」阶段。但做游戏服务器,分支策略直接决定团队协作效率。

3.1.1 分支模型怎么选?

我个人习惯用Git Flow的简化版。为什么?因为游戏服务器迭代快,但又要稳定。

  • master分支:只放线上版本。每次合并必须经过Code Review。
  • develop分支:日常开发的主干。所有feature分支从这里拉。
  • feature/xxx分支:每个新功能一个分支。比如feature/chat-system。
  • release分支:发版前一周拉出来,只修bug,不加功能。
  • hotfix分支:线上出紧急bug时用。修完直接合master和develop。

避坑指南:我曾经有个项目,团队20个人全在develop上开发。结果某天有人push了一个未完成的副本系统,整个服务器启动不了。从那以后,我强制要求feature分支必须经过CI才能合入。

3.1.2 提交信息规范

你想想看,如果提交信息全是「fix bug」、「update」,三个月后谁看得懂?我定了个简单规则:

类型(模块): 简短描述

示例:
feat(login): 添加微信登录支持
fix(match): 修复匹配超时后玩家状态未重置
refactor(db): 重构玩家数据缓存逻辑
docs(readme): 更新部署文档

类型我用这几个:feat、fix、refactor、docs、test、chore。模块名对应项目里的目录名。

3.1.3 .gitignore里该放什么?

嗯,这里要注意。游戏服务器项目里,有些文件绝对不能进版本库:

  • 编译产物:.o、.exe、.dll
  • 配置文件里的敏感信息:数据库密码、密钥
  • 日志文件:*.log
  • IDE配置:.idea、.vscode
  • 临时文件:*.tmp、*.swp

小技巧:我习惯在项目根目录放一个gitignore模板,每次新项目直接复制。省得漏掉什么。

3.2 Docker容器化部署:一次构建,到处运行

说白了,Docker就是给你的游戏服务器套个「箱子」。箱子里环境一模一样,不管搬到哪台机器上都能跑。

3.2.1 Dockerfile怎么写?

以C++游戏服务器为例,我一般用多阶段构建:

# 第一阶段:编译
FROM ubuntu:20.04 AS builder
WORKDIR /app
COPY . .
RUN apt-get update && apt-get install -y cmake g++
RUN cmake . && make -j4

# 第二阶段:运行
FROM ubuntu:20.04
WORKDIR /app
COPY --from=builder /app/server .
COPY config/ ./config/
EXPOSE 8888
CMD ["./server"]

为什么用多阶段?因为最终镜像里只有可执行文件和配置,没有编译工具链。镜像体积从2GB降到200MB。我在项目中遇到过,镜像太大导致部署时网络传输慢,玩家等得骂娘。

3.2.2 docker-compose编排多服务

游戏服务器通常不是单体的。登录服、游戏服、数据库、缓存,得一起跑。docker-compose就是干这个的:

version: '3.8'
services:
  login-server:
    build: ./login
    ports:
      - "8080:8080"
    depends_on:
      - redis
      - mysql

  game-server:
    build: ./game
    ports:
      - "8888:8888"
    depends_on:
      - redis
      - mysql

  redis:
    image: redis:6-alpine
    volumes:
      - redis-data:/data

  mysql:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
    volumes:
      - mysql-data:/var/lib/mysql

volumes:
  redis-data:
  mysql-data:

注意:密码别硬编码在docker-compose.yml里。用环境变量文件.env,记得把.env加到.gitignore里。我曾经看到有人把生产库密码直接push到GitHub上,那场面...嗯,不提了。

3.3 CI/CD流水线:让机器替你干活

手动部署?那是上个时代的事了。CI/CD的核心思想很简单:代码一提交,自动编译、测试、部署。我选GitLab CI,因为和GitLab集成得好,不用额外搭Jenkins。

3.3.1 .gitlab-ci.yml配置

stages:
  - build
  - test
  - deploy

build-job:
  stage: build
  script:
    - docker build -t game-server:${CI_COMMIT_SHORT_SHA} .
    - docker tag game-server:${CI_COMMIT_SHORT_SHA} registry.example.com/game-server:latest
    - docker push registry.example.com/game-server:latest
  only:
    - develop
    - master

test-job:
  stage: test
  script:
    - docker run game-server:${CI_COMMIT_SHORT_SHA} ./run_tests
  only:
    - develop

deploy-staging:
  stage: deploy
  script:
    - ssh deploy@staging-server "docker pull registry.example.com/game-server:latest && docker-compose up -d"
  only:
    - develop

deploy-production:
  stage: deploy
  script:
    - ssh deploy@prod-server "docker pull registry.example.com/game-server:latest && docker-compose up -d"
  only:
    - master
  when: manual

你看,develop分支的代码合并后,自动构建镜像、跑测试、部署到测试服。master分支的部署到生产服,但需要手动确认——这个「手动确认」救过我很多次。

3.3.2 流水线里加什么检查?

我建议至少加这三道关卡:

  1. 编译检查:代码能不能编译通过?
  2. 单元测试:核心逻辑有没有被破坏?
  3. 代码风格检查:有没有人偷偷用了tab而不是空格?

个人经验:我曾经在流水线里加了性能测试——每次提交都跑一次压测,看响应时间有没有明显变化。虽然跑一次要10分钟,但抓到过好几次因为加日志导致性能下降的问题。

3.4 统一开发规范:别让代码变成「屎山」

游戏服务器项目,代码量动辄几十万行。没有规范,三个月后连自己写的代码都看不懂。

3.4.1 代码风格

我用的C++,风格是这样的:

规则 示例 说明
命名 类名:PlayerManager
函数:GetPlayerById
变量:player_count
类名大驼峰,函数大驼峰,变量小写下划线
缩进 4个空格 不用tab,不同编辑器tab宽度不一样
大括号 if (condition) {
    doSomething();
}
左大括号不换行
行长度 不超过120字符 太长的话,代码review时横向滚动很烦

3.4.2 日志格式

日志这东西,平时没人看,出问题的时候就是救命稻草。我定了个统一格式:

[时间] [日志级别] [模块] [线程ID] 消息内容

示例:
[2024-01-15 14:30:22.123] [INFO] [LoginServer] [12345] 玩家[10001]登录成功,IP: 192.168.1.1
[2024-01-15 14:30:22.456] [ERROR] [GameServer] [12346] 玩家[10001]创建房间失败,原因:房间已满

为什么要有线程ID?因为游戏服务器是多线程的,没有线程ID,你根本不知道日志是哪个线程打的,排查并发问题时会疯掉。

小技巧:我习惯在日志里加一个「请求追踪ID」。玩家每次操作都带一个唯一ID,这样可以把一次操作的所有日志串起来。排查线上问题时,直接搜这个ID就行。

3.4.3 配置文件规范

配置文件用JSON还是YAML?我选YAML。因为支持注释,而且可读性好。但有个坑:YAML对缩进敏感,一个空格不对就解析失败。

# 游戏服务器配置
server:
  name: "game-server-01"
  port: 8888
  max_connections: 10000
  
database:
  host: "192.168.1.100"
  port: 3306
  name: "game_db"
  # 密码通过环境变量注入,不写在这里
  password: ${DB_PASSWORD}

log:
  level: "info"
  path: "/var/log/game-server/"
  max_size: 100  # MB
  max_backups: 30

嗯,到这里,开发环境的基础设施就搭好了。Git管代码,Docker管环境,CI/CD管自动化,规范管质量。这套东西我用了好几年,从几个人到上百人的团队都适用。下一章,咱们开始写真正的游戏服务器代码。