1.什么是可扩展性
可扩展性表明了当需要增加资源以执行更多工作时系统能够获得划算的等同提升的能力。
系统容量表示在一定时间内能够完成的工作量。容量必须是可以有效利用的,系统最大的吞吐量并不等同于容量。大多数基准测试
能够衡量一个系统的最大吞吐量,但真实的系统一般不会使用到极限。如果达到最大吞吐量,则性能会下降,并且响应时间变得不可
接受且非常的不稳定。我们将系统的真实容量定义为在保证可接受的性能的情况下能够达到的吞吐量。
从不同层次来看待容量问题:
1.数据量
2.用户量
3.用户活跃度
4.相关数据集的大小
可扩展性是当增加资源已处理负载和增加容量时系统能够获得的投资产出率(ROI)。
USL(可扩展定律) : 线性扩展的偏差可通过2个因素来建模:
1.无法并发执行的一部分工作
Amdahl 定律,它会导致吞吐量趋于平衡。如果不发任务无法并行,那么不管你如何分而治之,该任务至少需要串行部分的时间。
2.需要交互的另外一部分工作
内部节点间或者进程间通信。这种通信的代价取决于通信信道的数量,而信道的梳理将按照系统内工作者数量的二次方增长。因此,
最终开销比带带来的收益增长的更快,这是产生扩展性倒退的原因。
线性扩展,Amdahl 扩展,USL 扩展。大部分真实的系统看起来像 USL 扩展。
USL 可以用来理解为什么系统增长无法带来等同的效益,它也揭示了一个构建高可用可扩展性系统的重要法则:在系统内尽量避免串行化和交互。
可以衡量一个系统并使用回归来确定串行和交互的量,你可以将它作为容量规划和性能预测评估的最优上限值。也可以检查系统是怎么偏离USL模型的,
将其作为最差下限值以指出系统的哪一部分没有表现出它应有的性能。
另外一个可以理解可扩展性的框架是约束理论,它解释了如何通过减少依赖事件和统计变化来改进系统的吞吐量和性能。
2.扩展MySQL
垂直扩展(向上扩展) : 购买更强悍的机器
水平扩展(向外扩展) : 将任务分配到多台计算机上
向内扩展 : 很少或者不需要的数据,进行归档或者清理
人们通常只有在无法满足增加的负载时才会考虑到可扩展性,具体表现为工作负载从cpu密集型变成IO密集型,并发查询的竞争,以及不断扩大的延迟。主要原因是
查询的复杂度增加或者内存中驻留着一部分不再使用的数据或者索引。
规划可扩展性最困难的部分是估算需要承担的负载到底有多少。这个值不一定非常精确,但必须在一定的数量级范围内。过高,浪费资源;过低,则难以应付可能的负载。
另外还需要大致正确的估计日程表---也就是说,需要知道底线在哪里。对于一些应用,一些简单的原型可以很好的工作几个月,从而有时间去筹建一个更加可扩展的架构。
对于其他的一些应用,你可能需要当前的架构能够为未来2年提供足够的容量。
以下问题可以帮助规划可扩展性:
1.应用的功能完成了多少
2.预期的最大负载的多少
3.如果依赖系统的每个部分来分担负载,在某个部分失效时会发生什么
为扩展赢得时间:
1.优化性能
2.购买性能更强的硬件
向外扩展:
可以把向外扩展策略划分为3个部分:复制,拆分以及数据分片。
最简单的向外扩展的方法是通过复制将数据分发到多个服务器上,然后将备库用于读查询。这种技术对以读为主的应用很有效。但它也有一些缺点,例如重复缓存,但
如果数据规模有限这就不是问题。
另外一个比较常见的向外扩展的方法是将工作负载分布到多个节点。
1.按功能拆分
按功能拆分,或者说按职责拆分,意味着不同的节点执行不同的任务。将独立的服务器或节点分配给不同的应用,这样每个节点只包含它的特定应用所需要的数据。
不能通过按功能划分来无限的进行扩展。
2.数据分片
数据分片是最通用且最成功的方法。它把数据分割成一小片,或者说一块,然后存储到不同的节点中。
数据分片在和某些类型的按功能划分联合使用时非常有用。大多数分片系统也有一些 '全局的' 数据不会被分片(例如城市列表)。全局数据一般存储在单个节点上,
并且通常保存在类似 memcache 这样的缓存里。大多数应用只会对需要的数据进行分片---通常是那些增长得非常庞大的数据。
可以通过用户id来对文章和评论进行分片,而将用户信息保留在单个节点上。
采用数据分片的应用通常会有一个数据库访问抽象层,用以降低应用和分片数据存储之间通信的复杂度,但无法完全隐藏分片。太多的抽象会导致低效率。如果想扩展
写容量,就必须切分数据。
3.选择分区键
数据分片最大的挑战是查找和获得数据:如何查找数据取决于如何进行分片。我们的目标是对那些重要并且频繁查询的数据减少分片(记住,可扩展性法则
其中之一就是要避免不同节点之间的交互)。这其中最重要的是如何为数据选择一个或多个分区键。分区键决定了每一行分配到哪个分区中。
如果知道一个对象的分区键,就可以回答以下2个问题:
1.应该在哪里存储数据
2.应该从哪里取得希望得到的数据
使用哈希来分配,可扩展性不好。使用主键值哈希简化了判断数据存储在何处的操作,单却可能增加获取数据的难度。
跨多个分片的查询比单个分片的查询性能要差,但只要不涉及太多的分片,也不会太糟糕。最糟糕的情况是不知道的需要的数据存储在哪里,这个时候就需要
扫码所有的分片。
一个好的分区键常常是数据库中一个非常重要的实体的主键。这些键决定了分片单元。
确定分区键一个比较好的办法是用实体---关系图,或一个等效的能显示所有实体及其关系的工具来展示数据模型。尽量把相关联的实体靠的更近。这样可以
直观的找出候选分区键。
选择分区键的时候,尽可能选择那些能够避免跨分区片的查询,但同时也要让分片足够小,以避免过大的数据片导致问题。如果可能,应该期望分片尽可能同样
小,这样在为不同数量分片进行分组的时候很容易平衡。
4.多个分区键
复杂的数据模型会使得数据分片更加困难。例如,需要将博客应用按照用户id和文章id进行分片。如果有这种情形:频繁的读取某个用户的所有文章,以及某个
文章所有的评论。如果希望这2个查询都落在同一个分片上,就需要从2个维度进行分片。
5.跨分片查询
大多数分片应用多少都有一些查询需要对多个分片的数据进行聚合或者关联操作。如何让这类查询很好的执行,是实现数据分片的架构中最难的部分。虽然从应用的
角度来说这是一条查询,但实际上需要拆分成多条并行执行的查询,每个分片上执行一条。一个设计良好的数据库抽象层能够减轻这个问题,但类似的查询仍然比分片
内的查询要慢且更加昂贵,所以通常更依赖缓存。
一些语言,比如PHP,对并行执行多条查询的支持不够好。普遍的做法是使用C或者Java编写一个辅助应用来执行查询并聚合结果集。PHP应用只需要查询辅助应用即可。
跨分片查询也可以借助汇总表来执行。可以遍历所有的分片来生成汇总表并将结果在每个分片上冗余。如果在每个分片上存储重复数据太过浪费,也可以把汇总表放到
另外一个数据存储中,这样就只需要一份存储了。
未分片的数据存储在全局节点中,可以使用缓存来分担负载。
如果数据的均衡分布非常重要,一些应用会采用随机分片的方式。跨分片查询并不是数据分片面临的唯一难题。维护数据一致性同样困难。
6.分配数据,分配和节点
分片和节点不存在一一对应的关系,应该尽可能的让分片的大小比节点容量小很多,这样就可以在单个节点上存储多个分片。
保持分片足够小更容易管理。这将使数据的备份和恢复更加容易,如果表足够小,那么像修改表结构这样的操作更加容易。小一点的分片也便于转移,这样有助于重新
分片容量,平衡各个节点的分片。转移分片的效率一般都不高。通常需要将受影响的分片设置为只读模式,提取数据,然后转移到另外一个分片。这包括使用mysqldump
获取数据,用mysql命令将其重新导入。
除了在节点间转移分片,你还要考虑在分配间转移数据,并且尽量不中断整个应用提供服务。'易于管理的大小'指的是保持表足够小,以便能在5或10分钟提供日常的
维护工作。
7.在节点上部署分片
需要确定如何在节点上部署数据分片:
1.每个分片使用单一数据库,并且数据库名要相同。典型的应用场景是需要每个分片都能镜像到原应用的结构。这在部署多个应用实例,并且每个实例对应一个分片时
很有用。
2.将多个分片的表放到一个数据库中,每个表名上包含分片号。这种配置下,单个数据库可以支持多个数据库分片。
3.为每个分片使用一个数据库,并且在数据库中包含所有应用需要的表。在数据库名中包含分片号,但表明不包含分片号。当应用连接到单个数据库并且不在查询指定
数据库名时,这种做法很常见。其优点是无需为每个分片专门编写查询,也便于对只使用单个数据库的应用进行分片。
4.每个分片使用一个数据库,并在数据库名和表名中包含分片号。
5.在每个节点运行多个mysql实例,每个实例上有一个或多个分片,可以使用上面提到的任意组合来安排分片。
我们倾向于使用每个分片一个数据库的方式,并且把分片号写到数据库名和表名中。这会增加例如 alter table 这类操作的复杂度,但也有优点:
1.如果分片全部在一个数据库中,转移分片比较容易
2.因为数据库本身是文件系统中的一个目录,所以可以很方便的管理一个分片的文件
3.如果分片互不关联,则很容易查看分片的大小
4.全局唯一表名可以避免误操作。
8.固定分配
将数据分配到分片中有2种主要的方法:固定分片和动态分配。两种方法都需要一个分区函数,使用行的分区键作为输入,返回存储该行的分片。
固定分配使用的分区函数仅仅依赖于分区键的值。哈希函数和取模运算就是很好的例子。这些函数按照每个分区键的值将数据分散到一定数量的'桶'中。
固定分配的主要优点是简单,开销低,甚至可以在应用中直接硬编码。
缺点是:
1.如果分片很大且数量不多,就很难平衡不同分片间的负载
2.固定分配的方式无法自定义数据放到哪个分片上,这一点对于那些在分片间负载不均衡的应用来说有其重要。一些数据可能比其他的更加活跃,如果这些
数据都分配到一个分片中,固定分配的方式就无法通过热点数据转移的方式来平衡负载。
3.修改分片策略通常比较困难,因为需要重新分配已有的数据。
正式由于这些限制,我们倾向于使用为新应用使用动态分配的方式。但如果是为已有的应用做分片,使用固定分配策略可能更加容易些。
9.动态分配
另外一个是选择动态分配,将每个数据单元映射到一个分片。这个表本身就是分区函数。给定分区键(用户id)的值就可以获得分片号。如果该行不存在,
就从目标分片中找到并将其加入到表中。也可以推迟更新---这就是动态分配的含义。
create table user_to_share (
user_id int not null,
shard_id int not null,
primary key(user_id)
);
动态分配增加了分区函数的开销,因为需要额外调用一次外部资源。出于效率的考虑,这种架构通常需要更多的分层。
动态分配的最大好处是可以对数据存储位置进行细粒度的控制。这使得均衡分配数据到分片更加容易,并可提供适应未知改变的灵活性。
动态分配可以在简单的键---分片映射的基础上建立多层的分片策略。
使用动态分配策略,可以生成不均衡的分片。动态分配以及灵活的利用分片亲和性有助于减轻规模扩大而带来的跨分片查询问题。
10.混合动态分配和固定分配
可以混合使用动态分配和固定分配。目录映射不太大的时候,动态分配可以很好的胜任。但如果分片单元太多的话,效果就变得很差。
11.显式分配
第三种分配策略是在应用插入信贷数据行时,显式的选择目标分片。这种策略在已有的数据上很难做到。所以在为应用增加分片时很少使用,但在某些情况下,
还是有用的。
这个方法是把数据分片号编码到 id 中,这和之前提到的避免主---主复制主键冲突策略比较相似。例如,假设应用要创建一个用户3,将其分配到第11个分片中,
并使用 bigint 列的高8位来存储分片号。这样最近的 id 就是(11<<56)+3,即 792633534417207299。应用可以很方便的从中抽取用户id和分片号,如下:
select (792633534417207299 >> 56) as shard_id, 792633534417207299 & ~(11 << 56) as user_id 。
这种方法的好处是每个对象id同时包含了分区键。如果要从数据库中检索某个特定的评论,无需知道哪个用户评率它;对象id会告诉你到哪里找。如果对象是通过
用户id动态分片的,就得先找到该评论的用户,然后通过目录服务器找到对应的数据分片。
另外一个解决方案是将分区键存储在一个单独的列里。
显式分配的缺点是分片方式是固定的,很难做到分片间的负载均衡。但结合固定分配和动态分配,该方法就能够很好的工作。不再像之前那样哈希到固定数目的桶里
并将其映射到节点,而是将桶作为对象的一部分进行编码。这样就能够控制数据的存储位置,因此可以将相关联的数据一起放到同样的分片中。
BoardReader 使用了该技术的另一个变种:它把分区键编码到Sphinx 的文档ID内。这使得在分片数据存储中查找每个查询结果的关联数据变得容易。
12.重新均衡分片数据
如果有必要,可以通过在分片间移动数据来达到负载均衡。然后,我们也应该尽量避免重新均衡分片数据,业务这可能会影响用户使用。在分片间转移数据也使得
为应用增加新特性更加困难,因为新特性可能还需要包含针对重新均衡脚本的升级。如果分片足够小,就无需这么做;也可以经常移动整个分片来重新均衡负载,这个
比移动分片中的部分数据要容易的多。
一个比较好的策略是使用动态分片策略,并将新数据随机分配到分片中。当一个分片快满的时候,设置一个标志位,告诉应用不要再往这里放数据了。
另外一种使用的多的策略是为每个分片设置2台备库,每个备库都有该分片的完整数据。然后每个备库负责其中一半的数据,并完全停止在主库上查询。
13.生成全局唯一ID
当希望把一个现有的系统转换成分片数据存储时,经常会需要在多台机器上生成全局唯一的id。单一数据存储的时候通常可以使用 auto_increment 列来获取
唯一id。涉及多态服务器就不奏效了。以下几种办法可以解决:
1.使用 auto_increment_increment 和 auto_increment_offset
这2个服务器变量可以让mysql以期望的值和偏移量来增加 auto_increment 列的值。
2.在全局节点中年创建表
在一个全局的数据库节点中创建一个包含 auto_increment 列的表。
3.使用memcached
在 memcached 的api中有一个 incr() 函数,可以自动增长一个数字并返回结果。另外也可以使用 Redis。
4.批量分配数字
应用可以从一个全局节点中申请一批数字,用完后再申请。
5.使用复合值
可以使用一个复合值来做唯一ID,例如分片号和自增数的组合。
6.使用 GUID 值
可以使用 UUID() 函数来生成全局唯一值。注意,尽管这个函数在基于语句的复制时不能正确的复制,但可以先获得这个值,再放到应用的内存中,然后作为
数字在查询中使用。GUID 的值很大且不连续,因此不适合做 InnoDB 的主键。
如果使用全局分配器来产生唯一ID,要注意避免单点争用成为应用的瓶颈。
虽然 memcached 方法执行速度快,但不具备持久性。每次重启 memcache 服务都要重新初始化缓存里面的值。由于需要先找到所有分片中最大值,因此这个过程非常
缓慢且难以实现原子性。
14.分片工具
在设计数据分片应用时,首先要做的事情是编写能够查询多个数据源的代码。
如果没有任何抽象层,直接让应用访问多个数据源,是个很差的设计,因为这会增加大量的编码复杂性。最好的办法是将数据源隐藏在抽象层中。这个抽象层主要完成
以下任务:
1.连接到正确的分片并执行查询
2.分布式一致性校验
3.跨分片结果集聚合
4.跨分片关联操作
5.锁和事务管理
6.创建新的数据分片
Hibernate Shares, HiveDB, Shard-Query,ScaleBase,ScalArc,dbShares,Sphinx
通过多实例扩展:
一个分片较多的架构可能会更有效的利用硬件。mysql并不能完全发挥现代硬件的性能。
不要在一台性能强劲的服务器上只运行一个服务器实例。你可以让分片足够小,以使每台服务器上都能放置多个分片,每个服务器上运行多个实例,然后划分服务器的硬件
资源,将其分配给每个实例。
还有一个选择是运行多个mysql实例,每个实例监听不能网络端口,或绑定到不同的ip地址。
这个时候网络可能会成为瓶颈。可以通过多块网卡并进行绑定来解决这个问题。但Linux内核可能会不理想,这取决于内核版本,因为老的内核对每个绑定的设备的网络中断
只能使用一个cpu,因此不要把太多的连线绑定到很少的虚拟设备上,否则会遇到内核层的网络瓶颈。新的内核有所改善。
另外一个方法是将每个mysql实例绑定到特定的cpu核心上。这有2点好处:1.由于mysql内部的扩展性限制,当核心数较少时,能够在每个核心上获得更好的性能。
2.当实例在多个核心上运行线程时,由于需要在多核心上同步共享数据,因而会有一些额外的开销。这可以避免硬件本身的可扩展性限制。限制mysql到少数几个核心能够帮助
减少cpu核心之间的交互。将进程绑定到具有相同物理套接字的核心上可以获得最优的效果。
通过集群扩展:
理想的扩展方案是单一逻辑数据库能够存储尽可能多的数据,处理尽可能多的查询,并入期望的那样增长。
最终一致性,BASE, 矢量时钟,CAP理论。
1.Mysql Cluster
2.Clustrix
3.ScaleBase
4.GenieDB
5.Akiban
向内扩展 :
处理不断增长的数据和负载最简单的办法是对不再需要的数据进行归档和清理。这种做法并不能用来代替其他策略,但可以作为争取时间的短期策略。
需要考虑以下几点:
1.对应用的影响
2.要归档的行
3.维护数据一致性
4.避免数据丢失
5.解除归档
保持活跃数据独立:
即使并不真的把老数据转移到别的服务器,许多应用也能受益于活跃数据和非活跃数据的隔离。这有助于高效的利用缓存,并为活跃和不活跃的数据使用不同的硬件和架构。
下面列举了几种做法:
1.将表划分为几个部分
分表是一个比较明智的做法,特别是整张表无法完全加载到内存时。数据库本身只缓存 '热' 数据,但事实上这取决于存储引擎。如果用的是 InnoDB,每次缓存一页,而
一页能存储100个用户,但只有 10% 是活跃的,那么 InnoDB 可能认为所有的页都是 '热' 的。拆成2个表可以明显改善内存利用率。
2.MySQL 分区
对表进行分区,能够帮助把最近的数据留在内存中。
3.基于时间的数据分区
如果应用不断有新的数据进来,一般新数据总是比旧数据更加活跃。
3.负载均衡
负载均衡的基本思路很简单:在一个服务器集群中尽可能的平均负载量。通常的做法是在服务器前端设置一个负载均衡器。然后负载均衡器将请求连接路由到最空闲的可用服务器。
负载均衡有5个常见的目的:
1.可扩展性
2.高效性
3.可用性
4.透明性
5.一致性
常见解决方案:Wackamole, DNS, LVS, 硬件负载均衡器,TCP代理,MySQL Proxy 以及在应用中管理负载均衡。大多数使用 HaProxy
1.直接连接
有些人认为负载均衡就是配置在应用和mysql服务器之间的东西。但这并不是唯一的负载均衡方法。你可以在保持应用和mysql连接的情况下使用负载均衡。事实上,集中化的
负载均衡系统只有在存在一个对等置换的服务器池时才能很好的工作。如果应用需要做一些决策,例如在备库上执行读操作是否安全,就需要直连到服务器。
1.复制上的读/写分离
mysql复制产生了多个数据副本,你可以选择在备库还是主库上执行查询。由于备库复制是异步的,因此主要的难点在于如何处理备库上的脏数据。应该将备库用作只读的,
而主库可以同时处理读和写查询。
通常需要修改应用以适应这种分离需求。然后应用就可以使用主库来进行写操作,并将读操作分配到主库和备库上;如果不太关心数据是否脏的,可以使用备库,而对需要
及时数据的请求使用主库。我们称为读/写分离。
比较常见的读写分离的方法如下:
1.基于查询分离
最简单的分离方法是将所有不能容忍脏数据的读和写查询分配到主动或主库服务器上。其他的读查询分配到备库或者被动服务器上。
2.基于脏数据分离
这是对基于查询分离方法的小改进。需要做一些额外的工作,让应用检查复制延迟,以确定备库数据是否太旧。
3.基于会话分离
另一个决定是否从备库读数据的稍微复杂一点的方法是判断用户自己是否修改了数据。用户不需要看到其他用户的最新数据,但需要看到自己的更新。可以在会话层
设置一个标志位,表明做了更新,就将该用户的查询在一段时间内总是指向主库。这是我们通常推荐的策略,因为它是在简单和有效之间的一种很好的妥协。
如果有足够想象力,可以把基于会话的分离方法和复制延迟监控结合起来。如果用户在10秒之前更新了数据,而所有备库延迟在5秒内,就可以安全的从备库中读取数据。
但为整个会话选择同一个备库是一个很好的主意,否则用户可能会奇怪有些备库的更新速度比其他服务器要慢。
4.基于版本分离
这和基于会话的分离方法相似:你可以跟踪对象的版本号以及/或者时间戳,通过从备库读取对象的版本或时间戳来判断数据是否足够新。如果备库的数据太旧,可以从
主库获取最新的数据。即使对象本身没有变化,但如果是顶层对象,只要下面的任何对象发生了变化,也可以增加版本号,这简化了脏数据检查。
5.基于全局版本/会话分离
这个办法是基于版本分离和基于会话分离的变种。当应用执行写操作时,在提交事务后,执行一次 show master status 操作。然后在缓存中存储主库日志坐标,
作为被修改对象以及/或者会话的版本号。当应用连接到备库时,执行 show slave status 并将备库上的坐标和缓存中的版本号相对比。如果备库相比记录点更新,
就可以安全的读取备库数据。
大多数读/写分离解决方案都需要监控复制延迟来决策读查询的分配,不管是通过复制或负载均衡器,或是一个中间系统。如果这么做,需要注意通过 show slave status
得到的 Seconds_behind_master 列的值是不能准确的用于监控延迟。
2.修改应用的配置
还有一个分发负载的方法是重新配置应用。
3.修改DNS名
这是一个比较粗糙的方法。只读服务器拥有一个dns名,写操作的服务器拥有另外一个dns名。如果备库能跟上主库,那就把只读dns名指定给备库,当出现延迟的时候,再将
该dns名指定给主库。
这种dns技术非常容易实现,但也有很多缺点。最大的问题是无法完全控制dns。缺点如下:
1.修改dns并不是立即生效的,也不是原子的。将dns的变化传递到整个网络或在网络间传播都需要比较长的时间。
2.dns数据会在各个地方缓存下来,它的过期时间是建议性质的,不是强制的。
3.可能需要应用或服务器重启才能使修改后的dns完全生效
4.多个ip地址公共一个dns名并依赖于轮询行为来均衡请求,这不是一个好主意。因为轮询的行为并不总是可预知的。
5.dba 可能没有权限直接访问dns
除非应用很简单,否则依赖于不受控制的系统会非常危险。你可以通过修改 /etc/hosts 文件而非dns来改善对系统的控制。当发布一个对该文件的更新时,会知道该变更
已经生效。这比等待缓存的dns失效要好很多。
4.转移ip地址
一些负载均衡解决方案依赖于在服务器间转移虚拟地址,一般能很好的工作。服务器不会根据dns名去监听网络流量,而是根据指定的ip地址去监听流量,所以转移ip地址
允许dns名保持不变。你可以通过arp命令强制使ip地址的更改快速而且原子性的通知到网络上。
最普遍的技术是 Pacemaker,这是Linux-HA 项目的 Heartbeat 工具的继承者。
一个比较方便的技术是为每个物理服务器分配一个固定的ip地址。该ip地址规定在服务器上,不再改变。然后可以为每个逻辑上的'服务'使用一个虚拟的ip地址。它们能
很方便的再服务器间转移,这使得转移服务和应用实例无需再重新配置应用,因此更加容易。
2.引入中间件
上面讨论的方案都是假定应用跟mysql直接相连的。但很多负载均衡方案都会引入一个中间件,作为网络通信的代理。它一边接收所有的通信请求,另外一边将这些请求派发到指定
的服务器上,然后把执行结果发送回请求的机器上。如 HapProxy。
1.负载均衡器
mysql 连接都只是正常的tcp/ip连接,所以可以在mysql上使用多用途负载均衡器。但是由于mysql专有的特性,因此会多一些限制。
1.除非负载均衡器知道mysql的真实负载,否在在分发请求时可能无法做到很好的负载均衡。不是所有的请求都是等同的,但多用途负载均衡器通常对所有的请求都一视同仁。
2.许多负载均衡器知道如何检查一个http请求并把会话'固定'到一个服务器上以保护web服务器上的会话状态。mysql连接也是有状态的,但负载均衡器可能并不知道如何把所有
从单个http会话发送的请求'固定'到一个mysql服务器上,这会损失一部分效率。(如果单个会话的请求都是发到同一个mysql服务器,缓存会更有效率)
3.连接池和长连接可能会阻塞负载均衡器分发连接请求。假如,一个连接池打开了预先配置好的连接数,负载均衡器在已有的4个mysql服务器上分发这些连接。现在增加了2个以上
的mysql服务器。由于连接池不会请求新连接,因而新的服务器会一直空闲的。池中的连接会在服务器间不公平的分配负载,导致一些服务器超出负载,一些则几乎没有负载。连接池
方案只有它们本身能够处理负载均衡时才能工作的很好。
4.许多用途的负载均衡器只会针对http服务器做健康检查和负载检查。一个简单的负载均衡器最少能够核实服务器在一个tcp端口上接受的连接。更好的负载均衡器能够自动发起一个
http请求,并检查返回值以确定这个web服务器是否正常运转。mysql并不接受到3306的http请求,因此需要自己来构建健康检查的方法。你可以在mysql服务器上安装一个http
服务器软件,并将负载均衡器指向一个脚本,这个脚本检查mysql服务器状态并返回一个对应的值。
2.负载均衡算法
随机,轮询,最少连接数,最快响应,哈希,权重
3.在服务器池中增加/移除服务器
在通知负载均衡器有新服务器加入之前,可以暂时把select映射到一台活跃服务器上。然后在新开启的服务器上重放。
在配置连接池中的服务器时,要保证有足够多未使用的容量,以备在撤下服务器做维护时使用,或者当服务器失效时可以派上用场。举个例子,如果你发现每个mysql服务器一般有100
个连接,应该设置池中的每个服务器的 max_connections 为 200。这样算一半的服务器失效,服务器池整体也能处理同样的数量的请求。
3.一主多备的负载均衡
最常见的复制拓扑就是一个主库加多个备库。这个架构不具备很好的可扩展性,但可以通过一些办法结合负载均衡来获得很好的效果。
1.功能分区
对于特定目的的可以通过配置备库或者一组备库来极大的扩展容量。常见的功能包括报表,分析,数据仓库,以及全文检索。
2.过滤和数据分区
可以使用复制过滤技术在相似的备库上对数据进行分区。只要数据在主库上已经被隔离到不同的数据库或表中,这种方法就可以奏效。
3.将部分写操作转移到备库
主库并不总是需要处理写操作的所有工作。你可以分解查询,并在备库上执行其中的一部分,从而显著减少主库的工作量。
4.保证备库跟上主库
如果要在备库执行某种操作,它需要即使知道数据处于哪个时间点---哪怕需要等待一会儿才能达到这个点---可以使用函数 master_pos_wait() 阻塞直到备库赶上了设置的
主库同步点。另外一种方案是使用复制心跳来检查延迟情况。
5.同步写操作
也可以使用 master_pos_wait() 函数来确保写操作已经被同步到一个或者多个备库上。如果应用需要模拟同步复制来保证数据的安全性,就可以在多个备库上轮流执行
master_pos_wait() 函数。这就类似创建了一个 '同步屏障',当任意一个备库出现复制延迟时,都可能花费很长的时间完成,所以最好在确实需要的时候才使用这种方法。