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


// 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...)
return c
}

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

package dao

import (
"context" "github.com/bilibili/kratos/pkg/net/rpc/warden" "google.golang.org/grpc" "fmt"
demoapi "call-server/api"
"google.golang.org/grpc/balancer/roundrobin"
) // target server addrs.
const target = "direct://default/10.0.75.2:30001,10.0.75.2:30002" // NOTE: example // NewClient new member grpc client
func NewClient(cfg *warden.ClientConfig, opts ...grpc.DialOption) (demoapi.DemoClient, error) {
client := warden.NewClient(cfg, opts...)
conn, err := client.Dial(context.Background(), target)
if err != nil {
return nil, err
}
// 注意替换这里:
// NewDemoClient方法是在"api"目录下代码生成的
// 对应proto文件内自定义的service名字,请使用正确方法名替换
return demoapi.NewDemoClient(conn), nil
} // NewClient new member grpc client
func NewGrpcConn(cfg *warden.ClientConfig, opts ...grpc.DialOption) (*grpc.ClientConn, error) {
fmt.Println("-----tag: NewGrpcConn...")
//opts = append(opts, grpc.WithBalancerName(roundrobin.Name))
client := warden.NewClient(cfg, opts...) conn, err := client.Dial(context.Background(), target)
if err != nil {
return nil, err
} return conn, nil
}

target 填上两个服务ip

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

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

package pool

import (
"context"
"errors"
"io"
"time" xtime "github.com/bilibili/kratos/pkg/time"
) var (
// ErrPoolExhausted connections are exhausted.
ErrPoolExhausted = errors.New("container/pool exhausted")
// ErrPoolClosed connection pool is closed.
ErrPoolClosed = errors.New("container/pool closed") // nowFunc returns the current time; it's overridden in tests.
nowFunc = time.Now
) // Config is the pool configuration struct.
type Config struct {
// Active number of items allocated by the pool at a given time.
// When zero, there is no limit on the number of items in the pool.
Active int
// Idle number of idle items in the pool.
Idle int
// Close items after remaining item for this duration. If the value
// is zero, then item items are not closed. Applications should set
// the timeout to a value less than the server's timeout.
IdleTimeout xtime.Duration
// If WaitTimeout is set and the pool is at the Active limit, then Get() waits WatiTimeout
// until a item to be returned to the pool before returning.
WaitTimeout xtime.Duration
// If WaitTimeout is not set, then Wait effects.
// if Wait is set true, then wait until ctx timeout, or default flase and return directly.
Wait bool
} type item struct {
createdAt time.Time
c io.Closer
} func (i *item) expired(timeout time.Duration) bool {
if timeout <= 0 {
return false
}
return i.createdAt.Add(timeout).Before(nowFunc())
} func (i *item) close() error {
return i.c.Close()
} // Pool interface.
type Pool interface {
Get(ctx context.Context) (io.Closer, error)
Put(ctx context.Context, c io.Closer, forceClose bool) error
Close() error
}

dao

dao中添加一个连接池。

package dao

