Neo's Blog

不抽象就无法深入思考
不还原就看不到本来面目!

0%

slab allcator的机制

memcache内存分配示意图

综合上面的介绍,memcached的内存分配策略就是:按slab需求分配page,各slab按需使用chunk存储。

这里有几个特点要注意,

Memcached分配出去的page不会被回收或者重新分配

Memcached申请的内存不会被释放

slab空闲的chunk不会借给任何其他slab使用

nginx内存池

有几个:

  1. nginx_pool_s 虽然包含了 ngx_pool_data,但是nginx_pool_s本身的内存管理还是通过ngx_pool_data来进行分配的
  2. 可以理解为:nginx_pool_s是一种特殊类型的ngx_pool_data。
  3. nginx_pool_s 维护了current指针,来指向下一个用来分配内存的小内存块链表节点
  4. nginx_pool_s 维护了large指针,来指向下一个用来分配内存的d大内存块链表节点
  5. ngx_pool_data 维护了next指针 来指向下一个nginx_pool_s

  1. 基于hash的freelist
  2. 由于STL知道分配出去的内存对象大小,所以他技巧性地用了union来减少内碎片。

Reactor 模式首先是事件驱动的,有一个或多个并发输入源,有一个Service Handler,有多个Request Handlers;这个Service Handler会同步的将输入的请求(Event)多路复用的分发给相应的Request Handler。如果用图表示的如下:

Reactor模式

其实在设计模式层面,IO多路复用也是采用 Reactor 模式的。

IO 多路复用模型可以看成是 Reactor 模式在 IO 模型上的应用。

在我看来,高性能指的是,我们的系统能够在尽可能短的时间内完成用户的请求,也就是说latency尽可能的低。

如果说系统能够支撑更大的吞吐量,能够承载更多的同时在线。我们除了降低latency,还可以考虑加机器(如果还没有reach到某个系统平瓶颈的前提下)

提高吞吐量

增加并发进程数

注意:无限增加,不会无限提升性能(对系统的扩展性有要求)

性能拐点模型

减少响应时间

IO密集型:优化数据库索引、加缓存

CPU密集型:优化算法时间复杂度、减少不必要运算等

并发读的核心优化理念是尽量减少用户到服务端来“读”数据,或者让他们读更少的数据;

并发写的处理原则也一样,它要求我们在数据库层面独立出来一个库,做特殊的处理。

从一个架构师的角度来看,要想打造并维护一个超大流量并发读写、高性能、高可用的系统,在整个用户请求路径上从浏览器到服务端我们要遵循几个原则,就是要保证

  1. 用户请求的数据尽量少
  2. 完成这个请求的路径尽量短、依赖尽量少 =》 上游到下游的请求数尽量少

也就两个大的方向:提升单次效率、减少不必要的请求

  1. 动静分离方案(移除无关依赖、减少请求)
  2. 热点的发现与隔离(缩短热点的服务路径)
  3. 请求的削峰与分层过滤(异步化,减少无必要的依赖)
  4. 服务端的极致优化(缩短热点的服务路径)

一些概念

有合理的办法应对系统的增长(数据量、流量、复杂性)

一个良好适配应用的可扩展架构,是围绕着假设(assumption)建立的:哪些操作是常见的?哪些操作是罕见的?这就是所谓负载参数。如果假设最终是错误的,那么为扩展所做的工程投入就白费了,最糟糕的是适得其反。

数据库、缓存、依赖的第三方、复杂均衡、交换机带宽都是系统扩展时需要考虑的点

分层

首先要分层,分层是实现扩展的必要条件;分了层才能让系统有更好的可扩展能力;

架构分层

我们将系统分为如下几层:

接入层

主要负责负载均衡,要求负载均衡策略的时间复杂度要尽量简单,追求$O(lgn)$的时间复杂度,最坏不能超过$O(n)$。对于更坏时间复杂度的均衡策略,性能上会扛不住。

应用层

业务层:按照业务拆分、按照重要性拆分(轻重分离,核心、非核心)、按照请求来源(客户端、web、内网等)—这个跑偏了,这些点主要服务于可用性。

这里侧重点在于无状态,我们一定要确保我们的服务无状态。

存储层

存储层做扩展会更麻烦一些。

一般情况下,要求我们根据业务量提前估计好存储容量。根据计算出来的总的存储量,提前做足够的数据分片。

换言之,对于存储层,我们的一种思路是:早做扩展,以确保将来不做扩展;

另外一种思路是,添加必要的路由层(数据访问路由层)或者路由算法(数据表倍增法),来确保将来的扩展对上层不可见。

manager层

  1. 抽象service层可能提供的一些原子能力
  2. 封装对第三方接口的调用

池化技术
核心思想:用空间换时间,期望使用预先创建好的对象来减少频繁创建对象的性能开销,同时还统一管理了对象,降低了对象的使用成本。
例子:
(1)数据库连接池-最小数量、最大数量、如果超过最大则等待
(2)线程池-最小数量、最大数量、有界队列(监控队列中元素的个数)
线程池大小:区分IO密集、CPU密集
(3)内存池

超时事件管理

IO模型

线程模型,一般会综合考虑IO模型、超时事件如何管理、线程池、消息队列等

5种IO模型

当应用程序需要通过操作系统进行IO通信时,一共有下面5种IO模型供选择。

(同步)阻塞IO

(同步)非阻塞IO

(同步)IO多路复用

复用IO的基本思路就是通过slect或poll、epoll 来监控多fd ,来达到不必为每个fd创建一个对应的监控线程,从而减少线程资源创建的目的。

(异步)信号驱动IO

FATAL缺点:信号I/O在大量IO操作时可能会因为信号队列溢出导致没法通知。

异步(非阻塞)IO

缺点:技术比较新,可能还有些坑没有填完;另外就是异步IO会让应用比较复杂,代码变得难以理解(异步不符合人的思考习惯)