腾讯课堂后台研发流程与规范 - 2021

AI 摘要: 本文主要介绍了课堂后台研发流程中的规范要点,包括业务域划分、微服务设计、命名规范、发布准备、监控等内容。文章详细阐述了各个方面需要注意的事项和操作步骤,让读者可以更好地理解研发流程,并且遵循相应的规范来提高工作效率和质量。关键词:研发流程、规范要点、业务域划分、微服务设计、命名规范、发布准备、监控

1. 后台研发流程

  1. 缺乏成体系的研发标准导致遇到各种问题
    • 作为入职新人,有没有研发流程和标准的文档参考?
    • 刚接到一个需求,课堂标准的研发流程是怎样的?(Thanos、Coding、Edudocker、 蓝盾、Tolosty、NoHost、Apollo、七彩石、北极星…)
    • 要开发一个 Go 服务,需要遵循哪些研发规范?(服务命名、PB 格式、目录分层、编码 规范、错误码处理、日志打印规范…)
    • 都在说 DDD,那么课堂业务涉及哪些业务域,每个业务域分别包含哪些业务模块?
    • 服务需要某个功能/函数/方法,有没有直接可以参考的? (比如分布式锁、分布式事 务、用户 UID 获取、两个 slice 求差集…)
    • CR 有哪些注意、日志应该如何、错误码如何规范化?
    • Git 分支命名提交有哪些规范?服务变更发布规范是怎样的,发布要检查哪些 CheckList,新建 STKE 集群有哪些标准化的?(研发流程一环,事故高发地)
  2. 常见问题(吐槽较多、延期的部分原因)
    • 方案评估:和产品、依赖研发沟通不足,导致后期产品需求变更或技术方案调整
    • 需求分工:需求任务拆分不合理(最长路径挂 Owner 自己头上)
    • 需求排期:需求排期过于乐观(研发环境、协调沟通、人员请假、紧急问题处理…)
    • 需求分析:遗漏产品功能,测试、体验阶段发现
    • 需求自测:缺乏自测验证,测试阶段来回返工
    • 需求发布:缺乏对生产环境的敬畏,发布引发现网问题(缺乏服务依赖梳理、发布 CR、配置变更、遗漏、上线服务 panic…)
    • 线上运营:缺乏监控告警,观测不足,导致对服务可用性存在担忧
    • STKE 配置:没有开启 HPA、没有就绪检测导致服务更新告警
    • ….

1.1. 在线需求 Owner 工作内容

1.2. 开发人员研发前\中\后节点关注

  1. 需求研发前(分析阶段)
    • 需求预审:输出 tapd 单(出具产品 目标、内容、确认 Owner)
    • 需求分析:方案调研选型&输出技术 方案文档(包含背景/架构设计/流程/ 存储/PB 设计/研发 Demo 等)
    • 可行性分析
    • 资源预估:Owner 预估人力、分工、 时间(确定人/事/时间)
  2. 需求研发中(编码阶段)
    • 研发准备:依赖资源提前申请、代 码预研(准备测试环境/依赖服务/Git 分支)
    • 代码编写:实际代码编写和调试 (包含服务分层/编码/CR/Bug 修复 /UT/接口测试…)
    • 进度同步:Owner 拉开发群,定期 同步需求进度&风险(包含 PM/产品/ 研发/Leader 等干系人)
    • 需求测试:先自测再联调测试(包 含 UT/接口/集成/联调/压力/测试团队 测试,减少返工)
  3. 需求研发后(发布阶段)
    • 需求发布:遵循教育后台发布规 范(包括代码发布、SQL 执行、 配置变更、服务器调整等所有可 能影响线上结果的动作)
    • 运营观测:需求涉及服务接口请 求量、资源负载、告警配置(配 备面板观察)
    • 需求总结:回顾研发过程得失好 坏,总结经验

1.3. 技术方案模板(包含需求方案、架构设计模板)

  1. 需求方案模板
    1. 需求介绍: 需求介绍主要描述需求的背景、目标、范围等
    2. 需求分析: 全方位地描述需求相关的信息(5W 分析法)
    3. 复杂度分析: 复杂度常见的有高可用、高性能、可扩展等,要有详细的逻辑推导,避免完全拍脑袋式决策
    4. 方案对比评估:
      • 方案罗列:1~3 个备选方案,每个备选方案需要描 述关键的实现,无须描述具体的实现细节
      • 方案要点:性能、可用性、硬件成本、项目投入 、复杂度、安全性、可扩展性等
      • 方案选择:不单单考虑性能高低、技术是否优越 这些纯技术因素,结合业务的需求特点、运维团 队的经验、已有的技术体系、团队人员的技术水 平综合评估选择
  2. 架构设计模板
    1. 总体方案: 总体方案需要从整体上描述方案的结构,其核心内容就是架构图,以及针对架构图的描述,包括模块或者子系统的职责描述、核心流程
    2. 架构总览: 从整体上描述方案的结构,给出架构图以及架构的描述包括模块或者子系统的职责描述、核心流程
    3. 核心流程: 业务或技术处理的核心流程图示说明
    4. 详细设计: 包括高可用、高性能、可靠性、扩展性、安全性
    5. 部署方案: 硬件要求、云 PaaS 服务配置、服务容量评估
    6. 演进规划: 规划和设计的需求比较完善,但如果一次性全部做完,项目周期可能会很长,因此可以采取分阶段实施,第一期做什么、第二期做什么

1.4. 研发进度同步 – TRobot 关联 tapd 单

添加 Robot 机器人 -> 需求进度 -> 需求关联 -> 自动同步

1.5. 课堂研发环境说明

  • 研发平台:Coding (CICD、代码安检扫描)
  • 代码管理:Git 工蜂平台
  • 联调:Edudocker
  • 测试:Nohost
  • 后台服务快速发布:trpc_edu 脚手架二进制文件同步
  • 配置中心:七彩石
  • 服务注册:北极星、阿波罗 …

1.6. 需求单\Bug 单提单、关单规范

