【微服务网关——服务发现】

1.服务发现

1.1 介绍

  • 服务发现是指用注册中心来记录服务信息,以便其他服务快速查找已注册服务
  • 服务发现分类:
    • 客户端服务发现
    • 服务端服务发现

1.2 客户端服务发现

客户端服务发现(Client-side Service Discovery)是一种微服务架构中的模式,用于让客户端应用动态地发现并调用其他服务的实例,而无需通过一个中介(例如负载均衡器或服务网关)。它通常用于分布式系统中,通过客户端直接决定并选择与哪个服务实例通信,从而实现服务发现和负载均衡。
在这里插入图片描述

1.3 服务端服务发现

服务端服务发现(Server-side Service Discovery)是另一种服务发现模式,与客户端服务发现相对。在这种模式中,服务的实例发现和负载均衡由服务端组件处理,客户端只需将请求发送给一个固定的入口点(如负载均衡器或 API 网关),由这个入口点负责将请求路由到合适的服务实例。
在这里插入图片描述

2.zookeeper

2.1 zookeeper介绍

Apache ZooKeeper 是一个用于分布式系统中的协调服务。它提供了一套高效、可靠的分布式协调工具,用于实现服务注册、配置管理、同步、领导者选举等功能。Zookeeper 的设计初衷是简化分布式应用中的协调任务,从而使应用开发更容易。

  • 是一个分布式数据库(程序协调服务),Hadoop子项目
  • 树状方式维护节点方数据的增、删、改、查
  • 监听通知机制:通过监听可以获取相应消息事件(内容,子节点)

2.2 zookeeper安装

安装zookeeper

  • 参考官方文档安装
    • http://zookeeper.apache.org/doc/r3.6.0/zookeeperStarted.html
    • 下载时需要注意下载的是编译过的二进制文件,不是源码
    • 不然会爆错:找不到或无法加载主类 org.apache.zookeeper.server.quorum.QuorumPeerMain
  • 解压缩
  • 编辑 conf/zoo.cfg
tickTime=2000
dataDir=/var/lib/zookeeper
clientPort=2181
  • 运行 bin/zkServer.sh start

2.3 zookeeper核心功能

在这里插入图片描述

  • 持久节点
    • 一直存在服务器上
  • 临时节点
    • 会话失效,节点自动清理
  • 顺序节点
    • 节点创建,自动分配序列号

2.3.1 增删改查API

package main

import (
	"fmt"
	"github.com/samuel/go-zookeeper/zk"
	"time"
)

var (
	host = []string{"127.0.0.1:2181"}
)

func main() {
	//连接客户端
	conn, _, err := zk.Connect(host, 5*time.Second)
	if err != nil {
		panic(err)
	}

	//增
	if _, err := conn.Create("/test_tree2", []byte("tree_content"),
		0, zk.WorldACL(zk.PermAll)); err != nil {
		fmt.Println("create err", err)
	}

	//查
	nodeValue, dStat, err := conn.Get("/test_tree2")
	if err != nil {
		fmt.Println("get err", err)
		return
	}
	fmt.Println("nodeValue", string(nodeValue))

	//改,需要先查询得到版本号
	if _, err := conn.Set("/test_tree2", []byte("new_content"),
		dStat.Version); err != nil {
		fmt.Println("update err", err)
	}

	//删除,也,需要先查询得到版本号
	_, dStat, _ = conn.Get("/test_tree2")
	if err := conn.Delete("/test_tree2", dStat.Version); err != nil {
		fmt.Println("Delete err", err)
		//return
	}

	//验证存在
	hasNode, _, err := conn.Exists("/test_tree2")
	if err != nil {
		fmt.Println("Exists err", err)
		//return
	}
	fmt.Println("node Exist", hasNode)

	//增加
	if _, err := conn.Create("/test_tree2", []byte("tree_content"),
		0, zk.WorldACL(zk.PermAll)); err != nil {
		fmt.Println("create err", err)
	}

	//设置子节点,如果上游节点不存在则会报错
	if _, err := conn.Create("/test_tree2/subnode", []byte("node_content"),
		0, zk.WorldACL(zk.PermAll)); err != nil {
		fmt.Println("create err", err)
	}

	//获取子节点列表
	childNodes, _, err := conn.Children("/test_tree2")
	if err != nil {
		fmt.Println("Children err", err)
	}
	fmt.Println("childNodes", childNodes)
}

