go 进阶 go-zero相关: 三. go-zero 微服务基础示例

目录

  • 一. go-zero 微服务基础
    • 安装 ETCD
      • 1. docker 安装运行etcd
      • 2. windows 安装 etcd
  • 二. go-zero使用goctl命令创建一个普通的服务
  • 三. go-zero使用goctl命令创建一个rpc服务
    • 1. api结构介绍
    • 2. rpc服务端搭建示例
      • /logic下编写业务接口
      • /server下将业务接口封装到rpcServer
      • 介绍main()入口函数
      • 调用rpc服务接口示例
    • 3. rpc客户端搭建示例
      • 配置文件示例
      • 解析配置文件的Config结构体示例
      • ServerContext 服务运行上下文增加rpc客户端相关逻辑
      • main()函数 与中间件
  • 四. go-zero RPC支持http
  • 五. 一些问题
    • 1. http请求自动解析校验
    • 2. 缓存
    • 3. 高可用
      • 级联超时控制
      • 重试
    • 4. 负载均衡
    • 5. 可观测性
    • 6. go-stash
    • 7. 基于JWT实现自动鉴权
    • 8. 分布式事物
    • 9. 其它
    • MapReduce
      • go-zero提供的MapReduce
    • Graceful shutdown
    • 并发控制工具
    • 资源控制工具
    • 分布式高可用延迟任务框架
    • 极简Kafka Pub/Sub框架
    • go-stash框架
    • 不只是微服务,集成了很多常用的工具集
  • go 学习路线

一. go-zero 微服务基础

  1. 在上一个文档中,我们创建了一个简单的go-zero服务示例, go-zero 是一个集成了各种工程实践的包含 web 和 rpc 框架,有如下主要特点
  1. 强大的工具支持,尽可能少的代码编写
  2. 极简的接口
  3. 完全兼容 net/http
  4. 支持中间件,方便扩展
  5. 高性能
  6. 面向故障编程,弹性设计
  7. 内建服务发现、负载均衡
  8. 内建限流、熔断、降载,且自动触发,自动恢复
  9. API 参数自动校验
  10. 超时级联控制
  11. 自动缓存控制
  12. 链路跟踪、统计报警等
  13. 高并发支撑,稳定保障了晓黑板疫情期间每天的流量洪峰
  1. 接下来根据go-zero的特点一个一个的学习
  2. 一个基于go-zero构建的物联网云平台ithings
  3. 学习博客
  4. go-zero学习博客与项目示例

安装 ETCD

1. docker 安装运行etcd

  1. 此处通过docker安装
docker run -d --name Etcd-server --network app-tier --publish 2379:2379 --publish 2380:2380 --env ALLOW_NONE_AUTHENTICATION=yes --env ETCD_ADVERTISE_CLIENT_URLS=http://etcd-server:2379 bitnami/etcd:latest
  1. 但是windows版本docker执行上方命令时可能会报"Error response from daemon: network xxxx not found"异常,因为在windows docker下自定义的网络类型在计算机重启之后会被注销,重启之后找不到了
  2. 此处手动点击docker工具运行etcd,可能需要设置ALLOW_NONE_AUTHENTICATION=yes环境变量

