• 后端的优化


    后端优化分为四个方向

    • 组件配置调优,偏运维
    • 架构调优,偏架构
    • 代码层面的调优,偏开发
    • CDN 加速

    配置调优

    以 Nginx、PHP、MySQL 为例。

    LNMP中web高并发优化配置以及配置详解
    https://phpartisan.cn/news/55.html

    Nginx

    从简单粗暴的角度,就是提高连接数。

    增加进程数,每个 CPU 配置一个进程。

    进程数配置项: worker_processes
    CPU 配置项: worker_cpu_affinity ,该选项使得 Nginx 每个进程都执行在不同 CPU

    提高单进程允许的最多连接数。

    配置项: worker_connections

    理论上一台机器的最大连接数 = worker_processes * worker_connections

    PHP-FPM

    总体思想是控制进程数。

    选项:pm

    • static
      固定进程数。如果是 PHP 专用服务器,则可以将其设置为固定,并给定一个比较大的值。
    • dynamic (默认)
      根据以下几个因素变化:
      • 启动时进程数
      • 最大进程数
      • 至少有多少个空闲进程,少了就创建新空闲进程
      • 至多有多少个空闲进程,多了就销毁空闲进程

    每个 PHP-FPM 进程大致占用 20 MB 的内存,用内存除以 20 MB 就是极限数量。但是要注意,如果设置极限数量,在有其他应用占用较大内存时,会导致服务异常。

    PHP

    去掉没有用到的扩展。

    启用 OPCache 扩展。

    MySQL(InnoDB)

    MySQL 的内存缓存大小对于性能的影响较大。

    MySQL 的缓存分为两部分:

    • 索引
    • 行记录

    配置项是: innodb_buffer_pool_size

    这也是索引不能加太多的原因。索引加太多会导致索引占用更多的缓存,进而使得行记录的缓存减少。

    索引的更多优化:

    • 索引不要加到重复数据多的列上。
      索引有一个参数 Cardinality,用于评估索引中唯一值的数目的估值。如果该值和表行数的比值小于一定程度,则不会使用索引。

    • 字段太长应使用部分索引。

    • 使用短 ID 作为主键。因为辅助索引的叶子节点存储的是主键,如果主键太大,会使得辅助索引也变大。因此通常使用自增 ID 而不是 UUID 作为主键。

    • 必要情况下创建联合索引。多条件情况下,单表只会命中其中一个单列索引。

    架构调优

    瓶颈主要在数据库。

    Nginx

    使用双 Nginx 服务器(或者更多),用上 Keepalived + VIPA 组合确保高可用。

    可以设置多个 VIPA ,分布到不同机器上,这些机器互为主备。接着让域名同时解析到这些 VIPA。这样可以充分利用多台 Nginx 服务器,并且保证高可用。

    MySQL(InnoDB)

    从读性能和写性能两方面入手。

    提高读性能:

    • 添加从机(冗余数据),读写分离。读取数据时,从不同的从机读取。
      一般一主三从,两从用于提供服务,一从用于后台访问。

      后台访问的服务如果是大数据服务,则可为这台机器设置更多索引来提升读性能。但会给运维带来维护的麻烦,所以慎用。通常来说保持与其他服务器相同的配置。

    • 水平切分。将表中的旧数据转存到同库其他表或者其他库。
      可以优先考虑分库。因为磁盘满的时候,还是要把表迁移到其他库。
    • 垂直切分。将表中不常用的和长度较大的字段拆到另一张表。
    • 冷热分离。如果只有近三个月的数据访问量大,则将近三个月的数据尽量放到固态硬盘。将三个月之前的数据放到机械硬盘。
    • 索引外置。把数据冗余一份到 Elastic Search 里面。
    • 外部缓存。业务数据缓存到 Redis 里面。Cache Aside Pattern。

    注:所有数据冗余都会带来数据一致性的问题。

    两种一致性问题:

    • 主从不一致

      • 业务允许时无视不一致
      • 强制读主。从库读不到时再去主库读一次。
      • 选择性读主(Redis)。数据更新通知 Redis,毫秒级缓存,查询前先看更新的数据是否在 Redis 里面,有则读主。
    • 缓存不一致(Redis)
      发生在写后立即读。缓存了旧数据。
      通过 binlog 了解主从同步进度,同步完删除缓存。

    提高写性能:

    • 多主多写
      要解决 ID 冲突的问题。两种方式:
      • 设置不同起始 ID ,提高自增 ID 步长(会导致数据库配置不一致)
      • 客户端生成 ID。生成 ID 的方式可以参考分布式 ID 的几种生成方式。

    分库:

    • 单 key
      场景:用户表查询比登录多
      其他字段如果要加速,则专门做一个单字段到 UID 的映射表(可放入缓存加速)
    • 1 对多
      场景:用户查订单比订单查用户多
      用户订单。订单 ID 携带用户 ID 的信息。让同一个用户的订单落在同一个库。
    • 多对多
      场景:关注与粉丝。
      创建两个库,分别用其中一个字段作为分库依据。
    • 多 key
      场景:买家比卖家查订单多,查订单比查用户多
      忽略最少的部分,退化为 1 对多。
      架构不能为 1% 的性能而带来 20% 甚至更高的复杂性。

    服务

    无状态化,可根据需要横向扩容。

    用 JWT(Json Web Token)验证身份。

    文件存储放分布式文件存储上面,如 MinIO。

    代码层面

    分为:

    • 减少连接次数
    • 多线程/多进程
    • 缓存
    • 数据库

    减少连接次数

    例如项目中有一个模块,要传输脚本到目标机器上执行。分为两步:

    1. 传输脚本
    2. 执行

    要建立两次连接。

    优化方式:将脚本 base64_encode,然后把执行命令拼接在后面。

    echo "base64_encoded string" | base64 -d -i > /usr/local/src/xxx.sh; bash xxx.sh "param0";
    

    多线程/多进程

    碰到有多个耗时任务,为每个任务创建一个新的线程或者进程执行。

    缓存

    分为应用内缓存和外置缓存。

    应用内缓存有些场景需要自己维护多台机器之间的缓存信息,根据情况使用。

    外置缓存(如 Redis/Memcached)。

    将请求外部接口的数据缓存到 Redis,减少接口调用的耗时。

    MySQL(InnoDB)

    总体思想是尽可能减少数据量,尽可能早结束查询,尽可能命中索引,尽可能减小锁的粒度。

    在执行语句前,先用 Explain 查看执行计划,尽量命中索引,避免全表扫描。

    1. 尽量避免使用 select *,需要多少字段拿多少字段

    2. 非唯一索引尽量使用 limit

    3. 使用索引来代理 limit 处理分页
      limit 会扫描前面不要的数据,然后逐一抛弃。在 Where 里面指定 ID 范围会更快。
      业务层提供上一页和下一页的操作,避免用户一次跳多页。URL 要使用 after_xxx ,避免用户直接修改 page。例如 GitHub 的 release 列表界面。

    4. 用 Union 替代 OR
      注:MySQL 的优化器会尝试使用索引合并来自动优化 OR。

    5. 当数据集不会重复时,用 Union All 替代 Union

    6. 联合索引最左匹配原则

    7. 联合索引在范围查询的字段后就不会再走索引了

    8. 删除由最左匹配原则覆盖的索引

    9. 使用 like 时,避免把 % 放前面
      放在前面不走索引。

    10. 使用 Where 加更精确的条件限制来减少传输的数据量
      以前见过判断用户登录用户名密码的时候,把整个用户表查出来再逐一判断的代码。

    11. 避免对索引列使用 MySQL 内置函数。

    12. 优先使用 Inner Join 而不是其他 Join。

    13. 如果使用 Left Join 或者 Right Join,驱动表数据量尽可能小。

    14. 避免在索引列上使用不等号。如果索引能用范围扫描,则使用范围操作符。
      例如 a != 1,转化为 a < 1 AND a > 1。

    15. 大量数据使用批量分块插入数据
      其中一个影响因素是锁。一个事务插入已知数量的多条数据,只需获取一次锁。

    16. 使用覆盖索引
      使用索引就能获取想要的值,不需要从数据表中读。
      用于辅助索引。
      因为索引的执行顺序是:

      • 用辅助索引找到主键
      • 通过主键索引找到数据
        如果 select 的值只包括辅助索引和主键,则使用覆盖索引。
    17. 尽量不要在 select 字段多的时候使用 Distinct

    18. 批量删除数据要谨慎

      • 分批操作。
      • 如果全部数据删除,且不需要恢复,则使用 truncate 。
      • 如果不是全部删除,则把保留的数据插入到新表,再整个删除旧表。

      批量删除会加锁
      批量删除过程要写 undo 日志,一旦回滚,需要更多时间

    19. 避免数据类型隐式转换
      隐式转换会使索引失效

  • 相关阅读:
    QT多重继承的时候,要把QObject放在最前面,否则报错——C++认为人性本恶,默认都是私有的,这点和Delphi的世界观不一样
    动态库的搜索路径
    载入OpenSSL的动态库——学会使用tryToLoadOpenSslWin32Library和QPair
    Physical Standby Database Failover
    ARM和X86功耗差别的深层原因探讨
    Qt 鼠标样式特效探索样例(一)——利用时间器调用QWidget.move()函数
    QT---线程间通信
    Qt :非window子窗体的透明度设置
    QT:给Widget设置背景图片——设置Widget的调色板,调色板使用图片和背景色
    用友CDM系统,将货位间商品移库单(一步)修改为内调出入库单(一步)方法使用
  • 原文地址:https://www.cnblogs.com/schaepher/p/12846028.html
Copyright © 2020-2023  润新知