干掉 “注册中心” 的微服务架构,go-micro 内存存储服务

0、前言

有人说:真正的架构师,不是在项目中引入多少模块,而是能干掉多少模块。

如果你的架构师有一天把你喊到小黑屋里,悄悄的和你说:“目前咱们项目先不考虑负载均衡,我发现咱们用 consul 做这个注册中心,好像只用到了服务注册发现的功能,那你说,我们能不能把这个 consul 去掉,因为你 rpc 通信现在不也只是从注册中心拿到 ip 和 port,然后访问么,那我直接写死,理论是可行的吧?”
不要着急反驳,先想想真的不可能吗?

1、前置环境

1.1、环境配置

版本说明
go1.13.6语言
go-micro1.18.0微服务框架
protoc3.11.4protocol buffer 生成工具

1.2、go-micro demo 项目

1.2.1、新建下述目录与文件

在这里插入图片描述

1.2.2、编写协议

在 hello.proto 写入下面的 SayHello。

syntax = "proto3";
package proto;

option go_package = "proto/pb";

message HelloReq {
  int32 id = 1;
}

message HelloResp {
  string msg = 1;
}

service SayHelloHandler {
  rpc SayHello(HelloReq) returns(HelloResp){}
}

在这里插入图片描述
cd 到项目的 proto 目录,输入 protoc --micro_out=../ --go_out=../ hello.proto 生成对应的协议实现。
在这里插入图片描述
在这里插入图片描述

1.2.3、服务方(A)

我们将 A 作为服务方,即:我们在 A 实现 SayHello 协议,并且启动服务等待 B 服务调用。
具体的如何实现就不在赘述了,感兴趣的同学请自行学习 grpc 相关知识。
实现如下:

type SayHelloHandler struct {
}

func (shh *SayHelloHandler) SayHello(ctx context.Context, req *pb.HelloReq, resp *pb.HelloResp) error {
	fmt.Println(req.Id)
	resp.Msg = "hello world"
	return nil
}

在这里我们打印一下 req 带来的 Id,并设置 resp 的 Msg 为 hello world,返回给调用者。
OK,剩下的就很简单了,启动 go-micro 服务
A.go 文件完整代码如下:

package main

import (
	"context"
	"fmt"
	"github.com/Mor1aty/we_need_not_consul/proto/pb"
	"github.com/micro/go-micro"
	"log"
)

func main() {
	server := micro.NewService(
		micro.Name("A"),
		micro.Address(":8002"),
		micro.Version("1.0"),
	)

	shh := new(SayHelloHandler)
	if err := pb.RegisterSayHelloHandlerHandler(server.Server(), shh); err != nil {
		log.Fatalf("A server register SayHello failed, err: %v", err)
	}

	server.Init()

	if err := server.Run(); err != nil {
		log.Fatalf("A server run failed, err: %v", err)
	}
}

type SayHelloHandler struct {
}

func (shh *SayHelloHandler) SayHello(ctx context.Context, req *pb.HelloReq, resp *pb.HelloResp) error {
	fmt.Println(req.Id)
	resp.Msg = "hello world"
	return nil
}

在这里插入图片描述
这里我们先不把 A 服务注册到某个地方,因为我们的目标毕竟是干掉注册中心

1.2.4、调用方(B)

调用方我就不过多赘述了,B.go 文件代码如下:

package main

import (
	"context"
	"fmt"
	"github.com/Mor1aty/we_need_not_consul/proto/pb"
	"github.com/micro/go-micro"
	"log"
)

func main() {
	server := micro.NewService()

	shhs := pb.NewSayHelloHandlerService("A", server.Client())
	resp, err := shhs.SayHello(context.Background(), &pb.HelloReq{
		Id: 10,
	})

	if err != nil {
		log.Fatalf("SayHello request failed, err: %v", err)
	}
	fmt.Println(resp)
}

在这里插入图片描述

2、go-micro 插件

首先我们会想到 go-micro 是否已经提供了这样的插件(不需要注册中心的插件)。成为一个合格 gopher 的第一堂课就是认识到其他的都是假的,只有 github 才是真的。
找到 asim 的 go-plugins 项目,我们惊喜的发现,register 目录下有一个 memory 似乎符合我们的要求。
在这里插入图片描述
memory,内存。那放在 registry 的目录下的意思是不是指注册到内存里的意思,如果说可以把内存作为注册中心,那岂不是变相的干掉了注册中心!
OK,我们直接引入看看。
A.go

reg := memory.NewRegistry()

server := micro.NewService(
	micro.Name("A"),
	micro.Address(":8002"),
	micro.Version("1.0"),
	micro.Registry(reg),
)

在这里插入图片描述
B.go

reg := memory.NewRegistry()
server := micro.NewService(
	micro.Registry(reg),
)

在这里插入图片描述

我们直接 go run 尝试一次
在这里插入图片描述
这里可以看到 A 服务被注册到了 memory 中。
然而,我们 go run B 服务就会发现,他找不到 A 服务。
在这里插入图片描述
这是为什么呢?

3、为什么找不到?

