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

目录介绍

接上一节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.Userb, 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.Usererr := 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 liststr := `{"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.protopackage rpcserviceclientimport ("context""demo/rpcservice/rpcservice""github.com/zeromicro/go-zero/zrpc""google.golang.org/grpc"
)type (Request  = rpcservice.RequestResponse = rpcservice.ResponseRpcservice 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.hqwc.cn/news/20897.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

5.带你入门matlab常见分布的期望和方差(matlab程序)

代码及运行结果 %%  二项分布的期望和方差 clear all; n1100; p10.3; [m1,v1]binostat(n1,p1) %100*0.3 100*0.3*0.7 %% %% 均匀分布的期望和方差 clear all; a11; b15; [m1,v1]unifstat(a1,b1) %% 正态分布的期望和方差 clear all; n12; n23; [m1,v1]normstat(n1,n2) %%…

SuperMap iClient3D for Cesium最短路径分析

作者:Mei 目录 前言实现思路实现步骤1、构建二维网络数据集1.1拓扑检查1.2线拓扑数据集处理1.3构建二维网络数据集 2、发布网络分析服务3、实现代码 前言 在交通、消防业务场景中,如果某地发生火灾或者交通事故,需要快速规划出最短抢救路线&a…

maven-依赖管理-下

依赖冲突 特殊优先 特殊优先∶当同级配置了相同资源的不同版本&#xff0c;后配置的覆盖先配置的(提醒&#xff1a;要尽量避免这种没有意义的冲突)修改D:\java_projects\maven_A\pom.xml, 引入mysql5.1 <?xml version"1.0" encoding"UTF-8"?> &…

vue2 element-ui el-cascader地址省市区分开单独写

使用 npm 或 yarn 安装 element-china-area-data 包&#xff1a; npm install element-china-area-data 在你的代码中导入 element-china-area-data import { regionData } from element-china-area-data let that; 完整代码 <template><div><el-form ref&quo…

什么是事件循环 Event Loop

一、什么是事件循环 事件循环&#xff08;Event Loop&#xff09;是单线程的JavaScript在处理异步事件时进行的一种循环过程&#xff0c;具体来讲&#xff0c;对于异步事件它会先加入到事件队列中挂起&#xff0c;等主线程空闲时会去执行事件队列&#xff08;Event Queue&…

【AGC】认证服务HarmonyOS(api9)实现手机号码认证登录

【问题背景】 近期AGC上线了HarmonyOS(api9)平台的SDK&#xff0c;这样api9的设备也能使用认证服务进行快速认证登录了。下面为大家带来如何使用auth SDK&#xff08;api9&#xff09;实现手机号码认证登录。 【开通服务】 1.登录AppGallery Connect&#xff0c;点击“我的项…

松鼠回家(最短路+二分)

D-松鼠回家_2023河南萌新联赛第&#xff08;一&#xff09;场&#xff1a;河南农业大学 (nowcoder.com) #include<bits/stdc.h> using namespace std; #define int long long const int N2e510; map<int,int>a; int n,m,st,ed,h; struct node{int x,y; }; vector&l…

揭晓!2023年6月CSDN城市之星西安赛道获奖名单及评选规则解析

&#x1f337;&#x1f341; 博主 libin9iOak带您 Go to New World.✨&#x1f341; &#x1f984; 个人主页——libin9iOak的博客&#x1f390; &#x1f433; 《面试题大全》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33…

更快更复杂之—Mendix如何支持多种AI部署模式

在过去十年&#xff0c;LCAP市场逐渐崛起的同时&#xff0c;计算能力不断提高、大数据可用性不断增强&#xff0c;预计未来数年&#xff0c;低代码应用平台&#xff08;LCAP&#xff09;的市场将增长30%左右&#xff0c;并带动人工智能&#xff08;AI&#xff09;迎来新的春天。…

【Linux后端服务器开发】进程与地址空间概述

目录 一、进程创建 二、进程状态 1. 运行状态R 2. 睡眠状态S 3. 僵尸状态Z 4. 孤儿进程 三、进程优先级 PRI 四、地址空间的层次结构 五、虚拟地址和物理地址 一、进程创建 fork()函数创建子进程&#xff0c;若创建成功&#xff0c;则给父进程返回子进程的pid&#x…

HTTP以及Servlet的学习

HTTP和Servlet 联系&#xff1a; HTTP是一个通信协议&#xff0c;而Servlet是服务器端程序&#xff0c;用于处理HTTP请求。Servlet通常用于处理HTTP请求&#xff0c;在服务器上生成动态内容&#xff0c;并生成HTTP响应。HTTP协议就是Servlet处理的基础。 区别&#xff1a; …

【图像处理】Python判断一张图像是否亮度过低

比如&#xff1a; 直方图&#xff1a; 代码&#xff1a; 这段代码是一个用于判断图像亮度是否过暗的函数is_dark&#xff0c;并对输入的图像进行可视化直方图展示。 首先&#xff0c;通过import语句导入了cv2和matplotlib.pyplot模块&#xff0c;用于图像处理和可视化。 i…