一個(gè)提高go開發(fā)效率的秘密武器,一天開發(fā)完成了一個(gè)極簡(jiǎn)版社區(qū)后端服務(wù)

community-single
community-single是一個(gè)極簡(jiǎn)版社區(qū)的后端服務(wù),主要包括用戶的注冊(cè)、登錄、關(guān)注等功能,創(chuàng)作內(nèi)容(文本、圖片、視頻)的發(fā)布、評(píng)論、點(diǎn)贊、收藏等功能,這些功能在各個(gè)社區(qū)平臺(tái)、視頻平臺(tái)、直播平臺(tái)等都比較常見,可以作為學(xué)習(xí)參考用,點(diǎn)擊查看完整的項(xiàng)目代碼?https://github.com/zhufuyi/sponge_examples/blob/main/7_community-single?。
community-single項(xiàng)目一開始設(shè)計(jì)為單體web服務(wù),整個(gè)服務(wù)由生成代碼工具sponge輔助完成,sponge生成web服務(wù)代碼過程中剝離了業(yè)務(wù)邏輯與非業(yè)務(wù)邏輯兩部分代碼,這里的非業(yè)務(wù)邏輯代碼指的是web服務(wù)框架代碼,主要包括:
經(jīng)過封裝的gin代碼
服務(wù)治理(日志、限流、熔斷、鏈路跟蹤、服務(wù)注冊(cè)與發(fā)現(xiàn)、指標(biāo)采集、性能分析、配置中心、資源統(tǒng)計(jì)等)
編譯構(gòu)建和部署腳本(二進(jìn)制、docker、k8s)
CI/CD(jenkins)
除了web服務(wù)框架代碼,其他都屬于業(yè)務(wù)邏輯代碼。
把一個(gè)完整web服務(wù)代碼看作一個(gè)雞蛋,蛋殼看作是web服務(wù)框架代碼,蛋殼內(nèi)部看作是業(yè)務(wù)邏輯代碼,業(yè)務(wù)邏輯代碼又包含了蛋白和蛋黃兩部分,蛋黃是業(yè)務(wù)邏輯的核心(需要人工編寫的代碼),例如定義mysql表、定義api接口、編寫具體邏輯代碼都屬于蛋黃部分。蛋白是業(yè)務(wù)邏輯代碼的核心與web框架代碼連接的橋梁(自動(dòng)生成,不需要人工編寫),例如根據(jù)proto文件生成的注冊(cè)路由代碼、handler方法函數(shù)代碼、參數(shù)校驗(yàn)代碼、錯(cuò)誤碼、swagger文檔等,這些都屬于蛋白部分。web服務(wù)雞蛋模型剖析圖如下圖所示:

因此開發(fā)一個(gè)完整web服務(wù)項(xiàng)目聚焦在了定義數(shù)據(jù)表、定義api接口、在模板代碼中編寫具體業(yè)務(wù)邏輯代碼這3個(gè)節(jié)點(diǎn)上,也就是業(yè)務(wù)邏輯的核心代碼(蛋黃),其他代碼(蛋殼和蛋白)是由sponge生成,可以幫助你少寫很多代碼,下面介紹從0開始到完成項(xiàng)目的開發(fā)過程。
開發(fā)過程依賴工具sponge,需要先安裝sponge,點(diǎn)擊查看安裝說明?https://github.com/zhufuyi/sponge/blob/main/assets/install-cn.md?。
定義數(shù)據(jù)表和api接口
根據(jù)業(yè)務(wù)需求,首先要定義數(shù)據(jù)表和api接口,這是業(yè)務(wù)邏輯代碼核心(圖1中的蛋黃部分),后面需要根據(jù)數(shù)據(jù)表和api接口(IDL)來生成代碼(圖1中的蛋殼和蛋白兩部分)。
定義數(shù)據(jù)表
這是已經(jīng)定義好的mysql表?https://github.com/zhufuyi/sponge_examples/blob/main/7_community-single/test/sql/community.sql
定義api接口
在proto文件定義api接口、輸入輸出參數(shù)、路由等,下面是已經(jīng)定義好的api接口的proto文件,在這里可以查看這些文件 https://github.com/zhufuyi/sponge_examples/blob/main/7_community-single/api/community/v1
user.proto
relation.proto
like.proto
comment.proto
collect.proto
開發(fā)中不大可能一次性就定義好業(yè)務(wù)所需的mysql表和api接口,增加或更改是很常見的事,修改mysql表和proto文件后,如何同步更新到代碼里,在下面的編寫業(yè)務(wù)邏輯代碼章節(jié)中介紹。
生成項(xiàng)目代碼
定義了數(shù)據(jù)表和api接口之后,然后在sponge的界面上根據(jù)proto文件生成web服務(wù)項(xiàng)目代碼。進(jìn)入sponge的UI界面,點(diǎn)擊左邊菜單欄【protobuf】--> 【W(wǎng)eb類型】-->【創(chuàng)建web項(xiàng)目】,填寫相關(guān)參數(shù)生成web項(xiàng)目代碼,如下圖所示:

解壓代碼,修改文件夾名稱(例如community-single),一個(gè)服務(wù)只需生成代碼一次。 這就完成搭建了一個(gè)web服務(wù)的基本框架(圖1中的蛋殼部分),接著可以在web服務(wù)框架內(nèi)編寫業(yè)務(wù)邏輯代碼了。
編寫業(yè)務(wù)邏輯代碼
經(jīng)過sponge剝離后的業(yè)務(wù)邏輯代碼只包括proto文件和mysql表這兩部分,編寫業(yè)務(wù)邏輯代碼基本都是圍繞這兩部分開展。
編寫與proto文件相關(guān)的業(yè)務(wù)邏輯代碼
進(jìn)入項(xiàng)目community-single目錄,打開終端,執(zhí)行命令:
這個(gè)命令生成了接口模板代碼、注冊(cè)路由代碼、api接口錯(cuò)誤碼、swagger文檔這四個(gè)部分代碼,這是都是圖1中的蛋白部分。
(1)?生成的接口模板代碼,在internal/handler
目錄下,文件名稱與proto文件名一致,后綴名是_login.go
,名稱分別有:
collect_logic.go,?comment_logic.go,?like_logic.go,?post_logic.go,?relation_logic.go,?user_logic.go
在這些文件里面的方法函數(shù)與proto文件定義的rpc方法名一一對(duì)應(yīng),默認(rèn)每個(gè)方法函數(shù)下有簡(jiǎn)單的使用示例,只需在每個(gè)方法函數(shù)里面編寫具體的邏輯代碼,上面那些文件代碼是已經(jīng)編寫過具體邏輯之后的代碼。
(2)?生成注冊(cè)路由代碼,在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
在這些文件里面的設(shè)置api接口的中間件,例如jwt鑒權(quán),各個(gè)接口添加中間件代碼模板已經(jīng)存在,只需要取消注釋代碼就可以使中間件生效,支持路由分組和單獨(dú)路由來設(shè)置中間件。
(3)?生成接口錯(cuò)誤碼,在internal/ecode
目錄下,文件名稱與proto文件名一致,后綴是_http.go
,名稱分別有:
collect_http.go,?comment_http.go,?like_http.go,?post_http.go,?relation_http.go,?user_http.go
在這些文件里面的默認(rèn)錯(cuò)誤碼變量與proto文件定義的rpc方法名一一對(duì)應(yīng),在這里添加或更改業(yè)務(wù)相關(guān)的錯(cuò)誤碼,注意錯(cuò)誤碼不能重復(fù),否則會(huì)觸發(fā)panic。
(4)?生成swagger文檔,在docs
目錄下,名稱為apis.swagger.json
如果在proto文件添加或更改了api接口,需要重新再執(zhí)行一次命令make proto
更新代碼,會(huì)發(fā)現(xiàn)在internal/handler
、internal/routers
、internal/ecode
目錄下出現(xiàn)后綴名為日期時(shí)間的代碼文件,打開文件,把新增或修改部分代碼復(fù)制到同名文件代碼中即可。復(fù)制完新增代碼后,執(zhí)行命令make clean
清除這些日期后綴文件。
make proto
命令生成的代碼是用來連接web框架代碼和業(yè)務(wù)邏輯核心代碼的橋梁,也就是蛋白部分,這種分層生成代碼的好處是減少編寫代碼。
編寫與mysql表相關(guān)的業(yè)務(wù)邏輯代碼
前面生成的web服務(wù)框架代碼和根據(jù)proto文件生成的業(yè)務(wù)邏輯的部分代碼,都還沒有包括對(duì)mysql表的操作,因此需要根據(jù)mysql表生成dao(數(shù)據(jù)訪問對(duì)象)代碼,dao代碼包括了對(duì)表的增刪改查代碼、緩存代碼、model代碼,這是都是圖1中的蛋白部分。
進(jìn)入sponge的UI界面,點(diǎn)擊左邊菜單欄【Public】--> 【生成dao CRUD代碼】,填寫相關(guān)參數(shù)生成dao代碼,如下圖所示:

解壓dao代碼,把internal目錄移動(dòng)到community-single目錄下,這樣就完成添加了對(duì)mysql表的增刪改查操作接口。當(dāng)有新添加的mysql表時(shí),需要再次指定mysql表生成dao代碼。
指定mysql表生成的dao代碼包括三個(gè)部分。
(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
這是生成的對(duì)應(yīng)gorm的go結(jié)構(gòu)體代碼。
(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
編寫業(yè)務(wù)代碼過程中,為了提高性能,有可能使用到緩存,有時(shí)候?qū)Ρ淼哪J(rèn)緩存(CRUD)不能滿足要求,需要添加緩存代碼,sponge支持一鍵生成緩存代碼,點(diǎn)擊左邊菜單欄【Public】--> 【生成cache代碼】,填寫參數(shù)生成代碼,然后把解壓的internal目錄移動(dòng)到community-single目錄下,然后在業(yè)務(wù)邏輯中直接調(diào)用緩存接口。
(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
編寫業(yè)務(wù)代碼過程中會(huì)涉及到操作mysql表,有時(shí)候?qū)Ρ淼哪J(rèn)操作(CRUD)不能滿足要求,這時(shí)需要人工編寫自定義操作mysql表的函數(shù)方法與實(shí)現(xiàn)代碼,例如comment.go、post.go等都包含少部分人工定義的操作msyql表的方法函數(shù)。
在開發(fā)過程中有時(shí)會(huì)修改或新增mysql表,基于mysql表生成的代碼需要同步到項(xiàng)目代碼中,分為兩種情況處理:
修改mysql表之后更新代碼處理方式:只需根據(jù)修改后的表生成新model代碼,替換舊的model代碼。點(diǎn)擊左邊菜單欄【Public】--> 【生成model代碼】,填寫參數(shù),選擇更改的mysql表,然后把解壓的internal目錄移動(dòng)到community-single目錄下,并確認(rèn)替換。
新增mysql表之后處理方式:只需根據(jù)新增的表生成新的dao代碼,添加到項(xiàng)目目錄下。點(diǎn)擊左邊菜單欄【Public】--> 【生成dao代碼】,填寫參數(shù),選擇新增的mysql表,然后把解壓的internal目錄移動(dòng)到community-single目錄下。
測(cè)試api接口
編寫了業(yè)務(wù)邏輯代碼后,啟動(dòng)服務(wù)測(cè)試api接口,在第一次啟動(dòng)服務(wù)前,先打開配置文件(configs/community.yml
)設(shè)置mysql和redis地址,然后執(zhí)行命令編譯啟動(dòng)服務(wù):
在瀏覽器訪問?http://localhost:8080/apis/swagger/index.htm
?,進(jìn)入swagger界面,如下圖所示:

從圖中看到有些api接口右邊有一把鎖標(biāo)記,表示請(qǐng)求頭會(huì)攜帶鑒權(quán)信息Authorization,服務(wù)端接收到請(qǐng)求是否做鑒權(quán),由服務(wù)端決定,如果服務(wù)端需要做鑒權(quán),可以在各個(gè)internal/routers/xxx_handler.pb.go
文件中設(shè)置,也就是取消鑒權(quán)的注釋代碼,使api接口的鑒權(quán)中間件生效。
服務(wù)治理
生成的web服務(wù)代碼中包含了豐富的服務(wù)治理插件,有些服務(wù)治理插件默認(rèn)是關(guān)閉的,根據(jù)實(shí)際需要開啟使用,統(tǒng)一在配置文件configs/community.yml
進(jìn)行設(shè)置。
除了web服務(wù)提供的服務(wù)治理插件,也可以使用自己的服務(wù)治理插件,建議在internal/routers/routers.go
引入自己的服務(wù)治理插件。
日志
日志插件默認(rèn)是開啟的,默認(rèn)是輸出到終端,默認(rèn)輸出日志格式是console,可以設(shè)置輸出格式為json,設(shè)置日志保存到指定文件,日志文件切割和保留時(shí)間。
在配置文件里的字段logger
設(shè)置:
限流
限流插件默認(rèn)是關(guān)閉的,自適應(yīng)限流,不需要設(shè)置其他參數(shù)。
在配置文件里的字段enableLimit
設(shè)置:
熔斷
熔斷插件默認(rèn)是關(guān)閉的,自適應(yīng)熔斷,支持自定義請(qǐng)求返回錯(cuò)誤碼(默認(rèn)500和503)進(jìn)行熔斷,在internal/routers/routers.go
設(shè)置。
在配置文件里的字段enableCircuitBreaker
設(shè)置:
鏈路跟蹤
鏈路跟蹤插件默認(rèn)是關(guān)閉的,鏈路跟蹤依賴jaeger服務(wù)。
在配置文件里的字段enableTrace
設(shè)置:
在jaeger界面上查看鏈路跟蹤信息文檔說明,https://go-sponge.com/zh-cn/service-governance?id=%e9%93%be%e8%b7%af%e8%b7%9f%e8%b8%aa
服務(wù)注冊(cè)與發(fā)現(xiàn)
服務(wù)注冊(cè)與發(fā)現(xiàn)插件默認(rèn)是關(guān)閉的,支持consul、etcd、nacos三種類型。
在配置文件里的字段registryDiscoveryType
設(shè)置:
指標(biāo)采集
指標(biāo)采集功能默認(rèn)是開啟的,提供給prometheus采集數(shù)據(jù),默認(rèn)路由是/metrics
。
在配置文件里的字段enableMetrics
設(shè)置:
使用prometheus和grafana采集指標(biāo)和監(jiān)控服務(wù)的文檔說明。
性能分析
性能分析插件默認(rèn)是關(guān)閉的,采集profile的默認(rèn)路由是/debug/pprof
,除了支持go語言本身提供默認(rèn)的profile分析,還支持io分析,路由是/debug/pprof/profile-io
。
在配置文件里的字段enableHTTPProfile
設(shè)置:
通過路由采集profile進(jìn)行性能分析方式,通常在開發(fā)或測(cè)試時(shí)使用,如果線上開啟會(huì)有一點(diǎn)點(diǎn)性能損耗,因?yàn)槌绦蚝笈_(tái)一直定時(shí)記錄profile相關(guān)信息。sponge生成的web服務(wù)對(duì)此做了一些改進(jìn),平時(shí)停止采集profile,用戶主動(dòng)觸發(fā)系統(tǒng)信號(hào)時(shí)才開啟和關(guān)閉采集profile,采集profile保存到/tmp/服務(wù)名稱_profile目錄
,默認(rèn)采集為60秒,60秒后自動(dòng)停止采集profile,如果只想采集30秒,發(fā)送第一次信號(hào)開始采集,大概30秒后發(fā)送第二次信號(hào)表示停止采集profile,類似開關(guān)一樣。
這是采集profile操作步驟:
注:只支持linux、darwin系統(tǒng)。
資源統(tǒng)計(jì)
資源統(tǒng)計(jì)插件默認(rèn)是開啟的,默認(rèn)每分鐘統(tǒng)計(jì)一次并輸出到日志,資源統(tǒng)計(jì)了包括系統(tǒng)和服務(wù)本身這兩部分的cpu和內(nèi)存相關(guān)的數(shù)據(jù),資源統(tǒng)計(jì)包含了自動(dòng)觸發(fā)采集profile功能,當(dāng)連續(xù)3次統(tǒng)計(jì)本服務(wù)的CPU或內(nèi)存平均值,CPU或內(nèi)存平均值占用系統(tǒng)資源超過80%時(shí),自動(dòng)觸發(fā)采集profile,默認(rèn)采集為60秒,采集profile保存到/tmp/服務(wù)名稱_profile目錄
,從而實(shí)現(xiàn)自適應(yīng)采集profile,比通過人工發(fā)送系統(tǒng)信號(hào)來采集profile又改進(jìn)了一步。
在配置文件里的字段enableHTTPProfile
設(shè)置:
配置中心
目前支持nacos作為配置中心,配置中心文件configs/community_cc.yml
,配置內(nèi)容如下:
而服務(wù)的配置文件configs/community.yml
復(fù)制到nacos界面上配置。使用nacos配置中心,啟動(dòng)服務(wù)命令需要指定配置中心文件,命令如下:
使用nacos作為配置中心的文檔說明?https://go-sponge.com/zh-cn/service-governance?id=%e9%85%8d%e7%bd%ae%e4%b8%ad%e5%bf%83
持續(xù)集成與部署
sponge生成的web服務(wù)包括了編譯和部署腳本,編譯支持二進(jìn)制編譯和docker鏡像構(gòu)建,部署支持二進(jìn)制部署、docker部署、k8s部署三種方式,這些功能都統(tǒng)一集成在Makefile
文件里,使用make命令就可以很方便的執(zhí)行指定編譯或部署服務(wù)。
除了使用make命令編譯和部署,還支持自動(dòng)化部署工具Jenkins,默認(rèn)的Jenkins設(shè)置在文件Jenkinsfile
,支持自動(dòng)化部署到k8s,如果需要二進(jìn)制或docker部署,需要對(duì)Jenkinsfile
進(jìn)行修改。
使用Jenkins持續(xù)集成和部署的文檔說明 https://go-sponge.com/zh-cn/cicd
服務(wù)壓測(cè)
壓測(cè)服務(wù)時(shí)使用的一些工具:
http壓測(cè)工具wrk或go-stress-testing。
服務(wù)開啟指標(biāo)采集功能,使用prometheus采集服務(wù)指標(biāo)和系統(tǒng)指標(biāo)進(jìn)行監(jiān)控。
服務(wù)本身的自適應(yīng)采集profile功能。
壓測(cè)指標(biāo):
并發(fā)度: 逐漸增加并發(fā)用戶數(shù),找到服務(wù)的最大并發(fā)度,確定服務(wù)能支持的最大用戶量。
響應(yīng)時(shí)間: 關(guān)注并發(fā)用戶數(shù)增加時(shí),服務(wù)的平均響應(yīng)時(shí)間和響應(yīng)時(shí)間分布情況。確保即使在高并發(fā)下,響應(yīng)時(shí)間也在可接受范圍內(nèi)。
錯(cuò)誤率: 觀察并發(fā)增加時(shí),服務(wù)出現(xiàn)錯(cuò)誤或異常的概率。使用壓測(cè)工具進(jìn)行長(zhǎng)時(shí)間并發(fā)測(cè)試,統(tǒng)計(jì)各并發(fā)級(jí)別下的錯(cuò)誤數(shù)量和類型。
吞吐量: 找到服務(wù)的最大吞吐量,確定服務(wù)在高并發(fā)下可以支持的最大請(qǐng)求量。這需要不斷增加并發(fā),直到找到吞吐量飽和點(diǎn)。
資源利用率: 關(guān)注并發(fā)增加時(shí),CPU、內(nèi)存、磁盤I/O、網(wǎng)絡(luò)等資源的利用率,找到服務(wù)的資源瓶頸。
瓶頸檢測(cè): 通過觀察高并發(fā)情況下服務(wù)的性能指標(biāo)和資源利用率,找到系統(tǒng)和服務(wù)的硬件或軟件瓶頸,以便進(jìn)行優(yōu)化。
穩(wěn)定性: 長(zhǎng)時(shí)間高并發(fā)運(yùn)行可以檢測(cè)到服務(wù)存在的潛在問題,如內(nèi)存泄露、連接泄露等,確保服務(wù)穩(wěn)定運(yùn)行。這需要較長(zhǎng)時(shí)間的并發(fā)壓測(cè),觀察服務(wù)運(yùn)行指標(biāo)。
對(duì)服務(wù)進(jìn)行壓測(cè),主要是為了評(píng)估其性能,確定能支持的最大并發(fā)和吞吐量,發(fā)現(xiàn)當(dāng)前的瓶頸,并檢測(cè)服務(wù)運(yùn)行的穩(wěn)定性,以便進(jìn)行優(yōu)化或容量規(guī)劃。
總結(jié)
這是使用工具sponge從開發(fā)到部署的實(shí)戰(zhàn)項(xiàng)目示例,具體流程如下:
定義mysql表
在proto文件定義api接口
根據(jù)proto文件生成web框架代碼
根據(jù)proto文件生成業(yè)務(wù)邏輯相關(guān)代碼
根據(jù)mysql表生成dao代碼
在指定模板文件中編寫具體邏輯代碼
在swagger測(cè)試驗(yàn)證api接口
按需啟用服務(wù)治理功能
持續(xù)集成與部署
服務(wù)壓測(cè)
看起來流程很多,真正需要人工編寫代碼的只有1、2、6這三個(gè)核心業(yè)務(wù)流程,其他流程涉及到的代碼或腳本由sponge生成,使用sponge剝離非業(yè)務(wù)邏輯代碼和業(yè)務(wù)邏輯代碼,讓開發(fā)項(xiàng)目時(shí)只需要聚焦在業(yè)務(wù)邏輯的核心代碼上,同時(shí)也使得項(xiàng)目代碼變得規(guī)范統(tǒng)一,不同的程序員都可以迅速上手。再結(jié)合編程輔助工具Copilot或Codeium寫代碼,開發(fā)變得更高效、輕松。
community-single是單體web服務(wù),如果用戶量激增后,單體服務(wù)無法滿足吞吐量,可以拆分成多個(gè)微服務(wù),web單體服務(wù)拆分成微服務(wù)過程,只換了蛋殼(web框架換成gRPC框架)和蛋白(http handler相關(guān)代碼換成rpc service相關(guān)代碼),蛋黃(核心業(yè)務(wù)邏輯代碼)不變,核心業(yè)務(wù)邏輯代碼可以無縫的移植到微服務(wù)代碼中,使用sponge可以很容易的完成這個(gè)轉(zhuǎn)換過程。