go微服务框架kratos学习笔记七(kratos warden 负载均衡 balancer)


本节看看kratos的学习负载均衡策略的使用。

kratos 的负载均衡和服务发现一样也是基于grpc官方api实现的。

grpc官方的负载均衡自带了一个round-robin轮询策略、即像一个for循环一样挨个服的发请求、但这显然不能满足我们的需求、于是kratos自带了两种负载均衡策略:

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负载均衡算法


  1. // NewClient returns a new blank Client instance with a default client interceptor.
  2. // opt can be used to add grpc dial options.
  3. func NewClient(conf *ClientConfig, opt ...grpc.DialOption) *Client {
  4. c := new(Client)
  5. if err := c.SetConfig(conf); err != nil {
  6. panic(err)
  7. }
  8. c.UseOpt(grpc.WithBalancerName(p2c.Name))
  9. c.UseOpt(opt...)
  10. return c
  11. }

demo

本节使用在笔记四kratos warden-direct方式client调用 使用的direct服务发现方式、和相关代码。

demo操作

1、分别在两个docker中启动一个grpc demo服务。

2、启动一个client demo服务采用默认p2c负载均衡方式调用grpc SayHello()方法

demo server

1、先启动demo服务 (其实就是一个kratos工具new出来的demo服务、代码可参考笔记四、或者在最后的github地址里面获取整个demo完整代码):

demo client

  1. package dao
  2. import (
  3. "context"
  4. "github.com/bilibili/kratos/pkg/net/rpc/warden"
  5. "google.golang.org/grpc"
  6. "fmt"
  7. demoapi "call-server/api"
  8. "google.golang.org/grpc/balancer/roundrobin"
  9. )
  10. // target server addrs.
  11. const target = "direct://default/10.0.75.2:30001,10.0.75.2:30002" // NOTE: example
  12. // NewClient new member grpc client
  13. func NewClient(cfg *warden.ClientConfig, opts ...grpc.DialOption) (demoapi.DemoClient, error) {
  14. client := warden.NewClient(cfg, opts...)
  15. conn, err := client.Dial(context.Background(), target)
  16. if err != nil {
  17. return nil, err
  18. }
  19. // 注意替换这里:
  20. // NewDemoClient方法是在"api"目录下代码生成的
  21. // 对应proto文件内自定义的service名字,请使用正确方法名替换
  22. return demoapi.NewDemoClient(conn), nil
  23. }
  24. // NewClient new member grpc client
  25. func NewGrpcConn(cfg *warden.ClientConfig, opts ...grpc.DialOption) (*grpc.ClientConn, error) {
  26. fmt.Println("-----tag: NewGrpcConn...")
  27. //opts = append(opts, grpc.WithBalancerName(roundrobin.Name))
  28. client := warden.NewClient(cfg, opts...)
  29. conn, err := client.Dial(context.Background(), target)
  30. if err != nil {
  31. return nil, err
  32. }
  33. return conn, nil
  34. }

target 填上两个服务ip

其中我多加了一个NewGrpcConn() 函数 、主要用来提取grpc连接。这里我用了kratos自带的pool类型来做连接池。

关于这个池、它在 kratos pkg/container/pool 有两种实现方式 SliceList方式。

  1. package pool
  2. import (
  3. "context"
  4. "errors"
  5. "io"
  6. "time"
  7. xtime "github.com/bilibili/kratos/pkg/time"
  8. )
  9. var (
  10. // ErrPoolExhausted connections are exhausted.
  11. ErrPoolExhausted = errors.New("container/pool exhausted")
  12. // ErrPoolClosed connection pool is closed.
  13. ErrPoolClosed = errors.New("container/pool closed")
  14. // nowFunc returns the current time; it's overridden in tests.
  15. nowFunc = time.Now
  16. )
  17. // Config is the pool configuration struct.
  18. type Config struct {
  19. // Active number of items allocated by the pool at a given time.
  20. // When zero, there is no limit on the number of items in the pool.
  21. Active int
  22. // Idle number of idle items in the pool.
  23. Idle int
  24. // Close items after remaining item for this duration. If the value
  25. // is zero, then item items are not closed. Applications should set
  26. // the timeout to a value less than the server's timeout.
  27. IdleTimeout xtime.Duration
  28. // If WaitTimeout is set and the pool is at the Active limit, then Get() waits WatiTimeout
  29. // until a item to be returned to the pool before returning.
  30. WaitTimeout xtime.Duration
  31. // If WaitTimeout is not set, then Wait effects.
  32. // if Wait is set true, then wait until ctx timeout, or default flase and return directly.
  33. Wait bool
  34. }
  35. type item struct {
  36. createdAt time.Time
  37. c io.Closer
  38. }
  39. func (i *item) expired(timeout time.Duration) bool {
  40. if timeout <= 0 {
  41. return false
  42. }
  43. return i.createdAt.Add(timeout).Before(nowFunc())
  44. }
  45. func (i *item) close() error {
  46. return i.c.Close()
  47. }
  48. // Pool interface.
  49. type Pool interface {
  50. Get(ctx context.Context) (io.Closer, error)
  51. Put(ctx context.Context, c io.Closer, forceClose bool) error
  52. Close() error
  53. }

