go-zero微服务实战——服务构建

news2024/11/15 4:52:30

目录介绍

接上一节go-zero微服务实战——基本环境搭建。搭建好了微服务的基本环境,开始构建整个微服务体系了,将其他服务也搭建起来。

order的目录结构,如下

在这里插入图片描述

  • 根目录
    • api服务
    • rpc服务
    • 自定义逻辑层logic
    • 自定义参数层models
    • 自定义工具层util

api服务和rpc服务都是基于goctl一键生成的,当然这是小编的目录,各位到也可以自定义目录结构,或者参考其他优秀的目录结构。go-zero官网也提供了官方的目录结构go-zero项目结构

  • api服务
    • config
    • handler
    • logic
    • svc
    • types

在这里插入图片描述

  • rpc服务
    • etc
    • intenel
    • rpcservice
    • rpcserviceclient

在这里插入图片描述

首先解释一个各个目录是干什么的,两个服务api和rpc是go-zero生成的,其内部目录都是对接服务本身的。logic和models,util是公共的部分。

**公共logic和服务内部的logic是不一样的,公共部分是公用的,例如返回订单列表,完成查询返回结果等,而服务的logic则是进一步对公共logic的私有化封装,主要表现是返回的数据不通用,对于api服务来说,logic最后返回结构体或结构体数组等数据即可,因为zero的api封装httpx对序列化,这些是框架完成的。但是对于rpc服务来说,好需要将这些数据转化为字符串的格式才可以传输,所以服务内部的logic就在于将公共logic数据转化为便于传输的格式。**其他目录就不再介绍了go-zero.dev官网上都有。

服务构建

前一节构建了order服务,本节将构建user和product服务器,项目和order基本一样。唯一的区别是user中存在一个登录即用户名认证过程,该过程需要从rpc客户端传递数据到rpc服务端。

user数据库表
在这里插入图片描述

公共logic代码

// 验证账户
func Ideatify(account string, pass string) error {
	var user models.User
	b, err := db.Engine.Where("username = ?", account).Get(&user)

	if err != nil {
		fmt.Printf("logic list err%v", err)
		return err
	} else if b && (err != nil) {
		return errors.New("用户不存在")
	} else if user.Password == pass {
		return nil
	} else {
		return errors.New("密码错误")
	}
}

api的handler函数

api服务部分,路由此处省略,挂载到/login下即可。

func UserIdentify() http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		var req models.User
		err := httpx.ParseJsonBody(r, &req)
		if err != nil {
			httpx.WriteJson(w, 500, fmt.Sprintf("err%v", err))
			return
		}
		err = orderlogic.Ideatify(req.Username, req.Password)
		if err != nil {
			//
			httpx.WriteJson(w, 500, map[string]string{"code": "200", "message": err.Error()})
			return
		}
		httpx.OkJson(w, map[string]string{"code": "200", "message": "登录成功"})
	}
}

rpc的logic重写封装部分

// 继承rpc服务器方法
func Identify(in *rpcservice.Request) (*rpcservice.Response, error) {
	reqstr := in.GetReqJson()
	var req models.User
	_ = json.Unmarshal([]byte(reqstr), &req)
	err := orderlogic.Ideatify(req.Username, req.Password)
	if err != nil {
		fmt.Printf("rpc err:%v", err)
		return &rpcservice.Response{ResJson: err.Error()}, err
	}
	//o 赚json字符串
	return &rpcservice.Response{ResJson: "true"}, nil
}

rpc服务方法重写(方法注册)

//继承
func (s *RpcserviceServer) List(ctx context.Context,in *rpcservice.Request) (*rpcservice.Response, error) {
	return logic.Identify(in)
}

客户端调用

注意修改端口,user端口改为9001。

import (
	"context"
	"fmt"
	"rpcclient/rpcservice"

	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
)

