千万级入口服务[Gateway]框架设计(二)

news2024/11/24 6:32:14

本文将以技术调研模式编写,非技术同学可跳过。

文章目录

    • 背景
      • 实现二:开源 go-plugin
      • Demo 实现
      • Benchwork 基准性能
      • 小结
    • 附录

背景

基于组件(插件)模式设计构建的入口服务,在使用 Go 原生包 plugin 实现的时候,会存在功能缺陷问题,不足以支撑预期能力。

注:详细见上文 《千万级入口服务[Gateway]框架设计(一)》

千万级入口服务[Gateway]框架设计(一)
本文将继续介绍另一种关于 go-plugin 的开源实现。

实现二:开源 go-plugin

针对前文提到的几个问题,go-plugin 提出以 rpc 协议通信的方式进行组件搭建。
在这里插入图片描述

将每个组件进行服务封装,主程序、组件之间以通信方式进行交互,这样可以完全规避原生的不足。

  1. 组件是 Go 接口的实现:这让组件的编写、使用非常自然。对于组件的作者来说,他只需要实现一个Go 接口即可;对于组件的用户来说,他只需要调用一个Go 接口即可。
  2. 跨语言支持:组件可以基于任何主流语言编写,同样可以被任何主流语言消费
  3. 支持复杂的参数、返回值:go-plugin 可以处理接口、io.Reader/Writer 等复杂类型
  4. 双向通信:为了支持复杂参数,宿主进程能够将接口实现发送给组件,组件也能够回调到宿主进程
  5. 内置日志系统:任何使用 log 标准库的的组件,都会将日志信息传回宿主机进程。宿主进程会在这些日志前面加上组件二进制文件的路径,并且打印日志
  6. 协议版本化:支持一个简单的协议版本化,增加版本号后可以基于老版本协议的组件无效化。
  7. 标准输出/错误同步:组件以子进程的方式运行,这些子进程可以自由的使用标准输出/错误,并且打印的内容会被自动同步到宿主进程,宿主进程可以为同步的日志指定一个 io.Writer
  8. TTY Preservation:组件子进程可以链接到宿主进程的 stdin 文件描述符,以便要求 TTY 的软件能正常工作
  9. 宿主进程升级:宿主进程升级的时候,组件子进程可以继续允许,并在升级后自动关联到新的宿主进程
  10. 加密通信:gRPC 信道可以加密
  11. 完整性校验:支持对组件的二进制文件进行 Checksum
  12. 稳定性保障:组件崩溃了,不会导致宿主进程崩溃
  13. 容易安装:只需要将组件放到某个宿主进程能够访问的目录

Demo 实现

实现分为三部分:主程序、组件程序、公共库。公共库可与主程序所属库共同,组件程序进行包引用即可。

  • 主程序
package main

import (
	"fmt"
	"log"
	"os"
	"os/exec"
	"time"

	"github.com/hashicorp/go-hclog"
	"github.com/hashicorp/go-plugin"

	util "XXXXX"
)

var pluginMap = map[string]plugin.Plugin{
	// 插件名称到插件对象的映射关系
	"s":   &util.GreeterPlugin{},
	"bar": &util.GreeterPlugin{},
}

func main() {
	// 创建hclog.Logger类型的日志对象
	logger := hclog.New(&hclog.LoggerOptions{
		Name:   "plugin",
		Output: os.Stdout,
		Level:  hclog.Debug,
	})
	req := util.Req{}
	for i := 0; i < 10; i++ {
		fmt.Println("for i:", i) // 验证热加载功能
		req.Str = RandStr(i)
		var mod string
		if mod = Dispatch(req.Str); len(mod) < 1 {
			fmt.Println("don't deal str")
			os.Exit(1)
		}
		// 两种方式选其一
		// 以exec.Command方式启动插件进程,并创建宿主机进程和插件进程的连接
		// 或者使用Reattach连接到现有进程
		client := plugin.NewClient(&plugin.ClientConfig{
			HandshakeConfig: util.HandshakeConfig,
			Plugins:         pluginMap,
			// 创建新进程,或使用Reattach连接到现有进程中
			Cmd:    exec.Command(mod),
			Logger: logger,
		})
		// 关闭client,释放相关资源,终止插件子程序的运行
		defer client.Kill()

		// 返回协议客户端,如rpc客户端或grpc客户端,用于后续通信
		rpcClient, err := client.Client()
		if err != nil {
			log.Fatal(err)
		}

		// 根据指定插件名称分配新实例
		raw, err := rpcClient.Dispense(req.Str)
		if err != nil {
			log.Fatal(err)
		}

		// 像调用普通函数一样调用接口函数就ok,很方便是不是?
		greeter := raw.(util.Greeter)
		fmt.Println(greeter.Greet())
		fmt.Println(greeter.Speak(req.Str))
		fmt.Println(greeter.Execute(req).Msg)
		time.Sleep(1 * time.Second)
	}
}

