第四课 Go容器化微服务系统实战-链路追踪观望台

第四课 Go容器化微服务系统实战-链路追踪观望台

tags:

  • GO
  • 慕课网

categories:

  • 链路追踪jaeger
  • 数据库外键
  • 数据库事务

第一节 链路追踪简介

1.1 链路追踪jaeger基本介绍

  1. 微服务链路追(jaeger)踪作用: 它是用来监视和诊断基于微服务的分布式系统
    • 用于服务依赖性分析,辅助性能优化
  2. 微服务链路追踪(jaeger)主要特性
    • 高扩展性
    • 原生支持OpenTracing
    • 可观察性

1.2 jaeger-术语Span

  1. Span是Jaeger 中的逻辑工作单元
  2. Span具有操作名称,操作的开始时间和持续时间
  3. 它的跨度可以嵌套并排序以建立因果关系模型
  4. 微服务链路追踪(jaeger)-术语Span包含的对象
    • Operation name:操作名称(也可以称作Span name )
    • Start timestamp:起始时间
    • Finish timestamp:结束时间
    • Span tag :一组键值对构成的Span标签集合
    • Span log :一组Span的日志集合
    • SpanContext: span 上下文对象
    • References ( Span间关系)︰相关的零个或者多个Span

1.3 jaeger的调用过程

在这里插入图片描述

  1. 一次调用链分析
    在这里插入图片描述

1.4 jaeger的组件

