微服务设计原则——高性能:池化

2024-08-19 14:04:01 浏览数 (3)

1.池化由来

池化(Pooling)是一种优化技术,旨在提高系统性能和资源利用率,特别是在高并发环境中。通过池化,系统可以重用资源,而不是每次都创建和销毁这些资源。

池化的目的是完成资源复用,避免资源重复创建、销毁来提高性能。

常见的池子有内存池、连接池、线程池、对象池…

内存、连接、线程、对象等都是资源,创建和销毁这些资源都有一个特征, 那就是会涉及到很多系统调用或者网络 IO。 每次都在请求中去创建这些资源,会增加处理耗时,但是如果我们用一个 容器(池) 把它们保存起来,下次需要的时候,直接拿出来使用,避免重复创建和销毁浪费的时间。

2.内存池

我们都知道,在 C/C 中分别使用 malloc/free 和 new/delete 进行内存的分配,其底层调用系统调用 sbrk/brk。频繁的系统调用分配释放内存不但影响性能还容易造成内存碎片,内存池技术旨在解决这些问题。正是这些原因,C/C 中的内存操作并不是直接调用系统调用,而是已经实现了自己的一套内存管理,malloc 主要有三大实现:

  • ptmalloc:glibc 的实现。
  • tcmalloc:Google 的实现。
  • jemalloc:Facebook 的实现。

虽然标准库的实现在操作系统内存管理的基础上再加了一层内存管理,但应用程序通常也会实现自己特定的内存池,如为了引用计数或者专门用于小对象分配。所以看起来内存管理一般分为三个层次。

3.线程池

线程池是一个预先创建并维护一定数量线程的集合。当有任务需要执行时,线程池会从池中取出一个空闲线程来执行任务,而不是每次都创建新线程。

线程创建是需要分配资源的,这存在一定的开销,如果一个任务就创建一个线程去处理,这必然会影响系统的性能。线程池的可以限制线程的创建数量并重复使用,从而提高系统的性能。

线程池可以分类或者分组,不同的任务使用不同的线程组,可以进行隔离以免互相影响。对于分类,可以分为核心和非核心,核心线程池一直存在不会被回收,非核心可能对空闲一段时间后的线程进行回收,从而节省系统资源,等需要时在按需创建放入池子中。

在 Java 中,可以使用 ExecutorService,在 Python 中可以使用 concurrent.futures.ThreadPoolExecutor。其他语言也有类似的线程池实现。

代码语言:javascript复制
// Java 示例
ExecutorService executor = Executors.newFixedThreadPool(10);
executor.submit(() -> {
    // 处理任务
});

4.连接池

常用的连接池有数据库连接池、redis连接池、TCP连接池等等,其主要目的是通过复用来减少创建和释放连接的开销。连接池实现通常需要考虑以下几个问题:

  • 初始化:启动即初始化和惰性初始化。启动初始化可以减少一些加锁操作和需要时可直接使用,缺点是可能造成服务启动缓慢或者启动后没有任务处理,造成资源浪费。惰性初始化是真正有需要的时候再去创建,这种方式可能有助于减少资源占用,但是如果面对突发的任务请求,然后瞬间去创建一堆连接,可能会造成系统响应慢或者响应失败,通常我们会采用启动即初始化的方式。
  • 连接数目:权衡所需的连接数,连接数太少则可能造成任务处理缓慢,太多不但使任务处理慢还会过度消耗系统资源。
  • 连接取出:当连接池已经无可用连接时,是一直等待直到有可用连接还是分配一个新的临时连接。
  • 连接放入:当连接使用完毕且连接池未满时,将连接放入连接池(包括连接池已经无可用连接时创建的临时连接),否则关闭。
  • 连接检测:长时间空闲连接和失效连接需要关闭并从连接池移除。常用的检测方法有:使用时检测和定期检测。

5.对象池

严格来说,各种池都是对象池的的具体应用,包括前面介绍的三种池。

对象池跟各种池一样,也是缓存一些对象从而避免大量创建同一个类型的对象,同时限制了实例的个数。如 Redis 中 0-9999 整数对象就通过对象池进行共享。在游戏开发中对象池经常使用,如进入地图时怪物和 NPC 的出现并不是每次都是重新创建,而是从对象池中取出。

在微服务中,使用对象池来管理缓存对象(如 Redis 缓存、内存缓存),可以提高缓存的效率。

6.小结

池化技术在微服务架构中通过提高资源的重用率,减少资源创建和销毁的开销,显著提升系统性能。线程池、连接池、对象池等技术可以有效管理并发任务和资源,提高系统的响应速度和吞吐量。通过合理的池化策略和管理,微服务能够更高效地处理高并发负载,优化资源利用率。

参考文献

服务高并发、高性能、高可用实现方案- 杨岂

1 人点赞