性能&并发

高并发解决方案

  • 应用和静态资源分离
  • 页面缓存
  • 集群与分布式
  • CDN
  • 底层优化

海量数据解决方案

  • 缓存
  • 页面静态化
  • SQL优化
  • 分库分表
  • 索引优化
  • 分离活跃用户
  • 读写分离
  • NoSQL

Nginx和Apache的区别

两者最核心的区别在于 apache 是同步多进程模型,一个连接对应一个进程,而 nginx 是异步的,多个连接(万级别)可以对应一个进程

Swoole 和 PHP-FPM 的相同和不同之处

Swoole 和 FPM 都是基于服务器提升 PHP 解析性能的方案,进程模型是相同的。

相同

  • 一个 manager 多个 worker
  • manager 负责管理接收请求数据,转发数据,管理子进程(拉起和关闭)
  • worker 负责处理请求数据

不同

  • FPM 的 worker 进程是同步阻塞的,Swoole 的 worker 进程是异步非阻塞的
  • Swoole http-server 和 FPM 的差异是后者是常住内存的,PHP 程序变成长生命周期,变量和对象在使用请求后不会被销毁,可以复用

TIP

在 WEB 流程中,最早使用 cgi 协议用于 server 的通讯,这种方式会导致高并发情况下,会频繁创建、销毁进程,影响性能。之后出现了 fast-cgi ,采用多进程(进程池)来管理,避免进程频繁销毁。后来随着各种复杂的框架(比如 Laravel)的出现,使得每次请求时框架在初始化过程中会频繁创建大量对象,这又在一定程度上会影响性能,所以就出现了 Swoole 这种基于 PHP 的 http-server,可以将 PHP 对象的声明周期常驻内存,避免了重复创建、销毁大量 PHP 对象。(基于 Swoole 的 http-server 可能会导致一些内存泄漏)

接口设计:RESETFUL

接口设计:幂等

  • 幂等概念
    • 保证唯一
  • 产生原因
    • rpc 调用时网络延迟(重试发送请求)
    • 表单重复提交

幂等性是系统的接口对外的一种承诺,而不是实现。承诺只要调用接口成功,外部多次调用对系统的影响是一致的,声明为幂等的接口会认为外部调用失败是常态,并且失败之后必然会有重试。

有些接口是天然实现幂等性的,比如查询接口。对于查询接口,你查询一次和两次,对于系统来说没有任何影响,查出的结果是一样的。

对增加、删除、更新操作做幂等处理

  • 全局唯一 id

根据业务和操作的内容生成一个全局 id,在执行操作以先判断存储系统中是否存在这个全局 id 来判断这个操作是否已经执行。如果不存在则把全局 id,存到存储系统中,如果存在则表示该操作已经执行。

高可靠的幂等服务。

  • 去重表

新建一张去重表(唯一键),在接口操作数据的事务中增加将唯一标识(比如订单 id)写入去重表,如果重复创建数据库会跑出唯一约束异常,操作就会回滚。

比如在支付场景中,如果一个订单只会支付一次,所以订单 ID 可以作为唯一标识。这时,我们就可以建一张去重表,并且把唯一标识作为唯一索引,在我们实现时,把创建支付单据和订单号写入去重表,放在一个事务中,如果重复创建,数据库会抛出唯一约束异常,操作就会回滚。

  • 插入或更新

通过数据库唯一键,实现 createOrUpdate 操作

高并发下的数据处理

缓存

缓存是现在系统中必不可少的模块,并且已经成为了高并发高性能架构的一个关键组件

缓存能解决的问题

  • 提升性能
  • 缓解数据库压力

缓存的使用场景

  • 对数据实时性要求不高
  • 对性能要求高

常用的缓存介质Redis和Memcached区别

  • Redis支持复杂的数据结构
  • Redis原生支持集群模式
  • Redis只使用单核,Memcached可以使用多核。平均每一个核上Redis在存储小数据时比Memcached性能更高。而在100k以上的数据中,Memcached性能要高于Redis。

缓存的三种模式

  • Cache Aside 更新模式(同时更新缓存和数据库)
    • 失效:应用程序先从 cache 取数据,没有得到,则从数据库取数据,成功后放到缓存
    • 命中:应用程序从 cache 中取数据,取到后返回
    • 更新:先把数据存到数据库,成功后,再让缓存失效
  • Read/Write Through 更新模式(先更新缓存,缓存负责同步更新到数据库)
  • Write Behind Caching 更新模式(先更新缓存,缓存定时一部更多到数据库)
三种缓存模式的优缺点:
  • Cache Aside 更新模式实现起来比较简单,但是需要维护两个数据存储,一个是缓存(Cache),一个是数据库(Repository)。
  • Read/Write Through 更新模式只需要维护一个数据存储(缓存),但是实现起来要复杂一些。
  • Write Behind Caching 更新模式和 Read/Write Through 更新模式类似,区别是 Write Behind Caching 更新模式的数据持久化操作是异步的,但是 Read/Write Through 更新模式的数据持久化操作是同步的。优点是直接操作内存速度快,多次操作可以合并持久化到数据库。缺点是数据可能会丢失,例如系统断电等。

缓存雪崩(失效)

缓存雪崩可以简单的理解为:由于原有缓存失效,新缓存未到生成时间,所有原本应该访问缓存的请求都去查询数据库而对数据库 CPU 和内存造成巨大压力,严重的会造成数据库宕机,从而形成一系列连锁反应,造成整个系统崩溃。

  • 解决方案
    • 加锁排队 加锁排队的解决方式分布式环境的并发问题,有可能还要解决分布式锁的问题;线程还会被阻塞,用户体验很差!因此,在真正的高并发场景下很少使用!
    • 缓存标记 记录缓存数据是否过期,如果过期会触发通知另外的线程在后台去更新实际key的缓存
    • 为key设置不同的缓存失效时间,避免大量缓存同时失效

缓存穿透(击穿)

缓存穿透是指用户查询的数据,在数据库中没有,自然在缓存中也不会有。这样就导致了用户在查询的时候,查询了一次缓存,又查询了一次数据库,然后返回空。

  • 解决方案
    • 布隆过滤器 将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。
    • 将数据库查询到为空的数据进行缓存,并设置失效时间,当此key对应的数据在DB中存在时,缓存失效后通过此key再去访问数据就能拿到新的value了,这样可以简单粗暴的避免对数据库产生较大的查询压力。

热点key

缓存中的某些key对应的value存储在集群中的一台机器,使得所有流量涌向同一机器,成为系统的瓶颈。

解决方案
  • 客户端热点key缓存,将热点key对应的value缓存在客户端本地,并且守则一个失效时间。每次读请求都先检查key是否存在本地缓存,如果存在直接返回,如果不存在再去访问接口读取服务端缓存。
  • 将热点key分散为多个子key,然后存储到缓存集群的不同机器上

换算 QBS->机器