一个提高go开发效率的秘密武器,一天内开发完成一个极简版社区后端服务

community-single#

community-single 是一个极简版社区的后端服务,主要包括用户的注册、登录、关注等功能,创作内容 (文本、图片、视频) 的发布、评论、点赞、收藏等功能,这些功能在各个社区平台、视频平台、直播平台等都比较常见,可以作为学习参考用,点击查看完整的项目代码

community-single 项目一开始设计为单体 web 服务,整个服务由生成代码工具 sponge 辅助完成,sponge 生成 web 服务代码过程中剥离了业务逻辑与非业务逻辑两部分代码,这里的非业务逻辑代码指的是 web 服务框架代码,主要包括:

  • 经过封装的 gin 代码
  • 服务治理 (日志、限流、熔断、链路跟踪、服务注册与发现、指标采集、性能分析、配置中心、资源统计等)
  • 编译构建和部署脚本 (二进制、docker、k8s)
  • CI/CD(jenkins)

除了 web 服务框架代码,其他都属于业务逻辑代码。

把一个完整 web 服务代码看作一个鸡蛋,蛋壳表示 web 服务框架代码,蛋白和蛋黄都表示业务逻辑代码。蛋黄是业务逻辑的核心 (需要人工编写的代码),例如定义 mysql 表、定义 api 接口、编写具体逻辑代码都属于蛋黄部分。蛋白是业务逻辑核心代码与 web 框架代码连接的桥梁 (自动生成,不需要人工编写),例如根据 proto 文件生成的注册路由代码、handler 方法函数代码、参数校验代码、错误码、swagger 文档等都属于蛋白部分。web 服务鸡蛋模型剖析图如下图所示:

web-http-pb-anatomy

图 1 web 服务代码的组成结构图

因此开发一个完整 web 服务项目聚焦在了定义数据表定义 api 接口在模板代码中编写具体业务逻辑代码这 3 个节点上,也就是业务逻辑的核心代码 (蛋黄),其他代码 (蛋壳和蛋白) 是由 sponge 生成,可以帮助你少写很多代码,下面介绍从 0 开始到完成项目的开发过程。

开发过程依赖工具 sponge,需要先安装 sponge,点击查看安装说明



定义数据表和 api 接口#

根据业务需求,首先要定义数据表和 api 接口,这是业务逻辑代码核心 (图 1 中的蛋黄部分),后面需要根据数据表和 api 接口 (IDL) 来生成代码 (图 1 中的蛋壳和蛋白两部分)。

定义数据表#

这是已经定义好的 mysql 表 community.sql


定义 api 接口#

在 proto 文件定义 api 接口、输入输出参数、路由等,下面是已经定义好的 api 接口的 proto 文件:


开发中不大可能一次性就定义好业务所需的 mysql 表和 api 接口,增加或更改是很常见的事,修改 mysql 表和 proto 文件后,如何同步更新到代码里,在下面的编写业务逻辑代码章节中介绍。



生成项目代码#

定义了数据表和 api 接口之后,然后在 sponge 的界面上根据 proto 文件生成 web 服务项目代码。进入 sponge 的 UI 界面,点击左边菜单栏【protobuf】–> 【Web 类型】–>【创建 web 项目】,填写相关参数生成 web 项目代码,如下图所示:

community-single-web

解压代码,修改文件夹名称 (例如 community-single),一个服务只需生成代码一次。 这就完成搭建了一个 web 服务的基本框架 (图 1 中的蛋壳部分),接着可以在 web 服务框架内编写业务逻辑代码了。



编写业务逻辑代码#

从上面图 1 中 web 服务代码鸡蛋模型解剖图看出,经过 sponge 剥离后的业务逻辑代码包括蛋白和蛋黄两部分,编写业务逻辑代码基本都是围绕这两部分开展。

编写与 proto 文件相关的业务逻辑代码#

进入项目 community-single 目录,打开终端,执行命令:

make proto

这个命令是根据 api/community/v1 目录下的 proto 文件生成了接口模板代码、注册路由代码、api 接口错误码、swagger 文档这四个部分代码,也就是图 1 中的蛋白部分。


