第6关 熔断 限流 负载均衡
  u6bdUTdLzeG0 2023年11月02日 77 0


学东西 = 建立架构 + 建立索引。

架构就好比本专栏的一个个关卡,目的是压缩信息,使信息系统化,结构化,拒绝零散,

索引就好比本文的目录,方便查。——c老师

再远的路也要一步一步地走,再大的目标也要一天一天地去分步实现。——我自己说的


第6关 熔断 限流 负载均衡

  • ​​6.1 熔断器作用和原理​​
  • ​​6.1.1 微服务熔断(hystrix-go)介绍​​
  • ​​6.1.1.1 微服务熔断(hystrix-go)的作用​​
  • ​​6.1.1.2 微服务熔断(hystrix-go)的4个目标​​
  • ​​6.1.1.3 微服务熔断(hystrix-go)使用过程中的一些原则​​
  • ​​6.1.1.4 微服务熔断(hystrix-go)的请求原理​​
  • ​​6.1.1.5 微服务熔断(hystrix-go)的3个熔断状态​​
  • ​​6.1.1.6 微服务熔断(hystrix-go)的5个重要字段​​
  • ​​6.1.1.7 微服务熔断(hystrix-go)的熔断计数器​​
  • ​​6.1.1.8 微服务熔断(hystrix-go)的观测面板安装​​
  • ​​6.2 限流的作用和原理​​
  • ​​6.3 负载均衡作用和原理​​
  • ​​6.4 微服务API网关​​
  • ​​6.5 server端 proto 文件编写​​
  • ​​6.6 service 端 model 开发​​
  • ​​6.7 server端 repository 开发​​
  • ​​6.8 server端口service开发​​
  • ​​6.9 common 独立使用​​
  • ​​6.10 server端 handler开发​​
  • ​​6.11 server 端 添加限流​​
  • ​​6.12 购物车API层 添加熔断​​
  • ​​6.13 购物车API层 添加负载均衡​​
  • ​​6.14 API 网关及熔断看板使用​​
  • ​​6.15 总结​​

6.1 熔断器作用和原理

本小节主要内容:

(1)微服务熔断,限流,负载均衡的作用和原理/架构的讲解

(2)微服务API网关介绍

(3)熔断,限流,负载均衡代码实现

(2)微服务API网关介绍

因为我们用到的熔断,限流和负载均衡是在客户端实现的。

客户端实现我们需要对API进行控制。

API 控制对外面暴露使用的是API网关。

(3)熔断,限流,负载均衡代码实现

还会看一看控制台。

6.1.1 微服务熔断(hystrix-go)介绍

(1)微服务熔断的作用

(2)Hystrix原理

(3)Docker 安装hystrix控制面板

6.1.1.1 微服务熔断(hystrix-go)的作用

(1)什么是服务雪崩效应?

因为我们在微服务里面,比如说服务a调用服务b的这种依赖关系,但是这个依赖关系在生产环境中比较多的话,比如说我的服务c因为被服务b依赖了,所以服务c挂了以后,本来服务b原本是正常的,但又因为服务c导致了服务b不正常,这样会导致级联反应,牵一发而动全身,即服务a依赖于服务b,服务c挂了以后会影响到服务a和服务b,这就导致了一个连锁反应,也就是常说的服务雪崩效应。

如图:

第6关 熔断 限流 负载均衡_熔断 限流 负载均衡

解读信息:

服务调用是正常状态的时候,我们是可以正常调用服务a的;

当服务不可用的时候,服务a下线了,服务a就不可用了。

连锁反应会导致整条服务链都会被断掉/挂掉。

如何解决服务雪崩效应呢?

这就要用到熔断器了。

见下一小节介绍。

6.1.1.2 微服务熔断(hystrix-go)的4个目标

(1)阻止故障的连锁反应

(2)快速失败并迅速恢复

迅速恢复不是正常的恢复,它是一个熔断的逻辑。

(3)回退并优雅降级

一旦检测到依赖的b或者是依赖的c出现了问题,我们会回退并且优雅的降级。

(4)提供实时的监控和告警

使用hystrix过程中有一些原则,见下一小节。

6.1.1.3 微服务熔断(hystrix-go)使用过程中的一些原则

(1)防止任何单独的依赖耗尽资源(线程)

在整个依赖链上,每个环节里面都要有特别消耗资源的一个环节。

(2)过载立刻切断并快速失败,防止排队

在依赖链中,如果有一个环节过载了,我们应该快速的切断,并且快速的失败,防止了后续的请求发生排队现象。

(3)尽可能提供回退以保护用户免受故障

提高用户体验。

(4)通过提供实时的指标,监控和告警,确保故障被及时发现

下一小节来看一下hystrix的请求原理。

6.1.1.4 微服务熔断(hystrix-go)的请求原理

第6关 熔断 限流 负载均衡_Go微服务_02

解读信息:

(1)

从command开始。以command为爬犁。

紫色的箭头部分是正常状态下的循环流程走向。

(2)

第一步就是判断熔断是否开启。