可以参考此规范要求测试完善 Bug 单内容,降低沟通成本

  1. BUG 单要求
    • bug 标题包含发现版本号、模块、复现描述
    • bug 复现地址链接
    • bug 详细描述复现步骤
    • bug 必现、偶现(偶现概率)
    • bug 实际结果和预期结果(描述+截图+视频)
    • 原视频和原图
    • 附件 bug 验证素材包
    • 上传 log,标注一下问题发生确切时间点
  2. BUG 单关单、转单规范: 尽量都把信息填完整,包括功能测试和现网反馈的单
    • bug 单评论信息认真填写
    • 码提交 git 时,带上 bug 单 id(--bug=),这样在 tapd 单上测试可以很直观的查看到

2. 微服务研发规范

Tips: 先搞清楚 What,才能思考 Why,最后才能去弄明白 How

我已经很清楚研发的流程步骤,那在研发过程中有哪些 规范需要遵循?

2.1. 理解微服务主要思想和原则

  • 分治思想,微服务拆分的度?
  • 康威定律/two 披萨思想,业务域/微服务拆分,RPC 等
  • 拆分带来的挑战:一致性、全连监控、服务治理、服务维护…
  • 数量众多的服务 CICD

参考:https://martinfowler.com/articles/microservices.html

2.2. 需求分析交付件 – iwiki 技术方案文档规范

2.2.1. 需求文档写在哪?

Tips: 业务域下尽量符合 MECE 原则,这样后 续找文档也会更加“内聚”

按业务域 > 业务需求 > 要点文档(序号 – 标题方式编写)

2.2.2. 需求文档标准模板内容参考

  1. 需求背景:说明清楚整体需求背景/核心问题/业务目标等
  2. 解决方案:产品/技术解决方案概述
  3. 技术选型:若存在多套可行方案,提供方案优劣对比
  4. 架构设计:画出整体架构设计
  5. 服务规划:规划采用哪些服务实现业务需求
  6. 核心流程:画出关键的业务流程/时序图
  7. 接口设计:设计服务提供的能力,输出接口 PB 设计
  8. 存储设计:使用的存储,缓存设计
  9. 容量预估:预估服务的调用量,后续做资源申请和压测评估
  10. 信息记录:每次群/会议/线下沟通的结论/待定信息做备注,方便回溯,避免遗漏

参考: https://iwiki.woa.com/page s/viewpage.action?pageId= 1049743017

2.3. 微服务初始化 – 业务域、所属模块确认

Tips:不合理的起微服务导致服务扩散过快,以至于服务边界不清晰、职责不够内聚,结果就是维护成本持续增加

2.3.1. 新起服务的流程

  1. 做好需求分析,明确服务提供的能力,敲定划分领域,起好服务名字
  2. 拉模块 Owner、Leader 沟通& 同步,确保服务定位清晰

2.3.2. 新起服务前,应该能够清楚回答下面几个问题?

  1. 新服务提供了哪些能力?(提供能力)
  2. 新增的命令字,是否一定需要新起服务?,是否有更合适的服务承载? (维护成本)
  3. 服务后续规划会扩充哪些能力?(扩展性)
  4. 服务归属的业务域/功能模块,是否能很好圈定所起服务?(DDD 思想)
  5. 服务是否边界清晰,职责明确?(单一职责)
  6. 服务是否涵盖太多能力,是否需要做进一步拆分?(KISS 原则、维护 成本)
  7. 服务命名是否清晰?(命名)
  8. 是否大家都有共同认识,组内有无其他建议?(共识)

2.3.3. 研发规范:遵循课堂业务域以及功能模块最新划分

Tips: 领域划分就是让所有人形成业务共识,清晰业务域边界,无论是业务、产品、研发

2.4. 服务领域、模块划分

参考: 腾讯课堂后台模块划分

2.5. 研发规范:微服务初始化 – PB 存放、命名约定

Tips: 上帝的归上帝,凯撒的归凯撒,PB 协议生成的内容,本应该与框架无关,仅与语言有关

2.5.1. PB 文件仓库

  1. java 服务 pb 管理: git.woa.com/csig_edu_pay_service/edu-pb-collection.git
  2. bingo 服务 pb 管理(按业务域划分): git.woa.com:ketang-service/pb/bingo.git
  3. trpc 服务 pb 管理(按业务域划分): git.woa.com:ketang-service/pb/trpc.git

2.5.2. PB 命名约定

  1. pb 文件名: go_edu_{server标识}.proto,server 描述不应该超过 2 个单词
  2. Server 命名: go_edu_{server标识}
  3. Service 命名:go_edu_{server标识} ,trpc 的一个 server 对应多个 service,通常为 1:1
  4. package 命名: trpc.ketang. go_edu_{server标识}
  5. go*package 路径: git.woa.com/ketang-service/pb/trpc/{业务域标识}/{模块标识}/ go_edu*{server 标识}

2.6. 研发规范:微服务初始化 – PB 范例

2.6.1. package 和 go_package 约定

  1. syntax协议采用 PB3 版本,package 按格式约定
  2. package名以 trpc.ketang 为固定前缀开头,后面接 server 名字全称,服务名仅支持小写、下划线,比如go_edu_lighthouse_manage
  3. option指明go_package位置
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
syntax = "proto3" ;

// package内容格式为trpc.ketang.{server},以trpc.ketang为固定前缀,标识这是一个trpc服务协议,app为ketang,server为你的服务进程名
package trpc.ketang.go_edu_lighthouse_manage;

// 注意:这里go_package指定的是协议生成文件pb.go在git上的地址
option go_package = "git.woa.com/ketang-service/pb/standard-pb/agency/tob/go_edu_lighthouse_manage";

// 企业入驻信息提交
message DoRegSubmitReq {
  uint32 com_id = 1; // 企业id。新建一个企业传0,修改一个企业传对应id
  string com_name = 2; // 企业名
  string com_code = 3; // 统一社会信用代码
  string contact_name = 4; // 企业联系人名
  string contact_phone = 5; // 企业联系人手机号
  string verify_code = 6; // 验证码
}
message DoRegSubmitRsp {}