(1) 生成的接口模板代码,在 internal/handler 目录下,文件名称与 proto 文件名一致,后缀名是_logic.go,名称分别有:

collect_logic.go, comment_logic.go, like_logic.go, post_logic.go, relation_logic.go, user_logic.go

在这些文件里面的方法函数与 proto 文件定义的 rpc 方法名一一对应,默认每个方法函数下有简单的使用示例,只需在每个方法函数里面编写具体的逻辑代码,上面那些文件代码是已经编写过具体逻辑之后的代码。


(2) 生成注册路由代码,在 internal/routers 目录下,文件名称与 proto 文件名一致,后缀名是_handler.pb.go,名称分别有:

collect_handler.pb.go, comment_handler.pb.go, like_handler.pb.go, post_handler.pb.go, relation_handler.pb.go, user_handler.pb.go

在这些文件里面的设置 api 接口的中间件,例如 jwt 鉴权,每个接口都已经存在中间件模板代码,只需要取消注释代码就可以使中间件生效,支持路由分组和单独路由来设置中间件。


(3) 生成接口错误码,在 internal/ecode 目录下,文件名称与 proto 文件名一致,后缀是_http.go,名称分别有:

collect_http.go, comment_http.go, like_http.go, post_http.go, relation_http.go, user_http.go

在这些文件里面的默认错误码变量与 proto 文件定义的 rpc 方法名一一对应,在这里添加或更改业务相关的错误码,注意错误码不能重复,否则会触发 panic。


(4) 生成 swagger 文档,在 docs 目录下,名称为 apis.swagger.json


如果在 proto 文件添加或更改了 api 接口,需要重新再执行一次命令 make proto 更新代码,会发现在 internal/handlerinternal/routersinternal/ecode 目录下出现后缀名为日期时间的代码文件,打开文件,把新增或修改部分代码复制到同名文件代码中即可。复制完新增代码后,执行命令 make clean 清除这些日期后缀文件。

make proto 命令生成的代码是用来连接 web 框架代码和业务逻辑核心代码的桥梁,也就是蛋白部分,这种分层生成代码的好处是减少编写代码。


编写与 mysql 表相关的业务逻辑代码#

前面生成的 web 服务框架代码和根据 proto 文件生成的业务逻辑的部分代码,都还没有包括对 mysql 表的操作,因此需要根据 mysql 表生成 dao (数据访问对象) 代码,dao 代码包括了对表的增删改查代码、缓存代码、model 代码,这些代码属于图 1 中的蛋白部分。

进入 sponge 的 UI 界面,点击左边菜单栏【Public】–> 【生成 dao CRUD 代码】,填写相关参数生成 dao 代码,如下图所示:

community-single-dao

解压 dao 代码,把 internal 目录移动到 community-single 目录下,这样就完成添加了对 mysql 表的增删改查操作接口。当有新添加的 mysql 表时,需要再次指定 mysql 表生成 dao 代码。


指定 mysql 表生成的 dao 代码包括三个部分。

(1) 生成 model 代码,在 internal/model 目录下,文件名称与 mysql 表名一致,分别有:

comment.go, commentContent.go, commentHot.go, commentLatest.go, post.go, postHot.go, postLatest.go, relationNum.go, user.go, userCollect.go, userComment.go, userFollower.go, userFollowing.go, userLike.go, userPost.go

这是生成的对应 gorm 的 go 结构体代码。


(2) 生成缓存代码,在 internal/cache 目录下文件,文件名称与 mysql 表名一致,分别有:

comment.go, commentContent.go, commentHot.go, commentLatest.go, post.go, postHot.go, postLatest.go, relationNum.go, user.go, userCollect.go, userComment.go, userFollower.go, userFollowing.go, userLike.go, userPost.go

编写业务代码过程中,为了提高性能,有可能使用到缓存,有时候对表的默认缓存 (CRUD) 不能满足要求,需要添加缓存代码,sponge 支持一键生成缓存代码,点击左边菜单栏【Public】–> 【生成 cache 代码】,填写参数生成代码,然后把解压的 internal 目录移动到 community-single 目录下,然后在业务逻辑中直接调用缓存接口。