在这里插入图片描述

  1. Jaeger Client的调用原理。
    ![在这里插入图片描述](https://img-blog.csdnimg.cn/2021070523530863.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2FhMTg4NTU5NTMyMjk=,size_16,color_FFFFFF,t_70

  2. 微服务链路追踪(jaeger)的五个重要组件

    • Jaeger-client(客户端库, 不同语言有不同库)
    • Agent(客户端代理,是一个监听的守护程序。把client的数据传给Collector)
    • Collector (数据收集处理)
    • Data Store (数据存储)
    • UI(数据查询与前端界面展示)

1.5 jaeger安装和常用端口

  1. 推荐博客:https://www.cnblogs.com/jiujuan/p/13235748.html
# 获取镜像all-in-one:docker地址:https://hub.docker.com/r/jaegertracing/all-in-one
docker pull jaegertracing/all-in-one:latest
# 启动容器
docker run -d --name jaeger \
  -e COLLECTOR_ZIPKIN_HTTP_PORT=9411 \
  -p 5775:5775/udp \
  -p 6831:6831/udp \
  -p 6832:6832/udp \
  -p 5778:5778 \
  -p 16686:16686 \
  -p 14268:14268 \
  -p 9411:9411 \
  jaegertracing/all-in-one:latest

在这里插入图片描述

第二节 商品模块开发

2.1 商品模块目录建立

  1. 用上节课方法自动生成我们的目录文件
sudo docker pull micro/micro
sudo docker run --rm -v $(pwd):$(pwd) -w $(pwd) micro/micro new product
  1. 新建domain文件夹,并在domain中创建model、repository、service三个文件夹。
  2. 删除go.mod,重新通过下面命令生成。
go mod init git.imooc.com/qnhyn/product
go mod tidy
  1. 新建common文件夹。

2.2 商品模块代码proto开发

  1. 编写proto。把product.proto文件放到product文件夹下。
  2. 编译生成go文件。
# 生成之后看下product.pb.micro.go 用到的版本 主要要是v2(v3会在编译时报错)
protoc *.proto --gofast_out=. --micro_out=. 
syntax = "proto3";

package go.micro.service.product;

service Product {
	rpc AddProduct(ProductInfo) returns (ResponseProduct){}
	rpc FindProductByID(RequestID) returns (ProductInfo){}
	rpc UpdateProduct(ProductInfo) returns (Response) {}
	rpc DeleteProductByID(RequestID) returns (Response) {}
	rpc FindAllProduct(RequestAll) returns (AllProduct){}
}

message ProductInfo {
	int64 id = 1;
	string product_name = 2;
	string product_sku = 3;
	double product_price = 4;
	string product_description = 5;
	int64  product_category_id = 6;
	// repeated 结构体对象
	repeated ProductImage product_image = 7;
	// 商品的尺寸
	repeated ProductSize product_size = 8;
	// 搜索引擎优化的展示的信息
	ProductSeo product_seo = 9;
}

message ProductImage {
	int64 id = 1;
	string image_name = 2;
	// 保证插入数据的幂等性
	string image_code = 3;
	string image_url = 4;
}

message ProductSize{
	int64 id = 1;
	string size_name = 2;
	string size_code = 3;
}

message ProductSeo {
	int64 id = 1;
	string seo_title = 2;
	string seo_keywords = 3;
	string seo_description =4;
	string seo_code = 6;
}

message ResponseProduct {
	int64 product_id = 1;
}

message RequestID {
	int64 product_id = 1;
}

message Response {
	string msg = 1;
}

message RequestAll{

}

message AllProduct{
	repeated ProductInfo product_info =1;
}

2.3 商品模块domain的model开发

  1. product.go
package model

type Product struct{
	ID int64 `gorm:"primary_key;not_null;auto_increment" json:"id"`
	ProductName string `json:"product_name"`
	ProductSku string `gorm:"unique_index:not_null" json:"product_sku"`
	ProductPrice float64 `json:"product_price"`
	ProductDescription string `json:"product_description"`
	ProductImage []ProductImage `gorm:"ForeignKey:ImageProductID" json:"product_image"`
	ProductSize []ProductSize `gorm:"ForeignKey:SizeProductID" json:"product_size"`
	ProductSeo ProductSeo `gorm:"ForeignKey:SeoProductID" json:"product_seo"`
}
  1. product_image.go 字段ImageCode可以保证数据的幂等性。
package model

type ProductImage struct {
	ID int64 `gorm:"primary_key;not_null;auto_increment" json:"id"`
	ImageName string `json:"image_name"`
	ImageCode string `gorm:"unique_index;not_null" json:"image_code"`
	ImageUrl string `json:"image_url"`
	ImageProductID int64 `json:"image_product_id"`
}
  1. product_size.go
package model

type ProductSize struct {
	ID int64 `gorm:"primary_key;not_null;auto_increment" json:"id"`
	SizeName string `json:"size_name"`
	SizeCode string `gorm:"unique_index;not_null" json:"size_code"`
	SizeProductID int64 `json:"size_product_id"`
}
  1. product_seo.go
package model

type ProductSeo struct {
	ID int64 `gorm:"primary_key;not_null;auto_increment" json:"id"`
	SeoTitle string `json:"seo_title"`
	SeoKeywords string `json:"seo_keywords"`
	SeoDescription string `json:"seo_description"`
	SeoCode string `json:"seo_code"`
	SeoProductID int64 `json:"seo_product_id"`
}

2.4 商品模块domain的repository开发

  1. product_repository.go 和前面不同。
    • 这里初始化表,一下可以初始化四张表
    • 删除时有开启事务回滚
package repository

import (
	"git.imooc.com/qnhyn/product/domain/model"
	"github.com/jinzhu/gorm"
)
type IProductRepository interface{
	InitTable() error
	FindProductByID(int64) (*model.Product, error)
	CreateProduct(*model.Product) (int64, error)
	DeleteProductByID(int64) error
	UpdateProduct(*model.Product) error
	FindAll()([]model.Product,error)
}

// NewProductRepository 创建productRepository
func NewProductRepository(db *gorm.DB) IProductRepository  {
	return &ProductRepository{mysqlDb:db}
}

type ProductRepository struct {
	mysqlDb *gorm.DB
}

// InitTable 初始化表 同时初始化四张表
func (u *ProductRepository)InitTable() error  {
	return u.mysqlDb.CreateTable(&model.Product{},&model.ProductSeo{},&model.ProductImage{},&model.ProductSize{}).Error
}

// FindProductByID 根据ID查找Product信息
func (u *ProductRepository)FindProductByID(productID int64) (product *model.Product,err error) {
	product = &model.Product{}
	return product, u.mysqlDb.Preload("ProductImage").Preload("ProductSize").Preload("ProductSeo").First(product,productID).Error
}

// CreateProduct 创建Product信息
func (u *ProductRepository) CreateProduct(product *model.Product) (int64, error) {
	return product.ID, u.mysqlDb.Create(product).Error
}

// DeleteProductByID 根据ID删除Product信息
func (u *ProductRepository) DeleteProductByID(productID int64) error {
	//开启事务
	tx := u.mysqlDb.Begin()
	defer func() {
		if r:= recover();r !=nil {
			tx.Rollback()
		}
	}()

	if tx.Error !=nil {
		return tx.Error
	}

	//删除
	if err:=tx.Unscoped().Where("id = ?",productID).Delete(&model.Product{}).Error;err!=nil{
		tx.Rollback()
		return err
	}

	if err:=tx.Unscoped().Where("images_product_id = ?",productID).Delete(&model.ProductImage{}).Error;err!=nil{
		tx.Rollback()
		return err
	}

	if err:=tx.Unscoped().Where("size_product_id = ?", productID).Delete(&model.ProductSize{}).Error;err!=nil{
		tx.Rollback()
		return err
	}

	if err:=tx.Unscoped().Where("seo_product_id = ?",productID).Delete(&model.ProductSeo{}).Error;err!=nil{
		tx.Rollback()
		return err
	}

	return tx.Commit().Error
}

// UpdateProduct 更新Product信息
func (u *ProductRepository) UpdateProduct(product *model.Product) error {
	return u.mysqlDb.Model(product).Update(product).Error
}

// FindAll 获取结果集
func (u *ProductRepository) FindAll()(productAll []model.Product,err error) {
	return productAll, u.mysqlDb.Preload("ProductImage").Preload("ProductSize").Preload("ProductSeo").Find(&productAll).Error
}

2.5 商品模块domain的service开发

  1. product_data_service.go
package service

import (
	"git.imooc.com/qnhyn/product/domain/model"
	"git.imooc.com/qnhyn/product/domain/repository"
)

type IProductDataService interface {
	AddProduct(*model.Product) (int64 , error)
	DeleteProduct(int64) error
	UpdateProduct(*model.Product) error
	FindProductByID(int64) (*model.Product, error)
	FindAllProduct() ([]model.Product, error)
}

// NewProductDataService 创建
func NewProductDataService(productRepository repository.IProductRepository) IProductDataService{
	return &ProductDataService{ productRepository }
}

type ProductDataService struct {
	ProductRepository repository.IProductRepository
}

// AddProduct 插入
func (u *ProductDataService) AddProduct(product *model.Product) (int64 ,error) {
	return u.ProductRepository.CreateProduct(product)
}

// DeleteProduct 删除
func (u *ProductDataService) DeleteProduct(productID int64) error {
	return u.ProductRepository.DeleteProductByID(productID)
}

// UpdateProduct 更新
func (u *ProductDataService) UpdateProduct(product *model.Product) error {
	return u.ProductRepository.UpdateProduct(product)
}

// FindProductByID 查找
func (u *ProductDataService) FindProductByID(productID int64) (*model.Product, error) {
	return u.ProductRepository.FindProductByID(productID)
}

// FindAllProduct 查找
func (u *ProductDataService) FindAllProduct() ([]model.Product, error) {
	return u.ProductRepository.FindAll()
}

2.6 商品模块Handle开发

  1. product.go
  2. common/swap.go 这个模块后期会提出成一个服务。现在先和上个服务一样
package common

import (
	"encoding/json"
)

// SwapTo 通过json tag 进行结构体赋值
func SwapTo(request, category interface{}) (err error)  {
	dataByte, err := json.Marshal(request)
	if err != nil {
		return
	}
	err = json.Unmarshal(dataByte, category)
	return
}
  1. product.go业务逻辑如下:
package handler

import (
	"context"
	"fmt"
	"git.imooc.com/qnhyn/product/common"
	"git.imooc.com/qnhyn/product/domain/model"
	"git.imooc.com/qnhyn/product/domain/service"
	. "git.imooc.com/qnhyn/product/proto/product"
)
type Product struct{
	ProductDataService service.IProductDataService
}

// AddProduct 添加商品
func (h *Product) AddProduct(ctx context.Context, request *ProductInfo, response *ResponseProduct) error {
	productAdd := &model.Product{}
	fmt.Println(request)
	if err := common.SwapTo(request,productAdd);err!=nil{
		return err
	}
	fmt.Println(productAdd)
	productID,err:=h.ProductDataService.AddProduct(productAdd)
	if err!=nil{
		return err
	}
	response.ProductId = productID
	return nil
}

// FindProductByID 根据ID查找商品
func (h *Product) FindProductByID(ctx context.Context, request *RequestID, response *ProductInfo) error {
	productData,err := h.ProductDataService.FindProductByID(request.ProductId)
	if err !=nil {
		return err
	}
	if err := common.SwapTo(productData,response);err !=nil {
		return err
	}
	return nil
}

// UpdateProduct 商品更新
func (h *Product) UpdateProduct(ctx context.Context, request *ProductInfo, response *Response) error {
	productAdd := &model.Product{}
	if err := common.SwapTo(request,productAdd);err!=nil{
		return err
	}
	err := h.ProductDataService.UpdateProduct(productAdd)
	if err != nil {
		return err
	}
	response.Msg = "更新成功"
	return nil
}

// DeleteProductByID 根据ID删除对应商品
func (h *Product) DeleteProductByID(ctx context.Context, request *RequestID, response *Response) error {
	if err := h.ProductDataService.DeleteProduct(request.ProductId);err!=nil {
		return err
	}
	response.Msg = "删除成功"
	return nil
}

// FindAllProduct 查找所有商品
func (h *Product) FindAllProduct(ctx context.Context, request *RequestAll, response *AllProduct) error  {
	productAll,err := h.ProductDataService.FindAllProduct()
	if err !=nil {
		return err
	}

	for _,v := range productAll {
		productInfo := &ProductInfo{}
		err := common.SwapTo(v,productInfo)
		if err != nil {
			return err
		}
		response.ProductInfo = append(response.ProductInfo,productInfo)
	}
	return nil
}

2.7 main.go入口文件开发

  1. main.go

    • 第一步:配置中心
    • 第二步:注册中心
    • 第三步:链路追踪
    • 第四步:数据库设置和初始化
    • 第五步:服务初始化和运行
  2. 写一个链路追踪的创建common函数。jaeger.go

package common

import (
	"github.com/opentracing/opentracing-go"
	"github.com/uber/jaeger-client-go"
	"github.com/uber/jaeger-client-go/config"
	"io"
	"time"
)

// NewTracer 创建链路追踪实例
func NewTracer(serviceName string, addr string) (opentracing.Tracer, io.Closer, error) {
	cfg := &config.Configuration{
		ServiceName:serviceName,
		Sampler:&config.SamplerConfig{
			Type:                     jaeger.SamplerTypeConst,
			Param:                    1,
		},
		Reporter: &config.ReporterConfig{
			BufferFlushInterval:        1*time.Second,
			LogSpans:                   true,
			LocalAgentHostPort:         addr,
		},
	}
	return cfg.NewTracer()
}
  1. http://192.168.242.142:8500/ 编写数据库配置 /micro/config/mysql
{
  "host":"127.0.0.1",
  "user":"root",
  "pwd":"123456",
  "database":"product",
  "port":3306
}
  1. main.go的编写, 运行测试
package main

import (
	"git.imooc.com/qnhyn/product/common"
	"git.imooc.com/qnhyn/product/domain/repository"
	service2 "git.imooc.com/qnhyn/product/domain/service"
	"git.imooc.com/qnhyn/product/handler"
	"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"
	opentracing2 "github.com/micro/go-plugins/wrapper/trace/opentracing/v2"

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

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

	product "git.imooc.com/qnhyn/product/proto/product"
)

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.product","localhost:6831")
	if err !=nil {
		log.Fatal(err)
	}
	defer io.Close()
	opentracing.SetGlobalTracer(t)

	//数据库设置
	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)

	//初始化
	//repository.NewProductRepository(db).InitTable()

	productDataService := service2.NewProductDataService(repository.NewProductRepository(db))

	// 设置服务
	service := micro.NewService(
		micro.Name("go.micro.service.product"),
		micro.Version("latest"),
		micro.Address("127.0.0.1:8085"),
		//添加注册中心
		micro.Registry(consul),
		//绑定链路追踪
		micro.WrapHandler(opentracing2.NewHandlerWrapper(opentracing.GlobalTracer())),
	)

	// Initialise service
	service.Init()

	// Register Handler
	product.RegisterProductHandler(service.Server(), &handler.Product{ProductDataService:productDataService})

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