func main() {
	//配置连连接参数(无加密)
	dial, _ := grpc.Dial("localhost:9001", grpc.WithTransportCredentials(insecure.NewCredentials()))
	defer dial.Close()
	//创建客户端连接
	client := rpcservice.NewRpcserviceClient(dial)
	//通过客户端调用方法
	res, err := client.Ping(context.Background(), &rpcservice.Request{ReqJson: "xiaoxu"})
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println(res)

	//order list
	str := `{
		"id":0,
		"username":"xiaoxu",
		"password":"1234567",
		"status":0
	}`
	r, err := client.List(context.Background(), &rpcservice.Request{ReqJson: str})
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println(r.ResJson)

}

别忘了_grpc.pbpb两个文件。

错误返回
在这里插入图片描述
正确返回
在这里插入图片描述

传入数据通过&rpcservice.Request{ReqJson: "xiaoxu"}Request结构体完成的。在pb文件下,这个客户端和服务端共有的。

在这里插入图片描述

以此方法逐个完成其他方法的封装和注册,另外完成product服务的构建。三个服务端口不同注意修改,api为8000系,rpc为9000系列。

product的api服务
在这里插入图片描述
rpc客户端代码完全一样改一下端接口9002即可

在这里插入图片描述

rpc远程调用

到上一小结三个服务就构建完成了。服务之间是应该可以互相调用的,就像客户端调用服务端一样。在其他服务调用其本身就是客户端,被调用的服务就相当于服务端。

在api和rpc服务的目录下都有一个主程序,都启动即可。如下图所示,注意三个服务一种药开6个终端分别启动。

在这里插入图片描述

在三个独立的api服务和rpc服务中,各自都只能操作相应的数据库,但是涉及多表查询是就需要rpc远程调用了。

在goctl目录下,存在XXXclent目录该目录提供了构造client实例的代码。
在这里插入图片描述

// Code generated by goctl. DO NOT EDIT.
// Source: rpcservice.proto

package rpcserviceclient

import (
	"context"

	"demo/rpcservice/rpcservice"

	"github.com/zeromicro/go-zero/zrpc"
	"google.golang.org/grpc"
)

type (
	Request  = rpcservice.Request
	Response = rpcservice.Response

	Rpcservice interface {
		Ping(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error)
	}

	defaultRpcservice struct {
		cli zrpc.Client
	}
)

func NewRpcservice(cli zrpc.Client) Rpcservice {
	return &defaultRpcservice{
		cli: cli,
	}
}

func (m *defaultRpcservice) Ping(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) {
	client := rpcservice.NewRpcserviceClient(m.cli.Conn())
	return client.Ping(ctx, in, opts...)
}

对比上一章节自定义的客户端,如下:

package main
import (
	"context"
	"fmt"
	"rpcclient/rpcservice"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
)

func main() {
	//配置连连接参数(无加密)
	dial, _ := grpc.Dial("localhost:9002", grpc.WithTransportCredentials(insecure.NewCredentials()))
	defer dial.Close()
	//创建客户端连接
	client := rpcservice.NewRpcserviceClient(dial)
	//通过客户端调用方法
	res, err := client.Ping(context.Background(), &rpcservice.Request{ReqJson: "xiaoxu"})
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println(res)

	// //order list
	// str := `{
	// 	"id":0,
	// 	"username":"xiaoxu",
	// 	"password":"123456",
	// 	"status":0
	// }`
	r, err := client.List(context.Background(), &rpcservice.Request{})
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println(r.ResJson)

}

rpcclient/rpcservice包是存放_grpc.pb和pb文件的目录

创建rpc服务端的方法是来源于_grpc.pb的NewRpcserviceClient

func NewRpcserviceClient(cc grpc.ClientConnInterface) RpcserviceClient {
	return &rpcserviceClient{cc}
}

对比可以看出,都是使用该方法构建的客户端实例,唯一的不同在于,自定义的客户端时通过grpc.Dial返回客户端对象,但是官方提供的代码通过返回zrpc.Client(内置连接对象)。但是官方提供的并未配置端口的直接入口。

从参数入手,由于都是调用的NewRpcserviceClient方法,那么参数都是*grpc.ClientConn类型。