(3) 生成 dao 代码,在 internal/dao 目录下,文件名称与 mysql 表名一致,文件分别有:

comment.go, commentContent.go, commentHot.go, commentLatest.go, post.go, postHot.go, postLatest.go, relationNum.go, user.go, userCollect.go, userComment.go, userFollower.go, userFollowing.go, userLike.go, userPost.go

编写业务代码过程中会涉及到操作 mysql 表,有时候对表的默认操作 (CRUD) 不能满足要求,这时需要人工编写自定义操作 mysql 表的函数方法与实现代码,例如 comment.go、post.go 等都包含少部分人工定义的操作 msyql 表的方法函数。


在开发过程中有时会修改或新增 mysql 表,基于 mysql 表生成的代码需要同步到项目代码中,分为两种情况处理:

  • 修改 mysql 表之后更新代码处理方式:只需根据修改后的表生成新 model 代码,替换旧的 model 代码。点击左边菜单栏【Public】–> 【生成 model 代码】,填写参数,选择更改的 mysql 表,然后把解压的 internal 目录移动到 community-single 目录下,并确认替换。
  • 新增 mysql 表之后处理方式:只需根据新增的表生成新的 dao 代码,添加到项目目录下。点击左边菜单栏【Public】–> 【生成 dao 代码】,填写参数,选择新增的 mysql 表,然后把解压的 internal 目录移动到 community-single 目录下。



测试 api 接口#

编写了业务逻辑代码后,启动服务测试 api 接口,在第一次启动服务前,先打开配置文件 (configs/community.yml) 设置 mysql 和 redis 地址,然后执行命令编译启动服务:

# 编译、运行服务
make run

在浏览器访问 http://localhost:8080/apis/swagger/index.htm ,进入 swagger 界面,如下图所示:

community-single-swagger

从图中看到有些 api 接口右边有一把锁标记,表示请求头会携带鉴权信息 Authorization,服务端接收到请求是否做鉴权,由服务端决定,如果服务端需要做鉴权,可以在各个 internal/routers/xxx_handler.pb.go 文件中设置,也就是取消鉴权的注释代码,使 api 接口的鉴权中间件生效。



服务治理#

生成的 web 服务代码中包含了丰富的服务治理插件,有些服务治理插件默认是关闭的,根据实际需要开启使用,统一在配置文件 configs/community.yml 进行设置。

除了 web 服务提供的服务治理插件,也可以使用自己的服务治理插件,建议在 internal/routers/routers.go 引入自己的服务治理插件。


日志#

日志插件默认是开启的,默认是输出到终端,默认输出日志格式是 console,可以设置输出格式为 json,设置日志保存到指定文件,日志文件切割和保留时间。

在配置文件里的字段 logger 设置:

# logger 设置
logger:
  level: "info"             # 输出日志级别 debug, info, warn, error,默认是debug
  format: "console"     # 输出格式,console或json,默认是console
  isSave: false           # false:输出到终端,true:输出到文件,默认是false
  logFileConfig:          # isSave=true时有效
    filename: "out.log"            # 文件名称,默认值out.log
    maxSize: 20                     # 最大文件大小(MB),默认值10MB
    maxBackups: 50               # 保留旧文件的最大个数,默认值100个
    maxAge: 15                     # 保留旧文件的最大天数,默认值30天
    isCompression: true          # 是否压缩/归档旧文件,默认值false


限流#

限流插件默认是关闭的,自适应限流,不需要设置其他参数。

在配置文件里的字段 enableLimit 设置:

  enableLimit: false    # 是否开启限流(自适应),true:开启, false:关闭


熔断#

熔断插件默认是关闭的,自适应熔断,支持自定义请求返回错误码 (默认 500 和 503) 进行熔断,在 internal/routers/routers.go 设置。

在配置文件里的字段 enableCircuitBreaker 设置:

  enableCircuitBreaker: false    # 是否开启熔断(自适应),true:开启, false:关闭


链路跟踪#

链路跟踪插件默认是关闭的,链路跟踪依赖 jaeger 服务。