这一节的标题表明了,这里主要是探究为什么找不到,不感兴趣的同学可以直接翻看下一节。

明明 A 服务已经被注册到 memory 里了,为什么会显示找不到呢?
为了解决这个问题,我翻遍了 go 中文网,go-micro 官网,百度,谷歌,反正是没有找到相关的资料。甚至就连 memory 这个插件也很少有地方提到。如果有找到相关资料的小伙伴,请私聊我,非常感谢。
没有办法,看源码吧
在这里插入图片描述
这里我们可以发现,github.com/micro/go-plugins/registry/memory 这个包是直接调用另一个地方的 memory 来实现功能。继续深入,我们发现这个插件调用的是 go-micro 提供的 memory。
在这里插入图片描述
可喜可贺,至少我们不必引入 github.com/micro/go-plugins/registry/memory 了。可以直接调用 go-micro 本身的 registry 了。
继续深入在他的 util 里我们可以发现。go-micro 注册服务到内存,具体是把服务以 record 结构体的形式存储到一个 map 中。
在这里插入图片描述
而这个 map 是 Registry 的一个属性。
在这里插入图片描述
那事情就明朗了,回去看我们 A.go 和 B.go 的代码。
在这里插入图片描述
在这里插入图片描述
A 和 B 分别 new 了一个 Registry,连“注册中心”都不是同一个,B 服务是一定访问不到 A 服务的。
那我们能否抽取出一个公共方法,维护同一个 registry,这样可行吗?我们试试
首先,在新建的 common 目录下新建一个 common.go,编写如下代码。

package common

import (
	"github.com/micro/go-micro/registry"
	"github.com/micro/go-plugins/registry/memory"
)

var reg registry.Registry

func GetReg() registry.Registry {
	if reg == nil {
		reg = memory.NewRegistry()
		return reg
	}
	return reg
}

在这里插入图片描述
修改 A.go 和 B.go
在这里插入图片描述
在这里插入图片描述
go run 一下
在这里插入图片描述
在这里插入图片描述
不幸的是,依然不行
实际上,这个想法在想出来的时候,就应该被否定了。甚至说这个想法都不应该出现。因为 A 和 B 在分开 go run 运行的情况下,他俩明显是两个不同的服务。他们会分别建立一个 common.go,那自然 reg 也不可能是通用的了。
在这里插入图片描述
能想到这个操作,可见我当时可能已经“失了智”。

4、那咋办嘛

实际上,到了这个时候,我差不多已经处于放弃的阶段了。在这个问题上,已经花费了一个下午的时间了(大部分用在谷歌百度上)。
今天早晨刷牙的时候,我忽然想到,服务注册只是把自己注册到一个注册中心里,如果说他只是做这个动作的话,那为什么我不能主动去完成这个动作呢?
在服务启动的时候,就把其他的服务注册到 memory 的那个 map 里。
有了思路,我们继续上路。
翻看源码,我们可以发现 memory 的 registry 提供了一个 Register 方法,通过这个方法可以把服务注册到 memory 里。
ok,改造一下我们的代码。
这里注意,go.mod 可以直接去除 github.com/micro/go-plugins/registry/memory 依赖,因为他也是调用 github.com/micro/go-micro/registry/memory 来工作的,我们就不需要 go-plugins 这个中间商了。
在这里插入图片描述
改造一下 A.go,服务端相对较为简单。只需要修改为原来的 memory 注册就好。注意上面的引用已经换成了 go-micro 的 registry。
在这里插入图片描述
主要是 B.go,这么多属性,我们该写什么呢?
在这里插入图片描述
不要慌,go-micro 给我们提供了一个 GetService 方法,帮助我们根据服务名获取服务,我们可以在 A 服务启动之后,调用这个方法来,来查看内存中存储 A 服务的属性。
我这里引入了 gin 框架,提供了一个 GET 服务来查看的。大家可以采用自己合适的方法来查看。
改造一下 A.go。
在这里插入图片描述

A.go :

package main

import (
	"context"
	"fmt"
	"github.com/Mor1aty/we_need_not_consul/proto/pb"
	"github.com/gin-gonic/gin"
	"github.com/micro/go-micro"
	"github.com/micro/go-micro/registry/memory"
	"github.com/micro/go-micro/web"
	"log"
)

func main() {

	reg := memory.NewRegistry()
	server := micro.NewService(
		micro.Name("A"),
		micro.Address(":8002"),
		micro.Version("1.0"),
		micro.Registry(reg),
	)

	r := gin.Default()
	r.GET("/findA", func(c *gin.Context) {
		service, err := reg.GetService("A")
		if err != nil {
			fmt.Println(err)
			c.String(200, "success")
			return
		}
		for _, s := range service {
			fmt.Printf("%#v\n", s)
			for _, n := range s.Nodes {
				fmt.Printf("%#v\n", n)
			}
		}
		c.String(200, "success")
	})

	webServer := web.NewService(
		web.Address(":8080"),
		web.Name("A"),
		web.Handler(r),
		web.MicroService(server),
	)

	shh := new(SayHelloHandler)
	if err := pb.RegisterSayHelloHandlerHandler(server.Server(), shh); err != nil {
		log.Fatalf("A server register SayHello failed, err: %v", err)
	}

	webServer.Init()

	if err := webServer.Run(); err != nil {
		log.Fatalf("A server run failed, err: %v", err)
	}
}