func (m *defaultRpcservice) Ping(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) {
	client := rpcservice.NewRpcserviceClient(m.cli.Conn())
	return client.Ping(ctx, in, opts...)
}

回到源码,看到Conn()方法指向如下图所示的结构体。

在这里插入图片描述
导航到该结构体的定义处,其是*grpc.ClientConn的一个实现类。

在这里插入图片描述
该实现类继承了Conn方法同时也扩展了另一个眼熟的方法dial如下,那么到这就知道该如何使用了吧。

在这里插入图片描述

直接调用dial方法配置端口,配置*grpc.ClientConn对象。注意这个方法和自定义的不一样,
自定义是直接调用grpc.Dial来自于grpc库,直接返回连接对象实例。而前者只是连接对象的一个配置端口和参数的方法。

// NewClient returns a Client.
func NewClient(target string, middlewares ClientMiddlewaresConf, opts ...ClientOption) (Client, error) {
	cli := client{
		middlewares: middlewares,
	}

	svcCfg := fmt.Sprintf(`{"loadBalancingPolicy":"%s"}`, p2c.Name)
	balancerOpt := WithDialOption(grpc.WithDefaultServiceConfig(svcCfg))
	opts = append([]ClientOption{balancerOpt}, opts...)
	if err := cli.dial(target, opts...); err != nil {
		return nil, err
	}

	return &cli, nil
}

上述源码来自zrpc提供了创建api构建zrpc.Client实例,作为官方提供的NewRpcservice方法的参数,于是请求的地址和端口就能配置了。如下:

得到的r就是_grpc.pb的RpcserviceClient对象,就可以实现方法调用了。

func GetRpcClientData() (string, error) {
	c, err := zrpc.NewClient(zrpc.RpcClientConf{
		Etcd: discov.EtcdConf{
			Hosts: []string{"127.0.0.1:9000"},
		},
	})
	if err != nil {
		return "", errors.New("rpc connect failed")
	}
	r := rpcserviceclient.NewRpcservice(c)
	r2, err := r.Ping(context.Background(), &rpcservice.Request{ReqJson: "xiaoxu"})
	if err != nil {
		return "", errors.New("rpc method anlyse failed")
	}
	return r2.ResJson, nil

}

上述方法使用了微服务的服务注册,下一章节讲,因此需要将服务再注册到服务中心中。到此函数已经注册两次了,第一次是继承服务器函数(服务器注册函数),第二次是客户端使用服务注册时将函数注册到服务中心。

上述代码构建使用discov.EtcdConf就是服务发现etcd的配置,上述代码是无法直接调用的,应为本地没有服务中心。

无服务中心服务注册的调用

func GetRpcClientPing() (string, error) {
	c, err := zrpc.NewClient(zrpc.RpcClientConf{
		Target: "127.0.0.1:9000",
	})
	if err != nil {
		return "", err
	}
	r := rpcserviceclient.NewRpcservice(c)
	r2, err := r.Ping(context.Background(), &rpcservice.Request{ReqJson: "xiaoxu"})
	if err != nil {
		return "", errors.New("rpc method anlyse failed")
	}
	return r2.ResJson, nil

}

使用Tartget属性就跳过服务中心。

import (
	"fmt"
	"testing"
)

func TestGetData(t *testing.T) {
	str, err := GetRpcClientPing()
	if err != nil {
		panic(err)
	}
	t.Log(str)
	fmt.Println(str)
}

在这里插入图片描述

rpcclient中注册自定义函数:

func TestGetList(t *testing.T) {
	str, err := GetRpcClientList()
	if err != nil {
		panic(err)
	}
	t.Log(str)
	fmt.Println(str)
}

在这里插入图片描述

在这里插入图片描述

测试通过,返回数据。该数据是字符串,还需要经过反序列化得到结构体数据数据。

部分参考自:https://juejin.cn/post/7041907188972912676。

gRPC Client 的开发

服务发现

在rpc远程调用时,连接的套接字是直接写在代码中的,如下图所示:

