iis服务器助手广告广告
返回顶部
首页 > 资讯 > 后端开发 > GO >Go创建Grpc链接池实现过程详解
  • 310
分享到

Go创建Grpc链接池实现过程详解

Go创建Grpc链接池Go Grpc 2023-03-03 14:03:17 310人浏览 薄情痞子
摘要

目录常规用法创建链接池创建链接池接口实现链接池接口关闭链接扩缩容性能测试常规用法 grpc 四种基本使用 请求响应模式客户端数据流模式服务端数据流模式双向流模式 常见的grpc调用

常规用法

grpc 四种基本使用

  • 请求响应模式
  • 客户端数据流模式
  • 服务端数据流模式
  • 双向流模式

常见的grpc调用写法

func main(){
	//... some code
	// 链接grpc服务
	conn , err := grpc.Dial(":8000",grpc.WithInsecure)
	if err != nil {
		//...log
	}
	defer conn.Close()
	//...some code

存在的问题:面临高并发的情况,性能问题很容易就会出现,例如我们在做性能测试的时候,就会发现,打一会性能测试,客户端请求服务端的时候就会报错:

rpc error: code = Unavailable desc = all SubConns are in TransientFailure, latest connection error: connection error: desc = "transport: Error while dialing dial tcp xxx:xxx: connect: connection refused

实际去查看问题的时候,很明显,这是 gRPC 的连接数被打满了,很多连接都还未完全释放。[#本文来源:janrs.com#]

gRPC 的通信本质上也是 TCP 的连接,那么一次连接就需要三次握手,和四次挥手,每一次建立连接和释放连接的时候,都需要走这么一个过程,如果我们频繁的建立和释放连接,这对于资源和性能其实都是一个大大的浪费。

在服务端,gRPC 服务端的链接管理不用我们操心,但是 gRPC 客户端的链接管理非常有必要关心,要实现复用客户端的连接。

创建链接池

创建链接池需要考虑的问题:

  • 连接池是否支持扩缩容
  • 空闲的连接是否支持超时自行关闭,是否支持保活
  • 池子满的时候,处理的策略是什么样的

创建链接池接口

type Pool interface {
	// 获取一个新的连接 , 当关闭连接的时候,会将该连接放入到池子中
   Get() (Conn, error)
	// 关闭连接池,自然连接池子中的连接也不再可用
   Close() error
	//[#本文来源:janrs.com#]
   Status() string
}

实现链接池接口

创建链接池代码

func New(address string, option Options) (Pool, error) {
   if address == "" {
      return nil, errors.New("invalid address settings")
   }
   if option.Dial == nil {
      return nil, errors.New("invalid dial settings")
   }
   if option.MaxIdle <= 0 || option.MaxActive <= 0 || option.MaxIdle > option.MaxActive {
      return nil, errors.New("invalid maximum settings")
   }
   if option.MaxConcurrentStreams <= 0 {
      return nil, errors.New("invalid maximun settings")
   }
   p := &pool{
      index:   0,
      current: int32(option.MaxIdle),
      ref:     0,
      opt:     option,
      conns:   make([]*conn, option.MaxActive),
      address: address,
      closed:  0,
   }
   for i := 0; i < p.opt.MaxIdle; i++ {
      c, err := p.opt.Dial(address)
      if err != nil {
         p.Close()
         return nil, fmt.Errorf("dial is not able to fill the pool: %s", err)
      }
      p.conns[i] = p.wrapConn(c, false)
   }
   log.Printf("new pool success: %v\n", p.Status())
   return p, nil
}

关于以上的代码,需要特别注意每一个连接的建立也是在 New 里面完成的,[#本文来源:janrs.com#]只要有 1 个连接未建立成功,那么咱们的连接池就算是建立失败,咱们会调用 p.Close() 将之前建立好的连接全部释放掉。

关闭链接池代码

// 关闭连接池
func (p *pool) Close() error {
   atomic.StoreInt32(&p.closed, 1)
   atomic.StoreUint32(&p.index, 0)
   atomic.StoreInt32(&p.current, 0)
   atomic.StoreInt32(&p.ref, 0)
   p.deleteFrom(0)
   log.Printf("[janrs.com]close pool success: %v\n", p.Status())
   return nil
}

从具体位置删除链接池代码

// 清除从 指定位置开始到 MaxActive 之间的连接
func (p *pool) deleteFrom(begin int) {
   for i := begin; i < p.opt.MaxActive; i++ {
      p.reset(i)
   }
}

销毁具体的链接代码

// 清除具体的连接
func (p *pool) reset(index int) {
   conn := p.conns[index]
   if conn == nil {
      return
   }
   conn.reset()
   p.conns[index] = nil
}

关闭链接

代码

func (c *conn) reset() error {
   cc := c.cc
   c.cc = nil
   c.once = false
   // 本文博客来源:janrs.com
   if cc != nil {
      return cc.Close()
   }
   return nil
}
func (c *conn) Close() error {
   c.pool.decrRef()
   if c.once {
      return c.reset()
   }
   return nil
}

在使用连接池通过 pool.Get() 拿到具体的连接句柄 conn 之后,会使用 conn.Close()关闭连接,实际上也是会走到上述的 Close() 实现的位置,但是并未指定当然也没有权限显示的指定将 once 置位为 false ,也就是对于调用者来说,是关闭了连接,对于连接池来说,实际上是将连接归还到连接池中。

扩缩容

关键代码

func (p *pool) Get() (Conn, error) {
   // the first selected from the created connections
   nextRef := p.incrRef()
   p.RLock()
   current := atomic.LoadInt32(&p.current)
   p.RUnlock()
   if current == 0 {
      return nil, ErrClosed
   }
   if nextRef <= current*int32(p.opt.MaxConcurrentStreams) {
      next := atomic.AddUint32(&p.index, 1) % uint32(current)
      return p.conns[next], nil
   }
   // 本文博客来源:janrs.com
   // the number connection of pool is reach to max active
   if current == int32(p.opt.MaxActive) {
      // the second if reuse is true, select from pool's connections
      if p.opt.Reuse {
         next := atomic.AddUint32(&p.index, 1) % uint32(current)
         return p.conns[next], nil
      }
      // the third create one-time connection
      c, err := p.opt.Dial(p.address)
      return p.wrapConn(c, true), err
   }
   // the fourth create new connections given back to pool
   p.Lock()
   current = atomic.LoadInt32(&p.current)
   if current < int32(p.opt.MaxActive) && nextRef > current*int32(p.opt.MaxConcurrentStreams) {
      // 2 times the incremental or the remain incremental  ##janrs.com
      increment := current
      if current+increment > int32(p.opt.MaxActive) {
         increment = int32(p.opt.MaxActive) - current
      }
      var i int32
      var err error
      for i = 0; i < increment; i++ {
         c, er := p.opt.Dial(p.address)
         if er != nil {
            err = er
            break
         }
         p.reset(int(current + i))
         p.conns[current+i] = p.wrapConn(c, false)
      }
	  // 本文博客来源:janrs.com
      current += i
      log.Printf("#janrs.com#grow pool: %d ---> %d, increment: %d, maxActive: %d\n",
         p.current, current, increment, p.opt.MaxActive)
      atomic.StoreInt32(&p.current, current)
      if err != nil {
         p.Unlock()
         return nil, err
      }
   }
   p.Unlock()
   next := atomic.AddUint32(&p.index, 1) % uint32(current)
   return p.conns[next], nil
}

Get 代码逻辑

  • 先增加连接的引用计数,如果在设定 current*int32(p.opt.MaxConcurrentStreams) 范围内,那么直接取连接进行使用即可。
  • 若当前的连接数达到了最大活跃的连接数,那么就看我们新建池子的时候传递的 option 中的 reuse 参数是否是 true,若是复用,则随机取出连接池中的任意连接提供使用,如果不复用,则新建一个连接。
  • 其余的情况,就需要我们进行 2 倍或者 1 倍的数量对连接池进行扩容了。

也可以在 Get 的实现上进行缩容,具体的缩容策略可以根据实际情况来定,例如当引用计数 nextRef 只有当前活跃连接数的 10% 的时候(这只是一个例子),就可以考虑缩容了。

性能测试

有关链接池的创建以及性能测试

mycodesmells.com/post/poolin…

以上就是Go创建Grpc链接池实现过程详解的详细内容,更多关于Go创建Grpc链接池的资料请关注编程网其它相关文章!

您可能感兴趣的文档:

--结束END--

本文标题: Go创建Grpc链接池实现过程详解

本文链接: https://www.lsjlt.com/news/198399.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • Go创建Grpc链接池实现过程详解
    目录常规用法创建链接池创建链接池接口实现链接池接口关闭链接扩缩容性能测试常规用法 gRPC 四种基本使用 请求响应模式客户端数据流模式服务端数据流模式双向流模式 常见的gRPC调用...
    99+
    2023-03-03
    Go创建Grpc链接池 Go Grpc
  • Go如何创建Grpc链接池
    这篇“Go如何创建Grpc链接池”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Go如何创建Grpc链接池”文章吧。常规用法g...
    99+
    2023-07-05
  • Oracle 区块链表创建过程详解
    大家好!我是只谈技术不剪发的 Tony 老师。 Oracle 21c 增加了一个非常强大的新功能:原生的区块链表(Blockchain Table)。Oracle 区块链表是一个具...
    99+
    2024-04-02
  • Android搭建grpc环境过程分步详解
    目录各种配置文件编写proto文件并编译编写简单的demo代码结语下面介绍的这个版本搭配是我研究好久好久才跑通的,这在我的电脑上是一组可行的配置,如果你使用了同样的配置跑不通,那可能...
    99+
    2023-05-15
    Android搭建grpc环境 Android grpc环境
  • Vue浏览器链接与接口参数实现加密过程详解
    目录场景思路过程1、设计格式2、加/解密方法实现3、加密处理位置4、解密处理位置小结场景 由于项目创建之前后端设计不合理,导致详情页链接参数id为顺序序数(例:1,2,3&helli...
    99+
    2022-12-21
    Vue链接加密 Vue接口参数加密
  • 详解VS2010实现创建并生成动态链接库dll的方法
    目录1、目的2、新建动态链接库3、添加导出类,并修改项目配置4、修改项目配置5、编译生成6、测试项目1、目的 在某些应用程序场景下,需要将一些类或者方法编译成动态链接库dll,以便别...
    99+
    2022-12-21
    VS创建生成动态链接库dll VS 动态链接库 VS dll
  • oracle创建用户过程详解
    1.首先用管理员用户登陆sqlplus: sqlplus "sysman/安装时设置的密码" 2.创建用户 create user userName identifi...
    99+
    2024-04-02
  • Go实现线程池(工作池)的两种方式实例详解
    worker pool简介 worker pool其实就是线程池thread pool。对于go来说,直接使用的是goroutine而非线程,不过这里仍然以线程来解释线程池。 在线程...
    99+
    2024-04-02
  • 如何实现Linux给文件或目录创建软链接、硬链接
    这篇文章主要讲解了“如何实现Linux给文件或目录创建软链接、硬链接”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“如何实现Linux给文件或目录创建软链接、硬链接”吧!当我们需要在不同的目录...
    99+
    2023-06-13
  • Go语言编程指南:单链表实现详解
    Go语言编程指南:单链表实现详解 在Go语言中,单链表是一种常见的数据结构,用于存储一系列元素并按顺序访问。本文将详细介绍单链表的实现原理,并给出具体的Go语言代码示例。 单链表的定义...
    99+
    2024-04-02
  • C语言图文并茂详解链接过程
    目录一、链接器的意义二、模块链接三、小结一、链接器的意义 连接器的主要作用是把各个模块之间相互引用的部分处理好,使得各个模块之间能够正确的衔接。 二、模块链接 静态链接 由链接器在...
    99+
    2024-04-02
  • 详解IDEA2020新建spring项目和c3p0连接池的创建和使用
    目录前言 1、环境准备:maven配置2、导入jar包:c3p0-0.9.5.4.jar和mysql-connector-java.jar 3、编写测试类测试连接...
    99+
    2024-04-02
  • 详解Java线程池的使用(7种创建方法)
    目录 1. 固定数量的线程池a.  线程池返回结果b. ⾃定义线程池名称或优先级2. 带缓存的线程池3. 执⾏定时任务 a.&nbs...
    99+
    2023-03-24
    Java线程池 Java线程池使用 线程池
  • springboot通过注解、接口创建定时任务详解
    目录springboot中定时任务的创建springboot通过注解创建定时任务首先引入pom直接上代码来一个栗子@Scheduled注解的各个参数springboot通过注接口创建...
    99+
    2024-04-02
  • JavaScript实现单链表过程解析
    前言: 要存储多个元素,数组是最常用的数据结构,但是数组也有很多缺点: 数组的创建通常要申请一段连续的内存空间,并且大小是固定的,所以当当前数组不能满足容量需求时,需要进行扩容,(一...
    99+
    2024-04-02
  • 创建Go工程化项目布局详解
    目录正文/cmd/internal/pkg/docs,/example,/pkg,/third_parth,/tools基础库项目布局kit包应该具备的特点应用程序项目布局/api/...
    99+
    2024-04-02
  • Go语言利用接口实现链表插入功能详解
    目录1. 接口定义1.1 空接口1.2 实现单一接口1.3 接口多方法实现2. 多态2.1 为不同数据类型的实体提供统一的接口2.2 多接口的实现3. 系统接口调用4. 接口嵌套5....
    99+
    2024-04-02
  • Springboot 配置线程池创建线程及配置 @Async 异步操作线程池详解
    目录前言一、创建一个Springboot Web项目二、新建ThreadPoolConfig三、新建controller测试四、演示结果前言 众所周知,创建显示线程和直接使用未配置的...
    99+
    2024-04-02
  • mysql创建存储过程及函数详解
    目录1. 存储过程1.1. 基本语法1.2 创建一个指定执行权限的存储过程1.3 DELIMITER 的使用2. 创建函数 1. 存储过程 1.1. 基本语法 create pr...
    99+
    2024-04-02
  • 详解Oracle创建用户权限全过程
    Oracle创建用户权限的全过程主要包括以下几个步骤:1. 使用具有创建用户权限的特权用户登录到Oracle数据库。2. 创建用户。...
    99+
    2023-09-22
    Oracle
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作