第三节 链路追踪效果

3.1 创建验证文件

  1. 创建一个客户端的server, 同样给它注册到注册中心。productClient.go。给服务端发起一个请求。
package main

import (
	"context"
	"fmt"
	"git.imooc.com/qnhyn/product/common"
	go_micro_service_product "git.imooc.com/qnhyn/product/proto/product"
	"github.com/micro/go-micro/v2"
	"github.com/micro/go-micro/v2/registry"
	consul2 "github.com/micro/go-plugins/registry/consul/v2"
	opentracing2 "github.com/micro/go-plugins/wrapper/trace/opentracing/v2"
	"github.com/opentracing/opentracing-go"
	"log"
)

func main()  {
	//注册中心
	consul := consul2.NewRegistry(func(options *registry.Options) {
		options.Addrs = []string{
			"127.0.0.1:8500",
		}
	})
	//链路追踪
	t,io,err:=common.NewTracer("go.micro.service.product.client","localhost:6831")
	if err !=nil {
		log.Fatal(err)
	}
	defer io.Close()
	opentracing.SetGlobalTracer(t)

	service := micro.NewService(
		micro.Name("go.micro.service.product.client"),
		micro.Version("latest"),
		micro.Address("127.0.0.1:8085"),
		//添加注册中心
		micro.Registry(consul),
		//绑定链路追踪 服务端绑定handle 客户端绑定client
		micro.WrapClient(opentracing2.NewClientWrapper(opentracing.GlobalTracer())),
	)
	
  // 访问服务器端服务	 
  productService:=go_micro_service_product.NewProductService("go.micro.service.product",service.Client())

	productAdd := &go_micro_service_product.ProductInfo{
		ProductName:        "imooc",
		ProductSku:         "cap",
		ProductPrice:       1.1,
		ProductDescription: "imooc-cap",
		ProductCategoryId:  1,
		ProductImage:       []*go_micro_service_product.ProductImage{
			{
				ImageName: "cap-image",
				ImageCode: "capimage01",
				ImageUrl:  "capimage01",
			},
			{
				ImageName: "cap-image02",
				ImageCode: "capimage02",
				ImageUrl:  "capimage02",
			},
		},
		ProductSize:        []*go_micro_service_product.ProductSize{
			{
				SizeName: "cap-size",
				SizeCode: "cap-size-code",
			},
		},
		ProductSeo:         &go_micro_service_product.ProductSeo{
			SeoTitle:       "cap-seo",
			SeoKeywords:    "cap-seo",
			SeoDescription: "cap-seo",
			SeoCode:        "cap-seo",
		},
	}
	response,err:=productService.AddProduct(context.TODO(),productAdd)
	if err !=nil {
		fmt.Println(err)
	}
	fmt.Println(response)
}

3.2 运行验证

  1. 先运行main。在运行两次客户端。刷新jaeger页面。看到两次信息。
  2. 第二次插入会报错重复问题。所以有错误。可以在jaeger上看报错日志。
    在这里插入图片描述

3.3 链路追踪小节

  1. 链路追踪数据写入的过程中可以加入 kafaka 缓冲压力
  2. 我们可以通过链路追踪开发现我们是否有循环调用
  3. 在链路中如非必要尽量避免带入异步场景的span,异步不确定啥时候返回。
  4. 其他应用场景和异步链路如何实现可以自己补充学习一下。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值