func Dispatch(str string) string {
	var mod string
	switch str {
	case "A":
		mod = "./X/A"
	case "B":
		mod = "./X/B"
	default:
	}
	return mod
}

func RandStr(i int) string {
	var str = "B"
	if i%2 == 0 {
		str = "A"
	}
	return str
}
  • 组件程序
package main

import (
	"os"

	"github.com/hashicorp/go-hclog"
	"github.com/hashicorp/go-plugin"

	util "XXX"
)

// Here is a real implementation of Greeter
type A struct {
	logger hclog.Logger
}

func (g *A) Greet() string {
	g.logger.Debug("message from A.Greet")
	return "A Hello!"
}

func (g *A) Speak(str string) string {
	return "Now A-" + str + " is speaking!"
}

func (g *A) Execute(req util.Req) util.Res {
	return util.Res{Msg: "Now A-" + req.Str + " is executing!"}
}

func main() {
	logger := hclog.New(&hclog.LoggerOptions{
		Level:      hclog.Trace,
		Output:     os.Stderr,
		JSONFormat: true,
	})

	greeter := &A{
		logger: logger,
	}
	// pluginMap is the map of plugins we can dispense.
	var pluginMap = map[string]plugin.Plugin{
		"A": &util.GreeterPlugin{Impl: greeter},
	}

	plugin.Serve(&plugin.ServeConfig{
		HandshakeConfig: util.HandshakeConfig,
		Plugins:         pluginMap,
	})
}


运行:go build -o ./X/A main-plugin-A.go

  • 公共库
package util

import (
	"net/rpc"

	"github.com/hashicorp/go-plugin"
)

type Req struct {
	Str string
}

type Res struct {
	Msg string
}

// Greeter is the interface that we're exposing as a plugin.
type Greeter interface {
	Greet() string
	Speak(string) string
	Execute(Req) Res
}

// Here is the RPC server that GreeterRPC talks to, conforming to
// the requirements of net/rpc
type GreeterRPCServer struct {
	// This is the real implementation
	Impl Greeter
}

func (s *GreeterRPCServer) Greet(args any, resp *string) error {
	*resp = s.Impl.Greet()
	return nil
}

func (s *GreeterRPCServer) Speak(str string, resp *string) error {
	*resp = s.Impl.Speak(str)
	return nil
}

func (s *GreeterRPCServer) Execute(req Req, resp *Res) error {
	*resp = s.Impl.Execute(req)
	return nil
}

// Here is an implementation that talks over RPC
type GreeterRPC struct{ client *rpc.Client }

func (g *GreeterRPC) Greet() string {
	var resp string
	err := g.client.Call("Plugin.Greet", new(any), &resp)
	if err != nil {
		// You usually want your interfaces to return errors. If they don't,
		// there isn't much other choice here.
		panic(err)
	}
	return resp
}

func (g *GreeterRPC) Speak(str string) string {
	var resp string
	err := g.client.Call("Plugin.Speak", str, &resp)
	if err != nil {
		// You usually want your interfaces to return errors. If they don't,
		// there isn't much other choice here.
		panic(err)
	}
	return resp
}

func (g *GreeterRPC) Execute(req Req) Res {
	var resp Res
	err := g.client.Call("Plugin.Execute", req, &resp)
	if err != nil {
		// You usually want your interfaces to return errors. If they don't,
		// there isn't much other choice here.
		panic(err)
	}
	return resp
}

type GreeterPlugin struct {
	// 内嵌业务接口
	// 插件进程会设置其为实现业务接口的对象
	// 宿主进程则置空
	Impl Greeter
}

// 此方法由插件进程延迟的调用
func (p *GreeterPlugin) Server(*plugin.MuxBroker) (any, error) {
	return &GreeterRPCServer{Impl: p.Impl}, nil
}

// 此方法由宿主进程调用
func (GreeterPlugin) Client(b *plugin.MuxBroker, c *rpc.Client) (any, error) {
	return &GreeterRPC{client: c}, nil
}

var HandshakeConfig = plugin.HandshakeConfig{
	ProtocolVersion:  1,
	MagicCookieKey:   "BASIC_PLUGIN",
	MagicCookieValue: "hello",
}

