【微服务】微服务治理:熔断、链路追踪

12 篇文章 1 订阅

1. 熔断

服务雪崩效应

  • 原因:由于延时或负载过高等导致请求积压,占用大量系统资源,服务器达到性能瓶颈,服务提供者不可用
  • 现象:上游服务故障导致下游服务瘫痪,出现连锁故障
  • 应对策略
    扩容
    控制流量
    熔断
    服务降级

熔断器像是一个保险丝。当我们依赖的服务出现问题时,可以及时容错。
一方面可以减少依赖服务对自身访问的依赖,防止出现雪崩效应;
另一方面降低请求频率以方便上游尽快恢复服务。

熔断与限流的区别

限速器(limiter)可以限制接口自身被调的频率

熔断器可监控所调用的服务的失败、超时情况,当依赖的上游服务失败过高时,熔断器开启,不再调用上游服务,转向降级策略,从而避免雪崩。

  • 它们最大的区别在于限流主要在server实现,而熔断主要在client实现

熔断器三种状态:

  • 关闭状态:服务正常,并维护一个失败率统计,当失败率达到阀值时,转到开启状态
  • 开启状态:服务异常,调用 fallback 函数,一段时间之后,进入半开启状态
  • 半开启装态:尝试恢复服务,失败率高于阀值,进入开启状态,低于阀值,进入关闭状态

熔断器golang实现

github.com/afex/hystrix-go,提供了 go 熔断器实现,使用上面也很方便,首先创建一个熔断器

hystrix.ConfigureCommand(
    "addservice", // 熔断器名字,可以用服务名称命名,一个名字对应一个熔断器,对应一份熔断策略
    hystrix.CommandConfig{
        Timeout:                100,  // 超时时间 100ms
        MaxConcurrentRequests:  2,    // 最大并发数,超过并发返回错误
        RequestVolumeThreshold: 4,    // 请求数量的阀值,用这些数量的请求来计算阀值,熔断器是否打开要满足这个条件;这里的设置表示至少有4个请求才进行ErrorPercentThreshold错误百分比计算
        ErrorPercentThreshold:  25,   // 错误率阀值,百分比。达到阀值,启动熔断
        SleepWindow:            1000, // 熔断尝试恢复时间,单位毫秒
    },
)
package main

import (
	"errors"
	"fmt"
	"math/rand"
	"time"

	"github.com/afex/hystrix-go/hystrix"
)

type Product struct {
	ID    int
	Title string
	Price int
}

func GetProduct() (Product, error) {
	r := rand.Intn(10)
	if r < 6 {
		time.Sleep(time.Second * 3)
	}
	return Product{
		ID:    1,
		Title: "radial",
		Price: 13,
	}, nil
}

func RecProduct() (Product, error) {
	return Product{
		ID:    100,
		Title: "radial_hks",
		Price: 13000,
	}, nil
}

// v1.0
func main_old() {
	rand.Seed(time.Now().UnixNano())
	for {
		p, _ := GetProduct()
		fmt.Println(p)
		time.Sleep(time.Second * 1)
	}
}

// v2.0
func main_() {
	rand.Seed(time.Now().UnixNano())
	for {
		err := hystrix.Do("get_product", func() error {
			p, _ := GetProduct()
			fmt.Println(p)
			return nil
		}, nil)
		if err != nil {
			fmt.Println(err)
		}
		time.Sleep(time.Second * 1)
	}
}

// v2.0
func main_2() {
	rand.Seed(time.Now().UnixNano())
	// config
	confidA := hystrix.CommandConfig{
		Timeout: 2000,
	}
	hystrix.ConfigureCommand("get_product", confidA)

	for {
		err := hystrix.Do("get_product",
			func() error {
				p, _ := GetProduct()
				fmt.Println(p)
				return nil
			},
			nil)
		if err != nil {
			fmt.Println(err)
		}
		time.Sleep(time.Second * 1)
	}
}

// v3.0
func main_fallback() {
	rand.Seed(time.Now().UnixNano())
	// config
	confidA := hystrix.CommandConfig{
		Timeout: 2000,
	}
	hystrix.ConfigureCommand("get_product", confidA)

	for {
		err := hystrix.Do("get_product",
			func() error {
				p, _ := GetProduct()
				fmt.Println(p)
				return nil
			}, func(e error) error {
				p, _ := RecProduct()
				fmt.Println(p)
				fmt.Println("降级用户")
				//return nil
				return errors.New("time out")
			})
		if err != nil {
			fmt.Println(err)
		}
		time.Sleep(time.Second * 1)
	}
}

// v4.0
func main() {
	rand.Seed(time.Now().UnixNano())
	// config
	confidA := hystrix.CommandConfig{
		Timeout:                2000,
		MaxConcurrentRequests:  5,
		RequestVolumeThreshold: 3,
		ErrorPercentThreshold:  20,
		SleepWindow:            int(time.Second * 15),
	}
	hystrix.ConfigureCommand("get_product", confidA)
	resChan := make(chan Product, 1)
	c, _, _ := hystrix.GetCircuit("get_product")
	for {
		errs := hystrix.Go("get_product",
			func() error {
				p, _ := GetProduct()
				resChan <- p
				return nil
			}, func(e error) error {
				p, _ := RecProduct()
				resChan <- p
				//return nil
				fmt.Println("降级用户")
				return errors.New("time out")
			})
		//if err != nil {
		//	fmt.Println(err)
		//}
		select {
		case product := <-resChan:
			fmt.Println(product)
		case err := <-errs:
			fmt.Println(err)
		}
		fmt.Println(c.IsOpen())
		time.Sleep(time.Second * 1)
	}
}