2.3.2 监听子节点变化

package main

import (
	"fmt"
	"github.com/e421083458/gateway_demo/proxy/zookeeper"
	"log"
	"os"
	"os/signal"
	"syscall"
)

var addr = "127.0.0.1:2002"

func main() {
	//获取zk节点列表
	zkManager := zookeeper.NewZkManager([]string{"127.0.0.1:2181"})
	zkManager.GetConnect()
	defer zkManager.Close()
	// 注册一个节点
	err := zkManager.RegistServerPath("/real_server", "127.0.0.1")
	err = zkManager.RegistServerPath("/real_server/test", "127.0.0.1:8823")
	err = zkManager.RegistServerPath("/real_server/test2", "127.0.0.1:8823")
	if err != nil {
		return
	}
	// 获取节点列表
	zlist, err := zkManager.GetServerListByPath("/real_server")
	fmt.Println("server node:")
	fmt.Println(zlist)
	if err != nil {
		log.Println(err)
	}

	//动态监听节点变化
	chanList, chanErr := zkManager.WatchServerListByPath("/real_server")
	go func() {
		for {
			select {
			case changeErr := <-chanErr:
				fmt.Println("changeErr")
				fmt.Println(changeErr)
			case changedList := <-chanList:
				fmt.Println("watch node changed")
				fmt.Println(changedList)
			}
		}
	}()
	time.Sleep(time.Second * 5)
	zkManager.RegistServerPath("/real_server/test3", "127.0.0.2:8888")
	
	//关闭信号监听
	quit := make(chan os.Signal)
	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
	<-quit
}

在这里插入图片描述

2.3.3 监听节点内容变化

package main

import (
	"fmt"
	"github.com/e421083458/gateway_demo/proxy/zookeeper"
	"log"
	"os"
	"os/signal"
	"syscall"
	"time"
)

var addr = "127.0.0.1:2002"
func main() {
	//获取zk节点列表
	zkManager := zookeeper.NewZkManager([]string{"127.0.0.1:2181"})
	zkManager.GetConnect()
	defer zkManager.Close()
	// 注册一个节点
	err := zkManager.RegistServerPath("/rs_server_conf", "192.168.1.101")
	if err != nil {
		fmt.Printf("2001:%v \n", err)
		return
	}
	// 获取节点列表
	zlist, err := zkManager.GetServerListByPath("/rs_server_conf")
	fmt.Println("server node:")
	fmt.Println(zlist)
	if err != nil {
		log.Println(err)
	}
	获取节点内容
	zc, _, err := zkManager.GetPathData("/rs_server_conf")
	if err != nil {
		log.Println(err)
	}
	fmt.Println("get node data:")
	fmt.Println(string(zc))

	//动态监听节点内容
	dataChan, dataErrChan := zkManager.WatchPathData("/rs_server_conf")
	go func() {
		for {
			select {
			case changeErr := <-dataErrChan:
				fmt.Println("changeErr")
				fmt.Println(changeErr)
			case changedData := <-dataChan:
				fmt.Println("WatchGetData changed")
				fmt.Println(string(changedData))
			}
		}
	}()
	// 尝试修改内容
	time.Sleep(5 * time.Second)
	_, z, err := zkManager.GetPathData("/rs_server_conf")
	if err != nil {
		return
	}
	err = zkManager.SetPathData("/rs_server_conf", []byte(addr), z.Version)
	if err != nil {
		fmt.Sprintf("2002:%v \n", err)
		return
	}
	//关闭信号监听
	quit := make(chan os.Signal)
	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
	<-quit
}

在这里插入图片描述

3.网关实现服务发现原理