type SayHelloHandler struct {
}

func (shh *SayHelloHandler) SayHello(ctx context.Context, req *pb.HelloReq, resp *pb.HelloResp) error {
	fmt.Println(req.Id)
	resp.Msg = "hello world"
	return nil
}

这里改造有些点需要特别注意,请大家详细看一下上面的 A.go 代码。

go run 一下。
在这里插入图片描述
这里我们可以发现,当服务注册的时候,Name,Version 是需要指定的,Metadata,Endpoints 可以不指定。
Nodes 只需要指定 Id,Address。Metadata 是可以不指定的。
Name,Version,Address 都好说,关键是这个 Id 怎么办,从打印中我们可以看到,这个 Id 是个随机的,那是不是意味着我们可以不指定,然后让 go-micro 自动生成呢?试一试。
在这里插入图片描述
改造一下 B.go
在这里插入图片描述

package main

import (
	"context"
	"fmt"
	"github.com/Mor1aty/we_need_not_consul/proto/pb"
	"github.com/micro/go-micro"
	"github.com/micro/go-micro/registry"
	"github.com/micro/go-micro/registry/memory"
	"log"
)

func main() {
	reg := memory.NewRegistry()
	if err := reg.Register(&registry.Service{
		Name:    "A",
		Version: "1.0",
		Nodes: []*registry.Node{
			{
				Address: "你的 ip:8002",
			},
		},
	}); err != nil {
		log.Fatalf("registry failed, err: %v", err)
	}
	server := micro.NewService(
		micro.Registry(reg),
	)

	shhs := pb.NewSayHelloHandlerService("A", server.Client())
	resp, err := shhs.SayHello(context.Background(), &pb.HelloReq{
		Id: 10,
	})

	if err != nil {
		log.Fatalf("SayHello request failed, err: %v", err)
	}
	fmt.Println(resp)
}

注意,打码的部分(Address)请自行替换成自己的ip。

go run 一下。
在这里插入图片描述
nice!史诗级突破,我们可以发现,他已经访问到 A 服务,只是被拒绝了而已。
那这里我进行了猜测,是不是 A 服务不仅用到了 go-micro 的 rpc,还引入了 gin 作为 http 服务呢?
毕竟 A 服务启动的是 web 那个 server,而不是 rpc 那个 server。有可能,先试试
我们把 A 服务引入 gin 去掉,同时换回 rpc server run。
在这里插入图片描述
在这里插入图片描述

package main

import (
	"context"
	"fmt"
	"github.com/Mor1aty/we_need_not_consul/proto/pb"
	"github.com/micro/go-micro"
	"github.com/micro/go-micro/registry/memory"
	"log"
)

func main() {

	reg := memory.NewRegistry()
	server := micro.NewService(
		micro.Name("A"),
		micro.Address(":8002"),
		micro.Version("1.0"),
		micro.Registry(reg),
	)

	//r := gin.Default()
	//r.GET("/findA", func(c *gin.Context) {
	//	service, err := reg.GetService("A")
	//	if err != nil {
	//		fmt.Println(err)
	//		c.String(200, "success")
	//		return
	//	}
	//	for _, s := range service {
	//		fmt.Printf("%#v\n", s)
	//		for _, n := range s.Nodes {
	//			fmt.Printf("%#v\n", n)
	//		}
	//	}
	//	c.String(200, "success")
	//})
	//
	//webServer := web.NewService(
	//	web.Address(":8080"),
	//	web.Name("A"),
	//	web.Handler(r),
	//	web.MicroService(server),
	//)

	shh := new(SayHelloHandler)
	if err := pb.RegisterSayHelloHandlerHandler(server.Server(), shh); err != nil {
		log.Fatalf("A server register SayHello failed, err: %v", err)
	}

	server.Init()

	if err := server.Run(); err != nil {
		log.Fatalf("A server run failed, err: %v", err)
	}
}

type SayHelloHandler struct {
}

func (shh *SayHelloHandler) SayHello(ctx context.Context, req *pb.HelloReq, resp *pb.HelloResp) error {
	fmt.Println(req.Id)
	resp.Msg = "hello world"
	return nil
}

go run 一下:
在这里插入图片描述
在这里插入图片描述
Nice!
成了。

5、总结

经过将近一个工作日的斗智斗勇,我终于实现了架构师大佬提出的去除注册中心的想法。尽管只是把注册中心从 consul, erueka 换成了内存而已。不过这也无需在引入新的模块了,也算是好事吧。
在这个过程中,我有了两个体会。一个是源码并没有那么可怕,只要细心一点还是能看明白的。另一个是 go 的文章真的好难找啊。跪求各位 gopher 大佬们多分享分享啊,我太难了
这边文章中使用的 we_need_not_consul 项目,我上传到了自己的 github,大家可以直接下载查看。地址:https://github.com/Mor1aty/we_need_not_consul

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值