dao

dao中添加一个连接池。

  1. package dao
  2. import (
  3. "context"
  4. "time"
  5. demoapi "call-server/api"
  6. "call-server/internal/model"
  7. "github.com/bilibili/kratos/pkg/cache/memcache"
  8. "github.com/bilibili/kratos/pkg/cache/redis"
  9. "github.com/bilibili/kratos/pkg/conf/paladin"
  10. "github.com/bilibili/kratos/pkg/database/sql"
  11. "github.com/bilibili/kratos/pkg/net/rpc/warden"
  12. "github.com/bilibili/kratos/pkg/sync/pipeline/fanout"
  13. xtime "github.com/bilibili/kratos/pkg/time"
  14. //grpcempty "github.com/golang/protobuf/ptypes/empty"
  15. //"github.com/pkg/errors"
  16. "github.com/google/wire"
  17. "github.com/bilibili/kratos/pkg/container/pool"
  18. "io"
  19. "reflect"
  20. "google.golang.org/grpc"
  21. )
  22. var Provider = wire.NewSet(New, NewDB, NewRedis, NewMC)
  23. //go:generate kratos tool genbts
  24. // Dao dao interface
  25. type Dao interface {
  26. Close()
  27. Ping(ctx context.Context) (err error)
  28. // bts: -nullcache=&model.Article{ID:-1} -check_null_code=$!=nil&&$.ID==-1
  29. Article(c context.Context, id int64) (*model.Article, error)
  30. //SayHello(c context.Context, req *demoapi.HelloReq) (resp *grpcempty.Empty, err error)
  31. //get an demo grpcConn/grpcClient/ from rpc pool
  32. GrpcConnPut(ctx context.Context, cc *grpc.ClientConn) (err error)
  33. GrpcConn(ctx context.Context) (gcc *grpc.ClientConn, err error)
  34. GrpcClient(ctx context.Context) (cli demoapi.DemoClient, err error)
  35. }
  36. // dao dao.
  37. type dao struct {
  38. db *sql.DB
  39. redis *redis.Redis
  40. mc *memcache.Memcache
  41. cache *fanout.Fanout
  42. demoExpire int32
  43. rpcPool pool.Pool
  44. }
  45. // New new a dao and return.
  46. func New(r *redis.Redis, mc *memcache.Memcache, db *sql.DB) (d Dao, cf func(), err error) {
  47. return newDao(r, mc, db)
  48. }
  49. func newDao(r *redis.Redis, mc *memcache.Memcache, db *sql.DB) (d *dao, cf func(), err error) {
  50. var cfg struct {
  51. DemoExpire xtime.Duration
  52. }
  53. if err = paladin.Get("application.toml").UnmarshalTOML(&cfg); err != nil {
  54. return
  55. }
  56. // new pool
  57. pool_config := &pool.Config{
  58. Active: 0,
  59. Idle: 0,
  60. IdleTimeout: xtime.Duration(0 * time.Second),
  61. WaitTimeout: xtime.Duration(30 * time.Millisecond),
  62. }
  63. rpcPool := pool.NewSlice(pool_config)
  64. rpcPool.New = func(ctx context.Context) (cli io.Closer, err error) {
  65. wcfg := &warden.ClientConfig{}
  66. paladin.Get("grpc.toml").UnmarshalTOML(wcfg)
  67. if cli, err = NewGrpcConn(wcfg); err != nil {
  68. return
  69. }
  70. return
  71. }
  72. d = &dao{
  73. db: db,
  74. redis: r,
  75. mc: mc,
  76. cache: fanout.New("cache"),
  77. demoExpire: int32(time.Duration(cfg.DemoExpire) / time.Second),
  78. rpcPool: rpcPool,
  79. }
  80. cf = d.Close
  81. return
  82. }
  83. // Close close the resource.
  84. func (d *dao) Close() {
  85. d.cache.Close()
  86. }
  87. // Ping ping the resource.
  88. func (d *dao) Ping(ctx context.Context) (err error) {
  89. return nil
  90. }
  91. func (d *dao) GrpcClient(ctx context.Context) (cli demoapi.DemoClient, err error) {
  92. var cc io.Closer
  93. if cc, err = d.rpcPool.Get(ctx); err != nil {
  94. return
  95. }
  96. cli = demoapi.NewDemoClient(reflect.ValueOf(cc).Interface().(*grpc.ClientConn))
  97. return
  98. }
  99. func (d *dao) GrpcConnPut(ctx context.Context, cc *grpc.ClientConn) (err error) {
  100. err = d.rpcPool.Put(ctx, cc, false)
  101. return
  102. }
  103. func (d *dao) GrpcConn(ctx context.Context) (gcc *grpc.ClientConn, err error) {
  104. var cc io.Closer
  105. if cc, err = d.rpcPool.Get(ctx); err != nil {
  106. return
  107. }
  108. gcc = reflect.ValueOf(cc).Interface().(*grpc.ClientConn)
  109. return
  110. }