3.1 网关实现客户端服务发现

在这里插入图片描述

3.2 网关实现服务端服务发现

在这里插入图片描述

  • 将服务注册到zookeeper中
  • 网关通过监听zookeeper中的事件来感知变化

4.网关拓展服务发现

  • 下游机器启动时创建临时节点:节点名与内容为服务地址
  • 以观察者模式构建负载均衡配置LoadBalanceConf
  • 负载均衡配置LoadBalanceConf与负载均衡器整合

4.1 观察者模式

观察者模式(Observer Pattern)是一种行为设计模式,它定义了一种一对多的依赖关系,使得多个观察者对象可以同时监听某一个主题对象。当这个主题对象的状态发生变化时,会通知所有观察者对象,使它们能够自动更新。观察者模式常用于实现事件处理系统,如用户界面事件、订阅/发布系统等。

  • 关键概念
  • 主题(Subject):也称为发布者(Publisher),它维护一组观察者对象,并提供注册和移除观察者的方法。当主题的状态发生变化时,会通知所有观察者。
  • 观察者(Observer):也称为订阅者(Subscriber),它定义了一个更新接口,用于接收来自主题的通知。每个观察者在接收到通知后,可以执行特定的操作。
  • 通知(Notification):指的是主题状态变化时向观察者发送的信号或消息。

4.2 以观察者模式构建负载均衡配置

在这里插入图片描述

package load_balance

import (
	"fmt"
	"github.com/e421083458/gateway_demo/proxy/zookeeper"
)

// 配置主题
type LoadBalanceConf interface {
	Attach(o Observer)
	GetConf() []string
	WatchConf()
	UpdateConf(conf []string)
}

type LoadBalanceZkConf struct {
	observers    []Observer
	path         string
	zkHosts      []string
	confIpWeight map[string]string
	activeList   []string
	format       string
}

func (s *LoadBalanceZkConf) Attach(o Observer) {
	s.observers = append(s.observers, o)
}

func (s *LoadBalanceZkConf) NotifyAllObservers() {
	for _, obs := range s.observers {
		obs.Update()
	}
}

func (s *LoadBalanceZkConf) GetConf() []string {
	confList := []string{}
	for _, ip := range s.activeList {
		weight, ok := s.confIpWeight[ip]
		if !ok {
			weight = "50" //默认weight
		}
		confList = append(confList, fmt.Sprintf(s.format, ip)+","+weight)
	}
	return confList
}

//更新配置时,通知监听者也更新
func (s *LoadBalanceZkConf) WatchConf() {
	zkManager := zookeeper.NewZkManager(s.zkHosts)
	zkManager.GetConnect()
	fmt.Println("watchConf")
	chanList, chanErr := zkManager.WatchServerListByPath(s.path)
	go func() {
		defer zkManager.Close()
		for {
			select {
			case changeErr := <-chanErr:
				fmt.Println("changeErr", changeErr)
			case changedList := <-chanList:
				fmt.Println("watch node changed")
				s.UpdateConf(changedList)
			}
		}
	}()
}

//更新配置时,通知监听者也更新
func (s *LoadBalanceZkConf) UpdateConf(conf []string) {
	s.activeList = conf
	for _, obs := range s.observers {
		obs.Update()
	}
}

func NewLoadBalanceZkConf(format, path string, zkHosts []string, conf map[string]string) (*LoadBalanceZkConf, error) {
	zkManager := zookeeper.NewZkManager(zkHosts)
	zkManager.GetConnect()
	defer zkManager.Close()
	zlist, err := zkManager.GetServerListByPath(path)
	if err != nil {
		return nil, err
	}
	mConf := &LoadBalanceZkConf{format: format, activeList: zlist, confIpWeight: conf, zkHosts: zkHosts, path: path}
	mConf.WatchConf()
	return mConf, nil
}

type Observer interface {
	Update()
}

type LoadBalanceObserver struct {
	ModuleConf *LoadBalanceZkConf
}

