- client: zRPC客户端,负责发起请求
- server: zRPC服务端,负责处理请求
- resolver: 服务注册模块,实现了gRPC的resolver.Builder接口并注册到gRPC
- discov: 服务发现模块,基于etcd实现服务发现功能
- balancer: 负载均衡模块,实现了p2c负载均衡算法,并注册到gRPC
- interceptor: 拦截器,对请求和响应进行拦截处理
Name: hello.rpc // 服务名
ListenOn: 127.0.0.1:9090 // 服务监听地址
Etcd:
Hosts:
- 127.0.0.1:2379 // etcd服务地址
Key: hello.rpc // 服务注册key
//直连模式
DirectScheme = "direct"
//服务发现模式
DiscovScheme = "discov"
//k8s模式
KubernetesScheme = "k8s"
- 首先会读取配置文件,获取到当前服务的配置信息与etcd连接配置,
- 创建服务运行需要的Context对象,
- 通过proto生成的RegisterXXXServer(),将当前rpc服务实例注册到rpc服务器,
- 执行zrpc.MustNewServer()创建RpcServer,
- 调用RpcServer下的Start()启动服务
import (
"flag"
"fmt"
"go_cloud_demo/rpc/internal/config"
"go_cloud_demo/rpc/internal/server"
"go_cloud_demo/rpc/internal/svc"
"go_cloud_demo/rpc/types/user"
"github.com/zeromicro/go-zero/core/conf"
"github.com/zeromicro/go-zero/core/service"
"github.com/zeromicro/go-zero/zrpc"
"google.golang.org/grpc"
"google.golang.org/grpc/reflection"
)
// 命令行参数读取配置文件所在路径
var configFile = flag.String("f", "rpc/etc/user.yaml", "the config file")
func main() {
flag.Parse()
//1.读取配置文件解析到Config结构体上
var c config.Config
conf.MustLoad(*configFile, &c)
//2.创房服务运行上下文
ctx := svc.NewServiceContext(c)
//3.将服务注册到rpc服务器,并且监听指定端口启动服务
//参数一"c.RpcServerConf":保存了当前rpc服务配置信息
//参数二"func(grpcServer *grpc.Server)"一个函数,当执行该函数时
//会调用通过proto生成的RegisterXXXServer(),将当前rpc服务实例注册到rpc服务器
s := zrpc.MustNewServer(c.RpcServerConf,
func(grpcServer *grpc.Server) {
user.RegisterUserServer(grpcServer, server.NewUserServer(ctx))
if c.Mode == service.DevMode || c.Mode == service.TestMode {
reflection.Register(grpcServer)
}
})
defer s.Stop()
fmt.Printf("Starting rpc server at %s...\n", c.ListenOn)
//4.启动
s.Start()
}
- 执行MustNewServer()创建rpcServer时内部创建registerEtcd函数,并将该函数封装到keepAliveServer结构体中
- 执行Start()启动服务时,内部获取到keepAliveServer中保存的registerEtcd函数并执行,实现服务注册
func MustNewServer(c RpcServerConf, register internal.RegisterFn) *RpcServer {
//调用NewServer()
server, err := NewServer(c, register)
if err != nil {
log.Fatal(err)
}
return server
}
// NewServer returns a RpcServer.
func NewServer(c RpcServerConf, register internal.RegisterFn) (*RpcServer, error) {
var err error
if err = c.Validate(); err != nil {
return nil, err
}
var server internal.Server
metrics := stat.NewMetrics(c.ListenOn)
serverOptions := []internal.ServerOption{
internal.WithMetrics(metrics),
internal.WithRpcHealth(c.Health),
}
//判断是否配置了etcd连接地址与当前服务名key,如果有
if c.HasEtcd() {
//执行NewRpcPubServer()
server, err = internal.NewRpcPubServer(c.Etcd, c.ListenOn, serverOptions...)
if err != nil {
return nil, err
}
} else {
server = internal.NewRpcServer(c.ListenOn, serverOptions...)
}
server.SetName(c.Name)
//设置拦截器
if err = setupInterceptors(server, c, metrics); err != nil {
return nil, err
}
rpcServer := &RpcServer{
server: server,
register: register,
}
if err = c.SetUp(); err != nil {
return nil, err
}
return rpcServer, nil
}
func NewRpcPubServer(etcd discov.EtcdConf, listenOn string, opts ...ServerOption) (Server, error) {
//创建一个registerEtcd 函数,
registerEtcd := func() error {
pubListenOn := figureOutListenOn(listenOn)
var pubOpts []discov.PubOption
if etcd.HasAccount() {
pubOpts = append(pubOpts, discov.WithPubEtcdAccount(etcd.User, etcd.Pass))
}
if etcd.HasTLS() {
pubOpts = append(pubOpts, discov.WithPubEtcdTLS(etcd.CertFile, etcd.CertKeyFile,
etcd.CACertFile, etcd.InsecureSkipVerify))
}
//创建pubClient
pubClient := discov.NewPublisher(etcd.Hosts, etcd.Key, pubListenOn, pubOpts...)
//执行pubClient.KeepAlive()方法,实现服务注册
return pubClient.KeepAlive()
}
//封装到keepAliveServer结构体中
server := keepAliveServer{
registerEtcd: registerEtcd,
Server: NewRpcServer(listenOn, opts...),
}
return server, nil
}
- keepAliveServer继承了一个Server结构体,通过这个Server属性保存了服务端公共的属性方法等
- 并且持有一个用来实现服务注册的函数类型属性registerEtcd
- 查看NewRpcServer()对keepAliveServer中的Server进行赋值的函数,内部会创建一个rpcServer结构体变量
- rpcServer中有一个middlewares属性,用来存储服务的中间件,还有一个baseRpcServer属性用于封装一些rpc服务端的公共属性和方法,会调用newBaseRpcServer()函数初始化这个属性
- 查看baseRpcServer比如存在用来存储流式拦截器的streamInterceptors属性,存储一元拦截器的器的unaryInterceptors属性等等
type keepAliveServer struct {
registerEtcd func() error
Server
}
func NewRpcServer(addr string, middlewares ServerMiddlewaresConf, opts ...ServerOption) Server {
var options rpcServerOptions
for _, opt := range opts {
opt(&options)
}
if options.metrics == nil {
options.metrics = stat.NewMetrics(addr)
}
return &rpcServer{
baseRpcServer: newBaseRpcServer(addr, &options),
middlewares: middlewares,
healthManager: health.NewHealthManager(fmt.Sprintf("%s-%s", probeNamePrefix, addr)),
}
}
func newBaseRpcServer(address string, rpcServerOpts *rpcServerOptions) *baseRpcServer {
var h *health.Server
if rpcServerOpts.health {
h = health.NewServer()
}
return &baseRpcServer{
address: address,
health: h,
metrics: rpcServerOpts.metrics,
options: []grpc.ServerOption{grpc.KeepaliveParams(keepalive.ServerParameters{
MaxConnectionIdle: defaultConnectionIdleDuration,
})},
}
}
type baseRpcServer struct {
//服务监听的地址,例如":8080"
address string
//用于提供grpc健康检查服务
health *health.Server
//用于收集和上报服务的统计信息,例如请求数、响应时间、错误数等
metrics *stat.Metrics
//用于存储创建grpc.Server对象时所需的配置数据
options []grpc.ServerOption
//用于存储添加的流拦截器
streamInterceptors []grpc.StreamServerInterceptor
//于存储添加的一元拦截器
unaryInterceptors []grpc.UnaryServerInterceptor
}
//setupInterceptors()作用:根据配置信息为rpc服务添加一些拦截器
//入参: svr是一个rpc服务对象
// c是一个RpcServerConf结构体,包含了rpc服务的配置信息
// metrics是一个stat.Metrics对象,用于收集和报告统计指标
func setupInterceptors(svr internal.Server, c RpcServerConf, metrics *stat.Metrics) error {
//1.如果配置中指定了CpuThreshold参数,表示要开启自适应限流功能
if c.CpuThreshold > 0 {
// 创建一个自适应限流器对象,设置CPU阈值为配置中的值
shedder := load.NewAdaptiveShedder(load.WithCpuThreshold(c.CpuThreshold))
// 为rpc服务添加一个一元拦截器,用于执行限流逻辑,并记录统计指标
svr.AddUnaryInterceptors(serverinterceptors.UnarySheddingInterceptor(shedder, metrics))
}
//2.如果配置中指定了Timeout参数,表示要开启超时控制功能
if c.Timeout > 0 {
// 为rpc服务添加一个一元拦截器,用于执行超时控制逻辑,超时时间为配置中的值
svr.AddUnaryInterceptors(serverinterceptors.UnaryTimeoutInterceptor(
time.Duration(c.Timeout) * time.Millisecond))
}
//3.如果配置中指定了Auth参数,表示要开启鉴权功能
if c.Auth {
// 调用setupAuthInterceptors函数,为rpc服务添加鉴权相关的拦截器
if err := setupAuthInterceptors(svr, c); err != nil {
return err
}
}
return nil
}
func (rs *RpcServer) AddUnaryInterceptors(interceptors ...grpc.UnaryServerInterceptor) {
//调用内部的Server对象的AddUnaryInterceptors方法
rs.server.AddUnaryInterceptors(interceptors...)
}
// server.go
func (s *Server) AddUnaryInterceptors(interceptors ...grpc.UnaryServerInterceptor) {
//将拦截器添加到unaryInterceptors切片中
s.unaryInterceptors = append(s.unaryInterceptors, interceptors...)
}
type RpcServer struct {
server internal.Server
register internal.RegisterFn
}
func (rs *RpcServer) Start() {
//默认会调用keepAliveServer下的Start()
if err := rs.server.Start(rs.register); err != nil {
logx.Error(err)
panic(err)
}
}
func (s keepAliveServer) Start(fn RegisterFn) error {
//1.先执行registerEtcd()注册服务地址
if err := s.registerEtcd(); err != nil {
return err
}
//2.启动服务
return s.Server.Start(fn)
}
//创建一个registerEtcd 函数,
registerEtcd := func() error {
pubListenOn := figureOutListenOn(listenOn)
var pubOpts []discov.PubOption
if etcd.HasAccount() {
pubOpts = append(pubOpts, discov.WithPubEtcdAccount(etcd.User, etcd.Pass))
}
if etcd.HasTLS() {
pubOpts = append(pubOpts, discov.WithPubEtcdTLS(etcd.CertFile, etcd.CertKeyFile,
etcd.CACertFile, etcd.InsecureSkipVerify))
}
//创建pubClient
pubClient := discov.NewPublisher(etcd.Hosts, etcd.Key, pubListenOn, pubOpts...)
//执行pubClient.KeepAlive()方法,实现服务发布以及连接
return pubClient.KeepAlive()
}
Publisher struct {
endpoints []string //edct的地址
key string //该服务的在etcd中的key前缀,从配置文件中取
//在etcd中的key(如果设置了id 则是key和id的组合,如果没有设置id,则是key和lease的组合)
fullKey string
id int64 //如果设置了则用于生成fullKey
value string //etcd的值,是RPC服务的地址
lease clientv3.LeaseID //etcd的lease id
quit *syncx.DoneChan //用于退出操作
//用于服务注册暂停.如果暂停则会注销etcd的lease,暂停后有两个后续操作 重启或者关闭
pauseChan chan lang.PlaceholderType
resumeChan chan lang.PlaceholderType //用于服务暂停后重启
}
// KeepAlive keeps key:value alive.
func (p *Publisher) KeepAlive() error {
//1.获取etcd连接
cli, err := internal.GetRegistry().GetConn(p.endpoints)
if err != nil {
return err
}
//2.服务注册
p.lease, err = p.register(cli)
if err != nil {
return err
}
proc.AddWrapUpListener(func() {
p.Stop()
})
//4.开启续约监听
return p.keepAliveAsync(cli)
}
NewPublisher()//Publish的构造方法
KeepAlive()//服务发布以及连接
Pause()//服务暂停
Resume()//服务重启
Stop()//服务停止
keepAliveAsync()//开启续约监听
func (p *Publisher) KeepAlive() error {
cli, err := p.doRegister()
if err != nil {
return err
}
proc.AddWrapUpListener(func() {
p.Stop()
})
return p.keepAliveAsync(cli)
}
func (p *Publisher) register(client internal.EtcdClient) (clientv3.LeaseID, error) {
//1.创建etcd lease 过期时间 TimeToLive为10秒
resp, err := client.Grant(client.Ctx(), TimeToLive)
if err != nil {
return clientv3.NoLease, err
}
lease := resp.ID
//2.创建fullKey,如果id有设置过则以key和id生成fullKey;如果id没有设置则以key和lease id生成
if p.id > 0 {
p.fullKey = makeEtcdKey(p.key, p.id)
} else {
p.fullKey = makeEtcdKey(p.key, int64(lease))
}
//3.将value存储到etcd中,绑定到刚创建的lease中
//通过Put方法进行注册
_, err = client.Put(client.Ctx(), p.fullKey, p.value, clientv3.WithLease(lease))
return lease, err
}
etcdctl get 配置文件中定义的服务key --prefix
// 异步地保持续约
func (p *Publisher) keepAliveAsync(cli internal.EtcdClient) error {
//1.调用etcd客户端的KeepAlive方法,创建一个用来续约的无缓存channel,
//该channel中保存了etcd keeplive状态,通过这个channel实现续约
ch, err := cli.KeepAlive(cli.Ctx(), p.lease)
if err != nil {
return err
}
//2.通过threading.GoSafe()安全的启动一个goroutine来处理续约响应和事件
threading.GoSafe(func() {
for {
//监听保存了etcd keeplive状态的channel,获取连接状态
select {
case _, ok := <-ch:
if !ok {
//如果通道返回false说明续约关闭,调用revoke()撤销租约,然后调用doKeepAlive()实现重试
p.revoke(cli)
if err := p.doKeepAlive(); err != nil {
logx.Errorf("etcd publisher KeepAlive: %s", err.Error())
}
return
}
case <-p.pauseChan:
//监听Publisher的pauseChan,获取是否有暂停操作
//如果收到暂停信号,执行revoke()撤销租约
logx.Infof("paused etcd renew, key: %s, value: %s", p.key, p.value)
p.revoke(cli)
//有暂停操作时注销后,通过resumeChan和quit监听后续是重启还是停止
select {
case <-p.resumeChan:
// 如果resumeChan收到恢复信号,则执行doKeepAlive()重试续约
if err := p.doKeepAlive(); err != nil {
logx.Errorf("etcd publisher KeepAlive: %s", err.Error())
}
return
case <-p.quit.Done():
// 如果收到退出信号,返回
return
}
case <-p.quit.Done():
//通过p.quit.Done()监听是否有停止操作,如果需要停止就直接注销当前租约
p.revoke(cli)
return
}
}
})
return nil
}
// 尝试与etcd保持续约
func (p *Publisher) doKeepAlive() error {
// 创建一个一秒间隔的定时器
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
for range ticker.C {
select {
case <-p.quit.Done():
// 如果收到退出信号,返回nil
return nil
default:
// 尝试注册服务并获取租约ID
cli, err := p.doRegister()
if err != nil {
// 如果注册失败,记录错误并跳出循环
logx.Errorf("etcd publisher doRegister: %s", err.Error())
break
}
// try to keep alive the lease with etcd
// 尝试与etcd保持续约
if err := p.keepAliveAsync(cli); err != nil {
// 如果续约失败,记录错误并跳出循环
logx.Errorf("etcd publisher keepAliveAsync: %s", err.Error())
break
}
// 如果续约成功,返回nil
return nil
}
}
return nil
}
在etcd上绑定的KeepAlive()方法中设置了一个定时器,定时时间为lease / 3大概3.3秒,也就是说每隔租约有效时间的三分之一会续约一次
- go-zero默认使用etcd作为注册中心
- 创建服务时要在yaml文件中配置Name当前服务名,Host当前服务ip,Port当前服务端口号,配置Etcd.Hosts注册中心地址, Etcd.Key当前服务需要调用的服务提供方名称
- 服务启动时执行MustLoad()将配置文件读取解析到一个Config结构体变量上
- 编写生产rpc服务的proto文件,基于该文件生产rpc服务,生成的文件中有一个RegisterXXXServer()函数,执行该函数将当前rpc服务实例注册到rpc服务器
- 执行zrpc.MustNewServer()创建RpcServer,需要传递这个RegisterXXXServer()函数
- 调用RpcServer下的Start()启动服务
- 执行c.HasEtcd()判断是否配置了etcd注册中心地址,如果配置了,执行NewRpcPubServer()函数
- 在NewRpcPubServer()中会创建一个名为registerEtcd的function函数,并将这个function封装到keepAliveServer结构体中
- 自此rpcServer创建成功,并封装了keepAliveServer结构体变量,内部持有一个注册服务的registerEtcd()函数
- 执行NewPublisher()创建Publisher结构体变量,也就是服务发现相关的核心
- 执行Publisher上的KeepAlive()方法实现服务发布以及连接功能
- endpoints 属性,内部保存了注册中心地址
- key属性:当前服务在注册中心的前缀
- 用于服务注册暂停的pauseChan和服务暂停后重启的resumeChan
- 表示当前配置项的租约信息的lease属性
- NewPublisher()//Publish的构造方法
- doRegister()—>register()服务注册
- Pause()//服务暂停
- Resume()//服务重启
- Stop()//服务停止
- keepAliveAsync()//
7.等方法(很多此处只摘要了一些重点关注的), 最核心的方法就是KeepAlive()与keepAliveAsync(),KeepAlive()中包装了doRegister()与keepAliveAsync()
- 首先创建etcd lease租约信息,租约中的TimeToLive过期时间默认为10秒
- 创建fullKey,如果id有设置过则以key和id生成fullKey;如果id没有设置则以key和lease id生成
- 将value设置到刚创建的lease中,调用EtcdClient上的Put方法进行注册,value就是服务的地址
- 监听保存了etcd keeplive状态的channel,不是true,则先注销当前租约然后再重新KeepLive()进行服务注册
- 监听Publisher的pauseChan,获取是否有暂停操作,有暂停操作,执行revoke()注销当前租约,并且在有暂停操作注销后,会监听resumeChan,如果返回true则重启调用KeepAlive()重新注册,会监听quit.Done()取消
- 监听Publisher的quit.Done()是否有取消信号,如果有调用revoke()注销
- leaseID: 租约ID,这个值在创建租约时由etcd分配。
- timeToLive: 租约持续时间,也就是服务注册的有效期,通常为10秒。如果超过了这个有效期,那么etcd就会将该服务注册信息删除。
- keepAliveResponseChan: 续租响应状态通道,用于保存etcd返回的续租结果。当续租成功时,keepAliveResponseChan会接收到一个true值,否则接收到false值。
- 服务注册时会创建当前服务的lease租约数据,只要租约未到期,会一直存在etcd中,服务消费方可以随时通过etcd查询服务注册信息并发起调用请求。在租约到期前,Publisher会通过keepAlive方法不断地向etcd发送续约请求,以延长租约的有效期。这样可以保证服务注册信息一直存在于etcd中,避免因为过期而被删除
- 在 Go 的默认情况下,如果一个协程中的函数出现 panic,那么这个协程和整个进程都会被直接终止
- 为了防止因为某个协程中的 panic 导致整个进程崩溃的情况发生,threding.GoSafe() 函数利用了 recover 函数的特性,在协程内部进行了 panic 和 recover 的处理,保证了程序的健壮性