wwqdrh

简介

介绍go-zero之前先说明什么是微服务、为什么要用微服务、微服务有哪些缺点

微服务是比代码更高粒度设计模式思想的体现,将功能高度相关的集中在一起是高内聚的体现,通过服务的拆分来降低系统之间的耦合度是低耦合的体现

不仅是代码的可维护、可测试、可扩展重要,应用层面的这些特性同样重要

至于高内聚低耦合、服务分层有什么优点,写过代码的应该都清楚就不赘述

另外,两个要点

1、不要因为代码规模太小而拒绝良好的设计,设计与代码规模无关,只与应用是否可维护、可扩展有关 2、不要为了拆成微服务而拆,没有良好的服务划分,拆出来只会加大服务之间的混乱程度

微服务需要解决的问题

服务注册与发现

主要就是让服务之间知道某个服务在某个地址,以及做负载均衡等

日志收集

各个服务拆分之后日志保存会很分散,一个个的去服务上查看日志显然不合适,需要有日志上报集中查看的功能

链路追踪

多个服务之间的调用链条需要有可视化的查看机制

服务监控

及时查看服务是否运行正常

分布式事务

涉及到数据修改,为了保证数据一致性,避免其中某个服务出错而数据无法回滚到正确的版本

服务治理

自适应降级能非常智能的保护服务自身,根据服务自身的系统负载动态判断是否需要降级。保护自身不被调用方压垮

限流,防止突发流量压垮系统

熔断,发起服务调用的时候,如果被调用方返回的错误率超过一定的阈值,那么后续的请求将不会真正发起请求,而是在调用方直接返回错误

go-zero如何解决这些问题的

在讲go-zero如何解决上述微服务要点之前,先说说go-zero有什么优点

代码自动生成

包括rpc、api、model三部分,其中rpc、api是gateway层,rpc是基于zrpc(grpc的封装)框架的服务之间调用服务。api是给外部用户使用的

model层是模型层+缓存服务的自动生成(但是有个问题,现在只能生成mysql的,但是问题不大,数据层的代码本身就有很多逻辑要自己写,这部分可以照旧使用原来的技术栈)

  • goctl api go -dir . -api user.api
  • goctl rpc protoc --go_out=. --go-grpc_out=. --zrpc_out=. user.proto
  • goctl model mysql ddl -c -dir . -src user.sql

api文件格式

type (
    // 用户登录
    LoginRequest {
        Mobile   string `json:"mobile"`
        Password string `json:"password"`
    }
    LoginResponse {
        AccessToken  string `json:"accessToken"`
        AccessExpire int64  `json:"accessExpire"`
    }
    // 用户登录

    // 用户注册
    RegisterRequest {
        Name     string `json:"name"`
        Gender   int64  `json:"gender"`
        Mobile   string `json:"mobile"`
        Password string `json:"password"`
    }
    RegisterResponse {
        Id     int64  `json:"id"`
        Name   string `json:"name"`
        Gender int64  `json:"gender"`
        Mobile string `json:"mobile"`
    }
    // 用户注册

    // 用户信息
    UserInfoResponse {
        Id     int64  `json:"id"`
        Name   string `json:"name"`
        Gender int64  `json:"gender"`
        Mobile string `json:"mobile"`
    }
    // 用户信息
)

service User {
    @handler Login
    post /api/user/login (LoginRequest) returns (LoginResponse)
    
    @handler Register
    post /api/user/register (RegisterRequest) returns (RegisterResponse)
}

@server(
    jwt: Auth
)
service User {
    @handler UserInfo
    post /api/user/userinfo () returns (UserInfoResponse)
}

容器文件自动生成

创建Dockerfile, goctl docker -go hello.go

docker build -t .... 创建镜像

k8s服务文件自动生成

goctl kube deploy -name redis -namespace adhoc -image redis:6-alpine -o redis.yaml -port 6379

这条命令的含义就是在adhoc命名空间中创建Deployment类型的资源,名字叫做redis,使用的镜像为redis:6-alpine, containerPort为6379

接下来是go-zero中如何解决上文所说的问题

服务注册与发现

部署etcd(这里为了简单仅部署单节点)

docker pull bitnami/etcd:3.5.3

docker run -d -p 2379:2379 -p 2380:2380 \
-e ETCD_ROOT_PASSWORD=123456 \
--name etcd3 bitnami/etcd:3.5.3

go-zero中配置(配置文件也是由上文的goctl自动生成的,执行一遍就知道了)

// 注册者
Etcd:
  Hosts:
  - 127.0.0.1:2379
  Key: user.rpc
  User: root
  Pass: "123456"

其中Key就是该服务注册到etcd中的名字

调用者,首先修改配置以及修改go中的结构体