func GetRpcClientPing() (string, error) {
	c, err := zrpc.NewClient(zrpc.RpcClientConf{
		Target: "127.0.0.1:9000",
	})
	if err != nil {
		return "", err
	}
	r := rpcserviceclient.NewRpcservice(c)
	r2, err := r.Ping(context.Background(), &rpcservice.Request{ReqJson: "xiaoxu"})
	if err != nil {
		return "", errors.New("rpc method anlyse failed")
	}
	return r2.ResJson, nil

}

这样的弊端在于当分布部署或者服务器更换时需要修改源代码的套接字,这样时非常不方便的。服务发现的是微服务治理的一种手段,功能在于使用服务注册后只需记录服务的名称,有注册中心自动查找该名称的服务,这样就脱离ip的强绑定了。

zero默认的服务中心是Etcd。服务etcd是一个注册与发现服务器,当然功能不止如此,首先在电脑上下载服务器。

  1. apt install etcd下载etcd
    在这里插入图片描述

  2. etcd启动服务

在这里插入图片描述

默认启动端口为2379。

在这里插入图片描述

启动会报错,那么如何将服务以名称的形式注册到etcd中呢?

官方教程

搭建etcd服务器

etcd官网

用 go-grpc 使用etcd发现

etcd服务注册与发现的原理和实现

  1. 服务注册

go-zero集成了etcd,在core/discov包下提供了注册与发现的方法。
在这里插入图片描述

章节到此结束,具体使用方法请看下一章节go-zero微服务实战——etcd服务注册与发现

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/739090.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

RAR Extractor Max - Unzip for Mac(简单易用的压缩软件)

RAR Extractor Max是一个软件应用程序,旨在从RAR档案中提取文件。RAR是一个流行的归档文件解压软件,广泛用于压缩和归档文件。RAR Extractor Max是专门为处理RAR文件而设计的,对于任何经常处理这种文件格式的人来说,这是一个有用的…

【运维工程师学习四】Web服务之Apache

【运维工程师学习四】Web服务之Apache 1、查询、安装apacherpm命令使用 2、验证httpd是否启动成功(1)、查看是否有httpd的进程(2)、查看是否有80端口在监听中(3)、CentOS7默认不带netstat命令,通…

自定义TagViewGroup

来看看如何实现最最基本的TagGroupView,该视图作为ViewGroup的作用是实现其中的子视图的自动换行,保证子视图能够按照在xml中传入的顺序来相对合理地显示到界面上。 注意:其实在Android技术已经非常完善的当下,其实我们已经不怎么…

【k8s】k8s的yaml文件解释,如何部署一个java.jar包

前言: k8s的yaml文件解释,如何部署一个java,jar包 YAML语法格式: 大小写敏感;使用缩进表示层级关系;不支持Tab键制表符缩进,只使用空格缩进;缩进的空格数目不重要,只要相同层级的元…

Imx6ull linux 设备树

一 linux设备树简介 1.1 linux设备树 Linux设备树是一种用于描述硬件设备信息的数据结构,它在Linux内核中发挥着越来越重要的作用 在系统上电后,BootLoader会将设备树传递给Linux内核,内核根据识别的树信息展开为platform_device、spi_dev…

如何用爬虫实现GPT功能

如何用爬虫实现GPT功能? GPT(Generative Pre-trained Transformer)和爬虫是两个完全不同的概念和技术。GPT是一种基于Transformer模型的自然语言处理模型,用于生成文本,而爬虫是一种用于从互联网上收集数据的技术。 …

贝叶斯 A/B 测试解释与Python实现

一、说明 A / B测试在各个行业中有许多应用。从试图确定最佳市场群体到医疗药物测试,它具有各种应用,并允许企业根据结果做出决策。有两种常见的A/B检验方法,频率主义方法和贝叶斯方法,两者都是从假设检验的基础出发的。在本文中&…

819. 递归求阶乘

链接: 819.递归求阶乘 题目: 请使用递归的方式求 nn 的阶乘。 输入格式 共一行,包含一个整数 nn。 输出格式 共一行,包含一个整数,表示 nn 的阶乘的值。 数据范围 1≤n≤101≤n≤10 输入样例: 3输出样例&am…

