学东西 = 建立架构 + 建立索引。
架构就好比本专栏的一个个关卡,目的是压缩信息,使信息系统化,结构化,拒绝零散,
索引就好比本文的目录,方便查。——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,这就导致了一个连锁反应,也就是常说的服务雪崩效应。
如图:
解读信息:
服务调用是正常状态的时候,我们是可以正常调用服务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)的请求原理
解读信息:
(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)保存熔断器的所有状态,调用次数,失败次数,被拒绝次数等信息
如果用过熔断,那么就必须知道熔断的主要流程,状态,实现原理。
实现原理:
解读信息:
(1)一个桶,它有4个状态。
用颜色来标识就是:
a. 绿色是成功状态。
b. 橙色是是失败的次数。
c. 粉红色是超时的次数。
d. 红色是被拒绝的次数。
熔断记录的状态会一个一个像这样把它给记录起来。
比如说到第10秒的时候,它的记录状态见上图第10秒。
当超过10秒的时候,它第一个将会被销毁,后面会一个一个地往上添加,这就是技术的原理;
然后它有的时候是根据10秒内的一个平均值进行判断的,这个主要是它的实现方式。
再来看信息上传的方式:
解读信息:
信息上传就是通过断路器。
断路器执行完了以后,我们就可以上报信息,上报完信息以后,我们写入到熔断器里面,写入的这个过程就是我们一个一个的建立,就是每一秒建立一个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)的漏桶算法原理:
解读信息:
(1)左边
左边水龙头 进水比较快,但是在水桶的下面有个出水口,出水口 出水是比较慢的,也是非常匀速的。
当我们来的请求量非常大的时候,就相当于进流量的水非常的大,这里会有一个储水桶,意思就是说不管你的水流量有多大,我的出水的频率是固定的,保护了我的服务端处理的频率是固定的,这样就是起到对我们服务端进行保护的作用,这里的水桶起到了缓冲的作用。
(2)右边
我们再来抽象,抽象过来以后就是:
当客户端请求的时候,服务端接入我的请求,当我未限流的时候,请求流量全到我的限流控制桶里面来,接到了这些请求以后,然后流量控制对你进行限流,就是对每个请求进行限流,然后固定的请求,即被限流完的请求,如果满足我这个算法,就把该请求放过去,不满足就拦截放到限流控制里面。
还有个令牌算法,这里先不介绍。
6.3 负载均衡作用和原理
1.负载均衡作用
(1)提高系统的扩展性
字面意思就是提高我们系统的处理能力。
漏桶算法,下面的出水口的出水频率【处理速度】是固定的,当处理速度不满足我们的需求的时候,我们会把处理后端的频率的速度提高。
通过 提高 负载均衡的 系统的扩展性,来提高服务端 处理速度的效率。
(2)支持请求:HTTP,HTTPS,TCP,UDP请求
(3)主要算法:循环算法和随机算法,不指定默认使用的是随机算法
负载均衡可以作用于服务端,也可以作用于客户端,这里使用服务端作为演示。
2.负载均衡的架构
解读信息:
(1)
当我们正常的API请求过来的时候,请求完了以后走到了API这一块,接着会来到服务ServerA这一块,这是一个正常的请求链路。
当ServiceA压力特别大的时候,我们就会用到负载均衡。
负载均衡它的架构就是这样:
我们会把服务a经过负载均衡这种方式,把它横向扩展出服务a2和服务a3,当然里面的业务逻辑都是一样的,我们主要是把这个服务把它给扩展起来。【扩展就像热备份一样。】
如果访问到a1的时候,第一次访问到a1,通过随机算法,第二次可能就会访问到a2。
这样就把系统做到了横向的扩展。提高了服务端的处理能力。
如果我们所有的请求只走a1进行判断处理的话,一旦请求量过大的话,a1很可能抗压扛不住,为了提高服务端的处理能力,通过负载均衡的机制,下次请求来的时候,通过随机算法,可能是服务a2、服务a3来处理请求。这样a1的压力就会小很多。【分了一部分流量到b,c处理去了】
可以这样形象化理解:
把3个洗脚盆,在相同的位置打通,当水流量达到一定深度的时候,水会在3个洗脚盆之间流动,这样每个洗脚盆所承载的水流量就会小很多。【把负载均衡当做物理上的连通器的管子来理解】
以上就是负载均衡的架构原理。
它相对于熔断,限流,容易理解。
负载均衡主要是写在客户端这一块。
6.4 微服务API网关
解读信息:
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失败。一定要细心!!!
其他方法:
已尝试,未解决问题。
参考链接
好,可以往下开始做了。
步骤1:
步骤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
步骤4:
拷贝之前项目的common下的文件,结果如下:
接着执行:
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
中所有的方法,这是接下来要实现的方法。
文件位置: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,输出结果如下:
注册中心也有了cart服务:
6.12 购物车API层 添加熔断
准备工作看截图:
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.14 API 网关及熔断看板使用
(0)查看本机ip
(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表加点料:
确保执行了以下2条命令:
docker pull cap1573/hystrix-dashboard
docker run -d -p 9002:9002 cap1573/hystrix-dashboard
(2)注册中心
多说一点:【可忽略】
(3) 浏览器访问:http://192.168.0.123:9002/hystrix
步骤4:浏览器访问:http://127.0.0.1:8080/cartApi/findAll?user_id=1
步骤5:查看熔断信息
步骤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.15 总结
未完待续,敬请期待…