UserRpc:
  Etcd:
    Hosts:
      - 127.0.0.1:2379
    Key: user.rpc
    User: root
    Pass: "123456"
type Config struct {
    ...
    UserRpc zrpc.RpcClientConf
}

然后将服务初始化到上下文中

type ServiceContext struct {
    Config  config.Config
    UserRpc user.User
}

func NewServiceContext(c config.Config) *ServiceContext {
    return &ServiceContext{
        Config:  c,
        UserRpc: user.NewUser(zrpc.MustNewClient(c.UserRpc)),
    }
}

之后就可以直接使用UserRpc进行rpc调用

日志收集

部署EFK架构,当然如果只是为了将日志集中起来方便查看,可以暂时需要elasticsearch+kibana

部署fluentd

docker pull fluent/fluentd:v1.14-debian

docker run -d -p 24224:24224 -v ${conf}:/fluentd/etc/fluent.conf fluent/fluentd:v1.14-debian

示例配置文件

参考: https://docs.fluentd.org/configuration/config-file

<source>
  @type   forward
</source>
<match *>
  @type              file
  path               /fluentd/log/${tag}
  append             true
  <format>
    @type            single_value
    message_key      log
  </format>
  <buffer tag,time>
    @type             file
    timekey           1d
    timekey_wait      10m
    flush_mode        interval
    flush_interval    30s
  </buffer>
</match>

将上传到flutend的操作封装进基础logger库的可选项以及包装成go-zero可使用的模式

参考: github.com/wwqdrh/logger

import (
    mylogx "github.com/wwqdrh/logger/logx"
    "github.com/zeromicro/go-zero/core/conf"
    "github.com/zeromicro/go-zero/core/logx"
)

func init() {
    l := logger.NewLogger(
        logger.WithLevel(zapcore.WarnLevel),
        logger.WithLogPath("./logs/info.log"),
        logger.WithName("info"),
        logger.WithFluentd(true, "127.0.0.1", 24224),
        logger.WithConsole(false),
    )

    writer, _ := mylogx.NewZeroWriter(l)
    logx.Must(err)
    logx.SetWriter(writer)
}

之后使用logx.Infof就可以将日志上传至服务中, 所有的服务的日志就已经收集到了flutend容器中

链路追踪

go-zero已经封装了,仅需部署jaeger之后修改服务的配置项进行启用

部署jaeger

docker pull jaegertracing/all-in-one:1.34

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:1.34

修改配置

Telemetry:
  Name: user.api
  Endpoint: http://127.0.0.1:14268/api/traces
  Sampler: 1.0
  Batcher: jaeger

效果

服务监控

go-zero已经集成

部署prometheus

docker pull bitnami/prometheus:2.9.2

docker run --name prometheus \
-v ${CURDIR}"/env/prometheus/prometheus.yml":/opt/bitnami/prometheus/data \
-p 9090:9090 \
    bitnami/prometheus:2.9.2

进行配置

Prometheus:
  Host: 0.0.0.0
  Port: 9081
  Path: /metrics

修改prometheus监听的服务配置(上文的prometheus.yml)

# my global config
global:
  scrape_interval: 15s # Set the scrape interval to every 15 seconds. Default is every 1 minute. 
  evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute.     
  # scrape_timeout is set to the global default (10s).

# Alertmanager configuration
alerting:
  alertmanagers:
    - static_configs:
        - targets:
          # - alertmanager:9093

# Load rules once and periodically evaluate them according to the global 'evaluation_interval'.  
rule_files:
  # - "first_rules.yml"
  # - "second_rules.yml"

# A scrape configuration containing exactly one endpoint to scrape:
# Here it's Prometheus itself.
scrape_configs:
  # The job name is added as a label `job=<job_name>` to any timeseries scraped from this config.
  - job_name: "prometheus"

    # metrics_path defaults to '/metrics'
    # scheme defaults to 'http'.

    static_configs:
      - targets: ["localhost:9090"]

  # 我们自己的商城项目配置
  - job_name: 'mall'
    static_configs:
      # 目标的采集地址
      - targets: ['192.168.110.113:9081']
        labels:
          # 自定义标签
          app: 'user-api'
          env: 'test'

      - targets: ['192.168.110.113:9091']
        labels:
          app: 'user-rpc'
          env: 'test'

效果

分布式事务

使用dtm

比较大的部分,详细文档查看: https://go-zero.dev/cn/docs/eco/distributed-transaction

服务治理

熔断器使用文档: https://go-zero.dev/cn/docs/blog/governance/breaker-algorithms

自适应降级文档: https://go-zero.dev/cn/docs/blog/governance/loadshedding

限流文档: https://go-zero.dev/cn/docs/blog/governance/periodlimit