熔断是否开启是通过记录器来判断的。

记录器会有信息的收集。

黄色的箭头会进行信息的上报,上报到计数器的时候,计数器会根据它的业务逻辑来判断熔断是否进行开启。

(3)

​第一步判断熔断是否开启会依赖计数器给出的最终结果。

如果熔断是没有开启的,就正常的进行下一步【紫色的箭头是正常的流程】,来判断熔断是否超过并发,记录器上面会有一个设置的并发值,超过了这个并发值它就走Callback的逻辑,如果没超过走 程序逻辑,这里的程序逻辑就是正常的进行调用。

程序逻辑走完了以后,看一下是否执行成功。

如果执行成功了以后,我们会到下一步判断执行是否超时,是否执行成功,是否超时,以及是否超过并发等等,这里的是菱形【黄色颜色块】,都会把这些信息进行信息上报,上报到计数器里面。

计数器它的作用是什么?

​当这些判断上面都是为成功的时候,它走紫色的箭头就是一个正常状态,如果它有一些异常,它都会执行Callback的逻辑。

以上就是熔断器整个调用过程以及它的整体架构的解读。

6.1.1.5 微服务熔断(hystrix-go)的3个熔断状态

(1)CLOSED 关闭状态:允许流量通过

允许流量通过,熔断还没开,可以正常的走我们的业务逻辑。

(2)OPEN 打开状态:不允许流量通过,即处于降级状态,走降级的逻辑

熔断开了。

(3)HALF_OPEN 半开状态:允许某些流量通过,如果出现超时,异常等情况,将进入OPEN状态,如果成功,那么将进入CLOSED状态。

6.1.1.6 微服务熔断(hystrix-go)的5个重要字段

(1)Timeout:执行command的超时时间,默认时间是1000毫秒

(2)MaxConcurrentRequests:最大并发量,默认值是10

(3)SleepWindow:熔断打开后多久进行再次尝试,默认值是5000毫秒

(4)RequestVolumeThreshold:10秒内的请求量,默认值是20,如果超过20那么就判断是否熔断

(5)ErrorPercentThreshold:判断熔断百分比,默认值是50%,如果超过50%这个默认值就启动熔断

6.1.1.7 微服务熔断(hystrix-go)的熔断计数器

(1)每一个Command都会有一个默认统计的控制器

(2)默认的统计控制器 DefaultMetricCollector进行收集记录

保存熔断器的所有状态。

比如6.1.1.5小节中设计的3种状态,它会保存当前是什么状态,以及调用的次数,失败的次数,被拒绝的次数等重要的信息。

(3)保存熔断器的所有状态,调用次数,失败次数,被拒绝次数等信息

如果用过熔断,那么就必须知道熔断的主要流程,状态,实现原理。

实现原理:

第6关 熔断 限流 负载均衡_1024程序员节_03

解读信息:

(1)一个桶,它有4个状态。

用颜色来标识就是:

a. 绿色是成功状态。

b. 橙色是是失败的次数。

c. 粉红色是超时的次数。

d. 红色是被拒绝的次数。

熔断记录的状态会一个一个像这样把它给记录起来。

比如说到第10秒的时候,它的记录状态见上图第10秒。

当超过10秒的时候,它第一个将会被销毁,后面会一个一个地往上添加,这就是技术的原理;

然后它有的时候是根据10秒内的一个平均值进行判断的,这个主要是它的实现方式。

再来看信息上传的方式:

第6关 熔断 限流 负载均衡_熔断 限流 负载均衡_04

解读信息:

信息上传就是通过断路器。

断路器执行完了以后,我们就可以上报信息,上报完信息以后,我们写入到熔断器里面,写入的这个过程就是我们一个一个的建立,就是每一秒建立一个Bucket,一共的时间窗口默认的是10秒,默认的10秒的窗口,会进入4个请求状态。

然后我们根据这些请求的状态判断熔断是否为开。

熔断计数器特点:

(1)有信息的上报

​(2)它会一个一个的把你的每一秒的信息,它成功了多少,它失败了多少,超时了多少,以及被拒绝了多少次,都会记录下来。

记录完了以后,当你查询熔断是否需要开的时候,它是根据这些信息进行来判断你的熔断器是否是开的。

以上就是熔断器的原理。

6.1.1.8 微服务熔断(hystrix-go)的观测面板安装

安装方法:

docker pull cap1573/hystrix-dashboard
docker run -d -p 9002:9002 cap1573/hystrix-dashboard
/*下一步行动是
*访问ip:9002控制面板
*/

6.2 限流的作用和原理

1.限流(uber/limit)的作用:

(1)限制流量,在服务端生效

限流和熔断是相辅相成的。

限流可以限制我们的流量。

限流是在服务端生效的,即限流是作用在服务端的,来保护服务端不被大流量压垮。

举个例子,我们去一家网红餐厅吃饭,到饭点的时候吃饭的人非常的多,这时候人流量的压力会非常的大,我们会发现餐厅的门口有好多好多凳子给用餐的顾客进行等候,这就类似我们的服务端的限流,大家坐在外面的凳子去等,就相当于我们服务端的限流,服务端就相当于餐厅。