service go_edu_lighthouse_manage {
  // 1.企业管理后台-企业入驻信息提交
  rpc DoRegSubmit(RegSubmitReq) returns (DoRegSubmitRsp);
}

2.6.2. 研发规范:微服务初始化 – PB stub 生成

1
2
3
4
5
$ trpc create -f -d . -p ./go_edu_lighthouse_manage.proto --rpconly
[create]
[create] Generate rpc stub success
[create] run cmd: mockgen -destination=go_edu_lighthouse_manage_mock.go -package=go_edu_lighthouse_manage -self_package=git.woa.com/ketang-service/pb/standard-pb/agency/tob/go_edu_lighthouse_manage --source=go_edu_lighthouse_manage.trpc.go
[create] 创建trpc工程 ```go_edu_lighthouse_manage``` 后处理成功

2.6.3. 生成的 stub 桩代码 RPC、App、Server、Service、Method 名字

查看go_edu_lighthouse_manage.trpc.go文件:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
//
// GoEduLighthouseManageServer_ServiceDesc descriptor for server.RegisterService
var GoEduLighthouseManageServer_ServiceDesc = server.ServiceDesc{
	ServiceName: "trpc.ketang.go_edu_lighthouse_manage.go_edu_lighthouse_manage",
	HandlerType: ((*GoEduLighthouseManageService)(nil)),
	Methods: []server.Method{
        {
            Name: "/trpc.ketang.go_edu_lighthouse_manage.go_edu_lighthouse_manage/DoRegSubmit",
            Func: GoEduLighthouseManageService_DoRegSubmit_Handler,
        },
        ..
    }
}
...

// 通过trpc脚手架生成的桩代码,注释部分的内容就是通过pb文件的package解析自动生成
func (c *goEduLighthouseManageClientProxyImpl) DoRegSubmit(ctx context.Context, req *DoRegSubmitReq, opts ...client.Option) (rsp *DoRegSubmitRsp, err error) {

	ctx, msg := codec.WithCloneMessage(ctx)

	msg.WithClientRPCName(GoEduLighthouseManageServer_ServiceDesc.Methods[4].Name)
	msg.WithCalleeServiceName(GoEduLighthouseManageServer_ServiceDesc.ServiceName)
	msg.WithCalleeApp("ketang")                          // 应用是课堂
	msg.WithCalleeServer("go_edu_lighthouse_manage")     // Server是 go_edu_lighthouse_manage
	msg.WithCalleeService("go_edu_lighthouse_manage")    // Service是 go_edu_lighthouse_manage,默认情况一个Server和Service是1对1,但也支持1对多
	msg.WithCalleeMethod("DoRegSubmit")                  // 调用的方法
	msg.WithSerializationType(codec.SerializationTypePB)

	callopts := make([]client.Option, 0, len(c.opts)+len(opts))
	callopts = append(callopts, c.opts...)
	callopts = append(callopts, opts...)
	rsp = &DoRegSubmitRsp{}

	err = c.client.Invoke(ctx, req, rsp, callopts...)
	if err != nil {
		return nil, err
	}
	codec.PutBackMessage(msg)

	return rsp, nil
}

2.7. 研发规范:微服务初始化 – DDD 服务目录分层

2.7.1. 进入业务模块 & 初始化项目目录

1
2
3
4
5
6
// 创建空项目
$ cd ke/account
$ mkdir trpc_ketang_account_demo && cd trpc_ketang_account_demo

// 快速初始化项目目录
$ mkdir -p app/{application,domain/{entity,repository,service},infrastructure/{dbs,rpc},interfaces} configs/{errcode,rainbow} proto internel test

2.7.2. 目录分层说明

  1. app为应用层,内部主要包含四层:
    • application: 应用层完成对业务领域进行服务编排,文件后缀用 _app.go 结尾,另外像redis这类操作需要在应用层来实时
    • domain: 业务领域层,业务核心逻辑实现,其内包含
      • 领域实体 entity: 领域内的实体对象,后缀 _entity.go
      • 仓储接口 repository: 领域服务依赖的接口,后缀 _repos.go,其内定义的仓储接口后续被基础设施实现
      • 领域服务 service: 领域对应用层提供的服务能力,后缀 _service.go
    • infrastructure: 基础设施层包含对仓储接口的实现,文件后缀_infra.go,常见有rainbownrdsrpcdbkafka等操作等
    • interfaces:接口层,主要做参数校验、DTO 数据转换等操作,后缀_intf.go
  2. conf 为服务配置,通过后缀区分不同环境 yaml 配置: \_pro.yaml、\_test.yaml、 \_dev.yaml、pre.yaml
  3. internal: 内部包,仅给服务内使用,暂不对外提供引用

2.7.3. 课堂 DDD 示例

2.7.4. DDD 的相关知识了解

  1. https://km.woa.com/group/29048/articles/show/476578
  2. 极客时间 - 《DDD 实战》

2.8. 微服务初始化 – Handler 服务目录分层

// todo

2.9. 微服务初始化 – Yaml 配置(global, server)

trpc 的 yaml 配置包含 4 个部分: global、server、client、 plugin,yaml 的配置参数采用小写

yaml 配置文件位置放在 /conf下:

  1. 开发: /conf/trpc_go_dev.yaml
  2. 测试: /conf/trpc_go_test.yaml
  3. 生产: /conf/trpc_go_pro.yaml

2.9.1. 配置 yaml 示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
global: #全局配置
  namespace: Test #环境类型,分正式Production\开发环境Development\测试环境Test
  env_name: test #环境名称,非正式环境下多环境的名称
