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

2023-11-16

目录

一. 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. 命令执行完毕后最终生成以下目录结构文件, 在生成的业务函数中编写业务代码(生成时业务函数只有方法结果,内部需要自己实现)
    在这里插入图片描述
  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. 编译命令执行完毕后,会生成一下代码:
    在这里插入图片描述
  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请求自动解析校验

在这里插入图片描述

2. 缓存

  1. 包括进程内缓存, 还有redis等三方缓存工具
    在这里插入图片描述

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. 链路追踪
    在这里插入图片描述
  2. 日志处理
    在这里插入图片描述
  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 书籍推荐
    在这里插入图片描述

  2. 入门
    在这里插入图片描述

  3. go进阶
    在这里插入图片描述

  4. 学习视频1
    在这里插入图片描述

  5. 学习视频2
    在这里插入图片描述

  6. 面试
    在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

  • 将 WAR 部署到 Tomcat(Spring Boot + Angular)

    我正在尝试使用以下命令部署 Spring Boot 应用程序WAR包装至Tomcat 10 应用程序已成功部署 但是 当我尝试访问端点时 它会导致404 未找到 战争文件 应用程序 war http localhost 8080 appli
  • java 拖放

    我尝试熟悉java中的拖放 但我发现的所有教程都是 让我生气 我想要的只是从 JList 包含在名为 UserPanel 的自制 JPanel 中 拖动 PublicUserLabel 并将其放入从 JTabbedPanel 继承的自制类中
  • Java:为什么.class文件中的方法类型包含返回类型,而不仅仅是签名?

    class 文件的常量池中有一个 NameAndType 结构 它用于动态绑定 该类可以 导出 的所有方法都被描述为 签名 返回类型 喜欢 getVector Ljava util Vector 当某些 jar 中方法的返回类型发生更改时
  • ZeroDateTimeBehavior=convertToNull 在使用 hibernate 的 jdbc url 中不起作用

    通过 extern 属性文件 url 指定如下 jdbc mariadb xxxxx 3306 xxxxx zeroDateTimeBehavior convertToNull 连接工作正常并且能够查询数据库 通过休眠 我创建了一个映射到带
  • Spring Data Jpa OneToMany 同时保存子实体和父实体?

    这是我的父实体 注意 为了简洁起见 删除了 getter setter lombok 注释 Entity public class Board Id GeneratedValue strategy GenerationType IDENTI
  • 传递自定义类型查询参数

    如何接受自定义类型查询参数 public String detail QueryParam request final MYRequest request 上面的行在启动服务器时出现错误 jersey server model ModelV
  • 使用 https 的 Web 服务身份验证给出错误

    我编写了一个简单的 Web 服务 并使用摘要和 HTTPS 身份验证来保护它 我已经使用 Java 中的 keytool 生成了我的证书 当我通过创建 war 文件在 Tomcat 中部署 Web 服务时 axis 的欢迎页面正确显示 但是
  • 在 Eclipse 中删除空块之前的新行

    我更喜欢奥尔曼式 http en wikipedia org wiki Brace style Allman style大括号 例如 if foo magical prancing unicorn stuff 而不是 if foo unma
  • JFrame 在连续运行代码时冻结

    我在使用时遇到问题JFrame 它会冻结 连续运行代码 下面是我的代码 点击时btnRun 我调用了该函数MainLoop ActionListener btnRun Click new ActionListener Override pu
  • 扩展多个类

    我知道 Java 不支持多重继承 因为不允许扩展多个类 我只是想知道我的问题是否有解决方法 我有一个名为CustomAction需要扩展两个抽象类 BaseAction and QuoteBaseAction 我无法更改这些抽象类中的任何一
  • 如何获取 JDBC 中 UPDATE 查询影响的所有行?

    我有一项任务需要使用更新记录PreparedStatement 一旦记录被更新 我们知道更新查询返回计数 即受影响的行数 但是 我想要的不是计数 而是受更新查询影响的行作为响应 或者至少是受影响的行的 id 值列表 这是我的更新查询 UPD
  • Spring Security 角色层次结构不适用于 Thymeleaf sec:authorize

    我正在使用 Spring Security 3 2 5 RELEASE 和 ThymeLeaf 2 1 4 RELEASE 我已经在安全上下文中定义了角色层次结构 在我的视图层中我正在使用sec authorize属性来定义菜单项 我希望看
  • 获取接收者的设备令牌以在 Firebase 中发送通知

    所以我正在学习如何使用 firebase 发送设备到设备的通知 我看到了这个answer https stackoverflow com a 42548586 5237289发送通知 看起来很简单 现在 我知道要获取发件人的令牌 它应该如下
  • 如果 Modelmapper 中的整个属性为空,如何排除它们

    ModelMapper 是否 http modelmapper org http modelmapper org 支持什么排除属性 如果该值为空 我刚刚找到了 PropertyMap 但这对我来说是一种限制 因为我必须描述我想要的特定属性
  • 如何更改 JAX-WS Web 服务的地址位置

    我们目前已经公开了具有以下 URL 的 JAX RPC Web 服务 http xx xx xx xx myservice MYGatewaySoapHttpPort wsdl http xx xx xx xx myservice MYGa
  • 如何在不使用 -cp 开关的情况下在 Groovy 中自动加载数据库 jar?

    我想简化调用 Oracle 数据库的 Groovy 脚本的执行 如何将 ojdbc jar 添加到默认类路径以便我可以运行 groovy RunScript groovy 代替 groovy cp ojdbc5 jar RunScript
  • 使用 Hibernate Envers 的复合表

    我有一个带有复合表的应用程序 其中包含一个额外的列 一切正常 直到我们添加 Hibernate Envers Audited org hibernate MappingException 无法读取 no pack response Resp
  • 亚马逊 Linux - 安装 openjdk-debuginfo?

    我试图使用jstack在 ec2 实例上amazon linux 所以我安装了openjdk devel包裹 sudo yum install java 1 7 0 openjdk devel x86 64 但是 jstack 引发了异常j
  • 假布尔值=真?

    我在一本书中找到了这段代码 并在 Netbeans 中执行了它 boolean b false if b true System out println true else System out println false 我只是不明白为什
  • FragmentMap + ActionBar 选项卡

    我一直在尝试插入一个MapView进入一个ActionBar Tab 但我什至无法解决问题 即使谷歌搜索 这是主要活动 Override public void onCreate Bundle savedInstanceState supe

