目录
一、索引优化
参见“索引优化”的单独章节。
二、请求合并
请求合并
如果查询请求过多,其实可以用内存来做缓存,比如memcache,比如redis,很多缓存方案,但是如果更新请求过多,那么缓存就没法用了。而更新请求往往比查询请求更消耗资源,这样系统i/o压力就非常大。
关于更新请求,是不是真的不能缓存呢?其实不是。
例如针对一个表的多个字段的update,可以合并到同一条语句中执行。
合并的分析方法
对一个毫不熟悉的系统,如何快速分析其冗余请求的构成和合并的可能性,以及合并可能带来的开销降低呢?
1、慢查询;
2、先show processlist;看到有疑问的SQL,去explain,然后set profiling=1;
看看索引是不是对的,看看哪些SQL本身是有问题的。
3、加SQL打印日志:
第一,看查询和更新的比例。
第二,看最多查询的数据表有哪些,最多更新的数据表有哪些。
第三,看最多查询的数据表最多查询的SQL是什么样子的,
最多更新的数据表最多执行更新的SQL是怎样的,算出各自每秒的请求频率。
第四,关键分析,最多查询的SQL,基于同一主键查询的比例多不多(潜台词,可以缓存化)。
最多更新的SQL,基于同一主键的更新的比例高不高(潜台词,可以合并请求,异步处理,
当然必须根据具体业务诉求再核对一遍)
三、需求裁剪
所谓需求裁剪,并不仅仅是功能实现,还包括性能指标,以及所谓的边界条件。
案例1:搜索大翻页问题,负载越高的问题。
淘宝搜索一个关键词,最多翻多少页?百度呢?google呢?
你们自行测试一下,这些巨头给出的搜索结果条目数,都是估算值,最大翻页数,基本不超过100页。
这就是设定了边界条件。
案例2:雪崩效应的处理。
当缓存扛不住,负载传递给数据库,瞬间过载,这就涉及一个灾难应急机制,简单说就是:降级服务,有损服务。在出现类似问题的时候,系统自动降级,将部分用户请求频次低,
价值低但是系统开销不低的功能或者数据临时阻断停止响应,确保整体系统的稳定性。
你损失的那些边界条件外的数据是1000个用户也不会有1个去查询的内容,
而如果你要满足这些可能需要付出500%甚至更多的系统开销和研发工作量,
这种对于很多早期创业公司来说,非常非常重要。
这就是思路的关键,用户对功能的需求,你的满足度的边界在哪里?很多程序员没有这个概念,对于数据规模小,请求并发少的应用来说,这个东西你不考虑也没什么大不了,但是如果面对数据规模大,请求并发大,你就应该有一个概念,如何设定需求的边界条件,既能满足用户的正常请求可以顺畅的响应,同时保证系统在开销可控的情况下稳定健壮的运营;而当系统出现类似单点故障,雪崩效应呈现的时候,如何设定新的边界条件,让用户在基本可用的情况下给系统一个恢复周期。这些问题都是需要提前思考,并且不断随着业务规模的增加而调整的。
案例3:关于主从分离同步的案例
刚开始做数据库主从读写分离的时候,经验也不是很丰富,然后发现一个问题,主从同步经常会有一个时延,虽然时间很短,大部分在1秒以内,但是在应用中,我们发现,用户发一个帖子,然后发完后就应该进入这个帖子的展示页吧,帖子发布到主数据库,而展示页调用的是从数据库,结果部分用户发完帖子,因为延迟,就看到了一个该帖子不存在的界面,这肯定是一个不好的情况么。当然,技术上肯定有各种解决方法,比如对这种新内容选择从主数据库访问,做一些标定等等,但是呢,我们就做了一个特别偷懒取巧的方案。什么方案呢?用户发完帖子后,先进入一个中转页,告诉用户您的帖子发布成功,3秒后自动进入帖子页。(对这个场景很多人都熟悉吧),就这么一个特简单甚至有点不是很友好的设计,主从同步延迟的问题就基本解决了。
四、分布式数据库及反范式设计
基本认识
● 用分库&拆表是解决数据库容量问题的唯一途径。
● 分库&拆表也是解决性能压力的最优选择。
● 分库 – 不同的数据表放到不同的数据库服务器中(也可能是虚拟服务器)
● 拆表 – 一张数据表拆成多张数据表,可能位于同一台服务器,也可能位于多台服务器(含虚拟服务器)。
去关联化原则
在设计之初,就要考虑去关联化,以备分库分表做准备。手段:反范式。
● 摘除数据表之间的关联,是分库的基础工作。
● 摘除关联的目的是,当数据表分布到不同服务器时,查询请求容易分发和处理。
● 学会理解反范式数据结构设计,所谓反范式:
第一要点是不用外键,不允许Join操作,不允许任何需要跨越两个表的查询请求。
第二要点是适度冗余减少查询请求,比如说,信息表,fromuid, touid, message字段外,还需要一个fromuname字段记录用户名,这样查询者通过touid查询后,能够立即得到发信人的用户名,而无需进行另一个数据表的查询。
● 去关联化处理会带来额外的考虑,比如说,某一个数据表内容的修改,对另一个数据表的影响。这一点需要在程序或其他途径去考虑。
分库方案
- 安全性拆分
将高安全性数据与低安全性数据分库,这样的好处第一是便于维护,第二是高安全性数据的数据库参数配置可以以安全优先,而低安全性数据的参数配置以性能优先。参见运维优化相关部分。
- 基于业务逻辑拆分
● 根据数据表的内容构成,业务逻辑拆分,便于日常维护和前端调用。
● 基于业务逻辑拆分,可以减少前端应用请求发送到不同数据库服务器的频次,从而减少链接开销。
● 基于业务逻辑拆分,可保留部分数据关联,前端web工程师可在限度范围内执行关联查询。
- 基于业负载压力拆分
● 基于负载压力对数据结构拆分,便于直接将负载分担给不同的服务器。
● 基于负载压力拆分,可能拆分后的数据库包含不同业务类型的数据表,日常维护会有一定的烦恼。
- 混合拆分组合
● 基于安全与业务拆分为数据库实例,但是可以使用不同端口放在同一个服务器上。
● 基于负载可以拆分为更多数据库实例分布在不同数据库上
例如,
基于安全拆分出A数据库实例,
基于业务拆分出B,C数据库实例,
C数据库存在较高负载,基于负载拆分为C1,C2,C3,C4等 实例。
数据库服务器完全可以做到 A+B+C1 为一台,C2,C3,C4各单独一台。
分表方案
数据量过大或者访问压力过大的数据表需要切分
纵向分表(常见为忙闲分表)
单数据表字段过多,可将频繁更新的整数数据与非频繁更新的字符串数据切分。
范例 user表 ,个人简介,地址,QQ号,联系方式,头像 这些字段为字符串类型,更新请求少;最后登录时间,在线时常,访问次数,信件数这些字段为整数型字段,更新频繁,可以将后面这些更新频繁的字段独立拆出一张数据表,表内容变少,索引结构变少,读写请求变快。
横向切表
● 等分切表,如哈希切表或其他基于对某数字取余的切表。等分切表的优点是负载很方便的分布到不同服务器;缺点是当容量继续增加时无法方便的扩容,需要重新进行数据的切分或转表。而且一些关键主键不易处理。
● 递增切表,比如每1kw用户开一个新表,优点是可以适应数据的自增趋势;缺点是往往新数据负载高,压力分配不平均。
● 日期切表,适用于日志记录式数据,优缺点等同于递增切表。
● 个人倾向于递增切表,具体根据应用场景决定。
热点数据分表
● 将数据量较大的数据表中将读写频繁的数据抽取出来,形成热点数据表。通常一个庞大数据表经常被读写的内容往往具有一定的集中性,如果这些集中数据单独处理,就会极大减少整体系统的负载。
● 热点数据表与旧有数据关系
可以是一张冗余表,即该表数据丢失不会妨碍使用,因源数据仍存在于旧有结构中。优点是安全性高,维护方便,缺点是写压力不能分担,仍需要同步写回原系统。
可以是非冗余表,即热点数据的内容原有结构不再保存,优点是读写效率全部优化;缺点是当热点数据发生变化时,维护量较大。
具体方案选择需要根据读写比例决定,在读频率远高于写频率情况下,优先考虑冗余表方案。
加载热点数据方案选择
定时从旧有数据结构中按照新的策略获取
在从旧有数据结构读取时动态加载到热点数据
剔除热点数据方案选择
基于特定策略,定时将热点数据中访问频次较少的数据剔除
如热点数据是冗余表,则直接删除即可,如不是冗余表,需要回写给旧有数据结构。
● 热点数据表可以用单独的优化的硬件存储,比如昂贵的闪存卡或大内存系统。
● 热点数据表的动态维护
● 通常,热点数据往往是基于缓存或者key-value 方案冗余存储
反范式设计(冗余结构设计)
反范式设计的概念
● 无外键,无连表查询。
● 便于分布式设计,允许适度冗余,为了容量扩展允许适度开销。
● 基于业务自由优化,基于i/o 或查询设计,无须遵循范式结构设计。
冗余结构设计所面临的典型场景
●原有展现程序涉及多个表的查询,希望精简查询程序
●数据表拆分往往基于主键,而原有数据表往往存在非基于主键的关键查询,无法在分表结构中完成
●存在较多数据统计需求(count, sum等),效率低下。
冗余设计方案
基于展现的冗余设计
● 为了简化展现程序,在一些数据表中往往存在冗余字段
基于查询的冗余设计
● 涉及分表操作后,一些常见的索引查询可能需要跨表,带来不必要的麻烦。确认查询请求远大于写入请求时,应设置便于查询项的冗余表。
冗余表要点
-数据一致性,简单说,同增,同删,同更新。
-可以做全冗余,或者只做主键关联的冗余,比如通过用户名查询uid,再基于uid查询源表。
基于统计的冗余结构
● 为了减少会涉及大规模影响结果集的表数据操作,比如count,sum操作。应将一些统计类数据通过冗余数据结构保存。
● 冗余数据结构可能以字段方式存在,也可能以独立数据表结构存在,但是都应能通过源数据表恢复。
历史数据表
● 历史数据表对应于热点数据表,将需求较少又不能丢弃的数据存入,仅在少数情况下被访问。
五、认识负载
负载的构成
● CPU开销是多少,是哪些进程和服务占用的。
● 内存开销是多少,是哪些进程和服务占用的,如果内存占用了swap分区,大量的硬盘虚拟内存操作,效率自然会直线下降。
● I/O开销 是多少,读请求的频率,写请求的频率,什么服务和什么操作占用了大量的i/o。
● 连接数是多少,是怎么分布的,比如http链接多少,数据库链接多少,memcache链接多少,当然更细致的三次握手的链接是多少。
了解这些,是优化的基础。
负载增长趋势
随着应用请求的增加,你的系统的负载是怎么增加的。
第一种,是线性增加,就是请求两倍,负载变成两倍。
第二种,是指数增加,请求两倍,负载变成四倍甚至更多。
第三种,收敛增加,随着你的请求增加规模,负载的增加低于线性增加并逐步收敛,比如说,大量使用缓存和异步更新,请求越多,缓存命中率越高,异步更新的请求合并率越高,这样负载的增加就呈现为收敛性,这样系统的支撑性就会很强大。
系统阈值
很多时候,我们系统出现瓶颈,并不是因为负载很高,而是因为某个请求规模超越了系统阈值,导致无法应答请求。
典型范例如:
syn flood攻击时,最大的syn连接池被占满,导致无法应答新的请求,而此时服务器负载非常之低,这就是典型服务器很闲但不响应的情况。
http链接数越界,http链接超时设置较长,大量链接没有释放,导致链接数超过默认最大值,http服务器无法响应新请求。
mysql链接数越界,大量使用常链接或不释放链接,导致大量sleep链接占满系统默认连接数,数据库无法响应新请求。
最大文件打开数越界,大量使用临时文件和缓存文件,大量的文件打开操作,而系统默认值没有调优。
要充分理解各种系统阈值,并针对自己的应用特性进行调优,才可以充分发挥系统硬件特性,实话说,很多系统或服务的默认阈值都偏低。
异常的监控和跟踪
要对各种异常敏感,很多严重的性能问题其实是有先兆的,比如偶尔的501错误,偶尔的访问卡顿,偶尔的链接出错,很多时候,用户刷新一下,这个问题就没有了,但是很可能此事系统已经进入了一个不稳定的状态。
有经验和有意识的架构师或运维专家,应该会做日志的跟踪和审计,随时查看这种错误信息的出现频率,并对此进行持续的跟踪监控,在高并发的真实环境中,在一定比例内,这样的偶发异常是非常难免的。