假如餐厅里面只能容纳50位客户,我们现在来了100位就要进行限流,如果不限流我们这100位客户每个人都吃不上饭,此时用户体验就会非常差。

(2)保护后端服务

我们的程序代码里面也是同样的道理,明明只能处理50个请求,你给我塞了100个,我的响应速度就会特别特别慢,用户体验会十分不友好,所以我们要限流,如果不限流除了用户体验不好,我们的服务端有可能就直接被大流量压垮掉,就不能正常提供服务了。

(3)与熔断互补

一个是客户端,我们发现了服务端不可用了,快速地熔断,走降级的逻辑。

服务端也需要一个保护,就是限流。

限流用到了 uber/limit 扩展。

2.限流(uber/limit)的漏桶算法原理:

第6关 熔断 限流 负载均衡_git_05

解读信息:

(1)左边

​左边水龙头 进水比较快,但是在水桶的下面有个出水口,出水口 出水是比较慢的,也是非常匀速的。

当我们来的请求量非常大的时候,就相当于进流量的水非常的大,这里会有一个储水桶,意思就是说不管你的水流量有多大,我的出水的频率是固定的,保护了我的服务端处理的频率是固定的,这样就是起到对我们服务端进行保护的作用,这里的水桶起到了缓冲的作用。

(2)右边

我们再来抽象,抽象过来以后就是:

当客户端请求的时候,服务端接入我的请求,当我未限流的时候,请求流量全到我的限流控制桶里面来,接到了这些请求以后,然后流量控制对你进行限流,就是对每个请求进行限流,然后固定的请求,即被限流完的请求,如果满足我这个算法,就把该请求放过去,不满足就拦截放到限流控制里面。

还有个令牌算法,这里先不介绍。

6.3 负载均衡作用和原理

1.负载均衡作用

(1)提高系统的扩展性

字面意思就是提高我们系统的处理能力。

漏桶算法,下面的出水口的出水频率【处理速度】是固定的,当处理速度不满足我们的需求的时候,我们会把处理后端的频率的速度提高。

通过 提高 负载均衡的 系统的扩展性,来提高服务端 处理速度的效率。

(2)支持请求:HTTP,HTTPS,TCP,UDP请求

(3)主要算法:循环算法和随机算法,不指定默认使用的是随机算法

负载均衡可以作用于服务端,也可以作用于客户端,这里使用服务端作为演示。

2.负载均衡的架构

第6关 熔断 限流 负载均衡_git_06

解读信息:

(1)

当我们正常的API请求过来的时候,请求完了以后走到了API这一块,接着会来到服务ServerA这一块,这是一个正常的请求链路。

当ServiceA压力特别大的时候,我们就会用到负载均衡。

负载均衡它的架构就是这样:

第6关 熔断 限流 负载均衡_git_07

我们会把服务a经过负载均衡这种方式,把它横向扩展出服务a2和服务a3,当然里面的业务逻辑都是一样的,我们主要是把这个服务把它给扩展起来。【扩展就像热备份一样。】

如果访问到a1的时候,第一次访问到a1,通过随机算法,第二次可能就会访问到a2。

这样就把系统做到了横向的扩展。提高了服务端的处理能力。

如果我们所有的请求只走a1进行判断处理的话,一旦请求量过大的话,a1很可能抗压扛不住,为了提高服务端的处理能力,通过负载均衡的机制,下次请求来的时候,通过随机算法,可能是服务a2、服务a3来处理请求。这样a1的压力就会小很多。【分了一部分流量到b,c处理去了】

可以这样形象化理解:

把3个洗脚盆,在相同的位置打通,当水流量达到一定深度的时候,水会在3个洗脚盆之间流动,这样每个洗脚盆所承载的水流量就会小很多。【把负载均衡当做物理上的连通器的管子来理解】

以上就是负载均衡的架构原理。

它相对于熔断,限流,容易理解。

负载均衡主要是写在客户端这一块。

​负载均衡 洗脚水 形象化理解​

6.4 微服务API网关

第6关 熔断 限流 负载均衡_ooc_08

解读信息:

1.

当我们请求一个API的时候,即我们通过正常的方式请求一个API的时候,比如说CartApi/FindAll这个请求发送到我们网关的时候,我们的网关接受到请求,然后根据网关的规则去请求go.micro.api.cartapi,cartapi这个服务里面的 CartApi.FindAll它会访问CartApi服务。

然后CartApi,通过这个服务我们再去请求后端的基础服务,这样大家可以清楚地看到,我们引入了API网关以后,请求和原来使用的时候是一样的,我们API是暴露出一个API是给大家使用的。

然后我们会对这个基础服务进行简单的聚合,这样我们就把这个架构分了三层:

第一就是我们API网关层,第二就是我们API层,第三就是我们的基础服务层。

​着重的来说一下这三层有什么作用。

(1)第一层就是Micro api网关。

