第六课 Go容器化微服务系统实战-性能监控能力完善
tags:
- GO
- 慕课网
categories:
- 监控系统
- Docker-compose
- promethues
第一节 监控系统介绍
1.1 promethues 基本介绍
- 它是一套开源的监控、报警、时间序列数据库的组合。
- 基本原理是通过HTTP协议周期性抓取被监控组件的状态
- 适合Docker、Kubernetes环境的监控系统
- promethues架构图
1.2 promethues重要组件
- Prometheus Server:用于收集和存储时间序列数据。
- Client Library:客户端库成相应的metrics并暴露给Prometheus server
- Push Gateway:主要用于短期的jobs
- Exporters:用于暴露已有的第三方服务的metrics 给Prometheus。比如:mysql、redis、mongo等
- Alertmanager:从 Prometheus server端接收到 alerts后,会进行去除重复,分组,并路由到对收的接受方式,发出报警。
1.3 promethues工作流程
- Prometheus server定期从配置好的jobs、exporters、Pushgateway中拉数据
- Prometheus server记录数据并且根据报警规则推送 alert 数据
- Alertmanager根据配置文件,对接收到的警报进行处理,发出告警。
- 在图形界面中,可视化采集数据
1.4 promethues相关概念
- promethue中的数据模型
- Prometheus中存储的数据为时间序列
- 格式上由metric的名字和一系列的标签(键值对)唯一标识组成
- 不同的标签则代表不同的时间序列
- metric(指标)类型
- Counter类型:一种累加的指标,如︰请求的个数,出现的错误数等
- Gauge类型:可以任意加减,如︰温度,运行的goroutines的个数
- Histogram类型:可以对观察结果采样,分组及统计,如∶柱状图
- Summary类型︰提供观测值的count和sum 功能,如:请求持续时间
- instance和jobs
- instance : 一个单独监控的目标,—般对应于一个进程。
- jobs:一组同种类型的instances (主要用于保证可扩展性和可靠性)
- grafana看板
- 拥有丰富dashboard和图表编辑的指标分析平台
- 拥有自己的权限管理和用户管理系统
- Grafana更适合用于数据可视化展示
第二节 订单领域开发环境
2.1 Docker-Compose安装使用
- 首先,使用Dockerfile定义应用程序的环境
- 其次,使用docker-compose.yml定义构成应用程序的服务
- 最后,执行docker-compose up命令来启动并运行整个应用程序
- 安装docker-compose
curl -L https://github.com/docker/compose/releases/download/1.26.2/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
chmod 777 /usr/local/bin/docker-compose
docker-compose --version
- docker-compose常用命令
# 后台运行
docker-compose up -d
# 列出项目中所有容器
docker-compose ps
# 停止和删除容器和网络
docker-compose down
- docker-compose.yml 例子。
#声明版本
version: "3"
#定义服务
services:
#服务名称,能够在同一个网络内按照名称访问
consul:
#说明采用的镜像地址
image: consul:1.6.0
#镜像对外映射的端口
ports:
- "8500:8500"
#服务名称,能够在同一个网络内按照名称访问
jaeger:
#说明采用的镜像地址
image: jaegertracing/all-in-one:latest
#镜像对外映射的端口
ports:
- "6831:6831/udp"
- "16686:16686"
#熔断看板
hystrix-dashboard:
#说明采用的镜像地址
image: mlabouardy/hystrix-dashboard
#镜像对外映射的端口
ports:
- "9002:9002"
#熔断看板
prometheus:
#说明采用的镜像地址
image: prom/prometheus
#把外部yml文件挂载到容器中
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
ports:
- "9090:9090"
#熔断看板
grafana:
#说明采用的镜像地址
image: grafana/grafana
#镜像对外映射的端口
ports:
- "3000:3000"
2.2 订单领域开发目录生成
- 用上节课方法自动生成我们的目录文件。
sudo docker pull micro/micro
sudo docker run --rm -v $(pwd):$(pwd) -w $(pwd) micro/micro new order
- 新建domain文件夹,并在domain中创建model、repository、service三个文件夹。
- 删除go.mod,重新通过下面命令生成。
go mod init git.imooc.com/qnhyn/cart
go mod tidy
- 新建common文件夹。
2.3 订单领域proto开发
- proto下order文件夹下order.proto编写。
- 生成go文件。
# 生成之后看下product.pb.micro.go 用到的版本 主要要是v2(v3会在编译时报错)
protoc *.proto --gofast_out=. --micro_out=.
- 不写条件可以为空如:AllOrderRequest
syntax = "proto3";
package go.micro.service.order;
service Order {
rpc GetOrderByID(OrderID) returns (OrderInfo){}
rpc GetAllOrder(AllOrderRequest) returns (AllOrder){}
rpc CreateOrder(OrderInfo) returns (OrderID){}
rpc DeleteOrderByID(OrderID) returns (Response){}
rpc UpdateOrderPayStatus(PayStatus) returns (Response){}
rpc UpdateOrderShipStatus(ShipStatus) returns (Response){}
rpc UpdateOrder(OrderInfo) returns (Response){}
}
message AllOrderRequest {
}
message AllOrder{
repeated OrderInfo order_info = 1;
}
message OrderID {
int64 order_id = 1;
}
message OrderInfo{
int64 id = 1;
int32 pay_status =2;
int32 ship_status =3;
double price =4;
repeated OrderDetail order_detail =5;
}
message OrderDetail {
int64 id = 1;
int64 product_id =2;
int64 product_num =3;
int64 product_size_id =4;
int64 product_price =5;
int64 order_id =6;
}
message Response{
string msg = 1;
}
message PayStatus{
int64 order_id =1;
int32 pay_status =2;
}
message ShipStatus{
int64 order_id =1;
int32 ship_status =2;
}
第三节 订单领域代码开发
3.1 订单领域domain开发-model
- domain下model下order.go.
package model
import "time"
type Order struct{
ID int64 `gorm:"primary_key;not_null;auto_increment",json:"id"`
OrderCode string `gorm:"unique_index;not_null",json:"order_code"`
PayStatus int32 `json:"pay_status"`
ShipStatus int32 `json:"ship_status"`
Price float64 `json:"price"`
OrderDetail []OrderDetail `gorm:"ForeignKey:OrderID" json:"order_detail"`
CreateAt time.Time
UpdateAt time.Time
}
- domain下model下order_detail.go
package model
type OrderDetail struct {
ID int64 `grom:"primary_key;not_null;auto_increment",json:"id"`
ProductID int64 `json:"product_id"`
ProductNum int64 `json:"product_num"`
ProductSizeID int64 `json:"product_size_id"`
ProductPrice float64 `json:"product_price"`
OrderID int64 `json:"order_id"`
}
3.2 订单领域domain开发-repository
- domain下repository下order_repository.go
package repository
import (
"errors"
"github.com/jinzhu/gorm"
"git.imooc.com/qnhyn/order/domain/model"
)
type IOrderRepository interface{
InitTable() error
FindOrderByID(int64) (*model.Order, error)
CreateOrder(*model.Order) (int64, error)
DeleteOrderByID(int64) error
UpdateOrder(*model.Order) error
FindAll()([]model.Order,error)
UpdateShipStatus(int64,int32) error
UpdatePayStatus(int64,int32) error
}
//创建orderRepository
func NewOrderRepository(db *gorm.DB) IOrderRepository {
return &OrderRepository{mysqlDb:db}
}
type OrderRepository struct {
mysqlDb *gorm.DB
}
//初始化表
func (u *OrderRepository)InitTable() error {
return u.mysqlDb.CreateTable(&model.Order{},&model.OrderDetail{}).Error
}
//根据ID查找Order信息
func (u *OrderRepository)FindOrderByID(orderID int64) (order *model.Order,err error) {
order = &model.Order{}
return order, u.mysqlDb.Preload("OrderDetail").First(order,orderID).Error
}
//创建Order信息
func (u *OrderRepository) CreateOrder(order *model.Order) (int64, error) {
return order.ID, u.mysqlDb.Create(order).Error
}
//根据ID删除Order信息
func (u *OrderRepository) DeleteOrderByID(orderID int64) error {
tx := u.mysqlDb.Begin()
//遇到错误回滚
defer func() {
if r:=recover();r!=nil {
tx.Rollback()
}
}()
if tx.Error !=nil {
return tx.Error
}
//彻底删除 Order 信息
if err:= tx.Unscoped().Where("id = ?",orderID).Delete(&model.Order{}).Error;err!=nil{
tx.Rollback()
return err
}
//彻底删除 OrderDetail 信息
if err:=tx.Unscoped().Where("order_id = ?",orderID).Delete(&model.OrderDetail{}).Error;err!=nil{
tx.Rollback()
return err
}
return tx.Commit().Error
}
//更新Order信息
func (u *OrderRepository) UpdateOrder(order *model.Order) error {
return u.mysqlDb.Model(order).Update(order).Error
}
//获取结果集
func (u *OrderRepository) FindAll()(orderAll []model.Order,err error) {
return orderAll, u.mysqlDb.Preload("OrderDetail").Find(&orderAll).Error
}
//更新订单的发货状态
func (u *OrderRepository) UpdateShipStatus(orderID int64,shipStatus int32) error {
db:=u.mysqlDb.Model(&model.Order{}).Where("id = ?",orderID).UpdateColumn("ship_status",shipStatus)
if db.Error !=nil{
return db.Error
}
if db.RowsAffected == 0 {
return errors.New("更新失败")
}
return nil
}
//更新订单的支付状态
func (u *OrderRepository) UpdatePayStatus(orderID int64,payStatus int32) error {
db:=u.mysqlDb.Model(&model.Order{}).Where("id = ?",orderID).UpdateColumn("pay_status",payStatus)
if db.Error !=nil{
return db.Error
}
if db.RowsAffected == 0 {
return errors.New("更新失败")
}
return nil
}
3.3 订单领域domain开发-service
- domain下service下order_data_service.go
package service
import (
"git.imooc.com/qnhyn/order/domain/model"
"git.imooc.com/qnhyn/order/domain/repository"
)
type IOrderDataService interface {
AddOrder(*model.Order) (int64, error)
DeleteOrder(int64) error
UpdateOrder(*model.Order) error
FindOrderByID(int64) (*model.Order, error)
FindAllOrder() ([]model.Order, error)
UpdateShipStatus(int64, int32) error
UpdatePayStatus(int64, int32) error
}
//创建
func NewOrderDataService(orderRepository repository.IOrderRepository) IOrderDataService {
return &OrderDataService{orderRepository}
}
type OrderDataService struct {
OrderRepository repository.IOrderRepository
}
//插入
func (u *OrderDataService) AddOrder(order *model.Order) (int64, error) {
return u.OrderRepository.CreateOrder(order)
}
//删除
func (u *OrderDataService) DeleteOrder(orderID int64) error {
return u.OrderRepository.DeleteOrderByID(orderID)
}
//更新
func (u *OrderDataService) UpdateOrder(order *model.Order) error {
return u.OrderRepository.UpdateOrder(order)
}
//查找
func (u *OrderDataService) FindOrderByID(orderID int64) (*model.Order, error) {
return u.OrderRepository.FindOrderByID(orderID)
}
//查找
func (u *OrderDataService) FindAllOrder() ([]model.Order, error) {
return u.OrderRepository.FindAll()
}
func (u *OrderDataService) UpdateShipStatus(orderID int64, shipStatus int32) error {
return u.OrderRepository.UpdateShipStatus(orderID,shipStatus)
}
func (u *OrderDataService) UpdatePayStatus(orderID int64, payStatus int32) error {
return u.OrderRepository.UpdatePayStatus(orderID,payStatus)
}
3.4 订单领域Handle开发
- 开发暴露服务的Handle下order.go
package handler
import (
"context"
"git.imooc.com/qnhyn/common"
"git.imooc.com/qnhyn/order/domain/model"
"git.imooc.com/qnhyn/order/domain/service"
. "git.imooc.com/qnhyn/order/proto/order"
)
type Order struct{
OrderDataService service.IOrderDataService
}
//根据订单ID查询订单
func (o *Order) GetOrderByID(ctx context.Context,request *OrderID,response *OrderInfo) error {
order,err:=o.OrderDataService.FindOrderByID(request.OrderId)
if err !=nil {
return err
}
if err:= common.SwapTo(order,response);err !=nil {
return err
}
return nil
}
//查找所有订单
func (o *Order) GetAllOrder(ctx context.Context, request *AllOrderRequest, response *AllOrder) error {
orderAll,err := o.OrderDataService.FindAllOrder()
if err !=nil {
return err
}
for _,v:=range orderAll{
order := &OrderInfo{}
if err:=common.SwapTo(v,order);err!=nil {
return err
}
response.OrderInfo = append(response.OrderInfo, order)
}
return nil
}
//创建订单
func (o *Order) CreateOrder(ctx context.Context,request *OrderInfo,response *OrderID) error {
orderAdd := &model.Order{}
if err:=common.SwapTo(request,orderAdd);err!=nil{
return err
}
orderID,err:=o.OrderDataService.AddOrder(orderAdd)
if err !=nil {
return err
}
response.OrderId = orderID
return nil
}
//删除订单
func (o *Order) DeleteOrderByID(ctx context.Context, request *OrderID, response *Response) error {
if err:=o.OrderDataService.DeleteOrder(request.OrderId);err!=nil {
return err
}
response.Msg = "删除成功"
return nil
}
//更新订单支付状态
func (o *Order) UpdateOrderPayStatus(ctx context.Context, request *PayStatus,response *Response) error {
if err:=o.OrderDataService.UpdatePayStatus(request.OrderId, request.PayStatus);err!=nil {
return err
}
response.Msg = "支付状态更新成功"
return nil
}
//更新发货状态
func (o *Order) UpdateOrderShipStatus(ctx context.Context, request *ShipStatus, response *Response) error {
if err:=o.OrderDataService.UpdateShipStatus(request.OrderId,request.ShipStatus);err!=nil{
return err
}
response.Msg = "发货状态更新成功"
return nil
}
//更新订单状态
func (o *Order) UpdateOrder(ctx context.Context, request *OrderInfo, response *Response) error {
order := &model.Order{}
if err:=common.SwapTo(request,order);err!=nil{
return err
}
if err:=o.OrderDataService.UpdateOrder(order);err!=nil {
return err
}
response.Msg = "订单更新成功"
return nil
}
第四节 main.go添加监控
4.1 prometheus工具函数
- prometheus.go
package common
import (
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/prometheus/common/log"
"net/http"
"strconv"
)
func PrometheusBoot(port int) {
http.Handle("/metrics",promhttp.Handler())
//启动web 服务
go func() {
err := http.ListenAndServe("0.0.0.0:"+strconv.Itoa(port),nil)
if err !=nil {
log.Fatal("启动失败")
}
log.Info("监控启动,端口为:"+strconv.Itoa(port))
}()
}
- 更新工具包
go clean -i git.imooc.com/qnhyn/common
go get git.imooc.com/qnhyn/common
4.2 main函数编写
- main.go
package main
import (
"git.imooc.com/qnhyn/common"
"git.imooc.com/qnhyn/order/domain/repository"
service2 "git.imooc.com/qnhyn/order/domain/service"
"git.imooc.com/qnhyn/order/handler"
order "git.imooc.com/qnhyn/order/proto/order"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/mysql"
"github.com/micro/go-micro/v2"
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/monitoring/prometheus/v2"
ratelimit "github.com/micro/go-plugins/wrapper/ratelimiter/uber/v2"
opentracing2 "github.com/micro/go-plugins/wrapper/trace/opentracing/v2"
"github.com/opentracing/opentracing-go"
)
var (
//qps = os.Getenv("QPS")
QPS = 1000
)
func main() {
// 1.配置中心
consulConfig,err:=common.GetConsulConfig("localhost",8500,"/micro/config")
if err !=nil {
log.Error(err)
}
// 2.注册中心
consul :=consul2.NewRegistry(func(options *registry.Options) {
options.Addrs = []string{
"localhost:8500",
}
})
// 3.jaeger 链路追踪
t,io,err := common.NewTracer("go.micro.service.order","localhost:6831")
if err !=nil {
log.Error(err)
}
defer io.Close()
opentracing.SetGlobalTracer(t)
// 4.初始化数据库
mysqlInfo := common.GetMysqlFromConsul(consulConfig,"mysql")
db,err:= gorm.Open("mysql",mysqlInfo.User+":"+mysqlInfo.Pwd+"@/"+mysqlInfo.Database+"?charset=utf8&parseTime=True&loc=Local")
if err !=nil {
log.Error(err)
}
defer db.Close()
//禁止副表
db.SingularTable(true)
//第一次运行的时候创建表
//tableInit := repository.NewOrderRepository(db)
//tableInit.InitTable()
//创建实例
orderDataService := service2.NewOrderDataService(repository.NewOrderRepository(db))
// 5.暴露监控地址
common.PrometheusBoot(9092)
// New Service
service := micro.NewService(
micro.Name("go.micro.service.order"),
micro.Version("latest"),
//暴露的服务地址
micro.Address(":9085"),
//添加consul 注册中心
micro.Registry(consul),
//添加链路追踪
micro.WrapHandler(opentracing2.NewHandlerWrapper(opentracing.GlobalTracer())),
//添加限流
micro.WrapHandler(ratelimit.NewHandlerWrapper(QPS)),
//添加监控
micro.WrapHandler(prometheus.NewHandlerWrapper()),
)
// Initialise service
service.Init()
// Register Handler
order.RegisterOrderHandler(service.Server(), &handler.Order{OrderDataService:orderDataService})
// Run service
if err := service.Run(); err != nil {
log.Fatal(err)
}
}
4.3 配置数据库信息
- http://192.168.242.142:8500/ 配置 /micro/config/mysql
{
"host":"127.0.0.1",
"user":"root",
"pwd":"123456",
"database":"order",
"port":3306
}
- 运行程序,如果报错/clientv3时出错
go mod edit -require=google.golang.org/grpc@v1.26.0
go get -u -x google.golang.org/grpc@v1.26.0
4.4 配置promethues和grafana
- docker-compose.yml 因为其他组件都已经启动了所以这里只启动需要的。
- prometheus.yml 挂载配置文件到/etc/prometheus/prometheus.yml
#声明版本
version: "3"
#定义服务
services:
#熔断看板
prometheus:
#说明采用的镜像地址
image: prom/prometheus
#把外部yml文件挂载到容器中
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
ports:
- "9090:9090"
#熔断看板
grafana:
#说明采用的镜像地址
image: grafana/grafana
#镜像对外映射的端口
ports:
- "3000:3000"
- prometheus.yml
- 设置监控地址 这个就是我们代码中暴露的监控端口
- 注意:本章采用配置文件记录监控目标,也可以采用配置中心的方式
global:
scrape_interval: 15s # 默认每15秒采集一次
external_labels:
monitor: 'go-micro-monitor'
scrape_configs:
#监控的服务
- job_name: 'order' #名称
scrape_interval: 5s #覆盖默认值,设置5秒一次
static_configs:
- targets: ['192.168.242.142:9092'] #设置监控地址
- 启动promethues和grafana
# 启动
docker-compose up -d
# 特别注意docker-compose 中down命令会清除数据
# 看下监控 选择target 点进去metric可以看到一些参数
http://192.168.242.142:9090/
# grafana看板 admin admin
http://192.168.242.142:3000/
4. 添加数据源,并测试一下
5. 简单看板配置