小工具      在线工具  汉语词典  dos游戏  css  js  c++  java

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

# go-zero,golang,微服务,go-zero,rpc 额外说明

收录于:15天前

目录介绍

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

order的目录结构如下

在这里插入图片描述

  • 根目录
    • API服务
    • 远程过程调用服务
    • 自定义逻辑层逻辑
    • 自定义参数层模型
    • 自定义工具层util

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

  • api服务
    • 配置
    • 处理程序
    • 逻辑
    • 服务中心
    • 类型

在这里插入图片描述

  • rpc服务
    • ETC
    • 因特内尔
    • RPC服务
    • rpc服务客户端

在这里插入图片描述

首先,解释一下每个目录的用途。这两个服务api和rpc都是go-zero生成的,它们的内部目录都是连接到服务本身的。逻辑、模型和实用程序是公共部分。

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

服务构建

订单服务是在上一节中构建的。本节将构建用户和产品服务器。项目与订单基本一致。唯一的区别是,用户中有一个登录或用户名认证过程,需要将数据从rpc客户端传递到rpc服务器。

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

公共逻辑代码

// 验证账户
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处理函数

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的逻辑重写了封装部分

// 继承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)
}

客户来电

注意修改端口。用户端口修改为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文件下,这个客户端和服务端共有的。

在这里插入图片描述

这样就一一完成了其他方法的封装和注册,完成了产品服务的构建。请注意,这三个服务端口是不同的。 api是8000系列,rpc是9000系列。

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

在这里插入图片描述

rpc远程调用

上一节总结的三项服务就完成了。服务应该能够相互调用,就像客户端调用服务器一样。当调用其他服务时,客户端本身就是客户端,被调用的服务就相当于服务端。

api和rpc服务目录下有主程序,启动即可。如下图,注意三个服务,一个药品,六个终端分别启动。

在这里插入图片描述

三个独立的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客户端开发

服务发现

远程调用RPC时,直接在代码中写入连接的socket,如下图:

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

}

这样做的缺点是分布式部署或者更换服务器时需要修改源码socket,非常不方便。服务发现是微服务治理的一种手段。其作用是,注册一个服务后,只需要记录该服务的名称,注册中心就会自动找到该名称的服务,从而摆脱了IP的强绑定。

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

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

  2. etcd启动服务

在这里插入图片描述

默认启动端口为2379。

在这里插入图片描述

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

官方教程

搭建etcd服务器

etcd 官方网站

使用 go-grpc 来发现使用 etcd

etcd服务注册与发现原理及实现

  1. 服务注册

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

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

. . .

相关推荐

额外说明

mac下qt程序添加程序图标

第一步 桌面新建一个文件夹,命名为 logo.iconset。将png图标(原图只能是.png文件)放进去。 第二步: 打开终端,cd到这个文件夹,依次执行下面的语句: sips -z 16 16 logo.png --out icon_16.png s

额外说明

JS实现获取当前时间和倒计时功能

虽说这两个小功能,网上一抓一大把吧,但是呢: 1、自己从来没有好好写过类似功能,也没琢磨过; 2、网上虽有但每次都要找啊; 不如自己写个现成的,有需要了就来这里找,有问题了就来这里改进,顺道琢磨琢磨。废话不多说,上代码。 window.οnlοad=fu

额外说明

RocketMq02_拷贝磁盘、Broker常用模式、磁盘阵列、集群搭建

文章目录 ①. 单机版本安装与启动 ②. 控制台的安装与启动 ③. 复制刷盘、Broker集群模式 ④. 磁盘阵列 - RAID ⑤.JBOD、RAID0 ⑥. RAID1、RAID10、01 ⑦. 搭建集群 - 异步两主两从 ①. 单机版本安装与启动

额外说明

day13---(01)登录功能后端实现

1、在service_ucenter模块中实现登录方法 (1) com/atguigu/ucenterservice/entity/vo中新建LoginVo类。 package com.atguigu.ucenterservice.entity.vo;

额外说明

NGUI ScrollView Panel一直抖的解决方案

抖的原因是,UIPanel一直在按照Clipping去调整 尽量吧Clipping的值调成整数,就不抖了

额外说明

fatal error RC1015: cannot open include file 'winres.h'.

错误描述 OS: Win 10 IDE: VS 2017 打开一个文物级MFC项目,加载界面文件时,弹出此错误。 问题分析 打开老项目时,一般会遇到各种不兼容问题,比如系统头文件或库文件更新或被踢出群聊了(由于系统迭代,会对系统头文件和库文件作一定修改)

额外说明

【扫雷源码】

基于C语言的扫雷示例: #include <stdio.h> #include <windows.h> #include <stdlib.h> #include <time.h> #include <stdbool.h> #define HA

额外说明

win10 --docker

关于 docker desktop 闪退问题: https://www.cnblogs.com/zhengyuanyuan/p/14412651.html 提示下载 wsl2 Linux kernel; https://learn.microsoft.c

额外说明

Cisco 路由器1.5作业 OSPF

要求 使用动态路由OSPF协议实现全网联通 一、基础配置 1、路由器1配置 en conf t int f0/0 ip add 192.168.1.18 255.255.255.252 no shut exit int s0/0/0 ip add

额外说明

宝塔面板wordpress_WordPress管理面板中的隐藏秘密选项面板

宝塔面板wordpress 这是您应该了解的很酷的小秘密之一。 WordPress 在管理面板中有一个隐藏的主选项面板。此页面显示了您博客的所有设置的组合,您不应该弄乱它。尽管如此,您仍然应该了解它,因为它很酷。 这是您应该知道的那些很酷的小秘密之一。

ads via 小工具