通过Micro api进行暴露的,然后我们移动APP可以通过统一的API网关的地址请求我们提供的接口,所以我们这个地址是不变的,通过后面的路由进行转发,转发到我们已经提供的这个接口上去,这一层不需要我们开发,主要启动Micro api就可以作为我们客户端的代理进行后端的请求。

(2)第二层就是聚合业务层。

聚合业务层就是我们暴露出去的API,然后我们实现了业务的聚合,我们把所有的基础服务,我们通过聚合业务层,就是API层,我们把基础服务查询到的一些数据把它也放在一起,通过我们的业务逻辑代码把它组装起来,然后把它返回到前面的请求上面来。

那么这样做有什么好处呢?

第一就是我们分层的架构设计,可以有效的复用我们的代码,比如说我们的基础代码,越基础的代码它越稳定,显得越稳定,然后复用率越高,确保我们底层的服务单一的职责,这呢主要是为了我们代码的复用率。

第二,通过分层的架构,我们还能提高它的扩展性,越是底层的它越需要在业务上进行稳定,不要经常改它。

然后我们涉及到是表层的东西,我们在业务逻辑上可能跟业务逻辑的不同,或者根据发展的不同,我们会经常的调整,所以它可以提高我们的扩展性,让我们去和底层的服务满足我们的业务需求。

(3)第三层就是基础服务层。

service层,处理我们最底层的业务逻辑,保证我们服务的单一职责。

这就是引入了API网关以后整体架构所带来的一个影响,就是把我们整个微服务架构分成了三层:

第一就是我们API的网关,第二个就是聚合业务,第三个就是我们的基础服务。

**聚合业务层如何去跟API网关进行关联?**下文会介绍。

那么我们用了API网关,下面就看一下API网关是怎么用的,它的路径怎么用,即我们一个请求路径过来以后它怎么用?

API网关路径说明:

(1)通过网关请求/greeter/say/hello这个路径,网关会将请求转发到go.micro.api.greeter服务的Say.Hello方法处理;

(2)go.micro.api是网关默认服务器名的前缀

(3)路径中/cartApi/cartApi/findAll 也可以写成 /cartApi/findAll

6.5 server端 proto 文件编写

0.准备工作:

(1)创建git.imooc仓库,名为cart

git remote add origin ssh://git@git.imooc.com:80/user369/cart.git
ssh://git@git.imooc.com:80/user369/cart.git

(2)创建goland模板工程

docker run --rm -v $(pwd):$(pwd) -w  $(pwd) -e ICODE=5CB0645D0EDFC5 cap1573/cap-micro new git.imooc.com/user369/cart

(3)编写proto文件

1.电商购物车代码开发

(1)购物车需求分析 & 项目目录创建

(2)购物车代码开发

(3)购物车加入熔断(客户端),限流(服务端),负载均衡(客户端)

(4)API网关使用以及最终效果展示

文件位置:​​cart/proto/cart/cart.proto​

主要做了一件什么事情呢?

添加购物车,清空购物车等增删改查的逻辑。

syntax = "proto3";

package go.micro.service.cart;

// 定义一个购物车服务
service Cart {
// 添加购物车接口
rpc AddCart(CartInfo) returns (ResponseAdd) {}
rpc CleanCart(Clean) returns (Response){}
rpc Incr(Item) returns (Response){}
rpc Decr(Item) returns (Response){}
rpc DeleteItemByID (CartID) returns (Response){}
rpc GetAll(CartFindAll) returns (CartAll){}
}

// 定义CartInfo消息
message CartInfo {
int64 id = 1;
int64 user_id =2;
int64 product_id = 3;
int64 size_id = 4;
int64 num =5;
}

message ResponseAdd{
int64 cart_id =1;
string msg =2;
}

message Clean {
int64 user_id =1;
}

message Response {
string meg =1;
}

message Item {
int64 id =1;
int64 change_num = 2;
}

message CartID{
int64 id =1;
}

message CartFindAll {
int64 user_id =1;
}

message CartAll {
repeated CartInfo cart_info =1;
}

执行:

make proto

6.6 service 端 model 开发

文件代码:​​cart/domain/model/cart.go​

package model

type Cart struct{
ID int64 `gorm:"primary_key;not_null;auto_increment" json:"id"`
ProductID int64 `gorm:"not_null" json:"product_id"`
Num int64 `gorm:"not_null" json:"num"`
SizeID int64 `gorm:"not_null" json:"size_id"`
UserID int64 `gorm:"not_null" json:"user_id"`
}

6.7 server端 repository 开发

文件位置:​​cart/domain/repository/cart_repository.go​

package repository

import (
"errors"
"git.imooc.com/keegan/cart/domain/model"
"github.com/jinzhu/gorm"
)

type ICartRepository interface {
InitTable() error
FindCartByID(int64) (*model.Cart,error)
CreateCart(*model.Cart)(int64,error)
DeleteCartByID(int64) error
UpdateCart(*model.Cart) error
FindAll(int64)([]model.Cart,error)

CleanCart(int64)error
IncrNum(int64,int64)error
DecrNum(int64,int64)error
}