Benchwork 基准性能

go test -bench BenchmarkMainDeal -benchtime=5s -benchmem
......timestamp=2023-06-08T15:07:35.095+0800
     963           6329922 ns/op          156723 B/op        844 allocs/op
PASS
ok      XXX  6.736s

小结

虽然开源组件打破了原生包的囧境,但其实质是本地的 RPC 调用,存在本地网络开销。对比前文我们原生包的 Benchwork 指标,性能相对不足。尤其是在调用过程中的序列化、反序列化,在频繁交互的场景下,是木桶璧的短板所在。

在这里插入图片描述

当然,针对 Go 来讲,可以使用 GRPC 协议,充分降低短板对整体的影响占比。在一些性能适中的场景下,是完全满足需求的。


附录

  • https://eli.thegreenplace.net/2023/rpc-based-plugins-in-go/

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

PSP - RoseTTAFold2 的 PDB 结果预处理

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://blog.csdn.net/caroline_wendy/article/details/131201456 RoseTTAFold2 是蛋白质结构预测算法&#xff0c;利用了深度学习和三维几何建模的技术&#xff0c;能够快速准确地预测蛋白质的…

【社区图书馆】《看漫画学Python:有趣、有料、好玩、好用(全彩修订版)》

背景 Python是一门既简单又强大的编程语言&#xff0c;被广泛应用于数据分析、大数据、网络爬虫、自动化运维、科学计算和人工智能等领域。Python也越来越重要&#xff0c;成为国家计算机等级考试科目&#xff0c;某些中小学也开设了Python编程课程。本书秉承有趣、有料、好玩…

【方法】PDF文档可以转图片吗?如何操作?

需要把PDF文档转换成图片&#xff0c;你会怎么做呢&#xff1f; 有些小伙伴可能会直接截图保存&#xff0c;确实是一个快捷的方法&#xff0c;但这种方法容易造成图片质量的损失&#xff0c;也可能遇到无法截图整个页面的情况&#xff0c;或者文档页面多&#xff0c;截图耗费很…

【Android -- JNI 和 NDK】JNI 基础知识以及如何使用

JNI 基础知识 我们来系统梳理一下JNI中涉及的基本知识。 JNI定义了以下数据类型&#xff0c;这些类型和Java中的数据类型是一致的&#xff1a; Java原始类型&#xff1a;jint, jbyte, jshort, jlong, jfloat, jdouble, jchar, jboolean这些分别对应这 java 的int, byte, shor…

java反射调用get/set方法

1 前言 最新工作中&#xff0c;遇到了通过反射调用get/set方法的地方&#xff0c;虽然反射的性能不是很好&#xff0c;但是相比较于硬编码的不易扩展&#xff0c;getDeclareFields可以拿到所有的成员变量&#xff0c;后续添加或删除成员变量时&#xff0c;不用修改代码&#x…

Vue组件——动态、缓存、异步组件

1. 动态组件 component 语法&#xff1a;静态&#xff1a;<component is"组件名"></component> 动态&#xff1a;<component :is"组件名"></component> 可以用来实现页面的切换&#xff1a; // 模板<component :is"n&q…

目标检测数据集---三星工业缺陷数据集

✨✨✨✨✨✨目标检测数据集✨✨✨✨✨✨ 本专栏提供各种场景的数据集,主要聚焦:工业缺陷检测数据集、小目标数据集、遥感数据集、红外小目标数据集,该专栏的数据集会在多个专栏进行验证,在多个数据集进行验证mAP涨点明显,尤其是小目标、遮挡物精度提升明显的数据集会在该…

如何利用工业RFID识别设备帮助企业提高产品质量?

ANDEAWELL作为国内知名的工业识别设备厂家&#xff0c;今天就跟大家一起来了解一下&#xff0c;企业如何利用工业RFID识别设备帮助企业提高产品质量。 1. 严格的质量控制流程 在生产过程中&#xff0c;我们需要建立一套严格的质量控制流程。这个流程应该包括原材料的采购、生产…

【前端 - CSS】第 16 课 - 伪类选择器(鼠标悬停状态)

欢迎来到博主 Apeiron 的博客&#xff0c;祝您旅程愉快 &#xff01; 时止则止&#xff0c;时行则行。动静不失其时&#xff0c;其道光明。 目录 1、缘起 2、伪类选择器 3、伪类 - 超链接&#xff08;拓展&#xff09; 4、总结 1、缘起 在 CSS 中&#xff0c;我们使用 hover…

4、nacos服务的linux部署