func (l *LoadBalanceObserver) Update() {
	fmt.Println("Update get conf:", l.ModuleConf.GetConf())
}

func NewLoadBalanceObserver(conf *LoadBalanceZkConf) *LoadBalanceObserver {
	return &LoadBalanceObserver{
		ModuleConf: conf,
	}
}

4.3 负载均衡配置LoadBalanceConf与负载均衡器整合

package main

import (
	"github.com/e421083458/gateway_demo/proxy/load_balance"
	"github.com/e421083458/gateway_demo/proxy/middleware"
	proxy2 "github.com/e421083458/gateway_demo/proxy/proxy"
	"log"
	"net/http"
)

var (
	addr = "127.0.0.1:2002"
)

func main() {
	mConf, err := load_balance.NewLoadBalanceZkConf("http://%s/base",
		"/real_server",
		[]string{"127.0.0.1:2181"},
		map[string]string{"127.0.0.1:2003": "20"})
	if err != nil {
		panic(err)
	}
	rb := load_balance.LoadBanlanceFactorWithConf(load_balance.LbWeightRoundRobin, mConf)
	proxy := proxy2.NewLoadBalanceReverseProxy(&middleware.SliceRouterContext{}, rb)
	log.Println("Starting httpserver at " + addr)
	log.Fatal(http.ListenAndServe(addr, proxy))
}

4.4 客户端服务发现实现

网关主动通过心跳检测区检测客户端的服务
在这里插入图片描述

  • 下游机器启动时无需进行任何操作
  • 以观察者模式构建负载均衡配置LoadBalanceConf
  • 负载均衡配置固定时间频率监测下游节点健康状况
package load_balance

import (
	"fmt"
	"net"
	"reflect"
	"sort"
	"time"
)

const (
	//default check setting
	DefaultCheckMethod    = 0
	DefaultCheckTimeout   = 2
	DefaultCheckMaxErrNum = 2
	DefaultCheckInterval  = 5
)

type LoadBalanceCheckConf struct {
	observers    []Observer
	confIpWeight map[string]string
	activeList   []string
	format       string
}

func (s *LoadBalanceCheckConf) Attach(o Observer) {
	s.observers = append(s.observers, o)
}

func (s *LoadBalanceCheckConf) NotifyAllObservers() {
	for _, obs := range s.observers {
		obs.Update()
	}
}

func (s *LoadBalanceCheckConf) GetConf() []string {
	confList := []string{}
	for _, ip := range s.activeList {
		weight, ok := s.confIpWeight[ip]
		if !ok {
			weight = "50" //默认weight
		}
		confList = append(confList, fmt.Sprintf(s.format, ip)+","+weight)
	}
	return confList
}

//更新配置时,通知监听者也更新
func (s *LoadBalanceCheckConf) WatchConf() {
	fmt.Println("watchConf")
	go func() {
		confIpErrNum := map[string]int{}
		for {
			changedList := []string{}
			for item, _ := range s.confIpWeight {
				conn, err := net.DialTimeout("tcp", item, time.Duration(DefaultCheckTimeout)*time.Second)
				//todo http statuscode
				if err == nil {
					conn.Close()
					if _, ok := confIpErrNum[item]; ok {
						confIpErrNum[item] = 0
					}
				}
				if err != nil {
					if _, ok := confIpErrNum[item]; ok {
						confIpErrNum[item] += 1
					} else {
						confIpErrNum[item] = 1
					}
				}
				if confIpErrNum[item] < DefaultCheckMaxErrNum {
					changedList = append(changedList, item)
				}
			}
			sort.Strings(changedList)
			sort.Strings(s.activeList)
			if !reflect.DeepEqual(changedList, s.activeList) {
				s.UpdateConf(changedList)
			}
			time.Sleep(time.Duration(DefaultCheckInterval) * time.Second)
		}
	}()
}

//更新配置时,通知监听者也更新
func (s *LoadBalanceCheckConf) UpdateConf(conf []string) {
	fmt.Println("UpdateConf", conf)
	s.activeList = conf
	for _, obs := range s.observers {
		obs.Update()
	}
}