// 创建cartRepository
func NewCartRepository(db *gorm.DB)ICartRepository{
return &CartRepository{mysqlDB:db}
}

type CartRepository struct {
mysqlDB *gorm.DB
}

// 初始化表
func (u *CartRepository)InitTable()error{
return u.mysqlDB.CreateTable(&model.Cart{}).Error
}

// 创建Cart信息
func(u *CartRepository)CreateCart(cart *model.Cart)(int64,error){
db := u.mysqlDB.FirstOrCreate(cart,model.Cart{ProductID:cart.ProductID,SizeID:cart.SizeID,UserID:cart.UserID})
if db.Error != nil{
return 0,db.Error
}
if db.RowsAffected == 0{
return 0,errors.New("购物车插入失败")
}
return cart.ID,nil
}

// 添加商品数量
func(u *CartRepository)IncrNum(cartID int64,num int64)error{
cart := &model.Cart{ID:cartID}
return u.mysqlDB.Model(cart).UpdateColumn("num",gorm.Expr("num+?",num)).Error
}

// 根据ID删除Cart信息
func(u *CartRepository)DeleteCartByID(cartID int64)error{
return u.mysqlDB.Where("id=?",cartID).Delete(&model.Cart{}).Error
}

// 根据用户ID来清空购物车
func(u *CartRepository)CleanCart(userID int64)error{
return u.mysqlDB.Where("user_id=?",userID).Delete(&model.Cart{}).Error
}

// 购物车减少商品
func(u *CartRepository)DecrNum(cartID int64,num int64)error{
cart := &model.Cart{ID:cartID}
db := u.mysqlDB.Model(cart).Where("num >= ?",num).UpdateColumn("num",gorm.Expr("num - ?",num))
if db.Error != nil{
return db.Error
}
if db.RowsAffected == 0 {
return errors.New("减少失败")
}
return nil
}

// 更新Cart信息
func(u *CartRepository)UpdateCart(cart *model.Cart)error{
return u.mysqlDB.Model(cart).Update(cart).Error
}

// 根据ID查找Cart信息
func(u *CartRepository)FindCartByID(cartID int64)(cart *model.Cart,err error){
cart = &model.Cart{}
return cart,u.mysqlDB.First(cart,cartID).Error
}

// 获取结果集
func (u *CartRepository)FindAll(userID int64)(cartAll []model.Cart,err error){
return cartAll,u.mysqlDB.Where("user_id=?",userID).Find(&cartAll).Error
}

6.8 server端口service开发

文件位置:​​cart/domain/service/cart_data_service.go​

package service

import (
"git.imooc.com/keegan/cart/domain/model"
"git.imooc.com/keegan/cart/domain/repository"
)

type ICartDataService interface {
AddCart(*model.Cart) (int64 , error)
DeleteCart(int64) error
UpdateCart(*model.Cart) error
FindCartByID(int64) (*model.Cart, error)
FindAllCart(int64) ([]model.Cart, error)

CleanCart(int64) error
DecrNum(int64,int64) error
IncrNum(int64,int64) error
}


//创建
func NewCartDataService(cartRepository repository.ICartRepository) ICartDataService{
return &CartDataService{ cartRepository }
}

type CartDataService struct {
CartRepository repository.ICartRepository
}


//插入
func (u *CartDataService) AddCart(cart *model.Cart) (int64 ,error) {
return u.CartRepository.CreateCart(cart)
}

//删除
func (u *CartDataService) DeleteCart(cartID int64) error {
return u.CartRepository.DeleteCartByID(cartID)
}

//更新
func (u *CartDataService) UpdateCart(cart *model.Cart) error {
return u.CartRepository.UpdateCart(cart)
}

//查找
func (u *CartDataService) FindCartByID(cartID int64) (*model.Cart, error) {
return u.CartRepository.FindCartByID(cartID)
}

//查找
func (u *CartDataService) FindAllCart(userID int64) ([]model.Cart, error) {
return u.CartRepository.FindAll(userID)
}

func (u *CartDataService) CleanCart(userID int64) error {
return u.CartRepository.CleanCart(userID)
}

func (u *CartDataService) DecrNum(cartID int64,num int64) error{
return u.CartRepository.DecrNum(cartID,num)
}

func (u *CartDataService) IncrNum(cartID int64,num int64) error {
return u.CartRepository.IncrNum(cartID,num)
}

6.9 common 独立使用

一定要把common设置成公有的仓库,否则go get git_url会报一下错误:

  cart git:(master) go get git.imooc.com/Bobby/common                          
