服务注册通常用于分布式系统或微服务架构中,是一种用于管理和发现这些分布式服务的机制。它的目标是让服务能够动态地找到其他服务,并能够与其进行通信,而无需显式地配置其位置信息

本文简单讲述使用etcd进行服务注册,基于kitex和hertz框架简单实现微服务应用

代码地址:https://github.com/T4t4KAU/Documents/tree/main/etcd-test

接口定义

使用thrift编写如下idl

add.thrift

namespace go api

struct AddRequest {
1: i32 first
2: i32 second
} struct AddResponse {
1: i32 sum
} service AddService {
AddResponse Add(AddRequest req)
}

echo.thrift

namespace go api

struct EchoRequest {
1: string message
} struct EchoResponse {
2: string message
} service EchoService {
EchoResponse Echo(1:EchoRequest req)
}

使用Kitex生成代码(此处不作赘述):

.
├── add
│   ├── handler.go
│   └── main.go
├── build.sh
├── echo
│   ├── handler.go
│   └── main.go
├── go.mod
├── go.sum
├── idl
│   ├── add.thrift
│   └── echo.thrift
├── kitex_gen
│   └── api
│   ├── add.go
│   ├── addservice
│   │   ├── addservice.go
│   │   ├── client.go
│   │   ├── invoker.go
│   │   └── server.go
│   ├── echo.go
│   ├── echoservice
│   │   ├── client.go
│   │   ├── echoservice.go
│   │   ├── invoker.go
│   │   └── server.go
│   ├── k-add.go
│   ├── k-consts.go
│   └── k-echo.go
├── kitex_info.yaml
└── script
└── bootstrap.sh

echo服务将参数message原样返回,add服务将参数中的两个整数求和后返回

代码实现

首先实现一个无服务注册的版本,在上述生成代码的基础上完善方法实现:

add/hander.go

// Add implements the AddServiceImpl interface.
func (s *AddServiceImpl) Add(ctx context.Context, req *api.AddRequest) (resp *api.AddResponse, err error) {
resp = new(api.AddResponse)
resp.Sum = req.First + req.Second
return
}

add/main.go

package main

func main() {
// 服务地址
addr, _ := net.ResolveTCPAddr("tcp", "127.0.0.1:9091") svr := api.NewServer(new(AddServiceImpl),
server.WithServerBasicInfo(&rpcinfo.EndpointBasicInfo{ServiceName: "add"}),
server.WithServiceAddr(addr),
) err := svr.Run() if err != nil {
log.Println(err.Error())
}
}

echo/handler.go

// Echo implements the EchoServiceImpl interface.
func (s *EchoServiceImpl) Echo(ctx context.Context, req *api.EchoRequest) (resp *api.EchoResponse, err error) {
resp = new(api.EchoResponse)
resp.Message = req.Message
return
}

echo/main.go

package main

func main() {
// 服务地址
addr, _ := net.ResolveTCPAddr("tcp", "127.0.0.1:9092") svr := api.NewServer(new(EchoServiceImpl),
server.WithServerBasicInfo(&rpcinfo.EndpointBasicInfo{ServiceName: "echo"}),
server.WithServiceAddr(addr),
) err := svr.Run() if err != nil {
log.Println(err.Error())
}
}

创建一个api包,作为两个服务的API网关,使用hertz来处理用户请求

目录结构:

.
├── add
│   ├── handler.go
│   └── main.go
├── api
│   ├── add.go
│   ├── echo.go
│   └── main.go
├── build.sh
├── echo
│   ├── handler.go
│   └── main.go
├── go.mod
├── go.sum
├── idl
│   ├── add.thrift
│   └── echo.thrift
├── kitex_gen
│   └── api
│   ├── add.go
│   ├── addservice
│   │   ├── addservice.go
│   │   ├── client.go
│   │   ├── invoker.go
│   │   └── server.go
│   ├── echo.go
│   ├── echoservice
│   │   ├── client.go
│   │   ├── echoservice.go
│   │   ├── invoker.go
│   │   └── server.go
│   ├── k-add.go
│   ├── k-consts.go
│   └── k-echo.go
├── kitex_info.yaml
└── script
└── bootstrap.sh

实现web handler函数,在handler中使用RPC请求服务

add:

package main

var addClient addservice.Client

func AddHandler(ctx context.Context, c *app.RequestContext) {
num1, _ := strconv.Atoi(c.Query("first"))
num2, _ := strconv.Atoi(c.Query("second")) // RPC请求
resp, err := addClient.Add(ctx, &api.AddRequest{
First: int32(num1), Second: int32(num2),
})
if err != nil {
c.JSON(http.StatusInternalServerError, utils.H{
"message": err.Error(),
})
return
} c.JSON(http.StatusOK, utils.H{
"message": resp.Sum,
})
}