2. 链路追踪(Tracing)

快速启动Jaeger

Jaeger官方文档

为了快速上手, 官方提供了"All in One"的docker镜像, 启动Jaeger服务只需要一行代码:

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 \
    --restart=always \
    jaegertracing/all-in-one:1.15

现在用于测试的服务端就完成了, 你可以访问http://{host}:16686来访问JaegerUI, 它像这样:

通过Jaeger上报Go应用数据

通过Jaeger上报Go应用数据

func NewJaegerTracer(serviceName string) (opentracing.Tracer, io.Closer) {
   sender, _ := jaeger.NewUDPTransport("",0)
   tracer, closer:= jaeger.NewTracer(serviceName,
        jaeger.NewConstSampler(true),
        jaeger.NewRemoteReporter(sender))
    return tracer, closer
}

在这里插入图片描述
在上图可以看到api层一共花了4.03s, 然后其中调用其他服务: 'service-1’花了2.12s, 而service-1又调用了’service-2’花费了2.12s, 用这样的图示很容易就能排查到系统存在的问题. 在这里我只展示了时间, 如果需要追踪其他信息(如错误信息)也是可以实现的.

通过Grpc中间件使用

在单体程序中, 父子Span通过context关联, 而context是在内存中的, 显而易见这样的方法在垮应用的场景下是行不通的.

package main

import (
	"context"
	"time"

	"github.com/Allenxuxu/microservices/lib/tracer"
	"github.com/Allenxuxu/microservices/srv/hello/handler"
	example "github.com/Allenxuxu/microservices/srv/hello/proto/example"
	"github.com/Allenxuxu/microservices/srv/hello/subscriber"
	"github.com/micro/go-micro"
	"github.com/micro/go-micro/service/grpc"
	"github.com/micro/go-micro/util/log"
	ocplugin "github.com/micro/go-plugins/wrapper/trace/opentracing"
	opentracing "github.com/opentracing/opentracing-go"
)

func Handler(ctx context.Context, msg *example.Message) error {
	log.Log("Function Received message: ", msg.Say)
	return nil
}

func main() {
	// New Service
	t, io, err := tracer.NewTracer("go.micro.srv.hello", "")
	if err != nil {
		log.Fatal(err)
	}
	defer io.Close()
	opentracing.SetGlobalTracer(t)

	service := grpc.NewService(
		micro.Name("go.micro.srv.hello"),
		micro.WrapHandler(ocplugin.NewHandlerWrapper(t)),
		micro.RegisterTTL(time.Second*15),
		micro.RegisterInterval(time.Second*10),
		// micro.Version("latest"),
	)

	// Initialise service
	service.Init()

	// Register Handler
	example.RegisterExampleHandler(service.Server(), new(handler.Example))

	// // Register Struct as Subscriber
	// micro.RegisterSubscriber("go.micro.srv.hello", service.Server(), new(subscriber.Example))

	// Register Function as Subscriber
	micro.RegisterSubscriber("/test", service.Server(), subscriber.Handler)

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

package tracer

import (
	"io"
	"time"

	opentracing "github.com/opentracing/opentracing-go"
	jaeger "github.com/uber/jaeger-client-go"
	jaegercfg "github.com/uber/jaeger-client-go/config"
)

// NewTracer 创建一个jaeger Tracer
func NewTracer(servicename string, addr string) (opentracing.Tracer, io.Closer, error) {
	cfg := jaegercfg.Configuration{
		ServiceName: servicename,
		Sampler: &jaegercfg.SamplerConfig{
			Type:  jaeger.SamplerTypeConst,
			Param: 1,
		},
		Reporter: &jaegercfg.ReporterConfig{
			LogSpans:            true,
			BufferFlushInterval: 1 * time.Second,
		},
	}

	sender, err := jaeger.NewUDPTransport(addr, 0)
	if err != nil {
		return nil, nil, err
	}

	reporter := jaeger.NewRemoteReporter(sender)
	// Initialize tracer with a logger and a metrics factory
	tracer, closer, err := cfg.NewTracer(
		jaegercfg.Reporter(reporter),
	)

	return tracer, closer, err
}

有时候Jaeger上有数据, 有时候没有

由于客户端和Jaeger-Agent之间是通过UDP协议传输的, 所以如果测试服务器与Jager-Agent服务之间是外网网络环境, 则可能会导致丢包, 通常包越大越容易丢包.

解决办法是将Agent部署到本机, 不过在开发环境为了方便也可以将客户端配置使用Jaeger-Collector, 这时会使用HTTP协议发送Spans.

在这里插入图片描述

组件1 : Instrumentation + OpenTracing API

opentracing.SetGlobalTracer()  //设置全局的tracer
opentracing.StartSpan() //开始一个span
opentracing.GlobalTracer().Inject() //把span信息注入到http请求里面
opentracing.GlobalTracer().Extract()  //从http请求里面解析出上一个服务的span信息

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值