import (
"context"
"time" demoapi "call-server/api"
"call-server/internal/model" "github.com/bilibili/kratos/pkg/cache/memcache"
"github.com/bilibili/kratos/pkg/cache/redis"
"github.com/bilibili/kratos/pkg/conf/paladin"
"github.com/bilibili/kratos/pkg/database/sql"
"github.com/bilibili/kratos/pkg/net/rpc/warden"
"github.com/bilibili/kratos/pkg/sync/pipeline/fanout"
xtime "github.com/bilibili/kratos/pkg/time"
//grpcempty "github.com/golang/protobuf/ptypes/empty"
//"github.com/pkg/errors" "github.com/google/wire"
"github.com/bilibili/kratos/pkg/container/pool"
"io"
"reflect"
"google.golang.org/grpc" ) var Provider = wire.NewSet(New, NewDB, NewRedis, NewMC) //go:generate kratos tool genbts
// Dao dao interface
type Dao interface {
Close()
Ping(ctx context.Context) (err error)
// bts: -nullcache=&model.Article{ID:-1} -check_null_code=$!=nil&&$.ID==-1
Article(c context.Context, id int64) (*model.Article, error)
//SayHello(c context.Context, req *demoapi.HelloReq) (resp *grpcempty.Empty, err error) //get an demo grpcConn/grpcClient/ from rpc pool
GrpcConnPut(ctx context.Context, cc *grpc.ClientConn) (err error)
GrpcConn(ctx context.Context) (gcc *grpc.ClientConn, err error)
GrpcClient(ctx context.Context) (cli demoapi.DemoClient, err error)
} // dao dao.
type dao struct {
db *sql.DB
redis *redis.Redis
mc *memcache.Memcache
cache *fanout.Fanout
demoExpire int32
rpcPool pool.Pool
} // New new a dao and return.
func New(r *redis.Redis, mc *memcache.Memcache, db *sql.DB) (d Dao, cf func(), err error) {
return newDao(r, mc, db)
} func newDao(r *redis.Redis, mc *memcache.Memcache, db *sql.DB) (d *dao, cf func(), err error) {
var cfg struct {
DemoExpire xtime.Duration
}
if err = paladin.Get("application.toml").UnmarshalTOML(&cfg); err != nil {
return
} // new pool
pool_config := &pool.Config{
Active: 0,
Idle: 0,
IdleTimeout: xtime.Duration(0 * time.Second),
WaitTimeout: xtime.Duration(30 * time.Millisecond),
} rpcPool := pool.NewSlice(pool_config)
rpcPool.New = func(ctx context.Context) (cli io.Closer, err error) {
wcfg := &warden.ClientConfig{}
paladin.Get("grpc.toml").UnmarshalTOML(wcfg)
if cli, err = NewGrpcConn(wcfg); err != nil {
return
} return
} d = &dao{
db: db,
redis: r,
mc: mc,
cache: fanout.New("cache"),
demoExpire: int32(time.Duration(cfg.DemoExpire) / time.Second),
rpcPool: rpcPool,
}
cf = d.Close
return
} // Close close the resource.
func (d *dao) Close() {
d.cache.Close()
} // Ping ping the resource.
func (d *dao) Ping(ctx context.Context) (err error) {
return nil
} func (d *dao) GrpcClient(ctx context.Context) (cli demoapi.DemoClient, err error) {
var cc io.Closer
if cc, err = d.rpcPool.Get(ctx); err != nil {
return
} cli = demoapi.NewDemoClient(reflect.ValueOf(cc).Interface().(*grpc.ClientConn))
return
} func (d *dao) GrpcConnPut(ctx context.Context, cc *grpc.ClientConn) (err error) {
err = d.rpcPool.Put(ctx, cc, false)
return
} func (d *dao) GrpcConn(ctx context.Context) (gcc *grpc.ClientConn, err error) {
var cc io.Closer
if cc, err = d.rpcPool.Get(ctx); err != nil {
return
} gcc = reflect.ValueOf(cc).Interface().(*grpc.ClientConn)
return
}

service

// SayHello grpc demo func.
func (s *Service) SayHello(ctx context.Context, req *pb.HelloReq) (reply *empty.Empty, err error) {
reply = new(empty.Empty)
var cc demoapi.DemoClient
var gcc *grpc.ClientConn
if gcc, err = s.dao.GrpcConn(ctx); err != nil {
return
}
defer s.dao.GrpcConnPut(ctx, gcc)
cc = demoapi.NewDemoClient(gcc)
//if cc, err = s.dao.GrpcClient(ctx); err != nil {
// return
//}
cc.SayHello(ctx, req)
fmt.Printf("hello %s", req.Name)
return
}

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

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. H3C 静态路由实现路由备份和负载分担

  2. JOISC2014 挂饰("01"背包)

    传送门: [1]:洛谷 [2]:BZOJ 参考资料: [1]:追忆:往昔 •题解 上述参考资料的讲解清晰易懂,下面谈谈我的理解: 关键语句: 将此题转化为 "01背包" 类问题,关 ...

  3. H3C配置BPDU的生成和传递

  4. H3C查看保存的配置文件

  5. maven仓库总结,maven私服搭建,批量mvn eclipse:eclipse

    配置pom.xml依赖包时在这里找包的描述: http://search.maven.org/#browse 以java为根目录. mvn archtype:generate -DgroupId=zt ...

  6. Web的大趋势:Java+大前端

    前后端分离,是目前Web开发的主流模式.而Java无疑是后端开发的王者,PHP和.NET目前仍处于水深火热之中,更像是在夹缝中求生存.而大前端,强势崛起!Java+大前端这一强强组合,面对其他Web领 ...

  7. tomcat日志传参乱码问题

    问题:      在centos系统下,tomcat8.0.36控制台日志打印会出现中文乱码. 解决方案:      在catalina.sh里加上 JAVA_OPTS="-Dfile.en ...

  8. [工具] Git版本管理(知识总结)

    对以下文档进行了简要总结,方面复习: [工具] Git版本管理(一)(基本操作) [工具] Git版本管理(二)(分支) [工具] Git版本管理(三)(工作流) [工具] Git版本管理(四)(贡献 ...

  9. tensorflow之tf.train.exponential_decay()指数衰减法

    exponential_decay(learning_rate,  global_steps, decay_steps, decay_rate, staircase=False, name=None) ...

  10. html页脚固定在底部的方法

    <style type="text/css"> html { height: 100%; } body { height: 100%; margin: 0; paddi ...