echo:

package main

var echoClient echoservice.Client

func EchoHandler(ctx context.Context, c *app.RequestContext) {
// RPC请求
resp, err := echoClient.Echo(ctx, &api.EchoRequest{
Message: c.Query("message"),
})
if err != nil {
c.JSON(http.StatusInternalServerError, utils.H{
"message": err.Error(),
})
} c.JSON(http.StatusOK, utils.H{
"message": resp.Message,
})
}

在主调函数中进行RPC客户端初始化和路由注册:

package main

func Init() {
var err error
addClient, err = addservice.NewClient("addservice",
client.WithHostPorts("127.0.0.1:9091"))
if err != nil {
panic(err)
}
echoClient, err = echoservice.NewClient("echoservice",
client.WithHostPorts("127.0.0.1:9092"))
if err != nil {
panic(err)
}
} func main() {
Init()
r := server.Default()
r.POST("/add", AddHandler)
r.GET("/echo", EchoHandler) r.Spin()
}

启动add和echo服务,再启动api,请求URL即可访问,此处省略测试过程

引入服务注册

在上述实现中,要显式的告诉API网关服务的地址,一旦这个地址发生变化,会导致服务不可用,接下来使用服务注册

使用docker启动etc:

docker run -d -p 10079:2379 --name etcd \
-e ETCD_LISTEN_CLIENT_URLS=http://0.0.0.0:2379 \
-e ETCD_ADVERTISE_CLIENT_URLS=http://0.0.0.0:2379 \
-e ETCDCTL_API=3 \
quay.io/coreos/etcd:v3.5.5

etcd服务器启动在10079端口

接下来修改服务代码:

add/main.go

package main

func main() {
// 服务注册
r, err := etcd.NewEtcdRegistry([]string{"127.0.0.1:10079"})
if err != nil {
panic(err)
} addr, _ := net.ResolveTCPAddr("tcp", "127.0.0.1:9091") svr := api.NewServer(new(AddServiceImpl),
server.WithServerBasicInfo(&rpcinfo.EndpointBasicInfo{ServiceName: "add"}), // 指定服务名称
server.WithServiceAddr(addr),
server.WithRegistry(r), // 设置服务注册
) err = svr.Run() if err != nil {
log.Println(err.Error())
}
}

echo/main.go

package main
func main() {
// 服务注册
r, err := etcd.NewEtcdRegistry([]string{"127.0.0.1:10079"})
if err != nil {
panic(err)
} addr, _ := net.ResolveTCPAddr("tcp", "127.0.0.1:9092") svr := api.NewServer(new(EchoServiceImpl),
server.WithServerBasicInfo(&rpcinfo.EndpointBasicInfo{ServiceName: "echo"}), // 指定服务名称
server.WithServiceAddr(addr),
server.WithRegistry(r), // 设置服务注册
) err = svr.Run() if err != nil {
log.Println(err.Error())
}
}

修改api代码,无须给出两个服务的地址了:

package main

func Init() {
var err error r, err := etcd.NewEtcdResolver([]string{"127.0.0.1:10079"})
if err != nil {
panic(err)
} addClient, err = addservice.NewClient("add",
client.WithResolver(r),
)
if err != nil {
panic(err)
}
echoClient, err = echoservice.NewClient("echo",
client.WithResolver(r),
)
if err != nil {
panic(err)
}
} func main() {
Init()
r := server.Default()
r.POST("/add", AddHandler)
r.GET("/echo", EchoHandler) r.Spin()
}

启动之后,服务正常使用