server: #服务端配置
  app: ketang #业务的应用名
  server: go_edu_lighthouse_manage #进程服务名
  admin:
    nic: eth1
    port: 8002 #admin的port
    read_timeout: 3000 #ms. 请求被接受到请求信息被完全读取的超时时间设置,防止慢客户端
    write_timeout: 60000 #ms. 处理的超时时间
  bin_path: /app/bin/ #二进制可执行文件和框架配置文件所在路径
  conf_path: /app/conf/ #业务配置文件所在路径
  data_path: /app/data/ #业务数据文件所在路径
  filter: #针对所有service处理函数前后的拦截器列表
    - simpledebuglog
    - recovery #拦截框架创建的业务处理协程panic
    - trpc_edu_log #教育全链路日志
  service: #业务服务提供的service,可以有多个
    - name: trpc.ketang.tob.go_edu_lighthouse_manage
      nic: eth1
      port: 18808 #服务监听端口 可使用占位符 ${port}
      network: tcp #网络监听类型  tcp udp
      protocol: trpc #应用层协议 trpc http
      timeout: 1000 #请求最长处理时间 单位 毫秒
    - name: trpc.ketang.tob.go_edu_lighthouse_manage_qapp
      nic: eth1 #绑定的网卡
      port: 18809 #服务监听端口
      network: tcp #网络监听类型
      protocol: qapp #应用层协议
      timeout: 1000 #请求最长处理时间 单位 毫秒
plugins: #插件配置
  selector: #针对trpc框架服务发现的配置
    polaris: #北极星服务发现的配置
      debug: true #开启 debug 日志
      log_dir: $HOME/polaris/log #北极星日志目录
      protocol: grpc #名字服务远程交互协议类型
      address_list: polaris-discover.oa.com:8090 #北极星后台服务地址
      enable_trans_meta: true #透传字段摘除前缀, 匹配北极星规则
  registry: #服务注册配置
    polaris: #北极星名字注册服务的配置
      register_self: true #是否注册,默认为 false,由 123 平台注册
      heartbeat_interval: 3000 #名字注册服务心跳上报间隔
      address_list: polaris-discover.oa.com:8090
      service:
        - name: trpc.ketang.tob.go_edu_lighthouse_manage
          namespace: Test #环境类型,分正式Production\开发环境Development\测试环境Test
          token: f049e5803ce74d11a80e9572ac7f6021 #服务注册所需要的 token
        - name: trpc.ketang.tob.go_edu_lighthouse_manage_qapp
          namespace: Test #环境类型,分正式Production\开发环境Development\测试环境Test
          token: 1ffbe530bb4941dd89a79713fa25ced1 #服务注册所需要的 token
  log: #日志配置
    default: #默认日志的配置,可支持多输出
      - writer: file #本地文件日志
        level: debug #本地文件滚动日志的级别
        writer_config:
          filename: ../log/go_edu_lighthouse_manage.log
          max_size: 100 #本地文件滚动日志的大小 单位 MB
          max_backups: 10 #最大日志文件数
          max_age: 7 #最大日志保留天数
          compress: false #日志文件是否压缩
  config:
    rainbow: #七彩石配置中心
      providers:
        - name: rainbow #provider名字,代码使用如:`config.WithProvider("rainbow")`
          appid: df3218f5-bc8e-47b3-92a6-be8694be72cd
          group: go_edu_lighthouse_manage
          type: kv
          env_name: Test
          file_cache: /tmp/go_edu_lighthouse_manage.backup
          enable_client_provider: true
          timeout: 2000
  tracing: #教育全链路日志插件
    trpc_edu_log: #日志配置
      business_type: edu #业务类型
      service_name: go_edu_lighthouse_manage #服务名
      gen_trace_id_if_empty: true #生成traceId
      trpcedulogcfg: #选项
        max_data_length: -1 #-1不限制,全部上报. 可能会影响性能.
        dcdef_report: req #仅上报req
        dccmd_report: #方法级的上报配置
        logdef_report: req_rsp #日志req和rsp均打印
        logcmd_report: #方法级的打日志配置
  1. global
    • 环境空间 namespace: Prdouction/Test/Development
    • 环境名 env_name: 可不填
  2. server
    • server 定义的:格式:go_edu_{srv描述}Git 仓库名、 Coding 平台、服务名、主调服务必须使用该名称,便于统一,示例: go_edu_lighthouse_manage
  3. service定义,支持trpcqapp协议
    • trpc 协议格式:trpc.ketang.{二级模块名}.go_edu_tob_lighthouse,端口奇数号(5 位数)
    • qapp 协议格式: trpc.ketang.{二级模块名}.go_edu_tob_lighthouse_qapp,端口奇数号+1

2.9.2. client

Tips: yaml 的 client 部分,需要放到七彩石配置中, key 为 client.yaml

  1. client 用于配置被调服务,支持统一的 namespace、timeout,每个被调服务也有 name、protocol、target 部分
  2. service 部分
    1. name 格式: 和被调服务命名保持一致
    2. target 格式: 使用北极星做服务发现
    3. timeout : 支持针对下游单独配置超时
    4. protocol : 支持 trpc/qapp
  3. yaml 的 client 部分,需要放到七彩石配置中, key 为client.yaml

2.9.3. plugin

Tips: plugin 部分为 trpc 框架的组件依赖,默认必备组件:

  • selector: 服务发现组件,限定使用北极星 polaris,用于当前服务通过何种 组件发现被调服务,最终会通过选择器获取注册 的被调服务的 IP、Port,用于后续的 RPC 请求
  • log: 日志组件,默认文件 file 输出,用于当前服务的日志如何管理、 输出位置、切割方式等
  • config: 配置组件,默认采用七彩石 rainbow 配置组件
  • tracing: 全链路跟踪组件,教育这边统一用 trpc_edu_log 进行日志分析

2.10. 编码规范

2.10.1. main.go 文件编写

Tips: main.go 仅包含仓储实现和服务初始化,不包含其他代码处理逻辑

2.10.2. 研发规范:golang 代码规范参考

  1. 腾讯内部 Go 编码风格: https://git.woa.com/standards/go
  2. https://go.dev/doc/effective_go

2.10.3. 研发规范:编码测试 - trpc 接口本地调试

