pb文件

创建项目成功后,进入api目录下可以看到api.proto文件:

option go_package = "api";
option (gogoproto.goproto_getters_all) = false;service RPCDemo {rpc Ping(.google.protobuf.Empty) returns (.google.protobuf.Empty);rpc SayHello(HelloReq) returns (.google.protobuf.Empty);rpc SayHelloURL(HelloReq) returns (HelloResp) {option (google.api.http) = {get: "/kratos-demo/say_hello"};};
}message HelloReq {string name = 1 [(gogoproto.moretags) = 'form:"name" validate:"required"'];
}message HelloResp {string Content = 1 [(gogoproto.jsontag) = 'content'];
}

运行:

kratos tool protoc --grpc --bm api.proto

命令可以得到api.pb.go 和 api.bm.go

api.proto是gRPC server的描述文件
api.pb.go是基于api.proto生成的代码文件,用于rpc调用,具体逻辑可在internal/service/serevice.go 内实现
api.bm.go是基于api.proto生成的代码文件,用于http调用,将参数绑定后,调用serevice.go中方法,并返回json结果。

参考

gogo/protobuf

注册server

进入internal/server/grpc目录打开server.go文件,可以看到以下代码,只需要替换以下注释内容就可以启动一个gRPC服务。

package grpcimport ("github.com/luslin/tools/kratos-demo/api""github.com/go-kratos/kratos/pkg/conf/paladin""github.com/go-kratos/kratos/pkg/net/rpc/warden"
)// New new a grpc server.
func New(svc api.RPCDemoServer) (ws *warden.Server, err error) {var (cfg warden.ServerConfigct paladin.TOML)if err = paladin.Get("grpc.toml").Unmarshal(&ct); err != nil {return}if err = ct.Get("Server").UnmarshalTOML(&cfg); err != nil {return}ws = warden.NewServer(&cfg)// 替换这里   RegisterRPCDemoServer 在 api.pb.go 中api.RegisterRPCDemoServer(ws.Server(), svc)ws, err = ws.Start()return
}

注册方法 internal/service/service.go

var Provider = wire.NewSet(New, wire.Bind(new(api.RPCDemoServer), new(*Service)))// Service service.
type Service struct {ac  *paladin.Mapdao dao.Dao
}// New new a service and return.
func New(d dao.Dao) (s *Service, cf func(), err error) {s = &Service{ac:  &paladin.TOML{},dao: d,}cf = s.Closeerr = paladin.Watch("application.toml", s.ac)return
}// SayHello grpc demo func.
func (s *Service) SayHello(ctx context.Context, req *api.HelloReq) (reply *empty.Empty, err error) {reply = new(empty.Empty)fmt.Printf("hello %s", req.Name)return
}// SayHelloURL bm demo func.
func (s *Service) SayHelloURL(ctx context.Context, req *api.HelloReq) (reply *api.HelloResp, err error) {reply = &api.HelloResp{Content: "hello " + req.Name,}fmt.Printf("hello url %s", req.Name)return
}// Ping ping the resource.
func (s *Service) Ping(ctx context.Context, e *empty.Empty) (*empty.Empty, error) {return &empty.Empty{}, s.dao.Ping(ctx)
}// Close close the resource.
func (s *Service) Close() {}

请进入internal/service内找到SayHello方法,注意方法的入参和出参,都是按照gRPC的方法声明对应的:

第一个参数必须是context.Context,第二个必须是proto内定义的message对应生成的结构体
第一个返回值必须是proto内定义的message对应生成的结构体,第二个参数必须是error
在http框架bm中,如果共用proto文件生成bm代码,那么也可以直接使用该service方法
建议service严格按照此格式声明方法使其能够在bm和warden内共用。

client调用

请进入internal/dao方法内,一般对资源的处理都会在这一层封装。
对于client端,前提必须有对应proto文件生成的代码,那么有两种选择:

拷贝proto文件到自己项目下并且执行代码生成
直接import服务端的api package
这也是业务代码我们加了一层internal的关系,服务对外暴露的只有接口

不管哪一种方式,以下初始化gRPC client的代码建议伴随生成的代码存放在统一目录下:

api/client_test.go