【Linux】查看系统各种信息的常用命令 (CPU、内存、进程、网口、磁盘、硬件、等等)

Linux是一种开源的类Unix操作系统,它有很多不同的发行版,如Ubuntu、CentOS、Debian等。Linux系统提供了很多命令行工具,可以让用户方便地查看和管理系统的各种信息,如硬件配置、内存使用、进程状态、网络连接等。本文将介绍一些常…

解决IDEA项目external libraries依赖包消失的问题

有时候电脑重启后,再打开IDEA上的项目时会出现external libraries目录下的依赖包都消失了的情况,只剩下了一个JDK的包 网上说可以通过刷新IDEA的缓存解决,但我试了没有效果,最后使用如下办法解决: 1.删除项目目录下的…

python接口自动化(二十五)--unittest断言——下(详解)

简介 本篇还是回归到我们最初始的话题,想必大家都忘记了,没关系看这里:传送门 没错最初的话题就是登录,由于博客园的登录机制改变了,本篇以我找到的开源免费的登录API为案例,结合 unittest 框架写 2 个用例…

MySQL原理探索——26 备库为什么会延迟好几个小时

在上一篇文章中,介绍了几种可能导致备库延迟的原因。你会发现,这些场景里,不论是偶发性的查询压力,还是备份,对备库延迟的影响一般是分钟级的,而且在备库恢复正常以后都能够追上来。 但是,如果备…

nvm的安装与使用5分钟极速上手

nvm的安装与使用5分钟极速上手 下载 nvm 并安装 nvm官网下载地址推荐下载 nvm-setup.zip 这一个,nvm-noinstall.zip下载之后不用安装,但是得自己配置setting.txt文件,以及环境变量,过于麻烦。nvm-setup.zip 会直接帮我们把环境变…

开篇(变量、运算符、进制)

一、Java简介 JDK(JRE(JVM & API)、Java language(tools & APIs));java源文件 → javac的编译 → class类(会生成一个或多个class文件); * 使用 jav…

什么是Qt Widgets?一组创建经典桌面应用UI的界面组件!

Qt 是目前最先进、最完整的跨平台C开发工具。它不仅完全实现了一次编写,所有平台无差别运行,更提供了几乎所有开发过程中需要用到的工具。如今,Qt已被运用于超过70个行业、数千家企业,支持数百万设备及应用。 Qt Widgets模块提供…

QGIS 根据点位批量出图

背景 在工作中,当有大量项目点位需要结合地图介绍时,则需要批量截图。于是有了今天的教程。 一 工具及材料准备 QGIS 插件 QuickMapServices 点位集合 CSV文件 其他图层文件 二 导入图层 这里根据实际需要导入自己对应格式的就好。 图层-添加…

计算机网络地址

1、ipv4地址 2、网络地址转换 3、子网划分和主机号

Spring MVC 注解实现

注解描述 注解描述Controller用于标记在一个类上,使用它标记的类就是一个SpringMVC Controller 对象,分发处理器将会扫描使用了该注解的类的方法,并检测该方法是否使用了RequestMapping 注解。Controller 只是定义了一个控制器类&#xff0c…

第七章 环境软件的安装

1、nodeJS安装 node -v node版本 安装多个node版本 pnpm i nvm -g nvm -v nvm ls 查看当前安装的版本 nvm install 18.7.0 安装指定的版本 nvm use 18.6.0 切换到别的版本 安装pnpm npm i pnpm -g pnpm -v 安装VSCode 官网直接下载 安装好后 需要配置 按住 CMD +…

基于单片机的老人防摔倒的设计与实现

功能介绍 以51单片机作为主控系统;通过LCD1602液晶显示屏显示当前的经纬度及时间的信息;温度传感器采集当前体温;通过GPS接收模块获得当前位置的位置的经度、纬度、时间和高度等信息;通过ADXL345检测老人摔倒的一瞬间重力加速度通…