1、下载nacoz注册中心 Nacos 快速开始通过这个里面查找地址进行下载, 2、下载之后通过xshell的xftp上传到服务器上&#xff0c;比如上传到home下的soft&#xff0c;如果没有soft进行创建 chmod对文件夹进行授权&#xff0c;不然xftp应该上传不到这个文件夹 3、解压缩 解压缩之…

【笔试强训选择题】Day26.习题(错题)解析

作者简介&#xff1a;大家好&#xff0c;我是未央&#xff1b; 博客首页&#xff1a;未央.303 系列专栏&#xff1a;笔试强训选择题 每日一句&#xff1a;人的一生&#xff0c;可以有所作为的时机只有一次&#xff0c;那就是现在&#xff01;&#xff01;&#xff01;&#xff…

100种思维模型之复利原理-77

关于复利&#xff0c;有一个广为流传的故事&#xff0c;相信大家都听过&#xff1a;在国际象棋的棋盘上&#xff0c;第一个格子放1粒米&#xff0c;第二格翻倍放2粒&#xff0c;第三格再翻倍放4粒&#xff0c;以此类推&#xff0c;下一格都是前一格的一倍&#xff0c;一直放到最…

Stimulsoft Reports用户手册:Report Designer介绍

Stimulsoft Reports.Net是一个基于.NET框架的报表生成器&#xff0c;能够帮助你创建结构、功能丰富的报表。StimulReport.Net 的报表设计器不仅界面友好&#xff0c;而且使用便捷&#xff0c;能够让你轻松创建所有报表&#xff1b;该报表设计器在报表设计过程中以及报表运行的过…

005、体系结构之TiKV_Raft日志

Raft日志 1、Raft与Multi Raft2、Raft 日志复制2.1、复制流程总览2.2、Propose2.3、Append2.3、Replicate(Append)2.4 Committed2.4 Apply 3、Raft Leader 选举3.1、原理3.2、节点故障Leader&#xff08;主副本&#xff09;选举⽇志复制 1、Raft与Multi Raft 一个region的大小是…

MPI期末复习指南

&#x1f34e; 博客主页&#xff1a;&#x1f319;披星戴月的贾维斯 &#x1f34e; 欢迎关注&#xff1a;&#x1f44d;点赞&#x1f343;收藏&#x1f525;留言 &#x1f347;系列专栏&#xff1a;&#x1f319; C/C专栏 &#x1f319;那些看似波澜不惊的日复一日&#xff0c;…

国家版权局正版化检查工具添加自定义检查软件及问题处理

使用国家版权局正版化检查工具进行软件正版化检查时&#xff0c;根据各个单位购买的正版化软件的不同&#xff0c;需要将自购软件和禁用软件增加到检查清单&#xff0c;本文件介绍添加自定义检查软件的方法及问题处理。 一、检查清单文件介绍及修改方法 国家版权局正版化检查工…

SpringMVC Controller 接收页面传递的中文参数出现乱码

问题描述 今天在使用SpringMVC做项目时候 controller 参数出现乱码 按照网上的搜索结果 对tomcat的server.xml和项目中的web.xml做出配置如下 在tomcat的server.xml中找到Connector标签然后对他重新配置 <Connector port"8080" protocol"HTTP/1.1"co…

Falcon 登陆 Hugging Face 生态

引言 Falcon 是由位于阿布扎比的 技术创新研究院 (Technology Innovation Institute, TII) 创建的一系列的新语言模型&#xff0c;其基于 Apache 2.0 许可发布。值得注意的是&#xff0c;Falcon-40B 是首个“真正开放”的模型&#xff0c;其能力可与当前许多闭源模型相媲美。这…

MySQL | 深入了解如何最大化利用 MySQL 函数(一)

前言 ✨欢迎来到小K的MySQL专栏&#xff0c;本节将为大家带来MySQL字符串函数和数学函数的讲解✨ 目录 前言一、字符串函数二、数学函数三、总结 一、字符串函数 函数作用UPPER(列|字符串)将字符串每个字符转为大写LOWER(列|字符串)将字符串每个字符转为小写CONCAT(str1,str2,…

AI 写作,30 秒上手,可别再说写作没思路了

你经常要与文字打交道吗&#xff0c;是不是也会有以下困惑&#xff1f; 写作难下笔写不好&#xff1f;课程制作难缺灵感&#xff1f;营销文案没吸引力&#xff1f;PPT制作耗时费力&#xff1f;短视频脚本没创意&#xff1f; ChatGPT 出现以后&#xff0c;嗅觉灵敏的先行者&…