随机推荐

  • 【分治算法】-1.金块问题:递归和分治策略

    例14 2 金块问题 有一个老板有一袋金块 每个月将有两名雇员会因其优异的表现分别被奖励一个金块 按规矩 排名第一的雇员将得到袋中最重的金块 排名第二的雇员将得到袋中最轻的金块 根据这种方式 除非有新的金块加入袋中 否则第一名雇员所得到的金
  • 了解搜索引擎技术

    百度 Google搜索引擎核心技术是怎么实现的 搜索引擎 搜索引擎 search engine 是指根据一定的策略 运用特定的计算机程序搜集互联网上的信息 在对信息进行组织和处理后 并将处理后的信息显示给用户 是为用户提供检索服务的系统 全
  • 源于老师的博客

    Javascript学习 老师的博客 JavaScript概述 ECMAScript和JavaScript的关系 1996年11月 JavaScript的创造者 Netscape公司 决定将JavaScript提交给国际标准化组织ECMA
  • VSCODE(十)C++语言特有设置

    C C 插件为C C 开发者提供了丰富的设置项 包括三个方面的设置 env 用户自定义的变量 可以通过类似
  • 人工神经网络建模过程,人工神经网络建模例子

    利用人工神经网络建立模型的步骤 人工神经网络有很多种 我只会最常用的BP神经网络 不同的网络有不同的结构和不同的学习算法 简单点说 人工神经网络就是一个函数 只是这个函数有别于一般的函数 它比普通的函数多了一个学习的过程 在学习的过程中 它
  • k8s优雅停服

    在应用程序的整个生命周期中 正在运行的 pod 会由于多种原因而终止 在某些情况下 Kubernetes 会因用户输入 例如更新或删除 Deployment 时 而终止 pod 在其他情况下 Kubernetes 需要释放给定节点上的资源时
  • freeswitch录音功能开启无法通话

    freeswitch录音问题 之前开启过代理模式 在dialPlan default xml中添加
  • 初学Android,图形图像之打砖块游戏(二十八)

    这个弹球游戏是没有砖块的打砖块游戏 简版 效果如下 package WangLi Graphics PinBall import java util Random import java util Timer import java uti
  • 操作系统实验七-内存页面置换算法的设计和主存储器空间的分配和回收

    个人博客地址 实验1 内存页面置换算法的设计 一 实验内容 实现最近最久未使用 LRU 置换算法 二 实验目的 LINUX中 为了提高内存利用率 提供了内外存进程对换机制 内存空间的分配和回收均以页为单位进行 一个进程只需将其一部分调入内存
  • 云管平台 — vRealize Suite

    原文地址 https blogs vmware com china 2017 11 08 E4 BA 91 E7 AE A1 E5 B9 B3 E5 8F B0 vrealize suite vRealize Suite 是 vRealiz
  • 取余运算的意义

    取余运算的意义一般是给一个数一个界定范围 就比如m n 100 就限定了m的的范围只能是0 100 更形象来说 我们可以把它想象成一个圆环 我们扩大n 就像当于在0 100这个圈内打转 我们再稍微扩展一下 n 0 while true n
  • C/C++ C++20 格式化库 std::format

    说明 文本格式化库提供 printf 函数族的安全且可扩展的替用品 有意使之补充既存的 C I O 流库并复用其基础设施 例如对用户定义类型重载的流插入运算符 头文件 include
  • npm ERR! missing script dev

    刚刚npm install之后执行npm run dev 出现的报错信息npm ERR missing script dev 1 一种可能时vue init webpack的时候多建了一层文件夹 然后运行的时候没有找到package jso
  • MySQL--udf提权

    udf提权 udf user defined function 即 用户自定义函数 是通过添加新函数 对MYSQL的功能进行扩充 如何获得udf文件 将文件放到哪才能让mysql承认这个函数 函数功能 为什么这东西能提权 自定义函数指令是直
  • Netty-UDP协议

    2019独角兽企业重金招聘Python工程师标准 gt gt gt 实现一个UDP应用关键的点 1 和tcp的不同 udp没有接收的说法 所以即使是接收端 也使用Bootstrap 2 指定channel为NioDatagramChanne
  • 汉字编码输入法综述

    2 汉字编码输入法综述 作者 戴石麟 sbxlm 126 com 本章打算分基础工作 理论研究和实用系统三个方面来对汉字编码输入技术的历史和现状进行综合评述 最后指出现有技术中存在的问题并预测今后技术的发展趋势 2 1基础工作 1974年8
  • java 导入自定义类

    eclipse导入很容易 昨天上课学了一下用记事本写java 导入自定义类 这就麻烦了 代码贴一下 方便操作 package tom jiafei public class SquareEquation double a b c doubl
  • 【SpringMVC】Jrebel 插件实现热部署与文件上传

    目录 一 JRebel 1 1 Jrebel介绍 1 2 Jrebel插件下载 1 3 Jrebel服务下载并启动 1 4 在线生成GUID 1 5 JRebel激活 1 6 相关设置 注意 二 文件上传 下载 2 1 导入pom依赖 2
  • MATLAB 拟合神经网络—— fitnet

    建立神经网络 语法 net fitnet hiddenSizes trainFcn hiddenSize 为隐藏层数 是一个行向量 分别表示从左到右的隐藏层神经元数 trainFcn 为训练函数 如下表所示 名称 函数 trainlm Le
  • go 进阶 go-zero相关: 三. go-zero 微服务基础示例

    目录 一 go zero 微服务基础 安装 ETCD 1 docker 安装运行etcd 2 windows 安装 etcd 二 go zero使用goctl命令创建一个普通的服务 三 go zero使用goctl命令创建一个rpc服务 1