v

 

 版权声明:本文为博主原创文章,转载请注明出处。 https://blog.csdn.net/gx864102252/article/details/81607894

RPC 客户端实现起来要比服务器简单,所以我们先讲客户端的实现原理和方法。当然,实现 RPC 客户端也具有一定的挑战性,其核心难点在于客户端往往并不是单线程的,我们需要考虑多线程下如何流畅使用客户端而不出现并发问题。

我们将根据下图所示的模型图逐步讲解: 
这里写图片描述 
在多线程客户端中,客户端和数据库之间会维护一个连接池。当线程中的代码需要访问数据库时,先从连接池中获取一个连接,与数据库交互完成后再将这个连接归还给线程池。所以对于业务线程来说,拿到的连接不会同时被其它线程共享,这样就有效避免了并发问题。

另外,服务器的性能往往随着并发连接数量的增加而下降,所以必须严格控制有效连接的数量。连接池的连接数量上限是数据库的一层堡垒,避免当业务繁忙、线程增多时给数据库带来明显的压力。

安全锁

连接池为多线程而设计,每个线程都会访问线程池对象,所以线程池需要使用锁来控制数据结构的安全。安全锁可以带来安全,但是也会导致性能受损。锁的临界区代码要尽量避免耗时的计算和 IO 操作。锁的力度还要尽可能的细,但是细粒度的锁代码编写起来也是有一定的难度,容易出错。

考虑到连接都是用来进行相对缓慢的 IO 操作,锁这样的内存型操作耗时相比 IO 操作可以忽略不计,所以采用粗粒度的锁可能会是一个非常明智的选择,在性能许可的前提下,代码写得简单不容易出错。

懒惰连接

连接池中的连接多为懒惰的连接,在需要的时候才会去向数据库申请新的连接。如果一个系统非常闲置,而提前开辟了太多的连接池那是对资源的浪费。

比如 Python 的应用程序多是单线程程序,但是为 Python 提供的连接池库为了通用型可不能不考虑多线程,毕竟 Python 的多线程在一些场合也是会经常使用的。懒惰的线程池可以保证只会对单线程的程序开辟一个连接。

懒惰的连接也有一个不好的地方,这也是冷启动常见的问题。

  1. 如果数据库连接参数不正确,需要在收到用户的请求进行显示的数据访问时才能发现。
  2. 服务器的代码需要经历一个热身的过程,早来的请求需要额外付出一次建立连接的耗时代价。

健康检查

连接池中管理的连接可能会因为网络原因而损坏断连。连接池需要保持内部管理的连接是健康可用的。

  1. 线程从连接池中申请连接返回之前,线程池要对连接进行检查,确定连接是通畅的。
  2. 线程将连接归还给连接池时,线程池对连接进行检查,确定连接没有被搞坏。
  3. 线程池定时对管理的连接进行检查 
    如果检查发现连接有问题,一般的做法有两种:
  4. 抛弃当前连接,连接池的连接数量减一,如果是在 borrow 方法里,那就再重新去连接池申请一个
  5. 修复当前连接,一般也就是执行重连操作。 
    一般检查一个连接可用性,使用Ping或者其他心跳方式。

超时策略

当业务线程繁忙时,连接池内部的连接可能会出现不够用的场景。一个请求 borrow 的线程等了很久也等不到空闲的连接。这就是超时问题。超时问题一般有三种解决方案 
1. 永不超时,等不到就接着等,这可能不是一种好的选择。 
2. 一定的时间拿不到后,就向外部跑出超时异常,中断业务逻辑。 
3. 如果发现连接池没有空闲连接,就去申请一个新的连接给调用方。调用方归还连接的时候,连接池计算当前缓存的连接数量,如果超过了最大空闲连接数,就将当前归还的连接直接销毁。也就是即用即走。

性能追踪

好的连接池还应该考虑到性能的可追踪性,当用户通过线程池分配的连接去访问数据库时,它的消息执行时间应该是可以被追踪被统计的。所以往往连接池还需要对原生的连接进行一定程度的包装,在关键的函数调用前后增加性能统计代码设计切面解决。并对外提供监听接口,以便将统计信息传递给外部监控模块。

多路复用 (multiplexing)

传统的 RPC 客户端都是一问一答的,同一个连接上连续的两个请求必须按先后顺序排队获取结果。高级 RPC 的客户端往往是同一个链接上可以同时进行多个请求,并且可以乱序执行。通过在请求里增加一个唯一的 ID 进行标识。服务器响应消息携带请求 ID 到客户端,客户端就可以将响应和请求进行关联。

HTTP1.x 协议是基于一问一答形式的,到了 HTTP2.0 就具备了多路复用的连接,Google 开源的 gRPC 正是基于 HTTP2.0 的多路复用的连接封装的一款高性能 RPC 框架。

多路复用的连接往往都是线程安全的,它支持多个线程同时写入请求而不会出现并发问题。但是实现多路复用的效果难度较大,实现一个同等功能的客户端,它的工作量往往是同步的好几倍。

单向请求

为了提升交互的性能,有些不是特别重要的请求可以不需要服务器进行响应,客户端在发送完请求之后也不需要等待结果直接返回。这就是 oneway 单向请求,单向请求往往适用于允许少量丢失的请求,例如日志信息。因为客户端在发送完之后并不关心服务器有没有收到,有可能连接突然断开,就会导致消息丢失了。

心跳

当客户端长期空闲时,服务器往往会自动关闭连接已减轻资源消耗。当客户端再次请求时,就会遇到连接已断开的错误。为了避免这种错误,一般有两种方法,一种是通过请求遇到连接错误时进行重连重试,另一种就是通过心跳方式告知服务器不要关闭连接。

py-redis的连接池

def get_connection(self, command_name, *keys, **options):
        "Get a connection from the pool"
        self._checkpid()
        try:
            connection = self._available_connections.pop()
        except IndexError:
            connection = self.make_connection()
        self._in_use_connections.add(connection)
        return connection
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

如果连接池中有可用连接,直接pop出一个连接,如果连接池为空,则创建一条连接池(值得一提的是整个连接池在初始化的时候可用连接池是一个空列表,所以在第一次调用该方法时才会有真正的连接,属于惰性连接)

def release(self, connection):
        "Releases the connection back to the pool"
        self._checkpid()
        if connection.pid != self.pid:
            return
        self._in_use_connections.remove(connection)
        self._available_connections.append(connection)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

将在用连接从在用连接池中移除,但是连接不销毁,直接添加在可用连接池中。

原文地址:https://www.cnblogs.com/decode1234/p/10741679.html