2. windows 安装 etcd

  1. 选择指定版本,下载etcd安装包github下载地址, 解压后的目录如下:(其中etcd.exe是服务端,etcdctl.exe是客户端,如果不需要特殊处理双击etcd.exe打开即可)
  2. etcd默认使用2379端口
  3. etcd启动如果需要自定义参数的话,需要指定etcd.conf配置文件,并且配置文件的内容需要是json格式,例如配置监听的端口{“listen-client-urls”:“http://localhost:12379”}
  4. 设置etcd使用指定配置文件启动运行: “etcd.exe --config-file etcd.conf”
  5. cmd控制台的几个命令
//查看当前安装版本校验是否安装启动成功
etcdctl --version
//设置API version的版本设为3(官方建议,并且3和2命令和功能方面有不少的差别)
set ETCDCTL_API=3
//通过put存储(下方表示存储了一个key为hello,值为world,存储成功显示ok)
etcdctl put hello world
//get取值
etcdctl get hello

二. go-zero使用goctl命令创建一个普通的服务

  1. 前置条件
  1. 安装配置golang开发环境
  2. 安装goctl
  1. 参考上一个文档,创建Project, 切换到根目录下,执行goctl命令创建服务,当前创建了一个user服务模块
goctl api new 服务名称
  1. 服务生成完毕后,编写"服务模块名.api"文件,切换到该模块下,执行命令通过".api"文件生成接口及该接口对应的业务函数
goctl api go -api user.api -dir . -style go_zero
  1. 命令执行完毕后最终生成以下目录结构文件, 在生成的业务函数中编写业务代码(生成时业务函数只有方法结果,内部需要自己实现)
    go 进阶 go-zero相关: 三. go-zero 微服务基础示例_第1张图片
  2. 查看etc/xxx-api.yaml配置文件中配置的该服务访问地址,端口号
  3. 启动服务,发起访问

三. go-zero使用goctl命令创建一个rpc服务

  1. 上方使用goctl命令创建了user服务,接下来演示基于goct命令创建rpc服务
  2. 创建project,或者在当前服务中创建rpc文件夹,在该文件夹下创建并编写用来生成rpc服务的".proto"文件,下方示例创建的"user.proto"表示:
  1. 创建一个user.proto文件,基于这个文件生成rpc服务
  2. 会生成一个名为User的rpcService
  3. 这个rpc服务中存在一个rpc接口Auth()
  4. 并且定义了Auth()接口的入参结构UserAuthReq, 反参结构UserAuthResp
syntax = "proto3";

package template;

//指定文件生成到哪个目录
option go_package = "./user";

//1.编写rpc服务接口,定义了一个名为User的rpcService
service User{
  //内部存在一个rpc接口,接口名为Auth()
  //入参类型为: UserAuthReq
  //反参类型为: UserAuthResp
  rpc Auth(UserAuthReq) returns (UserAuthResp);
}

message UserAuthReq{
  string token = 1;
}

message UserAuthResp{
  string userName = 1;
  string password = 2;
  map<string, string> extend = 3;
}
  1. 注意: 使用".proto"时需要安装相关插件,或执行下方指令,一键安装protoc​,​protoc-gen-go​,​protoc-gen-grpc-go
$ goctl env check -i -f
  1. 切换到".proto"文件所在目录,执行编译命令
goctl rpc protoc 文件名.proto --go_out=./types --go-grpc_out=./types --zrpc_out=. --style go_zero

1. api结构介绍

  1. 编译命令执行完毕后,会生成一下代码:
    go 进阶 go-zero相关: 三. go-zero 微服务基础示例_第2张图片
  2. 其中 types/XXX/xxx.pb.go文件与types/XXX/xxx_grpc.pb.go是通过proto生成的,参考grpc
  1. “xxx.pb.go”: 内部是编写这个rpc服务时自定义的结构体相关处理
  2. “xxx_grpc.pb.go”: 内部是启动这个rpc服务端,启动访问这个rpc客户端的相关代码
  1. 通过go-zero生成的
  1. 其中user.proto是用来生成rpc服务的IDL文件
  2. etc文件夹下的user.yaml是当前rpc服务运行时读取的配置文件(需要在内部配置etcd连接…)
  3. internal/config/config.go是用来解析配置文件的结构体
  4. internal/logic/auth_logic.go内部是对应Auth()这个接口的业务处理方法(业务方法内部需要自己编写)
  5. internal/svc/service_context.go内部是当前rpc服务运行时的ServiceContext上下文
  6. internal/server/user_server.go内部是用来配置启动当前rpc服务端相关的代码,用来启动当前rpc服务使用的
  7. xxxxClient/xxx.go(当前对应userClient/user.go)内部是用来配置访问当前这个rpc服务的客户端相关代码
  1. 注意点: 基于proto生成的"xxx_grpc.pb.go"文件中有启动这个rpc服务端,和启动访问这个rpc服务的客户端的相关代码,但是联合go-zero时,通过go-zero又针对rpc服务端生成了一个"internal/server/xxx_server.go"文件, 针对rpc客户端生成了一个"xxxClient/xxx.go"文件,实际使用的是这两个文件,通过这两个文件去调用proto生成的"xxx_grpc.pb.go"

2. rpc服务端搭建示例

/logic下编写业务接口

  1. 通过api结构介绍我们了解到,使用goctl+proto生成rpc服务时与普通服务相同:
  1. 会针对业务接口生成一个"internal/logic/xxx_logic.go"文件,
  2. 该文件中定义了业务结构体,该结构体上实现了业务方法,但是方法内部为空具体的业务逻辑需要自己实现,
  3. 并且提供了初始化这个结构体的函数
  4. 例如当前创建的rpc服务中存在一个Auth()接口,生成了auth_logic.go文件,查看这个文件
import (
	"context"
	"errors"
	"github.com/zeromicro/go-zero/core/logx"
	"go_cloud_demo/rpc/internal/svc"
	"go_cloud_demo/rpc/types/user"
)

// 业务结构体
type AuthLogic struct {
	ctx    context.Context
	svcCtx *svc.ServiceContext
	logx.Logger
}

// 初始化结构体函数
func NewAuthLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AuthLogic {
	return &AuthLogic{
		ctx:    ctx,
		svcCtx: svcCtx,
		Logger: logx.WithContext(ctx),
	}
}

// 业务方法
func (l *AuthLogic) Auth(in *user.UserAuthReq) (*user.UserAuthResp, error) {
	if len(in.Token) == 0 {
		return nil, errors.New("为获取到token")
	}
	return &user.UserAuthResp{
		UserName: "admin",
		Password: "12345",
	}, nil
}

/server下将业务接口封装到rpcServer

  1. 上方针对业务接口提供了业务结构体,并且提供了初始化这个业务结构体的函数, 此时需要将这个业务接口添加到当前的rpc服务中
  2. 在/server下生成了对应当前rpc服务的"xxx_server.go"文件,该文件内
  1. 针对当前服务创建了一个结构体
  2. 该结构体上实现了当前rpc服务对外的所有接口
  3. 每个接口内部会调用"/logic"中对应的初始化函数,初始化实际的业务结构体,通过这个结构体执行实际的业务接口
import (
	"context"

	"go_cloud_demo/rpc/internal/logic"
	"go_cloud_demo/rpc/internal/svc"
	"go_cloud_demo/rpc/types/user"
)

// 服务结构体(一个服务中只有一个)
type UserServer struct {
	svcCtx *svc.ServiceContext
	user.UnimplementedUserServer
}

// 初始化服务结构体
func NewUserServer(svcCtx *svc.ServiceContext) *UserServer {
	return &UserServer{
		svcCtx: svcCtx,
	}
}

// 实现Auth()业务方法
func (s *UserServer) Auth(ctx context.Context, in *user.UserAuthReq) (*user.UserAuthResp, error) {
	//内部会调用初始化logic下实现了实际业务方法结构体函数,
	//例如当前调用logic下NewAuthLogic(),
	//初始化获取实现了Auth()业务接口的AuthLogic结构体
	l := logic.NewAuthLogic(ctx, s.svcCtx)
	//调用实际的业务接口
	return l.Auth(in)
}

// 实现Test业务方法
func (s *UserServer) Test(ctx context.Context, in *user.UserAuthReq) (*user.UserAuthResp, error) {
	l := logic.NewTestLogic(ctx, s.svcCtx)
	return l.Test(in)
}
  1. 这个文件是go-zero与proto不同的地方,go-zero中对proto进行了封装在/server下又提供了一个"xxx_server.go"文件

介绍main()入口函数

  1. 在main方法中执行的大致流程:
  1. 读取读取配置文件,将配置文件解析到Config结构体
  2. 调用NewServiceContext()函数,封装服务执行上下文
  3. 执行通过proto生成的"xxx_grpc_pb.go"文件中的RegisterXXXXServer()函数,进行服务注册
  4. 执行zrpc下的MustNewServer() 创建rpc服务
package main

import (
	"flag"
	"fmt"

	"go_cloud_demo/rpc/internal/config"
	"go_cloud_demo/rpc/internal/server"
	"go_cloud_demo/rpc/internal/svc"
	"go_cloud_demo/rpc/types/user"

	"github.com/zeromicro/go-zero/core/conf"
	"github.com/zeromicro/go-zero/core/service"
	"github.com/zeromicro/go-zero/zrpc"
	"google.golang.org/grpc"
	"google.golang.org/grpc/reflection"
)

// 命令行参数读取配置文件所在路径
var configFile = flag.String("f", "rpc/etc/user.yaml", "the config file")

func main() {
	flag.Parse()
	//1.读取配置文件解析到Config结构体上
	var c config.Config
	conf.MustLoad(*configFile, &c)
	//2.创房服务运行上下文
	ctx := svc.NewServiceContext(c)

	//3.将服务注册到rpc服务器,并且监听指定端口启动服务
	//参数一"c.RpcServerConf":保存了当前rpc服务配置信息
	//参数二"func(grpcServer *grpc.Server)"一个函数,当执行该函数时
	//会调用通过proto生成的RegisterXXXServer(),将当前rpc服务实现注册到rpc服务器
	s := zrpc.MustNewServer(c.RpcServerConf,
		func(grpcServer *grpc.Server) {
			user.RegisterUserServer(grpcServer, server.NewUserServer(ctx))

			if c.Mode == service.DevMode || c.Mode == service.TestMode {
				reflection.Register(grpcServer)
			}
		})
	defer s.Stop()

	fmt.Printf("Starting rpc server at %s...\n", c.ListenOn)
	s.Start()
}
  1. 注意如果要启动这个rpc服务,需要启动etcd,并且在当前服务的配置文件中配置连接etcd,"/etc/xxx.yaml"配置文件示例
Name: user.rpc
ListenOn: 0.0.0.0:8080 #当前rpc服务访问地址
Etcd:
  Hosts:
  - 127.0.0.1:2379
  Key: user.rpc
  1. 启动服务后会读取配置文件信息解析设置到"internal/config/config.go"中的Config结构体上
package config

import (
	"github.com/zeromicro/go-zero/rest"
	"github.com/zeromicro/go-zero/zrpc"
)

type Config struct {
	rest.RestConf
	zrpc.RpcClientConf
}

调用rpc服务接口示例

package main

import (
	"context"
	"github.com/zeromicro/go-zero/core/discov"
	"github.com/zeromicro/go-zero/zrpc"
	"go_cloud_demo/rpc/types/user"
	"log"
)

func main2() {
	//1.创建zrpc客户端
	client := zrpc.MustNewClient(zrpc.RpcClientConf{
		Etcd: discov.EtcdConf{
			//etcd连接地址
			Hosts: []string{"127.0.0.1:2379"},
			//目标服务key
			Key: "user.rpc",
		},
	})

	//2.获取连接
	conn := client.Conn()

	//3.使用proto生成的模板文件,创建服务client客户端
	UserClient := user.NewUserClient(conn)
	//4.调用指定接口接收响应
	resp, err := UserClient.Auth(context.Background(), &user.UserAuthReq{Token: "数据"})
	if err != nil {
		log.Fatal(err)
	}
	log.Println(resp.Password)
}

3. rpc客户端搭建示例

  1. 我们还是通过goctl命令创建一个普通的go-zero服务,需求是当访问该服务中的接口时,会将请求转发到上面rpc服务的Auth()接口上
  2. 实现流程:
  1. 配置etcd地址,在etcd上获取rpc目标服务访问地址
  2. 解析配置文件的Config上添加保存rpc信息的属性
  3. 服务运行的ServiceContext上下文结构体上添加请求rpc服务所需要的上线文属性
  4. 查看上方根据go-zero针对rpc客户端生成的"/xxxclient/xxx.go"文件中的代码,编写rpc客户端
  5. 当前服务的所有请求都需要先执行一下目标服务的Auth()接口,将访问rpc服务的Auth()抽取为中间件
  6. 启动服务,访问接口进行测试

配置文件示例

Name: user-api
Host: 0.0.0.0
Port: 8888
#要与目标rpc服务端依依对应
Etcd:
  Hosts:
    - 127.0.0.1:2379
  Key: user.rpc

解析配置文件的Config结构体示例

  1. “internal/config/config.go”
package config

import (
	"github.com/zeromicro/go-zero/rest"
	"github.com/zeromicro/go-zero/zrpc"
)

type Config struct {
	rest.RestConf
	//该属性保存了访问rpc服务端所需要的信息
	zrpc.RpcClientConf
}

ServerContext 服务运行上下文增加rpc客户端相关逻辑

  1. “/internal/sc/service_context.go”
package svc

import (
	"github.com/zeromicro/go-zero/zrpc"
	"go_cloud_demo/rpc/types/user"
	"go_cloud_demo/rpc/userclient"
	"go_cloud_demo/user/internal/config"
)

type ServiceContext struct {
	Config       config.Config
	//用来创建rpc客户端的结构体
	RpcUser      userclient.User
	//访问rpc服务接口返回的数据
	UserAuthResp *user.UserAuthResp
}

func NewServiceContext(c config.Config) *ServiceContext {
	return &ServiceContext{
		Config:  c,
		//添加初始化rpc客户端逻辑
		RpcUser: userclient.NewUser(zrpc.MustNewClient(c.RpcClientConf)),
	}
}

main()函数 与中间件

package main

import (
	"context"
	"flag"
	"fmt"
	"go_cloud_demo/rpc/types/user"
	"net/http"

	"go_cloud_demo/user/internal/config"
	"go_cloud_demo/user/internal/handler"
	"go_cloud_demo/user/internal/svc"

	"github.com/zeromicro/go-zero/core/conf"
	"github.com/zeromicro/go-zero/rest"
)

// 命令行参数,配置文件地址
var configFile = flag.String("f", "user/etc/user-api.yaml", "the config file")

func main() {
	flag.Parse()
	//1.读取配置文件解析到Config结构体上
	var c config.Config
	conf.MustLoad(*configFile, &c)

	//2.创建服务运行上下文
	ctx := svc.NewServiceContext(c)
	server := rest.MustNewServer(c.RestConf)
	defer server.Stop()

	//3.编写访问rpc服务中Auth()接口的中间件函数
	//并注册中间件
	//意思是当前服务接收到请求后会先执行这个中间件函数,
	//中间件内的逻辑:获取请求头中的"token"如果不存在直接响应异常
	//如果存在执行ctx.RpcUser.Auth()请求rpc服务中的Auth()
	//获取响应结果,如果是error直接返回,如果不是则next()向下执行
	server.Use(func(next http.HandlerFunc) http.HandlerFunc {
		return func(w http.ResponseWriter, r *http.Request) {

			if r.Header.Get("token") == "" {
				w.WriteHeader(http.StatusUnauthorized)
				w.Write([]byte("Unauthorized"))
				return
			}
			auth, err := ctx.RpcUser.Auth(context.Background(), &user.UserAuthReq{Token: r.Header.Get("token")})
			if err != nil {
				w.WriteHeader(http.StatusUnauthorized)
				w.Write([]byte("Unauthorized"))
				return
			}
			ctx.UserAuthResp = auth
			next(w, r)
		}
	})

	//4.注册当前服务自己的业务接口
	handler.RegisterHandlers(server, ctx)

	//5.监听端口启动服务
	fmt.Printf("Starting server at %s:%d...\n", c.Host, c.Port)
	server.Start()
}

四. go-zero RPC支持http

  1. 上方示例中专门提供了一个http客户端服务来调用指定的rpc服务端,如何让一个rpc服务直接支持http
  2. 参考博客

五. 一些问题

  1. 为什么使用go-zero,或者go-zero有什么优点: go-zero是一个集成了各种工程实践的包含web和rpc框架, 有如下主要特点:
  1. 微服务: 完全兼容 net/http,并且支持grpc微服务,内建服务治理逻辑,提供了服务发现,负载均衡等功能
  2. 高可用: 内建限流、熔断、降载,且自动触发,自动恢复
  3. 内建超时级联控制,自动缓存控制,链路跟踪、统计报警等
  4. 使用go-stash优化logstash,提高日志收集性能
  5. 高性能,强大的工具支持,内部整合了大量的高并发处理工具,例如MapReduce,fx数据流处理工具等等
  6. 支持中间件,方便扩展
  7. 极简的接口,基于goctl命令生成,保证代码风格
  1. 注意点: go-zero 大部分组件是自研,比如 sqlx,httpx 等,满足 CRDU 的操作绰绰有余,但是与 gorm、gin 等专攻某一方向的开源项目相比还是有非常大的差距,会发现该框架有这样或那样的不足。这种情况下就需要提 RP 或自己 fork 一份魔改了。个人觉得这种情况比 Spring 或 Django 那样一个“全家桶” 改动起来要省力省心。

1. http请求自动解析校验

go 进阶 go-zero相关: 三. go-zero 微服务基础示例_第3张图片

2. 缓存

  1. 包括进程内缓存, 还有redis等三方缓存工具
    go 进阶 go-zero相关: 三. go-zero 微服务基础示例_第4张图片

3. 高可用

  1. 进程内限流, 控制并发请求数,
  2. 基于redis lua脚本令牌桶或漏桶方式实现微服务限流
  3. 基于google sre算法基于滑动窗口实现熔断机制,支持fallack降级
  4. 支持自适应分级降载(基于滑动窗口防止毛刺),以k8s举例
  1. cpu使用率到达80%触发k8s的HPA
  2. cpu使用率>90%时开始拒绝低优先级请求
  3. cpu使用率>95时开始拒绝高优先级请求
  4. 问题:k8s中的HAP是分钟级别的,当服务并发过高时没有触发k8s拒绝请求服务已经被打爆了

级联超时控制

重试

没有提供默认的重试,可以自己编写重试中间件

4. 负载均衡

  1. 基于etcd实现服务注册,服务发现

5. 可观测性

  1. 链路追踪
    go 进阶 go-zero相关: 三. go-zero 微服务基础示例_第5张图片
  2. 日志处理
    go 进阶 go-zero相关: 三. go-zero 微服务基础示例_第6张图片
  3. 监控报警
  4. 数据上报,例如上报的控制台服务, 上报到prometheus

6. go-stash

参考博客

7. 基于JWT实现自动鉴权

8. 分布式事物

参考博客

9. 其它

参考博客

MapReduce

  1. MapReduce是谷歌提出的一种并行可扩展的计算模型,主要用户海量离线数据的批处理,核心思想就是将一个大型的计算任务分治处理,并将计算结果进行合并
  2. 实现原理: 分两步
  1. 定义一个Map函数,通常情况下每个map中都存在结构,大小相同的初始化数据块,经过处理后将多个数据块转换为中间结果
  2. 定义一个reduce函数,通过Reduce节点接收中间数据,对中间数据进行合并,生成最终的处理结果
  1. MapReduce分治思想官方点的规范思路是:
  1. 先提供一个map函数,将一个大任务分割成多个小任务,处理获取到多个中间结果
  2. 然后提供一个reduce函数,聚合得到的多个中间结果,拿到最终的结果
  1. 换一种思路: map函数与reduce函数不要太限制它们的责任,在map中可以分割任务同时也可以处理任务,同样在reduce函数中可以聚合中间结果,在聚合中间结果前也可以处理任务,例如:
  1. 先提供一个map函数,将大任务分割为多个小任务,并且指定一个小任务需要几个reduce函数处理
  2. 然后提供一个reduce函数,在reduce函数中清洗数据进行指定梳理,处理完成后合并结果

go-zero提供的MapReduce

  1. go-zero提供了MapReduce处理工具在"github.com/zeromicro/go-zero/core/mr"中
  2. mr下提供了以下使用函数
//处理固定数量的依赖,返回error,有一个error立即返回
func Finish(fns ...func() error) error
//Finish方法功能类似,没有错误返回值
func FinishVoid(fns ...func())

func ForEach(generate GenerateFunc, mapper ForEachFunc, opts ...Option)
 
func MapReduce(generate GenerateFunc, mapper MapperFunc, reducer ReducerFunc, opts ...Option) (interface{}, error)

func MapReduceChan(source <-chan interface{}, mapper MapperFunc, reducer ReducerFunc,opts ...Option) (interface{}, error)

func MapReduceVoid(generate GenerateFunc, mapper MapperFunc, reducer VoidReducerFunc, opts ...Option) error
  1. 以mr.MapReduce()为例进行解释,在使用MapReduce时可能需要以下几个入参
//源码: 
//参数一generate: 用来生产数据的函数
//参数二mapper: 对generate生产的数据进行处理
//参数三reducer: 对mapper处理后的数据做聚合返回
func MapReduce(generate GenerateFunc, mapper MapperFunc, reducer ReducerFunc,
	opts ...Option) (interface{}, error) {
	panicChan := &onceChan{channel: make(chan interface{})}
	source := buildSource(generate, panicChan)
	return mapReduceWithPanicChan(source, panicChan, mapper, reducer, opts...)
}

//另外可能还有
//souece channel: 无缓冲的channel,用于 generte和mapper通信(generate生产的数据会写入source channel,mapper 则读取source channle的数据进行处理)
 
//collector channel: 有缓冲区的channe,缓冲区的长度是option.worker的数量(mapper处理完成后的数据写入collector channel,reduce读取collector数据进行处理)

//output channel: 无缓冲区channel,用于记录reducer处理后最终数据
  1. 使用示例1
func checkLegal(uids []int64) ([]int64, error) {
	r, err := mr.MapReduce(func(source chan<- interface{}) {
		// 这里是 generate | 将列表的下标值记录到 chan
		// 传入到 mappe
		for _, uid := range uids {
			source <- uid
		}
	}, func(item interface{}, writer mr.Writer, cancel func(error)) {
		// 这里是 mapper | 处理存入的 chan
		uid := item.(int64)
		ok, err := check(uid)
		if err != nil {
			cancel(err)
		}
		if ok {
			writer.Write(uid)
		}
	}, func(pipe <-chan interface{}, writer mr.Writer, cancel func(error)) {
		// 处理 reducer | 对 mapper 进行数据聚合
		var uids []int64
		for p := range pipe {
			uids = append(uids, p.(int64))
		}
		// 输出到结果
		writer.Write(uids)
	})
	if err != nil {
		log.Printf("check error: %v", err)
		return nil, err
	}
	// 对 MapReduce 结果进行转换 | 因为 MapReduce 返回的结果是 interface{} 跟咱们返回的Data类型不一致
	return r.([]int64), nil
}

func check(uid int64) (bool, error) {
	// do something check user legal
	return true, nil
}
  1. 使用示例2
func productDetail(uid, pid int64) (*ProductDetail, error) {
	var pd ProductDetail
	err := mr.Finish(func() (err error) {
		pd.User, err = userRpc.User(uid)
		return
	}, func() (err error) {
		pd.Store, err = storeRpc.Store(pid)
		return
	}, func() (err error) {
		pd.Order, err = orderRpc.Order(pid)
		return
	})

	if err != nil {
		log.Printf("product detail error: %v", err)
		return nil, err
	}

	return &pd, nil
}
  1. MapReduce使用注意事项
  1. mapper和reducer中都可以调用cancel,参数为error,调用后立即返回,返回结果为nil, error
  2. mapper中如果不调用writer.Write则item最终不会被reducer聚合
  3. reducer中如果不调用writer.Wirte则返回结果为nil, ErrReduceNoOutput
  4. reducer为单线程,所有mapper出来的结果在这里串行聚合

Graceful shutdown

k8s是延迟的,采用当前

并发控制工具

资源控制工具

比如多线程同时创建同一个数据库链接

分布式高可用延迟任务框架

极简Kafka Pub/Sub框架

go-stash框架

是logstash5倍的性能

不只是微服务,集成了很多常用的工具集

go 学习路线

  1. go 书籍推荐
    go 进阶 go-zero相关: 三. go-zero 微服务基础示例_第7张图片

  2. 入门
    go 进阶 go-zero相关: 三. go-zero 微服务基础示例_第8张图片

  3. go进阶
    go 进阶 go-zero相关: 三. go-zero 微服务基础示例_第9张图片

  4. 学习视频1
    go 进阶 go-zero相关: 三. go-zero 微服务基础示例_第10张图片

  5. 学习视频2
    go 进阶 go-zero相关: 三. go-zero 微服务基础示例_第11张图片

  6. 面试
    go 进阶 go-zero相关: 三. go-zero 微服务基础示例_第12张图片

你可能感兴趣的:(#,十四.,golang,微服务,java)