func NewLoadBalanceCheckConf(format string, conf map[string]string) (*LoadBalanceCheckConf, error) {
	aList := []string{}
	//默认初始化
	for item, _ := range conf {
		aList = append(aList, item)
	}
	mConf := &LoadBalanceCheckConf{format: format, activeList: aList, confIpWeight: conf}
	mConf.WatchConf()
	return mConf, nil
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

nginx的LNMP构建+discuz论坛

一、LNMP&#xff1a; L&#xff1a;linux 操作系统 N&#xff1a;nginx前端页面的web服务 P&#xff1a;PHP&#xff0c;是一种开发动态页面的编程语言&#xff0c;解析动态页面&#xff0c;起到中间件的作用&#xff08;在nginx和数据库的中间&#xff09;&#xff0c;在中…

该文件没有与之关联的程序来执行该操作,请安装应用,若已经安装应用,请在‘默认应用设置’页面中创建关联。

作为一个喜欢折腾桌面外观的人,我发现桌面上的快捷方式图标都有一个小箭头。于是,我按照网上的方法在注册表中删除了 IsShortcut 键。结果,重启后任务栏上的图标点击时出现了提示:“该文件没有与之关联的程序来执行该操作,请安装应用,若已经安装应用,请在‘默认应用设置…

UnityUGUI之三 Text

富文本 常用语法&#xff1a; 1.加粗 <b> text </b> 2.斜体 <i> text </i> 3.尺寸 <size?> text </size> 4.颜色 <color#ff0000> text </color>

html+js+css美观好看的动态404界面

中间的那一段话&#xff08;root开头的那一句&#xff09;是逐字输出的 那段话显示完后&#xff0c;自动显示超大号字体404 来都来了点个赞&#xff0c;关注一下呗&#x1f604;&#xff0c;本人发誓&#xff1a;你关注我&#xff0c;马上关注你 界面 源码在图片下面…

E1696 无法打开 源 文件 “point.h“

一段时间没碰vs2022突然导入一个项目就出现下面错误 在网上查了很多办法&#xff0c;都没什么有用。 试了试&#xff0c;相对路径可以解决。 但是每次都要用相对路径太麻烦了。 又试了试&#xff0c;发现还是硬件问题&#xff0c;就像摩托长期不开等到突然想开的时候就死活打…

通信软件开发之业务知识:PON口割接什么意思?

一 PON口割接&#xff08;原创总结&#xff09; 在通信领域&#xff0c;PON口割接指的是对无源光网络&#xff08;Passive Optical Network&#xff0c;PON&#xff09;端口进行的切换或调整操作。简单来说&#xff0c;就是对光纤网络中的某个端口进行重新连接或重新分配&…

2024鸿翼加速推进数据要素生产力,“五驾马车”再启新鸿图

过去的2023年&#xff0c;在大家逐步走出3年疫情&#xff0c;对经济复苏的美好期待中&#xff0c;一路“高开低走”的市场态势&#xff0c;相信让许多的数字化从业者感受到了业务的沮丧和寒意。 但是&#xff0c;即便整个行业受经济大环境影响&#xff0c;鸿翼依旧逆势取得了连…

UE5 04-重新加载当前场景

给关卡加一个淡出的效果 给关卡加一个淡入的效果, 这个最好放置在Player 上,这样切关卡依然有这个效果

使用Charles实现Android抓包,附带Charles破解教程

1.下载Charles 网址&#xff1a;下载Charles 安装完成后的界面&#xff1a; 2.配置http抓包 点击该选项 可以看到代理的 ip 和端口号 然后在手机的wifi中配置代理&#xff08;手机和电脑要在同一局域网&#xff09;&#xff0c;代理选择手动&#xff0c;并填入ip和端…

UE5 02-给物体一个扭矩力

需要注意的是: 1.弹簧臂 可以使用绝对旋转 这样就可以不跟随父物体Player的旋转 2.弹簧臂 进行碰撞测试勾选,当这个弹簧线被遮挡,摄像机会切换到碰撞点位置 进行碰撞测试勾选,当这个弹簧线被遮挡,摄像机不会切换到碰撞点位置

yolov8 目标检测快速streamlit可视化界面

参考&#xff1a; https://github.com/ultralytics/ultralytics/blob/2330caa50a8a8e0bb61408df8dca0721fb350dbe/ultralytics/solutions/streamlit_inference.py 版本&#xff1a; ultralytics 8.2.27 # Ultralytics YOLO &#x1f680;, AGPL-3.0 licen…

买卖股票的最佳时期含冷冻期(leetcode)

个人主页&#xff1a;Lei宝啊 愿所有美好如期而遇 也就有这样的状态转移方程&#xff1a; 买入&#xff1a;dp[i][0] max(dp[i-1][1] - prices[i], dp[i-1][0]); 可买入&#xff1a;dp[i][1] max(dp[i-1][1], dp[i-1][2]); 冷冻期&#xff1a;dp[i][2] dp[i-1][0] prices…

mybatis、mybatis-plus插件开发,实现数据脱敏功能

首先说一下mybatis中四大组件的作用&#xff0c;下面开发的插件拦截器会使用 四大组件Executor、StatementHandler、ParameterHandler、ResultSetHandler Executor&#xff1a; Executor 是 MyBatis 中的执行器&#xff0c;负责 SQL 语句的执行工作。它通过调度 StatementHan…

在SpringBoot 3.0环境下创建一个SpringBoot 项目

一、环境配置 1.专业版的IDEA 版本号&#xff1a;尽量选择不要太老&#xff0c;不要太早 这里以2023.3.1为例。 官网&#xff1a;Download IntelliJ IDEA – The Leading Java and Kotlin IDE (jetbrains.com) 破解版&#xff1a;网上找资料哦&#xff01;&#xff01;&#…

【Python】基于动态规划和K聚类的彩色图片压缩算法

引言 当想要压缩一张彩色图像时&#xff0c;彩色图像通常由数百万个颜色值组成&#xff0c;每个颜色值都由红、绿、蓝三个分量组成。因此&#xff0c;如果我们直接对图像的每个像素进行编码&#xff0c;会导致非常大的数据量。为了减少数据量&#xff0c;我们可以尝试减少颜色…

7.7、指针和函数

代码 #include <iostream> using namespace std;//实现两个数字进行交换 void swap01(int a, int b) {int temp a;a b;b temp;cout << "swap01a " << a << endl;cout << "swap01b " << b << endl; }void sw…

AUTOSAR NvM模块(七)

NvM工具配置demo 一切block的配置根据自己的需求&#xff01; NvMBlockDescriptor NvM Common MemIf General FeeBlockConfiguration FeeGeneral

Go语言学习:每日一练3

Go语言学习&#xff1a;每日一练3 目录 Go语言学习&#xff1a;每日一练3方法接口继承类型断言 方法 方法是一类有接收者参数的函数。 接收者的类型定义和方法的声明必须在一个包里 type MyInt intfunc (m MyInt) Add(add int) int {return int(m) add } //OR func (m *MyInt)…

苹果Mac电脑能玩什么游戏 Mac怎么运行Windows游戏

相对于Windows平台来说&#xff0c;Mac电脑可玩的游戏较少。虽然苹果设备的性能足以支持各种大型游戏&#xff0c;但由于系统以及苹果配套服务的限制&#xff0c;很多游戏无法在Mac系统中运行。不过&#xff0c;借助虚拟机软件&#xff0c;Mac电脑可以突破系统限制玩更多的游戏…

66.Python-web框架-Django-免费模板django-datta-able的分页的一种方式

目录 1.方案介绍 1.1实现效果 1.2django.core.paginator Paginator 类: Page 类: EmptyPage 和 PageNotAnInteger 异常: 1.3 templatetags 2.方案步骤 2.1创建一个common app 2.2创建plugins/_pagination.html 2.3 其他app的views.py查询方法 2.4在AIRecords.html里…