go: downloading git.imooc.com/Bobby/common v0.0.0-20211023172341-3ca91a307270
go get: git.imooc.com/Bobby/common@v0.0.0-20211023172341-3ca91a307270: verifying module: git.imooc.com/Bobby/common@v0.0.0-20211023172341-3ca91a307270: reading https://goproxy.cn/sumdb/sum.golang.org/lookup/git.imooc.com/Bobby/common@v0.0.0-20211023172341-3ca91a307270: 404 Not Found
server response:
not found: git.imooc.com/Bobby/common@v0.0.0-20211023172341-3ca91a307270: invalid version: git fetch -f origin refs/heads/*:refs/heads/* refs/tags/*:refs/tags/* in /tmp/gopath/pkg/mod/cache/vcs/e169ced1e74bf468df109ae2e457e8a88c0524be3199a4fb9267528b8b430912: exit status 128:
fatal: could not read Username for 'https://git.imooc.com': terminal prompts disabled

报错原因就是该common我设置的是私有的仓库导致go get git_url失败。一定要细心!!!

其他方法:

第6关 熔断 限流 负载均衡_ooc_09

已尝试,未解决问题。

​​参考链接​​

好,可以往下开始做了。

步骤1:

第6关 熔断 限流 负载均衡_1024程序员节_10

步骤2:创建git.imooc的名为common的仓库

接着在common目录下依次执行:

git init
echo "common" > README.md
git add .
git commit -m "first commit"
git remote add origin ssh://git@git.imooc.com:80/user369/common.git
git push -u origin master

// 可能会用到的命令
git remote -v
git remote set-url origin <your_remote_url>
git remote rm origin

步骤3:

在common目录下执行:

go mod init git.imooc.com/user369/common

​git远程仓库相关操作链接​

步骤4:

拷贝之前项目的common下的文件,结果如下:

第6关 熔断 限流 负载均衡_熔断 限流 负载均衡_11

接着执行:

go mod tidy

步骤5:

推送代码:

git stash
git pull ssh://git@git.imooc.com:80/user369/common.git
git stash pop
git add .
git commit -m "second commit"
git push -u origin master

6.10 server端 handler开发

找到 ​​cart.pb.micro.go​​ 中 ​​CartHandler​​ 中所有的方法,这是接下来要实现的方法。

第6关 熔断 限流 负载均衡_git_12

文件位置:​​cart/handler/cart.go​

package handler

import (
"context"
"git.imooc.com/keegan/cart/domain/model"
"git.imooc.com/keegan/cart/domain/service"
cart "git.imooc.com/keegan/cart/proto/cart"
"git.imooc.com/keegan/common"
)
type Cart struct{
CartDataService service.ICartDataService
}

//添加购物车
func (h *Cart) AddCart(ctx context.Context, request *cart.CartInfo, response *cart.ResponseAdd) (err error) {
cart := &model.Cart{}
common.SwapTo(request,cart)
response.CartId,err = h.CartDataService.AddCart(cart)
return err
}

//清空购物车
func (h *Cart) CleanCart(ctx context.Context,request *cart.Clean,response *cart.Response) error {
if err:= h.CartDataService.CleanCart(request.UserId);err !=nil {
return err
}
response.Meg = "购物车清空成功"
return nil
}

//添加购物车数量
func (h *Cart) Incr(ctx context.Context,request *cart.Item,response *cart.Response) error {
if err := h.CartDataService.IncrNum(request.Id,request.ChangeNum);err !=nil {
return err
}
response.Meg = "购物车添加成功"
return nil
}

//购物车减少商品数量
func (h *Cart) Decr(ctx context.Context,request *cart.Item,response *cart.Response) error {
if err := h.CartDataService.DecrNum(request.Id,request.ChangeNum);err !=nil {
return err
}
response.Meg = "购物车减少成功"
return nil
}
//删除购物车
func (h *Cart) DeleteItemByID(ctx context.Context,request *cart.CartID,response *cart.Response) error {
if err:=h.CartDataService.DeleteCart(request.Id);err!=nil {
return err
}
response.Meg = "购物车删除成功"
return nil
}
//查询用户所有的购物车信息
func (h *Cart) GetAll(ctx context.Context,request *cart.CartFindAll,response *cart.CartAll) error {
cartAll,err := h.CartDataService.FindAllCart(request.UserId)
if err !=nil {
return err
}

for _,v :=range cartAll {
cart := &cart.CartInfo{}
if err:= common.SwapTo(v,cart);err !=nil {
return err
}
response.CartInfo=append(response.CartInfo,cart)
}
return nil
}

6.11 server 端 添加限流

编写​​main.go​​文件内容的一些配置信息:

package main

import (
"git.imooc.com/keegan/cart/domain/repository"
service2 "git.imooc.com/keegan/cart/domain/service"
"git.imooc.com/keegan/cart/handler"
"git.imooc.com/keegan/common"
"github.com/jinzhu/gorm"
"github.com/micro/go-micro/v2"
log "github.com/micro/go-micro/v2/logger"
"github.com/micro/go-micro/v2/registry"
ratelimit "github.com/micro/go-plugins/wrapper/ratelimiter/uber/v2"
opentracing2 "github.com/micro/go-plugins/wrapper/trace/opentracing/v2"

consul2 "github.com/micro/go-plugins/registry/consul/v2"
"github.com/opentracing/opentracing-go"

cart "git.imooc.com/keegan/cart/proto/cart"

_ "github.com/jinzhu/gorm/dialects/mysql"
)

var QPS = 100

func main() {
//配置中心
consulConfig, err := common.GetConsulConfig("127.0.0.1", 8500, "/micro/config")
if err != nil {
log.Error(err)
}
//注册中心
consul := consul2.NewRegistry(func(options *registry.Options) {
options.Addrs = []string{
"127.0.0.1:8500",
}
})

//链路追踪
t, io, err := common.NewTracer("go.micro.service.cart", "localhost:6831")
if err != nil {
log.Error(err)
}
defer io.Close()
opentracing.SetGlobalTracer(t)

//数据库连接
mysqlInfo := common.GetMysqlFromConsul(consulConfig,"mysql")
//创建数据库连接
db,err := gorm.Open("mysql",mysqlInfo.User + ":" + mysqlInfo.Pwd + "@tcp(" + mysqlInfo.Host + ":" + mysqlInfo.Port + ")/"+ mysqlInfo.Database + "?charset=utf8&parseTime=True&loc=Local")
if err !=nil {
log.Error(err)
}
defer db.Close()
//禁止副表
db.SingularTable(true)

//第一次初始化
err = repository.NewCartRepository(db).InitTable()
if err !=nil {
log.Error(err)
}


// New Service
service := micro.NewService(
micro.Name("go.micro.service.cart"),
micro.Version("latest"),
//暴露的服务地址
micro.Address("0.0.0.0:8087"),
//注册中心
micro.Registry(consul),
//链路追踪
micro.WrapHandler(opentracing2.NewHandlerWrapper(opentracing.GlobalTracer())),
//添加限流
micro.WrapHandler(ratelimit.NewHandlerWrapper(QPS)),
)


// Initialise service
service.Init()

cartDataService := service2.NewCartDataService(repository.NewCartRepository(db))

// Register Handler
cart.RegisterCartHandler(service.Server(), &handler.Cart{CartDataService:cartDataService})

// Run service
if err := service.Run(); err != nil {
log.Fatal(err)
}
}

接着执行 go mod tidy

接着执行 go run main.go,输出结果如下:

第6关 熔断 限流 负载均衡_Go微服务_13

第6关 熔断 限流 负载均衡_1024程序员节_14

注册中心也有了cart服务:

第6关 熔断 限流 负载均衡_git_15

6.12 购物车API层 添加熔断

准备工作看截图:

第6关 熔断 限流 负载均衡_熔断 限流 负载均衡_16

docker run --rm -v $(pwd):$(pwd) -w $(pwd) -e ICODE=5CB0645D0EDFC5  cap1573/cap-micro new --type=api git.imooc.com/Bobby/cartApi

(1)编写proto文件

文件位置:​​cartApi/proto/cartApi/cartApi.proto​

syntax = "proto3";

package go.micro.api.cartApi;

service CartApi {
rpc FindAll(Request) returns (Response) {}
}

message Pair {
string key = 1;
repeated string values = 2;
}

message Request{
string method =1;
string path =2;
map<string,Pair> header =3;
map<string,Pair> get = 4;
map<string,Pair> post =5;
string body = 6;
string url = 7;
}

message Response {
int32 statusCode = 1;
map<string,Pair> header = 2;
string body = 3;
}

(2)执行 make proto

文件位置:​​cartApi/handler/cartApi.go​

package handler

import (
"context"
"encoding/json"
"errors"
"fmt"
cart "git.imooc.com/keegan/cart/proto/cart"
cartApi "git.imooc.com/keegan/cartApi/proto/cartApi"
"github.com/prometheus/common/log"
"strconv"
)

type CartApi struct{
CartService cart.CartService
}

// CartApi.Call 通过API向外暴露为/cartApi/findAll,接收http请求
// 即:/cartApi/call请求会调用go.micro.api.cartApi 服务的CartApi.Call方法
func (e *CartApi) FindAll(ctx context.Context,req *cartApi.Request,rsp *cartApi.Response) error {
log.Info("接受到 /cartApi/findAll 访问请求")
if _,ok := req.Get["user_id"]; !ok {
//rsp.StatusCode= 500
return errors.New("参数异常")
}
userIdString := req.Get["user_id"].Values[0]
fmt.Println(userIdString)
userId,err := strconv.ParseInt(userIdString,10,64)
if err != nil {
return err
}
//获取购物车所有商品
cartAll,err := e.CartService.GetAll(context.TODO(),&cart.CartFindAll{UserId:userId})
//数据类型转化
b,err := json.Marshal(cartAll)
if err != nil {
return err
}
rsp.StatusCode =200
rsp.Body = string(b)
return nil
}

6.13 购物车API层 添加负载均衡

(1)编写main.go相关配置信息

文件位置:​​cartApi/main.go​

package main

import (
"context"
"fmt"
go_micro_service_cart "git.imooc.com/keegan/cart/proto/cart"
"git.imooc.com/keegan/cartApi/handler"
"git.imooc.com/keegan/common"
"github.com/afex/hystrix-go/hystrix"
"github.com/micro/go-micro/v2"
"github.com/micro/go-micro/v2/client"
log "github.com/micro/go-micro/v2/logger"
"github.com/micro/go-micro/v2/registry"
consul2 "github.com/micro/go-plugins/registry/consul/v2"
"github.com/micro/go-plugins/wrapper/select/roundrobin/v2"
opentracing2 "github.com/micro/go-plugins/wrapper/trace/opentracing/v2"
"github.com/opentracing/opentracing-go"
"net"
"net/http"

cartApi "git.imooc.com/keegan/cartApi/proto/cartApi"
)

func main() {
//注册中心
consul := consul2.NewRegistry(func(options *registry.Options) {
options.Addrs = []string{
"127.0.0.1:8500",
}
})

//链路追踪
t,io,err := common.NewTracer("go.micro.api.cartApi","localhost:6831")
if err != nil {
log.Error(err)
}
defer io.Close()
opentracing.SetGlobalTracer(t)

//熔断器
hystrixStreamHandler := hystrix.NewStreamHandler()
hystrixStreamHandler.Start()
//启动端口
go func() {
err = http.ListenAndServe(net.JoinHostPort("0.0.0.0","9096"),hystrixStreamHandler)
if err !=nil {
log.Error(err)
}
}()


// New Service
service := micro.NewService(
micro.Name("go.micro.api.cartApi"),
micro.Version("latest"),
micro.Address("0.0.0.0:8086"),
//添加 consul 注册中心
micro.Registry(consul),
//添加链路追踪
micro.WrapClient(opentracing2.NewClientWrapper(opentracing.GlobalTracer())),
//添加熔断
micro.WrapClient(NewClientHystrixWrapper()),
//添加负载均衡
micro.WrapClient(roundrobin.NewClientWrapper()),
)

// Initialise service
service.Init()

cartService:=go_micro_service_cart.NewCartService("go.micro.service.cart",service.Client())

cartService.AddCart(context.TODO(),&go_micro_service_cart.CartInfo{

UserId: 3,
ProductId: 4,
SizeId: 5,
Num: 5,
})

// Register Handler
if err := cartApi.RegisterCartApiHandler(service.Server(), &handler.CartApi{CartService:cartService});err !=nil {
log.Error(err)
}

// Run service
if err := service.Run(); err != nil {
log.Fatal(err)
}
}

type clientWrapper struct {
client.Client
}

func (c *clientWrapper) Call(ctx context.Context, req client.Request, rsp interface{}, opts ...client.CallOption) error {
return hystrix.Do(req.Service()+"."+req.Endpoint(), func() error {
//run 正常执行
fmt.Println(req.Service()+"."+req.Endpoint())
return c.Client.Call(ctx,req,rsp,opts...)
}, func(err error) error {
fmt.Println(err)
return err
})
}

func NewClientHystrixWrapper() client.Wrapper {
return func(i client.Client) client.Client {
return &clientWrapper{i}
}
}

(2)执行go mod tidy 或者 goland中选择飘红点击 Sync按钮即可安装依赖包。

(3)推送cartApi代码到仓库

git init
git add .
git commit -m "first commit"
git remote add origin git_url
git push -u origin master

(4)

先到cart目录下执行:

go run main.go

再到cartApi目录下执行:

go run main.go

(5)

服务注册成功

第6关 熔断 限流 负载均衡_ooc_17

6.14 API 网关及熔断看板使用

(0)查看本机ip

第6关 熔断 限流 负载均衡_ooc_18

(1)启动网关

docker run --rm -p 8080:8080 -e ICODE=5CB0645D0EDFC5  cap1573/cap-micro --registry=consul --registry_address=192.168.0.123:8500 api --handler=api

给数据库的cart表加点料:

第6关 熔断 限流 负载均衡_1024程序员节_19

确保执行了以下2条命令:

docker pull cap1573/hystrix-dashboard
docker run -d -p 9002:9002 cap1573/hystrix-dashboard

(2)注册中心

第6关 熔断 限流 负载均衡_Go微服务_20

多说一点:【可忽略】

第6关 熔断 限流 负载均衡_熔断 限流 负载均衡_21

(3) 浏览器访问:http://192.168.0.123:9002/hystrix

第6关 熔断 限流 负载均衡_git_22

步骤4:浏览器访问:http://127.0.0.1:8080/cartApi/findAll?user_id=1

第6关 熔断 限流 负载均衡_1024程序员节_23

步骤5:查看熔断信息

第6关 熔断 限流 负载均衡_git_24

步骤6:

执行以下命令:

docker run --rm -p 8080:8080 -e ICODE=5CB0645D0EDFC5  cap1573/cap-micro --registry=consul --registry_address=192.168.0.123:8500 api --handler=api

第6关 熔断 限流 负载均衡_ooc_25

第6关 熔断 限流 负载均衡_ooc_26

6.15 总结

未完待续,敬请期待…



【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月08日 0

暂无评论

u6bdUTdLzeG0