service

  1. // SayHello grpc demo func.
  2. func (s *Service) SayHello(ctx context.Context, req *pb.HelloReq) (reply *empty.Empty, err error) {
  3. reply = new(empty.Empty)
  4. var cc demoapi.DemoClient
  5. var gcc *grpc.ClientConn
  6. if gcc, err = s.dao.GrpcConn(ctx); err != nil {
  7. return
  8. }
  9. defer s.dao.GrpcConnPut(ctx, gcc)
  10. cc = demoapi.NewDemoClient(gcc)
  11. //if cc, err = s.dao.GrpcClient(ctx); err != nil {
  12. // return
  13. //}
  14. cc.SayHello(ctx, req)
  15. fmt.Printf("hello %s", req.Name)
  16. return
  17. }

好了现在测试 、 布局如下 :

p2c

roundrobin

轮询方式只需要在NewGrpcConn()里面加语一句配置项即可,它会覆盖掉p2c的配置项。

opts = append(opts, grpc.WithBalancerName(roundrobin.Name))

grpc官方负载均衡工作流程

我们目前也只是使用了Api、最后来瞧瞧官方grpc的工作流程 :

gRPC开源组件官方并未直接提供服务注册与发现的功能实现,但其设计文档已提供实现的思路,并在不同语言的gRPC代码API中已提供了命名解析和负载均衡接口供扩展。

  1. 服务启动后,gPRC客户端通过resolve发起一个名称解析请求。名称会被解析为一个或更多的IP地址,每个地址指明它是一个服务器地址还是一个负载均衡器地址,并且包含一个Opt指明哪一个客户端的负载均衡策略应该被使用(例如: 轮询调度或grpclb)。

  2. 客户端实现一个负载均衡策略。

    注意:如果任何一个被解析器返回的地址是均衡器地址,那么这个客户端会使用grpclb策略,而不管请求的Opt配置的是哪种负载均衡策略。否则,客户端会使用一个Opt项配置负载均衡策略。如果没有负载均衡策略,那么客户端会使用默认的取第一个可用服务器地址的策略。

  3. 负载均衡策略对每一个服务器地址创建一个子通道。

  4. 当调用rpc请求时,负载均衡策略会决定应该发送到哪个子通道(例如: 哪个服务器)。

    grpclb策略下,客户端按负载均衡器返回的顺序发送请求到服务器。如果服务器列表为空,调用将会阻塞直到收到一个非空的列表。

源码

本节测试代码 : https://github.com/ailumiyana/kratos-note/tree/master/warden/balancer