package apiimport ("context""fmt""github.com/go-kratos/kratos/pkg/net/rpc/warden""github.com/golang/protobuf/ptypes/empty""google.golang.org/grpc""log""testing"
)
const (address     = "localhost:9000"
)
// 传统rpc调用
func TestNewClient(t *testing.T) {conn, err := grpc.Dial(address, grpc.WithInsecure())if err != nil {log.Fatal(err)}defer conn.Close()c := NewRPCDemoClient(conn)r, err := c.Ping(context.TODO(), &empty.Empty{})if err != nil {log.Fatal( err)}fmt.Println(r)rep, err := c.SayHelloURL(context.TODO(),&HelloReq{Name: "lin"})if err != nil {log.Fatal( err)}fmt.Println(rep.Content)}// kratos 封装的rpc调用
func TestClient2(t *testing.T) {client := warden.NewClient(&warden.ClientConfig{})cc, err := client.Dial(context.Background(), fmt.Sprintf("direct://default/%s", address))if err != nil {panic(err)}rpc_cli :=  NewRPCDemoClient(cc)rep, err := rpc_cli.SayHelloURL(context.TODO(),&HelloReq{Name: "lin"})if err != nil {log.Fatal( err)}fmt.Println(rep.Content)
}

服务注册与发现

服务注册与发现最简单的就是direct固定服务端地址的直连方式。也就是服务端正常监听端口启动不进行额外操作,客户端使用如下target:

direct://default/127.0.0.1:9000,127.0.0.1:9091

其中direct为协议类型,此处表示直接使用该URL内提供的地址127.0.0.1:9000,127.0.0.1:9091进行连接,而default在此处无意义仅当做占位符。

使用discovery

服务注册

要将本项目注册到discovery中:

func DiscoveryRegister() func(){hn, _ := os.Hostname()dis := discovery.New(nil)ins := &naming.Instance{Zone:     "sh001",Env:      "dev",AppID:    "kratos_grpc",Hostname: hn,Addrs: []string{"grpc://192.168.1.88:9000","http://192.168.1.88:9056",},}cancel, err := dis.Register(context.Background(), ins)if err != nil {panic(err)}// 省略...// 特别注意!!!// cancel必须在进程退出时执行!!!return cancel
}

在服务退出时,调用cancel从discovery中去掉本服务
可以在discovery中看到本服务:

"kratos_grpc":[{"region":"sh","zone":"sh001","env":"dev","appid":"kratos_grpc","hostname":"local","addrs":["grpc://192.168.1.88:9000"],"version":"","metadata":null,"status":1,"reg_timestamp":1589962982365217546,"up_timestamp":1589962982365217546,"renew_timestamp":1589962982365217546,"dirty_timestamp":1589962982358288851,"latest_timestamp":1589962982365217546}

要使用discovery需要在业务的NewClient前进行注册,代码如下:

package api
import ("context""fmt""github.com/go-kratos/kratos/pkg/naming/discovery""github.com/go-kratos/kratos/pkg/net/rpc/warden/resolver""github.com/go-kratos/kratos/pkg/net/rpc/warden""google.golang.org/grpc"
)// AppID .
const AppID = "kratos_grpc"func init()  {// NOTE: 注意这段代码,表示要使用discovery进行服务发现// NOTE: 还需注意的是,resolver.Register是全局生效的,所以建议该代码放在进程初始化的时候执行// NOTE: !!!切记不要在一个进程内进行多个不同中间件的Register!!!// NOTE: 在启动应用时,可以通过flag(-discovery.nodes) 或者 环境配置(DISCOVERY_NODES)指定discovery节点resolver.Register(discovery.Builder())
}
// NewClient new grpc client
func NewClient(cfg *warden.ClientConfig, opts ...grpc.DialOption) (RPCDemoClient, error) {client := warden.NewClient(cfg, opts...)cc, err := client.Dial(context.Background(), fmt.Sprintf("discovery://default/%s", AppID))if err != nil {return nil, err}// 注意替换这里:// NewDemoClient方法是在"api"目录下代码生成的// 对应proto文件内自定义的service名字,请使用正确方法名替换return NewRPCDemoClient(cc), nil
}

测试

func TestClient2(t *testing.T) {client := warden.NewClient(&warden.ClientConfig{})cc, err := client.Dial(context.Background(), fmt.Sprintf("discovery://default/%s", "kratos_grpc"))if err != nil {panic(err)}rpc_cli :=  NewRPCDemoClient(cc)rep, err := rpc_cli.SayHelloURL(context.TODO(),&HelloReq{Name: "lin"})if err != nil {log.Fatal( err)}fmt.Println(rep.Content)
}
结果:
INFO 05/20-16:28:56.596 grpc-access-log ts=0.002694286 path=/demo.service.v1.RPCDemo/SayHelloURL args=name:"lin"  ret=0 ip=192.168.1.88:9000
hello lin

target是discovery://default/${appid},当gRPC内进行解析后会得到scheme=discovery和appid,然后进行以下逻辑:

  • warden/resolver.Builder会通过scheme获取到naming/discovery.Builder对象(靠resolver.Register注册过的)
  • 拿到naming/discovery.Builder后执行Build(appid)构造naming/discovery.Discovery
  • naming/discovery.Discovery对象基于appid就知道要获取哪个服务的实例信息
参考

discovery部署与使用

使用ETCD

和使用discovery类似,只需要在注册时使用etcd naming即可

func init(){// NOTE: 注意这段代码,表示要使用etcd进行服务发现 ,其他事项参考discovery的说明// NOTE: 在启动应用时,可以通过flag(-etcd.endpoints) 或者 环境配置(ETCD_ENDPOINTS)指定etcd节点// NOTE: 如果需要自己指定配置时 需要同时设置DialTimeout 与 DialOptions: []grpc.DialOption{grpc.WithBlock()}resolver.Register(etcd.Builder(nil))
}

etcd的服务注册与discovery基本相同,可以传入详细的etcd配置项, 或者传入nil后通过flag(-etcd.endpoints)/环境配置(ETCD_ENDPOINTS)来指定etcd节点。

参考

etcd使用

负载均衡

grpc-go内置了round-robin轮询,但由于自带的轮询算法不支持权重,也不支持color筛选等需求,故需要重新实现一个负载均衡算法。

WRR (Weighted Round Robin)

该算法在加权轮询法基础上增加了动态调节权重值,用户可以在为每一个节点先配置一个初始的权重分,之后算法会根据节点cpu、延迟、服务端错误率、客户端错误率动态打分,在将打分乘用户自定义的初始权重分得到最后的权重值。

P2C (Pick of two choices)

本算法通过随机选择两个node选择优胜者来避免羊群效应,并通过ewma尽量获取服务端的实时状态。

服务端: 服务端获取最近500ms内的CPU使用率(需要将cgroup设置的限制考虑进去,并除于CPU核心数),并将CPU使用率乘与1000后塞入每次grpc请求中的的Trailer中夹带返回: cpu_usage uint64 encoded with string cpu_usage : 1000

客户端: 主要参数:

  • server_cpu:通过每次请求中服务端塞在trailer中的cpu_usage拿到服务端最近500ms内的cpu使用率
  • inflight:当前客户端正在发送并等待response的请求数(pending request)
  • latency: 加权移动平均算法计算出的接口延迟
  • client_success:加权移动平均算法计算出的请求成功率(只记录grpc内部错误,比如context deadline)
    目前客户端,已经默认使用p2c负载均衡算法grpc.WithBalancerName(p2c.Name):
// NewClient returns a new blank Client instance with a default client interceptor.
// opt can be used to add grpc dial options.
func NewClient(conf *ClientConfig, opt ...grpc.DialOption) *Client {c := new(Client)if err := c.SetConfig(conf); err != nil {panic(err)}c.UseOpt(grpc.WithBalancerName(p2c.Name))c.UseOpt(opt...)c.Use(c.recovery(), clientLogging(), c.handle())return c
}

go-kratos 微服务框架 warden模块使用相关推荐

  1. go kratos 微服务框架(笔记一)

    Kratos 微服务框架 1.简介 B站基于Golang实现的一个轻量级开源的面向微服务的框架. Kratos框架不限制您使用任何第三方库来进行项目开发,因此您可以根据喜好来选择库进行集成.我们也会逐 ...

  2. kratos 微服务框架商城实战初识 kratos

    准备工作 本机器这里已经安装好了 go.kratos.proto.wire.make 等所需的工具. 初始化项目目录 进入自己电脑中存放 Go 项目的目录 新建 kratos-shop/service ...

  3. kratos mysql_kratos微服务框架学习笔记一(kratos-demo)

    本文将为您描述kratos微服务框架学习笔记一(kratos-demo),教程操作步骤: 目录 kratos微服务框架学习笔记一(kratos-demo) kratos本体 demo kratos微服 ...

  4. golang 比较完美的通用框架,kratos v2.0 学习,一套轻量级 Go 微服务框架,包含大量微服务相关框架及工具。涵盖了互联网开发的常用功能模块的开发库。

    目录 前言 1,关于kratos v2.0 2,Principles 原则 2,Features 特色 3,Architecture 架构 4,总结 前言 本文的原文连接是: https://blog ...

  5. B站微服务框架Kratos详细教程(1)- 安装搭建

    Kratos Kratos是bilibili开源的一套Go微服务框架,包含大量微服务相关框架及工具. 名字来源于:<战神>游戏以希腊神话为背景,讲述由凡人成为战神的奎托斯(Kratos)成 ...

  6. go微服务框架Kratos简单使用总结

    Kratos是B站开源的一款go的微服务框架,最近PS5上的 战神·诸神黄昏比较火,主角就是奎托斯.这个框架的名字就取自他. 在进行框架选型时,对比了目前主流的很多go微服务框架,如Zero,最后对比 ...

  7. 技术研究院006---B站自用的微服务框架——Kratos

    大家都知道微服务有两个痛点,一个是如何拆分微服务,微服务的边界怎么划分制定:二是微服务上了规模之后如何管理,因为只要上了规模,任何小小的问题都可能会被放大,最后导致雪崩效应. Bilibili作为一个 ...

  8. Kratos战神微服务框架(一)

    目录 Kratos战神微服务框架 简介 目标 原则 特性 架构 CLI工具 安装 创建项目 项目结构 代码生成与运行 生成 运行 测试接口 Kratos战神微服务框架 简介 Kratos 一套轻量级 ...

  9. Kratos战神微服务框架(二)

    Kratos战神微服务框架(二) 目录 Kratos战神微服务框架(二) 项目结构 api编写 protobuf编写 使用makefile service层接口实现 biz层 data层 config ...

最新文章

  1. B/S系统常见缺陷整理和解决方案
  2. LeetCode Binary Tree Inorder Traversal
  3. golang错误处理
  4. 在Spring MVC Web应用程序中添加社交登录:集成测试
  5. iOS执行时工具-cycript
  6. 2021新媒体内容生态数据报告
  7. 信息学奥赛一本通(1045:收集瓶盖赢大奖)
  8. ajax手机号码验证,Jquery Validation 插件验证手机号
  9. 操作SSO对象模型时,异常“SSO_E_CANARY_VALIDATION_FAILURE”的处理
  10. k8s mysql 查询_MySql | 为什么大家都在说 Select * 效率低
  11. html节点替换代码,html之DOM对象replaceChild()方法(子节点替换)功能简介说明
  12. Java截取视频某一帧
  13. 【题解】 bzoj1135: [POI2009]Lyz (线段树+霍尔定理)
  14. AI伦理专家成立一个新的人工智能(AI)研究所 提供不同观点
  15. js设置,获取,删除属性(setAttribute, getAttribute, removeAttribute)
  16. QT课程设计:基于QT的图像处理程序
  17. 质量保证和质量控制(QA / QC),你们知道区别在哪里吗?
  18. Android 2.2开始支持安装APP2SD卡上
  19. Appro 2A lib 破解 crack
  20. 史上最全Java工程师面试题汇总

热门文章

  1. 汉服新闻查询易语言代码
  2. Centos7 yum安装mysql(完整版)
  3. 微信小程序知识点总结(二)
  4. IDEMIA与Storstockholms Lokaltrafik “SL”签署合同,推出全球首张公共交通EMV白标卡
  5. python撩妹技能_猜数字小游戏python实现(可用来撩妹撩汉)
  6. 以热爱·致热AI 星纵智能十周年庆典圆满落幕
  7. 湾区求职分享:三个月刷题拿到 Google offer,欢迎踊跃提问
  8. Java多个ppt合并脚本_Java 添加、合并PPT形状
  9. 深度解读如何在H5中完美融入VR技术
  10. 科里奥利力/科氏力(Coriolis Force)