在配置文件里的字段 enableTrace 设置:

  enableTrace: false    # 是否开启追踪,true:启用,false:关闭,如果是true,必须设置jaeger配置。
  tracingSamplingRate: 1.0      # 链路跟踪采样率, 范围0~1.0浮点数, 0表示不采样, 1.0表示采样所有链路


# jaeger 设置
jaeger:
  agentHost: "192.168.3.37"
  agentPort: 6831

在 jaeger 界面上查看链路跟踪信息文档说明


服务注册与发现#

服务注册与发现插件默认是关闭的,支持 consul、etcd、nacos 三种类型。

在配置文件里的字段 registryDiscoveryType 设置:

  registryDiscoveryType: ""    # 注册和发现类型:consul、etcd、nacos,如果为空表示关闭服务注册与发现。


# 根据字段registryDiscoveryType值来设置参数,例如使用consul作为服务发现,只需设置consul。
# consul 设置
consul:
  addr: "192.168.3.37:8500"

# etcd 设置
etcd:
  addrs: ["192.168.3.37:2379"]

# nacos 设置
nacosRd:
  ipAddr: "192.168.3.37"
  port: 8848
  namespaceID: "3454d2b5-2455-4d0e-bf6d-e033b086bb4c" # namespace id


指标采集#

指标采集功能默认是开启的,提供给 prometheus 采集数据,默认路由是 /metrics

在配置文件里的字段 enableMetrics 设置:

  enableMetrics: true    # 是否开启指标采集,true:启用,false:关闭

使用 prometheus 和 grafana 采集指标和监控服务的文档说明


性能分析#

性能分析插件默认是关闭的,采集 profile 的默认路由是 /debug/pprof,除了支持 go 语言本身提供默认的 profile 分析,还支持 io 分析,路由是 /debug/pprof/profile-io

在配置文件里的字段 enableHTTPProfile 设置:

  enableHTTPProfile: false    # 是否开启性能分析,true:启用,false:关闭

通过路由采集 profile 进行性能分析方式,通常在开发或测试时使用,如果线上开启会有一点点性能损耗,因为程序后台一直定时记录 profile 相关信息。sponge 生成的 web 服务对此做了一些改进,平时停止采集 profile,用户主动触发系统信号时才开启和关闭采集 profile,采集 profile 保存到 /tmp/服务名称_profile目录,默认采集为 60 秒,60 秒后自动停止采集 profile,如果只想采集 30 秒,发送第一次信号开始采集,大概 30 秒后发送第二次信号表示停止采集 profile,类似开关一样。

这是采集 profile 操作步骤:

# 通过名称查看服务pid
ps aux | grep 服务名称

# 发送信号给服务
kill -trap pid值

注:只支持 linux、darwin 系统。


资源统计#

资源统计插件默认是开启的,默认每分钟统计一次并输出到日志,资源统计了包括系统和服务本身这两部分的 cpu 和内存相关的数据,资源统计包含了自动触发采集 profile 功能,当连续 3 次统计本服务的 CPU 或内存平均值,CPU 或内存平均值占用系统资源超过 80% 时,自动触发采集 profile,默认采集为 60 秒,采集 profile 保存到 /tmp/服务名称_profile目录,从而实现自适应采集 profile,比通过人工发送系统信号来采集 profile 又改进了一步。

在配置文件里的字段 enableHTTPProfile 设置:

  enableStat: true    # 是否开启资源统计,true:启用,false:关闭


配置中心#

目前支持 nacos 作为配置中心,配置中心文件 configs/community_cc.yml,配置内容如下:

# nacos 设置
nacos:
  ipAddr: "192.168.3.37"    # 服务地址
  port: 8848                      # 监听端口
  scheme: "http"                # 支持http和https
  contextPath: "/nacos"       # 路径
  namespaceID: "3454d2b5-2455-4d0e-bf6d-e033b086bb4c" # namespace id
  group: "dev"                    # 组名称: dev, prod, test
  dataID: "community.yml"  # 配置文件id
  format: "yaml"                 # 配置文件类型: json,yaml,toml

而服务的配置文件 configs/community.yml 复制到 nacos 界面上配置。使用 nacos 配置中心,启动服务命令需要指定配置中心文件,命令如下:

./community -c configs/community_cc.yml -enable-cc

使用 nacos 作为配置中心的文档说明



持续集成与部署#

sponge 生成的 web 服务包括了编译和部署脚本,编译支持二进制编译和 docker 镜像构建,部署支持二进制部署、docker 部署、k8s 部署三种方式,这些功能都统一集成在 Makefile 文件里,使用 make 命令就可以很方便的执行指定编译或部署服务。

除了使用 make 命令编译和部署,还支持自动化部署工具 Jenkins,默认的 Jenkins 设置在文件 Jenkinsfile,支持自动化部署到 k8s,如果需要二进制或 docker 部署,需要对 Jenkinsfile 进行修改。

使用 Jenkins 持续集成和部署的文档说明



服务压测#

压测服务时使用的一些工具:

  • http 压测工具 wrkgo-stress-testing
  • 服务开启指标采集功能,使用 prometheus 采集服务指标和系统指标进行监控。
  • 服务本身的自适应采集 profile 功能。


压测指标:

  • 并发度: 逐渐增加并发用户数,找到服务的最大并发度,确定服务能支持的最大用户量。
  • 响应时间: 关注并发用户数增加时,服务的平均响应时间和响应时间分布情况。确保即使在高并发下,响应时间也在可接受范围内。
  • 错误率: 观察并发增加时,服务出现错误或异常的概率。使用压测工具进行长时间并发测试,统计各并发级别下的错误数量和类型。
  • 吞吐量: 找到服务的最大吞吐量,确定服务在高并发下可以支持的最大请求量。这需要不断增加并发,直到找到吞吐量饱和点。
  • 资源利用率: 关注并发增加时,CPU、内存、磁盘 I/O、网络等资源的利用率,找到服务的资源瓶颈。
  • 瓶颈检测: 通过观察高并发情况下服务的性能指标和资源利用率,找到系统和服务的硬件或软件瓶颈,以便进行优化。
  • 稳定性: 长时间高并发运行可以检测到服务存在的潜在问题,如内存泄露、连接泄露等,确保服务稳定运行。这需要较长时间的并发压测,观察服务运行指标。

对服务进行压测,主要是为了评估其性能,确定能支持的最大并发和吞吐量,发现当前的瓶颈,并检测服务运行的稳定性,以便进行优化或容量规划。



总结#

这是使用工具 sponge 从开发到部署的实战项目示例,具体流程如下:

  1. 定义 mysql 表
  2. 在 proto 文件定义 api 接口
  3. 根据 proto 文件生成 web 框架代码
  4. 根据 proto 文件生成业务逻辑相关代码
  5. 根据 mysql 表生成 dao 代码
  6. 在指定模板文件中编写具体逻辑代码
  7. 在 swagger 测试验证 api 接口
  8. 按需启用服务治理功能
  9. 持续集成与部署
  10. 服务压测

看起来流程很多,真正需要人工编写代码的只有 1、2、6 这三个核心业务流程,其他流程涉及到的代码或脚本由 sponge 生成,使用 sponge 剥离非业务逻辑代码和业务逻辑代码,让开发项目时只需要聚焦在业务逻辑的核心代码上,同时也使得项目代码变得规范统一,不同的程序员都可以迅速上手。再结合编程辅助工具 Copilot 或 Codeium 写代码,开发变得更高效、轻松。

community-single 是单体 web 服务,随着需求增加,功能越来越复杂,使得代码维护和开发变得困难,可以拆分成多个微服务,web 单体服务拆分成微服务过程,只换了蛋壳 (web 框架换成 gRPC 框架) 和蛋白 (http handler 相关代码换成 rpc service 相关代码),蛋黄 (核心业务逻辑代码) 不变,核心业务逻辑代码可以无缝的移植到微服务代码中,使用 sponge 可以很容易的完成这个转换过程。在下一篇文章介绍使用工具 sponge 如何把 community-single 拆分为微服务集群。

本作品采用《CC 协议》,转载必须注明作者和本文链接
本帖由系统于 1年前 自动加精
未填写
文章
11
粉丝
4
喜欢
22
收藏
22
排名:1261
访问:3464
私信
所有博文
社区赞助商