• 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

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

  • 相关阅读:
    图解SQL的Join(转)
    MySQ数据表设计
    关于数据库DML、DDL、DCL区别
    SQL多表连接查询
    Xcode报错Expected selector for Objective-C and Expected method body
    Mac上安装使用MYSQL以及Navicat数据库管理和PHP服务器配置
    更换app开发者账号
    Mac 下的 C++ 开发环境
    spring-retry 重试机制
    Ribbon的主要组件与工作流程
  • 原文地址:https://www.cnblogs.com/decode1234/p/10741679.html
Copyright © 2020-2023  润新知