安装 trpc_ui 工具,参考 iwiki 官网: https://git.woa.com/trpc-go/trpc-cli

  1. ~/.bashrc 配置一个别名,方便快速启动: alias trpc_ui_run='trpc-ui run --port=8000 >> /data/logs/trpc/trpc-ui.log 2>&1 &'
  2. 执行 trpc_ui_run ,然后通过访问 8000 端 口: http://localhost:8000/trpcui/#/tRPC-UI
  3. 选择加载我们的 proto 文件,ip 端口配置下
  4. 本地运行服务,然后通过 trpc-ui 请求调试,完成了一个本地简单 CMD 闭环

2.10.4. 研发规范:编码测试 – 单元测试

测试框架选择: https://github.com/golang/mock

Golang 单测 Tips:

  1. 先从最简单的 testing 包学习(无外部依赖的输入/输出测试)
  2. 面向接口编程,了解依赖注入、控制反转的思想
  3. 利用 gomock 框架的 mockgen 尝试对 go 的接口打桩,实现简单 mock test case
  4. 尝试让自己的代码更易于测试(代码行数控制、面向接口编程、依赖注入)
  5. 搞清楚测试的目标,要验证哪些内容,不要为了测试而测试
  6. 单测代码也需要维护

2.10.5. 命令执行

1
2
3
4
5
6
7
// 用例执行
go test -gcflags=all=-l -v -cover -run=Test_foo
go test -gcflags=all=-l -v -cover FunctionMock_test.go FunctionMock.go go test -gcflags=all=-l -v –cover
go test -gcflags=all=-l -v -cover ./...

// 覆盖率
go test -gcflags=all=-l -v -coverprofile=cover.out ./... go tool cover -html=cover.out -o=coverage.html

2.10.6. 其他 UT 资料参考

2.11. 研发规范:GIT 使用规范 – 分支命名、提交规范、发布

2.11.1. Git 分支命名规范

分支名命名规范分支作用示例
主干分支master主干分支,和现网代码保持同步master
特性分支feature/xxx_20220101特性开发分支,提交和发布都需要先和 master 同步feature/login_tracelog_20211103
Bug 修复分支bugfix/xxxx_20220101通常有 Bug 单和 Bug 排期bugfix/login_tracelog_20211103
Tagv主.次.修订版本多用于 Library 库v1.0.1

2.11.2. Git 分支管理流程

  1. 新建特性分支,比如 feature/lighthouse_20220323,用作多人协作开发分支
  2. 开发人员基于特性分支 Check 出自己开发分支:
    • feature/lighthouse_20220323_mars
    • feature/lighthouse_20220323_gold
  3. 各种开发完代码后,合入特性分支,完成 CR:
    • 同步 feature/lighthouse_20220323
    • feature/lighthouse_20220323_mars -> feature/lighthouse_20220323
  4. 发布阶段

2.11.3. Commit message 规范

  1. bug 改动: --bug=bugid+空格+本次改动说明
  2. 需求改动: --story=tapdid+空格+本次改动说明
  3. 其他改动: --other=本次改动说明

2.11.4. Git 分支操作规范

  1. 提交推送代码之前,必须先同步远端代码,解决冲突完后方可推送到远端仓库
  2. MR 被 Approve 合入过程,发现目标分支有冲突,需要提 MR 同学先解决冲突后,再次重新提交 MR
  3. MR 被 Merge 过程,需要提供 ChangeList 说明合入改动点和影响

2.11.5. 分支发布规范

走 Coding 发布,统一走主干发布,发布时候提供指定的 CommitID 或者 Tag 名称

2.12. 研发规范:CodeReview 规范

Tips: 新起的服务更应该把 CR 做扎实,避免“破窗效应”

2.12.1. CR 基本要求

  1. CR 平台:都在 GIT 平台上进行,便于回溯和统计
  2. CR 频次:
  • developer 以 1 天维度,遵循小批量提交
  • reviewer 最迟隔天完成 CR
  1. CR 规模:控制在 400 行代码以内
  2. CR 备注:提意见要具体,不要模棱两可(可以补充伪代码或示例代码)

2.12.2. CR CheckList

  1. 代码设计: 服务职责、代码架构设计、分层、文件命 名、接口设计
  2. 业务逻辑: 业务流程问题、边界问题、数据一致性等
  3. 运行性能: 锁冲突、协程泄露、SQL 索引
  4. 编码规范: 错误处理、可读性问题、测试用例、代码风格、注释、命名等(参考 go-standard 标准提升) 5 编码安全:账号越权问题、SQL 注入、CSRF、XSS 等问题

2.12.3. 其他资料参考:

2.13. 研发规范: 微服务组件选型约定

组件说明必须使用
RPC远程调用相关 Client 组件Share 库实现
DB数据库 Client 相关组件,包含 mysql、mongo、clickhouse、influxdb 等组件Mysql、TDSQL
Cache缓存相关组件,包括 ckv、redis 等Redis
MQ消息队列相关组件,主要包含 kafka、rabbitmq 组件Kafka
Config配置组件,包含 etcd、七彩石、apollo 等组件Rainbow
Storage存储组件,主要包含 cos、nfs 远端文件存储组件Cos
Search搜索组件ES
全链路链路日志跟踪ELK+Jager
面板监控支持将 STKE、全链路 ES、腾讯云、Prometheus 等数据源收集起来,统一展示Grafana
远程日志远程日志CLS
数据上报服务内关键业务数据上报组件Atta

第三方库选型,遵循无传染开源协议,优先采用扩展性、稳定第三方库:

2.14. 研发规范:服务日志规范 – 日志级别、内容格式

2.14.1. 采用正确的日志级别打印日志

