- 执行顺序
- 和return执行顺序
- 函数返回值初始化(默认零值)
- 有名函数返回值可被defer修改
- defer遇见panic (有recover则正常补获没有则defer链)
- defer中包含panic (defer之中的panic会覆盖原有的panic)
小于 1 分钟
这篇文章写的太细致了,amazing
- G goroutine
- P processor
- M thread
术语
- 全局队列 Global Queue
- P的本地队列 (max 256) (out of size move half to GP)
- P(runtime.GOMAXPROCS数量),程序启动时创建
- M 从P之中获取G运行,P本地队列空了从 [Global Queue] 放到P的本地队列或者从 [其他P的本地队列] 偷一半到自己P的本地队列
- M在不够用时可以新建M
小于 1 分钟
一、如何使用
1.创建Jaeger的Tracer追踪器
// InitJaeger 初始化一个opentracing.Tracer链路追踪实例
// 100%的请求都会记录跨度
// 初始化jaeger的指标
func InitJaeger(service string) (opentracing.Tracer, io.Closer) {
cfg := &jaegerConfig.Configuration{
ServiceName: service, // 指定了要被追踪的服务的名称
Sampler: &jaegerConfig.SamplerConfig{
// 采用恒定采样策略
// 意味着对于每一个请求或操作,都会按照固定的方式决定是否进行追踪
Type: "const",
// 与 Type 字段配合,决定具体的采样行为
Param: 1,
},
Reporter: &jaegerConfig.ReporterConfig{
// 设置为 true,表示要记录追踪的跨度(Span)信息到日志中
LogSpans: true,
// 指定了 Jaeger 收集器(Collector)的端点地址
// 追踪数据最终需要发送到 Jaeger 的收集器进行处理和存储
// 通过设置这个字段,告诉程序将追踪数据发送到哪里
// 指定客户端(应用程序)将追踪数据发送到的目标地址,即 Jaeger 收集器(Collector)的端点
// TODO 客户端主动push
// Jaeger 的这种配置下,是客户端主动将数据推送给收集器,
// 这种方式使得客户端对数据的发送有更多的控制权,能够根据自身的情况(如数据量、网络状况等)来决定何时发送数据
// 而不是等待服务端来请求
// TODO 需要做优化更改为kafka - 异步PUSH
CollectorEndpoint: config.Conf.JaegerConfig.Addr,
// 可选项
// 如果不配置CollectorEndpoint的话就需要配置这个Agent代理人端口
// jaeger-agent会接收这些数据,进行缓冲和批量处理后,再发送给jaeger-collector
// 将数据发送到jaeger - agent的6831/udp端口
LocalAgentHostPort: "127.0.0.1:6831",
},
}
// 基于前面初始化好的配置结构体 cfg
// 使用 NewTracer 方法来创建一个 Jaeger 追踪器(Tracer)以及一个用于关闭追踪器相关资源的函数 closer
// jaegerConfig.Logger(jaeger.StdLogger) 是在为追踪器设置日志记录器
tracer, closer, err := cfg.NewTracer(jaegerConfig.Logger(jaeger.StdLogger))
if err != nil {
logger.Fatal(err)
}
return tracer, closer
}
// 创建一个 Jaeger 追踪器(Tracer)
tracer, _ := InitJaeger(fmt.Sprintf("%s:%s",
config.Conf.Application.Name,
config.Conf.Application.Version))
大约 6 分钟
小于 1 分钟
-
枚举值注释必须完善
-
禁止出现硬编码
-
有基本的单元测试验证(尽量使用断言而不是手动log人工判定单测是否通过)
-
接口粒度应该细小、接口依赖应该明确、接口入参出参应该明确,对象化,最好可以做到依赖抽象而不是直接依赖具体的实现
-
禁止在接口之中有隐藏的依赖条件(入参之中没有的参数比如环境变量,入参应该是决定输出的唯一条件)
-
接口颗粒度要小一些(入参、出参尽可能简化易读),如果逻辑过于复杂应该对象化拆分,确保大接口拆出的小接口都走单元测试
-
禁止IDE右侧出现notice黄色告警(单词拼写错误、命名不规范、公有方法没注释等)
-
所有的指针访问都需要判定是否为nil
-
所有的数组访问都需要判定length防止数组越界
-
所有的除数都要判定被除数不是0
-
关于同步转异步,取决于业务场景。如果是接口响应不强依赖执行结果的,比如用户关注频道后发送欢迎语,关注成功后异步发送欢迎语即可,不能阻塞主要流程,因为用户端不强依赖欢迎语,但是需要快速进入主页面。
-
包命名规范
-
禁止滥用init,个人建议整个项目不应该有init,main函数也是唯一的执行入口
-
禁止滥用全局变量,应该保证依赖的关系足够清晰明了
-
模块依赖管理,包与包之间的依赖关系清晰(按功能分块按业务水平分层,按数据流垂直分层,每一层之间尽可能解耦),比如三层架构、领域驱动模型等都是追求分层,数据对象模型每一层之间都是解耦的,比如vo(view object视图)和持久化层对象po(数据库映射对象)肯定是转换过的,而不是直接将po往http对外接口抛。
-
模块依赖管理,包的依赖应当尽可能放在私有属性(并且以选项模式注入依赖)。优点在于依赖会更加清晰容易管理,以及对于单元测试会友好很多。比如:
大约 5 分钟
一、具体的go项目模块分层
├── application | 应用入口|可以理解为三层架构之中的 UI 表示层
│ ├── admin_service|实现后台管理系统CRUD当前微服务数据需要用到的 RPC 接口
│ │ ├── dto | 数据传输对象实体
│ │ └── grpc | RPC接口实现
│ ├── event | 内置事件 | 比如定时器 | 统一实现一个抽象 interface{ start stop } | 这里注意下就是这个包是不允许有init函数的,禁止自身调用自身,它的调用必须显式地写在 applicition.Init或者编译入口main函数之中
│ └── front_service
│ ├── dtos | 数据传输对象对外的restful接口用到的对象
│ └── http | controller层 | 如果涉及2个领域比如用户中心领域和商户领域 | 在这里将2个领域组装数据 | 这里获取到的各个domain object是一个复杂的对象转
│ └────── init.go | 有一个包变量App结构体 | 首先初始化db\kafka\redis\mongodb\es、再初始化domain service对象,然后以选项模式注入DB对象到领域对象,这里的init也不是用go的init而是Init暴露的方法 ,我这里几乎是禁止用隐藏的init函数
换为api 用的dto
├── config
│ └── files | 里面只有1个.toml文件 | 目前已经弃用 | 配置已经改用从nacos读取
├── domain
│ ├── common | 通用域 | 通用域是被其他域依赖 | 依赖注入也是选项模式注入到其他领域的私有属性 | 域之间是互相独立的互不访问的而通用域则可以注入进其他领域 | 还有支撑域等我这里分的没有那么细致
│ │ ├── entity | 域实体 domain.object 领域对象 | 当前的领域对外暴露的实体
│ │ └── repository | 仓储层
│ │ └──----------- po.go | persistent object 持久化层对象
│ │ └──----------- repository.go | 仓储层对外提供的接口的抽象
│ │ └──----------- repository_realization1.go | 仓储层CRUD的实现一 | 基于MySQL
│ │ └──----------- repository_realization2.go | 仓储层CRUD的实现二 | 基于Oracle | 正常只有1个实现
│ │ service.go | 领域对外暴露的服务 - 同样需要抽象和实现 | 这里有很多的 po 转 do 的操作
│ ├── user
│ │ ├── entity
│ │ └── repository
│ │ service.go
│ ├── good
│ │ ├── entity
│ │ ├── enum
│ │ └── repository
│ │ service.go
├── global | 全局的工具类 - 偏业务 | 可以被domain\event依赖的
│ ├── cache | 缓存key管理
│ ├── enum | 一些通用的枚举值
│ └── router | 路由 | restful
├── cmd | 运行入口 main.go | 运行逻辑大致是 配置加载 > application.Init > rpc.server.register > prometheus/event > run
├── interfaces
└── tools | 工具类 - 剥离业务 - 一小部分特有的 | 大部分的util工具被封装到团队独立的git仓库 | go.mod引用爱用哪个版本用哪个
└── utils
大约 6 分钟
使用OS包监听系统信号
package main
import (
"context"
"log"
"os"
"os/signal"
"syscall"
"time"
)
func main() {
ctx, cancel := context.WithCancel(context.Background())
ch := make(chan os.Signal, 1)
signal.Notify(ch, os.Interrupt, syscall.SIGTERM)
go func() {
<-ch
log.Println("Received termination, signaling shutdown")
cancel()
}()
go func(ctx context.Context) {
<-ctx.Done()
log.Println("context done")
os.Exit(0)
}(ctx)
time.Sleep(time.Minute)
}
小于 1 分钟
golang请求转发
package main
import (
"log"
"net/http"
"net/http/httputil"
"net/url"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
)
func main() {
s := g.Server()
s.SetPort(8899)
group := s.Group("/")
group.ALL("/api", func(r *ghttp.Request) {
ForwardHandler(r.Response.ResponseWriter, r.Request)
})
s.Run()
}
func ForwardHandler(writer http.ResponseWriter, request *http.Request) {
u, err := url.Parse("http://localhost:8083" + "?" + request.URL.RawQuery)
if nil != err {
log.Println(err)
return
}
proxy := httputil.ReverseProxy{
Director: func(request *http.Request) {
request.URL = u
},
}
proxy.ServeHTTP(writer, request)
}
小于 1 分钟
一、常用API
var rwMutex sync.RWMutex
rwMutex.Lock()
// 进行写操作
rwMutex.Unlock()
var rwMutex sync.RWMutex
rwMutex.RLock()
// 进行读操作
rwMutex.RUnlock()
大约 1 分钟