go微服务框架kratos学习笔记七(kratos warden 负载均衡 balancer)的更多相关文章

  1. go微服务框架kratos学习笔记五(kratos 配置中心 paladin config sdk [断剑重铸之日,骑士归来之时])

    目录 go微服务框架kratos学习笔记五(kratos 配置中心 paladin config sdk [断剑重铸之日,骑士归来之时]) 静态配置 flag注入 在线热加载配置 远程配置中心 go微 ...

  2. # go微服务框架kratos学习笔记六(kratos 服务发现 discovery)

    目录 go微服务框架kratos学习笔记六(kratos 服务发现 discovery) http api register 服务注册 fetch 获取实例 fetchs 批量获取实例 polls 批 ...

  3. go微服务框架kratos学习笔记四(kratos warden-quickstart warden-direct方式client调用)

    目录 go微服务框架kratos学习笔记四(kratos warden-quickstart warden-direct方式client调用) warden direct demo-server gr ...

  4. go微服务框架kratos学习笔记八 (kratos的依赖注入)

    目录 go微服务框架kratos学习笔记八(kratos的依赖注入) 什么是依赖注入 google wire kratos中的wire Providers injector(注入器) Binding ...

  5. go微服务框架kratos学习笔记九(kratos 全链路追踪 zipkin)

    目录 go微服务框架kratos学习笔记九(kratos 全链路追踪 zipkin) zipkin使用demo 数据持久化 go微服务框架kratos学习笔记九(kratos 全链路追踪 zipkin ...

  6. 微服务框架surging学习之路——序列化 (转载https://www.cnblogs.com/alangur/p/10407727.html)

    微服务框架surging学习之路——序列化   1.对微服务的理解 之前看到在群里的朋友门都在讨论微服务,看到他们的讨论,我也有了一些自己的理解,所谓微服务就是系统里的每个服务都 可以自由组合.自由组 ...

  7. golang微服务框架go-micro 入门笔记2.4 go-micro service解读

    本章节阐述go-micro 服务发现原理 go-micro架构 下图来自go-micro官方 阅读本文前你可能需要进行如下知识储备 golang分布式微服务框架go-micro 入门笔记1:搭建go- ...

  8. golang微服务框架go-micro 入门笔记2.3 micro工具之消息接收和发布

    本章节阐述micro消息订阅和发布相关内容 阅读本文前你可能需要进行如下知识储备 golang分布式微服务框架go-micro 入门笔记1:搭建go-micro环境, golang微服务框架go-mi ...

  9. golang微服务框架go-micro 入门笔记2.2 micro工具之微应用利器micro web

    micro web micro 功能非常强大,本文将详细阐述micro web 命令行的功能 阅读本文前你可能需要进行如下知识储备 golang分布式微服务框架go-micro 入门笔记1:搭建go- ...

随机推荐

  1. Vue 设置style属性

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  2. Python--day37--多进程中的方法join()

    1,多进程中的方法join()的作用: 感知一个子进程的结束,将异步的程序改为同步 #join() import time from multiprocessing import Process de ...

  3. BZOJ 4236 "JOIOJI"(前缀和+map+pair)

    传送门: [1]:BZOJ [2]:洛谷 •题解 定义数组 a,b,c 分别表示 'J' , 'O' , 'I' 的前缀和: 要想使区间 (L,R] 满足条件当且仅当 a[R]-a[L] = b[R] ...

  4. tf.train.slice_input_producer()

    tf.train.slice_input_producer处理的是来源tensor的数据 转载自:https://blog.csdn.net/dcrmg/article/details/7977687 ...

  5. MySQL视图 definer & invoker 权限

    1.创建视图 CREATE VIEW `NewView`AS SELECT `user`.USER_ID, `user`.USER_NAME, department.DEPT_ID, departme ...

  6. CSU 2005: Nearest Maintenance Point(Dijkstra + bitset)

    Description A county consists of n cities (labeled 1, 2, …, n) connected by some bidirectional roads ...

  7. jquery核心基础

    jquery对对象的操作:   检查对象类型: 老式的javascript使用typeOf()操作符,但他是不符合逻辑的,在某些情况下,typeOf()返回的不是一个正确的值,或者返回一个出乎意料的值 ...

  8. 2018-10-23-使用-Pandoc-把-Markdown-转-Docx

    title author date CreateTime categories 使用 Pandoc 把 Markdown 转 Docx lindexi 2018-10-23 10:56:18 +080 ...

  9. hdu 6851 Vacation(思维+贪心)

    传送门 •题意 有编号0到n,n+1辆车排队过红绿灯,从0到n离交通灯线越来越近 每辆车都有一个最大速度v,车身长度l,和离交通灯线的距离s, 一辆车头到达线则说明这辆车已到达线 如果一辆车前面没有紧 ...

  10. jquery中动态添加的标签绑定的click事件失效的解决办法

    把.click()换成.live('click',function(){})(如果你的jquery的版本是1.10之前) 把.click()换成.on('click',function(){})(jq ...