日志级别日志作用补充说明
trace非常具体的跟踪信息,仅用于开发调试,问题处理完后,打印日志代码会被清理掉开启 trace 级别,需要临时打印日志记录,过后需要删除 trace 跟踪日志
debug非常具体的调试信息,仅用于开发调试,原则不在现网使用,特殊情况(现网 Debug)过后,需要关闭开启到 debug 级别,在特定的代码位置,输出 debug 信息,为问题分析提供详细日志(开发调试、生产特殊情况)
info现网日志信息,通常用于辅助问题定位和分析特定的代码位置,输出 info 信息,用于辅助问题定位和分析,有时候还会起到统计作用
warn服务出现了一定问题,代码做了一定容错,仍然能继续运行比如 DB 查询失败,有降级返回缓存信息
error服务出现严重问题,无法完成正常的业务处理,必须有人处理和跟进比如数据库查询失败、RPC 网络调用超时、严重的业务逻辑或错误、nil 指针等

2.14.2. 日志格式要求

  1. 要求内容格式完整:用英文描述,包含主体、动作、事件,避免打印冗余的日志信息,避免有效日志信息被淹没
  2. 避免重复打印:避免每层函数打印重复日志,在最底层位置打印即可
  3. 携带上下文信息打印:通过 log.ErrorContextf、logInfoContextf 格式化输出,并附带请求上下文信息
  4. 特定标识区分关键信息:利用 “()”、“[]” 框出关键信息,比如 uid、aid 信息,用 log.Infof、logErrorf 格式化输出

2.15. 研发规范:错误码映射、划分、命名规范

2.15.1. 错误信息在接入层对外做错误码映射

2.15.2. 错误码管理

  1. 错误码服务ID(3位)+错误类型(2位)+错误编号(3位) – 服务 ID 与录入 Coding 的服务 ID 对齐
  2. 命名ErrXxx=errs.New(code, msg),遵循驼峰命名,使用 git.code.oa.com/trpc-go/trpc-go/errs
  3. 包名:错误码统一防止在根目录的/errcode/errcode.go文件内,包名为errcode,示例:errcode.ErrInputParams

2.15.3. 错误码分段

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package errcode

import "git.code.oa.com/trpc-go/trpc-go/errs"

// 初步定向错误域范围(服务ID前3位+
//	服务ID: 314
//	  系统逻辑错误:  314|10-50xxx
//		参数异常:      314|10|xxx
//		RPC错误:	    314|11|xxx
//		DB错误:	   	  314|12|001
//		Redis错误:  	314|13|001
//		ZK错误:	   	  314|14|001
//	业务逻辑错误:   31450xxx
//      参数错误:     314|50|xxx

var (
	ErrLogicParam       = errs.New(31410000, "请求参数异常,请检查参数")
	ErrRpcRequestFail   = errs.New(31411000, "系统请求错误,请稍后重试")
	ErrUserNotAllow     = errs.New(31450010, "业务数据异常,导入无权限")
	ErrNotAllowFile     = errs.New(31450020, "业务数据异常,不支持的文件类型")
	ErrClassImportLimit = errs.New(31450021, "业务数据异常,任务处理中")
  ...
)

2.16. 研发规范:七彩配置规范

Tips: 默认配置中心使用 Rainbown 七彩石服务作为配置中心

2.16.1. 七彩石配置文件

七彩石配置 key 文件,推荐统一使用 rainbowcfg 包名,放在服务的基础设施层:app/infrastructure/rainbowcfg/rainbowcfg.go

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package rainbowcfg

const (
	// TTLUIDCompany 跟申请人UID的企业信息缓存的过期时间
	TTLUIDCompany = "ttl.uid_company"

	// 开班赠课计划任务配置
	CronGiveCourseExpress  = "cron.give_course.express"   // 定时任务执行间隔
	CronGiveCourseLimit    = "cron.give_course.limit"     // 批量查询课程数
	CronGiveCourseSemaSize = "cron.give_course.sema_size" // 并发赠课量

	// 结班率计算计算任务配置
	CronCalcEndClassPctExpress = "cron.calc_end_class_pct.express" // 定时任务执行间隔
	CronCalcEndClassPctLimit   = "cron.calc_end_class_pct.limit"   // 批量查询课程数
  ...
)

2.16.2. 七彩石配置键名规范

  1. 键名格式:必须全部用小写、点、下划线组成,通常以模块开头,通过点号.分割模块,名称支持下划线_分割
  2. 过期时间类:ttl.开头,示例:ttl.uid_info、ttl.contact_info
  3. 服务配置类:conf.开头,示例:conf.redis、conf.mysql、conf.kafka、conf.clickhouse_dsn
  4. 业务配置类:模块.开头,实例:user.phone.dft_head_url、user.guest.dft_head_url

2.17. 研发规范:缓存使用规范

2.17.1. 缓存配置文件

缓存配置关联的 key 文件,推荐统一使用 rds 包名,同基础设施放一起:app/infrastructure/rds/rdkey.go

对应缓存读写实现,放app/infrastructure/rds/rds.go,涉及非常多模块的缓存读写,可以继续拆分rds.go文件,比如user_rds.gocourse_rds.go

2.17.2. Redis 键名规范

键名格式:全部采用小写模式,名称支持下划线_,均采用%d进行 format 格式替换

依赖库:git.woa.com/edu-online/share/component/cache/rdshand

示例:

1
2
3
4
5
6
// 用户基本信息
RKeyUserInfo = "user_info:%d"
..
// 获取对应key名
key := rdshand.RdKey(rdkey.KeyOpenAccount, uid)
...

2.18. 研发规范:MYSQL 库、表、列、索引创建使用规范

2.18.1. 命名规范

库、表、列、索引命名均采用小写和下划线_,禁止使用大写、连字符来命名,命名不要太长或太短,应该确保语义清晰

  1. 库名::尽量保持与应用名一致,库名选定应该和存储业务数据范围有关联关系
  2. 表名:
    • 必须 t_前缀
    • 表名使用集合术语或名词,不使用复数
    • 禁用保留字做表名
  3. 列名:
    • 禁用 f_前缀
    • 列表必须包含字段
      • 主键 ID,统一采用id unsigned bigint
      • 记录变跟时间,created_at ,格式采用 DateTime,加好索引
      • 记录更新时间,updated_at ,格式采用 DateTime,加好索引
      • 记录删除时间,deleted_at ,格式采用 DateTime,加好索引(不要用 del_flag)
    • 特定字段约束
      • 后台运营操作,需要包含统一operator操作记录人
  4. 索引名:
    • 采用idx_开头,例如idx_phone_number