Kitex微服务开发实践(ETCD服务注册)的更多相关文章

  1. OpenResty从入门到开发一个网关服务(使用etcd作为注册中心)

    简介 OpenResty(也称为 ngx_openresty)是一个全功能的 Web 应用服务器.它打包了标准的 Nginx 核心,很多的常用的第三方模块,以及它们的大多数依赖项. 通过揉和众多设计良 ...

  2. Topshelf 一个简化Windows服务开发的宿主服务框架

    Topshelf是 基于.net框架开发的宿主服务框架.该框架简化了服务的创建,开发人员只需要使用 Topshelf编写一个控制台程序,就能安装为Windows服务.之所以这样原因非常简单:调试一个控 ...

  3. 基于 WebRTC 技术的实时通信服务开发实践

    随着直播的发展,直播实时互动性变得日益重要.又拍云在 WebRTC 的基础上,凭借多年的开发经验,结合当下实际情况,开发 UPRTC 系统,解决了网络延时.并发量大.客户端解码能力差等问题. WebR ...

  4. SpringCloud 微服务最佳开发实践

    Maven规范 所有项目必须要有一个统一的parent模块 所有微服务工程都依赖这个parent,parent用于管理依赖版本,maven仓库,jar版本的统一升级维护 在parent下层可以有 co ...

  5. 基于 Docker 的微服务架构实践

    本文来自作者 未闻 在 GitChat 分享的{基于 Docker 的微服务架构实践} 前言 基于 Docker 的容器技术是在2015年的时候开始接触的,两年多的时间,作为一名 Docker 的 D ...

  6. 微服务架构实践 - 你只懂docker与spring boot就够了吗?

    微服务架构实践 - 你只懂docker与spring boot就够了吗? 作者 浮云发发 已关注 2017.02.27 02:50* 字数 2613 阅读 2583评论 6喜欢 35赞赏 2 微服务并 ...

  7. 升讯威微信营销系统开发实践:订阅号和服务号深入分析( 完整开源于 Github)

    GitHub:https://github.com/iccb1013/Sheng.WeixinConstruction因为个人精力时间有限,不会再对现有代码进行更新维护,不过微信接口比较稳定,经测试至 ...

  8. 放弃Dubbo,选择最流行的Spring Cloud微服务架构实践与经验总结

    http://developer.51cto.com/art/201710/554633.htm Spring Cloud 在国内中小型公司能用起来吗?从 2016 年初一直到现在,我们在这条路上已经 ...

  9. 微服务系列实践 .NET CORE

    从事这个行业转眼已经6年了,从当初刚毕业的在北京朝八晚十,从二环到五环,仍每天精力充沛的小愤青:再到深圳一点一滴的辛勤在软件行业的耕种,从当初单体应用架构到现在微服务架构的经历,回想起来自己的收获倒是 ...

  10. 基于OpenResty和Node.js的微服务架构实践

    什么是微服务? 传统的单体服务架构是单独服务包,共享代码与数据,开发成本较高,可维护性.伸缩性较差,技术转型.跨语言配合相对困难.而微服务架构强调一个服务负责一项业务,服务可以单独部署,独立进行技术选 ...

随机推荐

  1. Centos7.x 安装jenkins

    一.安装 前提:需查看是否安装了JDK 1.第一种方法 sudo wget -O /etc/yum.repos.d/jenkins.repo https://pkg.jenkins.io/redhat ...

  2. 2021-08-26:长度为N的数组arr,一定可以组成N^2个数字对。例如arr = [3,1,2],数字对有(3,3) (3,1) (3,2) (1,3) (1,1) (1,2) (2,3) (2

    2021-08-26:长度为N的数组arr,一定可以组成N^2个数字对.例如arr = [3,1,2],数字对有(3,3) (3,1) (3,2) (1,3) (1,1) (1,2) (2,3) (2 ...

  3. Rust如何引入源码作为依赖

    问题描述 通常我们在rust项目中引入第三方依赖包时,会直接指定包的版本,这种方式指定后,Cargo在编译时会从crates.io这个源中下载这些依赖包. [package] name = " ...

  4. Selenium - 基础知识介绍

    Selenium - 基础知识介绍 介绍 Selenium是ThoughtWorks员工在业余时间开发并维护的开源项目,并且在ThoughtWorks的项 目中被广泛应用. 简单地说,Selenium ...

  5. vue全家桶进阶之路10:修饰符

    Vue2 中的修饰符是指在指令后面添加点号(.)和修饰符名称的方式,用于控制指令的行为.修饰符可以分为事件修饰符和属性修饰符两种类型,下面分别介绍它们的作用和使用方法. 事件修饰符 事件修饰符用于控制 ...

  6. JavaScript模块化 之( Commonjs、AMD、CMD、ES6 modules)演变史

    经常在工作中使用define(['./modulename'],function(modulename){}),require(['modulename'],function(modulename){ ...

  7. es笔记七之聚合操作之桶聚合和矩阵聚合

    本文首发于公众号:Hunter后端 原文链接:es笔记七之聚合操作之桶聚合和矩阵聚合 桶(bucket)聚合并不像指标(metric)聚合一样在字段上计算,而是会创建数据的桶,我们可以理解为分组,根据 ...

  8. nas盒子内网穿透

    2023年5月27日星期六 -------------------------------------------------------------------------------------- ...

  9. (亲测有效-专门解决Mac环境)Pycharm 解决无法打开的问题

    前提是Mac 安装了PyCharm.app 1.第一步:先输入: cd /Applications/PyCharm.app/Contents/MacOS 2.第二步:查看无法打开pycharm的原因, ...

  10. Python批量填补遥感影像的无效值NoData

      本文介绍基于Python中ArcPy模块,对大量栅格遥感影像文件批量进行无效值(NoData值)填充的方法.   在处理栅格图像文件时,我们经常会遇到图像中存在有无效值(即NoData值)的情况. ...