2.18.2. 存储规范

优先考虑业务的扩展性,尽可能规避业务增长需要改表存储宽度的问题,具体显示宽度参考 MYSQL 存储占用

  • 数字:正数都加上 unsigned;较小枚举类型,统一用unsigned tinyint(1)unsigned tinyint(2)类型
  • 文本:长度较小字符串,统一采用varchar(255),存储占用 length 为1+length(实际字符);中等长度字符串可以采用 varchar 或者 text
  • 日期时间:优先采用 DateTime 格式

2.18.3. 索引规范

  1. 针对 where 条件、order byhavingjoin 等 SQL 查询,优先考虑增加索引
  2. 关联索引注意列查询的顺序,通过explain检测索引命中情况

2.18.4. SQL 编写规范

  • 关键字大写,比如 SELECT、FROM、WHERE 等
  • 适当分行增加可读性
  • 连表数量尽量控制在 3 张表以内,过多连表 Join 操作危害占用过多 DB 内存和计算资源

2.18.5. Mysql 规范更多学习参考

  1. https://git.woa.com/standards/sql
  2. https://www.sqlstyle.guide/
  3. 极客时间 - 《Mysql45 讲》

2.19. 编码安全规范:通用、业务代码安全规范

2.19.1. 业务代码安全规范

  • 输入校验:请求数据量上限、查询类型等
  • 访问控制:通过权限控制,防越权问题
  • 日志记录:人为操作需要有操作日志流水记录表
  • SQL 操作:预编译+参数绑定
  • 文件上传:限制上传大小、文件类型
  • Web 跨域:CORS 限制等
  • 会话管理:CSRF 防护、防重放攻击
  • 并发控制:使用并发安全的 map、数据一致性问题
  • 表单处理:入参检测、入参类型限定、XSS 防护
  • 内容过审:接入信安等防护检测系统,确保平台内容输出内容符合国家安全法规

2.19.2. 通用安全规范

  • 内存安全:协程泄露、nil 指针、panic 等
  • 文件操作:文件、目录的权限控制,授权校验,防止越权访问
  • 系统接口:限定安全 CMD 等
  • 通信安全:TLS 等、Token 票据校验
  • 数据安全:加密存储、打码脱敏输出
  • 加密解密:秘钥不存代码、不用弱密码

2.19.3. 更多参考

  1. https://git.woa.com/standards/security/blob/master/Go安全规范.md
  2. 腾讯基础安全库 Iwiki 文档

3. 服务发布变更规范

我的服务已经开发完成准备发布,应该关注哪些内容?

3.1. 发布规范知悉 - 前\中\后动作

Tips: 发布动作包括代码发布、配置发布、SQL 执行、服务器调整

3.1.1. 发布前准备

  • 是否完成有效的 CR
  • 是否是核心模块,提前知会
  • 确认容量评估和压测指标
  • 确认灰度方案和回滚方案
  • 确认发布检测方法
  • 确认是否高峰期/禁止发布期
  • 通过 Leader 发布评审
  • 发布群周知

3.1.2. 发布中执行

  • 按量灰度,不要一把梭
  • 有效检测,确认发布正常(配置/接口流量/服务日志/错误码/DB 存储/产品体验…)
  • 发布异常要及时上升 Leader
  • 发布异常要确认,不能存侥幸

3.1.3. 发布后值守

  • 常规模块值守 > 1H
  • 核心模块值守 > 2H,例如接入层、账号、资料、房间、课详、下单、支付、入口页等
  • 关注告警群是否有异常
  • 亲自体验功能是否符合预期
  • 发布结果同步
  • 发布收尾(代码合并、发布单关单等)

3.2. 服务容量评估(业务规模、压测、服务性能等)

Tips:发布之前做好服务容量评估,做到知己知彼

3.2.1. 容量评估

  • 业务规模:预估系统的访问规模(基于业务方提供的数据,结合分布式链路的依赖推断)
  • 服务性能:选定运行容器的 CPU、内存后,通过压测得到服务单一 Pod 的性能情况(控制 CPU 利用率在 70%以下)
  • 机器资源:业务规模/服务阈值得到预估实例数(结合 STKE 平台的 HPA 做到弹性扩缩容)

3.2.2. 压测类型选定

  • 单命令字压测:针对高频命令字,在星海平台进行压测测试(示例参考)
  • 全链路压测:针对课堂线上环境产品进行全量系统的压测(通常在大型需求/活动上线前后)
  • 场景压测:针对特定模块场景下进行压测(比如房间百万 PCU 压测)

3.2.3. 压测结果分析

  • 性能指标:失败率、RT 时延、吞吐量、错误信息为压测最常关注指标
  • 分析与优化:针对压测性能不符合预期,结合代码做优化,重复压测流程直至性能达标
  • 结果同步:在发布 CheckList 中罗列服务容量以及压测结果数据,方便关系人知悉服务性能基本情况

3.3. DB 变更操作规范

3.3.1. 操作平台:DBS

统一采用 DBS 操作,禁止使用 MYSQL 客户端对生产 DB 数据进行变更操作

3.3.2. 账号授权:最小授权原则

  • 业务 DB 读写账号按权限开发,禁止使用 root 账号
  • 业务日常管理的账号,仅开放 Select 查询权限,最小权限开放
  • root 账号由业务模块负责人统一管理

3.3.3. 现网 DB 变更流程:SQL 自测验证、风险评估、灰度操作、避开高峰期操作、操作知会

  1. 自测验证:
    • 在测试 DB 自测验证无误后,方可在提交生产执行
  2. 风险评估:
    • 清楚 SQL 执行后对现网用户的影响(比如更改大表长时间锁表对用户使用造成影响)
    • 执行性能评估(比如 SQL 批量导出更新,通过 Explain 评估耗时)
    • Select/Update/Delete 操作增加 Limit 限定
    • 涉及 DB 迁移类的处理,必要时拉上腾讯云 DB Helper 一起评估
  3. 灰度操作:
    • Update、Delete 批量操作,增加限定 Limit,评估性能、结果是否符合预期
  4. 避开高峰期操作:
    • 常规业务高峰:上午 8:00~10:00、下午 2:00~4:00、晚上 7:00~9:00
    • 特定业务高峰:疫情高峰、指定大会等,通常都有指定的发布时间窗口
    • 特殊情况需要走 Leader 申请审批
  5. 操作知会:
    • 变更周知 Leader 以及相关模块核心人员

3.4. 服务监控告警

tips: 通过服务监控清楚知道服务业务访问情况、有哪些应用和系统错误,是否对业务有影响 包括服务调用量、错误监控、资源使用情况等

3.4.1. 监控内容

  • 全链路监控:配置 ke.oa.com 上的服务监控,配置服务命令字的 RT、错误码监控
  • SKTE 资源监控:默认 Coding 自动配置 SKTE 资源监控
  • 云服务监控:云上告警各个组相关配合注意事项

3.4.2. 监控面板

相比离散的监控图表,可以将全链路、STKE 资源通过 Grafana 进行聚合呈现,让监控更体系化!

面板示例参考: 账号质量看板、账号机器资源看板、直播间监控看板、课堂核心指标看板

3.4.3. 告警群关注

全链路告警、STKE 告警、在线云资源告警、拨测告警

3.5. 发布 CheckList

tips: 对生产环境抱有敬畏之心

3.5.1. 发布须知

  1. 心态和清晰的头脑是至关重要的,清晰完备的 CheckList 可以尽可能规避人为因素的影响
  2. 不管是成功还是失败,都要做好发布同步周知,不要闷头处理
  3. 发布期间遇到问题时,优先保证恢复服务可用性,再来分析导致问题原因

3.5.2. 发布 CheckList 清单参考

  • 发布前知会:对生产做的任何变更都需要提前知会 Leader 和干系人,避免突发状况无法应对
  • 遗留问题:是否还有代码 Bug 没有解决,是否影响发布的节奏
  • 变更知会:发布后功能用户是否有感知,是否会对用户有影响,提前同步客服同学
  • 性能测试:服务是否高频核心服务,是否满足业务性能指标
  • 埋点/上报:新功能按需做好数据上报、日志埋点,用于后续数据对比和分析
  • 日志完善:日志级别是否改成 info 级别日志,info 级别的日志是否能够快速定位问题
  • 开关控制:支持特性/灰度/白名单开关控制,快速控制影响范围(比如代码重构、新产品功能特性)
  • 代码 CR:发布前,确保代码通过组内 CodeReview
  • 资源申请:提前走流程申请好云上资源
  • 服务配置:DB、Redis、依赖服务等配置信息提前准备
  • 服务依赖:确定服务发布依赖顺序,以及依赖服务是否需要扩容,做到提前知会依赖服务负责人
  • 数据备份:涉及存储数据批量变更,需要提前做好数据备份
  • 灰度方案:灰度方案是否合理,参考灰度发布规范
  • 功能验证:提前准备好服务正确性校验方法(比如 Toltoy、Curl、Postman、产品主流程体验等)
  • 突发预案:提前准备好回滚流程、异常处理方案应对突发预案,降低失败发布影响面和时长
  • 人员安排:大型需求发布后,需要安排人值守观察,方便遇到问题第一时间同步(比如手机号、大型活动)
  • 进度同步:发布进行/成功/失败,在发布群同步周知
  • 代码合并:发布完成后,是否完成分支代码的合并

3.6. 灰度发布,尽可能控制发布的影响面

tips:必须有服务灰度过程、有效的发布检测以及发布失败处理方案

3.6.1. 灰度发布过程

  1. 自行校验:踢流量,发布节点后自行校验
  2. 灰度校验:
    • 按名单灰度:针对名单内的 UID,服务走灰度逻辑
    • 按用户灰度:针对命中规则(比如特定尾号)的用户 UID,服务走灰度逻辑
    • 按流量灰度:通过负载均衡,控制访问服务的真实用户流量
  3. 灰度周期:
    • 核心基础服务(比如 CGI、Tiny 通道、账号、 订单支付、直播房间、资料等基础服务)需要拉长灰度时间
  4. 有效的发布检测:
    • 后台服务:通过 Tolsoty 接口+用例,检测 CMD 是否符合预期
    • CGI 检测:通过 Curl、Postman、Paw 等工具,检测 CGI 的 URL 是否符合预期
    • 存储查询:请求服务处理后,查询存储写入是否符合预期、消息队列有无消息等
    • 产品体验:通过预发布环境产品亲自体验,检测灰度是否符合预期
  5. 关注监控指标: 关注监控指标(全链路/云监控/STKE 监控)
  6. 发布回滚: 遇到异常立即启动应急预案(踢流量、回滚 Pod 镜像)
  7. 全量发布:通过负载均衡,分批扩大用户校验,直至全量 发布窗口期(特定节假日前、高峰时间段、晚间时间发布),需要向 Leader 申请发布报备

3.6.2. 服务发布:SKTE 配置要点

STKE 关键指标汇总:https://iwiki.woa.com/pages/viewpage.action?pageId=944586615

3.6.3. STKE 工作负载配置标准化指标

  • 固定 IP、PreStop 停止前冷静时间、服务就绪探针、服务存活探针
  • 内核参数(somaxconn)
  • HPA 弹性扩缩容(3 ~ 5 倍,结合服务容忍度配置,通常高并发服务 25~30%负载比较合理,常规服务 45%左右比较合适)
  • STKE 事件告警(工作负载扩缩容、Pod 调度、销毁事件)

3.6.4. 容灾部署

  1. 核心服务应用,必须多区域工作负载,避免区域资源扩容限制
  2. 核心服务存储,同城多活、异地灾备(成本考虑)

4. 完整文档