1.大型网站架构模式
1.分层 横向
2.分割 垂直
3.分布式 -- 应用和服务、数据和存储、静态资源、分布式配置、分布式计算、分布式锁、分布式文件系统
4.集群 -- 使用分布式已经将分层和分割后的模块独立部署,为了可扩展、高可用,需要将模块集群和部署,
多服务器部署相同应用构成集群,通过负载均衡共同提供服务
5.缓存 -- CDN(静态和热点缓存)、反向代理(静态缓存)、本地缓存、分布式缓存
6.异步 -- 业务之间的消息传递不是同步调用,而是将一个业务操作分成多个阶段,每个阶段之间通过共享数据方式异步执行进行协作
实现方式 --- 典型的 生产者消费者模式
单机 --- 共享内存队列
分布式 --- 通过分布式消息队列
提高可用性 --- 消费者发送故障,消息在队列中,生产者继续处理请求,压入队列,消费者回复后,可继续处理
加快响应速度 --- 异步话,不用等消费者处理就返回
消除并发访问高峰 --- 队列缓存请求,消费者按需处理
7.冗余
8.自动化
2.性能优化策略
1.性能分析
对请求经历的各个环节进行分析,排查可能出现瓶颈的地方,定位问题
检测请求处理各个环节日志
分析哪个环节响应时间不合理、超过预期
检查监控数据,分析影响性能的因素
:内存、磁盘、网络、CPU
:代码问题、架构设计不合理
:系统资源不足
2.性能优化
Web前端性能优化
浏览器访问优化
1.减少http请求,
合并请求
合并css,js,图片
2.使用浏览器缓存
静态资源文件,缓存在浏览器 -- 可通过版本号更新
通过设置HTTP头中的Cache-Control 和 Expires属性,设置缓存和有效期
更新静态资源时,不要批量更新,防止并发获取资源
3.启用压缩 -- gzip压缩
服务器端对文件压缩,浏览器解压缩,减少通信数据量
4.CSS放在页面最上面,JS方页面最下面
因为浏览器会在下载完全部CSS之后,才会对整个页面进行渲染
5.减少cookie传输
写入Cookie要慎重,减少Cookie中传输的数据量
CDN加速
CDN本质是一个缓存,将数据缓存在离用户最近的地方,方便用户以最快速度获取数据
一般缓存静态资源,静态网页
反向代理
代理网站接受Http请求,保护网站安全,在Web服务器和外网之间建立了一道屏障
安全功能:防止网络攻击
缓存加速功能:当用户第一次访问静态内容时,静态内容被缓存在反向代理服务器上,后续访问缓存
也可把一些模板页面和热点信息放这里,当缓存内容有变化时,通过内部机制通知缓存失效,或更新缓存
负载均衡功能
应用服务器性能优化
分布式缓存
1.缓存基本原理
访问速度快,经过计算后的结果
本质:
一个内存Hash表,根据请求迅速定位到数据返回
适用:
读写比高,很少变化的数据, eg:类目信息、热点数据、热门词、热门商品
2.合理适用缓存
问题:
频繁修改的数据
没有热点的访问
数据不一致和脏读
缓存可用性
防止缓存雪崩,缓存被击穿或失效,导致大量请求直达DB
缓存应该高可用,并有备份机制
3.缓存预热
应用或缓存系统启动时,就把热点数据载入
4.分布式缓存架构
缓存部署在多个服务组成的集群中
Redis
Memcached
异步操作
使用消息队列,将请求异步化,改善网站扩展性
请求数据发送给消息队列后立即返回,再由消息队列的消费者进程处理请求
削峰,处理并发请求
使用集群
使用负载均衡,为一个应用构建一个由多台服务器组成的服务器集群,将并发请求分发到多台服务器上处理,避免单点压力
代码优化
1.多线程
CPU计算密集型 线程数 <= CPU内核数
IO密集型 线程数 = CPU内核数*2+1
线程安全问题:
将对象设计成无状态
使用局部对象
并发访问资源时,使用锁
2.资源复用
减少开销大的系统资源的创建和销毁
数据库连接、网络通信连接、线程、复杂对象
解决:
1.单利模式
2.对象池 连接池、线程池
3.NIO
3.数据结构
1.HashCode生成
原始串 --MD5--> 信息指纹 --Hash算法--> HashCode
4.垃圾回收
选择合适的收集器,立即各种算法原理和使用场景
存储服务器性能优化
1.固态硬盘 加速
2.B+树 VS LSM树
3.分布式存储 HDFS
2.高可用架构
高可用架构目的:保证服务器硬件故障时,服务依然可用、数据能被正常访问
手段:
数据、服务的 冗余备份、失效转移
某些服务器宕机,将服务切换到其他可用的服务器上,磁盘损坏,则从备份磁盘读取数据
大型网站的分层架构和物理服务器的分布式部署,使得位于不同层次的服务器具有不同的可用性特点
应用层:
应对高并发,通过负载均衡将一组服务器组成一个集群,共同对外提供服务,当负载均衡设备通过心跳检测等手段,监控到某服务器不可用时,将其从集群剔除,
并将请求分发到集群中其他可用服务器
请求是无状态的,集群中应用层是对等的,每个服务器都可以处理,请求再任一台服务器中处理都不会影响最终的结果
服务层:划分可复用服务,集群部署
注册登录服务、Session管理服务、授权服务、账户管理服务
通过集群部署,保证高可用
服务层服务器被应用层通过分布式服务调用框架访问,分布式服务调用框架会在应用层客户端程序中实现软件负载均衡,
并通过服务注册中心对提供服务的服务器进行心跳检测,发下服务不可用,立即通知客户端修改服务访问列表,剔除不可用的服务器
数据层:
数据库、文件服务、缓存服务、搜索服务等数据存储于访问服务,部署到各自独立的服务器集群
数据服务器上存储着数据,为保证服务器宕机时不丢数据,数据访问服务不中断,需要在数据写入时进行数据同步复制,
将数据写入多台服务器,实现数据冗余备份,服务器宕机时,程序将访问切换到有备份数据的机器上
网站升级
发布时都需要关闭服务,重新部署系统,整个过程相当于服务器宕机,可用性架构要考虑和处理好网站升级发布引起的宕机
3.高可扩展、伸缩性架构
4.Redis数据过期和淘汰策略
参考:
https://yq.aliyun.com/articles/257459
redis原理总结:
https://blog.csdn.net/u010942020/article/details/79265827
redis内存管理与优化
https://yq.aliyun.com/articles/67122?spm=a2c4e.11153940.blogcont257459.25.57f121f5RpubsV
redis主从复制原理
https://www.cnblogs.com/kevingrace/p/5685332.html
redis集群原理
单线程的Redis为何快?
它所有的数据都在内存中,所有的运算都是内存级别的运算,而且单线程避免了多线程的切换性能损耗问题。
正因为 Redis 是单线程,所以要小心使用 Redis 指令,对于那些耗时的指令(比如keys),一定要谨慎使用,一不小心就可能会导致 Redis 卡顿
Redis 单线程如何处理那么多的并发客户端连接?
Redis的IO多路复用:redis利用epoll来实现IO多路复用,将连接信息和事件放到队列中,
依次放到文件事件分派器,事件分派器将事件分发给事件处理器
https://www.cnblogs.com/lojunren/p/3856290.html
Socket IO多路复用 事件处理器
1 连接应答处理器
2 ---> 1、2、3 ---> 文件事件分发器 ---> 命令请求处理器
3 命令回复处理器
过期数据清理时机
为了防止一次性清理大量过期Key导致Redis服务受影响,Redis只在空闲时清理过期Key
具体时机如下:
1.访问key时,会判断key是否过期,逐出过期key
2.CPU空闲时在定期serverCron任务中,逐出部分过期Key
3.每次事件循环执行时,逐出部分过期Key
过期数据清理算法
Redis过期Key清理的机制对清理的频率和最大时间都有限制,在尽量不影响正常服务的情况下,进行过期Key的清理,以达到长时间服务的性能最优。
Redis会周期性随机测试一批设置了过期时间的key并进行处理,测试到的已过期的key将被删除。具体的算法如下:
1.Redis配置项hz定义了serverCron任务的执行周期,默认为10,即CPU空闲时每秒执行10次;
2.每次过期key清理的时间不超过CPU时间的25%,即若hz=1,则一次清理时间最大为250ms,若hz=10,则一次清理时间最大为25ms;
3.清理时依次遍历所有的db;
4.从db中随机取20个key,判断是否过期,若过期,则逐出;
5.若有5个以上key过期,则重复步骤4,否则遍历下一个db;
6.在清理过程中,若达到了25%CPU时间,退出清理过程;
这是一个基于概率的简单算法,基本的假设是抽出的样本能够代表整个key空间,redis持续清理过期的数据直至将要过期的key的百分比降到了25%以下。这也意味着在长期来看任何给定的时刻已经过期但仍占据着内存空间的key的量最多为每秒的写操作量除以4.
由于算法采用的随机取key判断是否过期的方式,故几乎不可能清理完所有的过期Key;
调高hz参数可以提升清理的频率,过期key可以更及时的被删除,但hz太高会增加CPU时间的消耗
在逐出算法中,根据用户设置的逐出策略,选出待逐出的key,直到当前内存小于最大内存值为主.
可选逐出策略如下:
volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用 的数据淘汰
volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数 据淘汰
volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据 淘汰
allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
no-enviction(驱逐):禁止驱逐数据
相关最佳实践
不要放垃圾数据,及时清理无用数据
实验性的数据和下线的业务数据及时删除;
key尽量都设置过期时间
对具有时效性的key设置过期时间,通过redis自身的过期key清理策略来降低过期key对于内存的占用,同时也能够减少业务的麻烦,不需要定期手动清理了.
单Key不要过大
给用户排查问题时遇到过单个string的value有43M的,也有一个list 100多万个大成员占了1G多内存的。这种key在get的时候网络传输延迟会比较大,需要分配的输出缓冲区也比较大,在定期清理的时候也容易造成比较高的延迟. 最好能通过业务拆分,数据压缩等方式避免这种过大的key的产生。
不同业务如果公用一个业务的话,最好使用不同的逻辑db分开
从上面的分析可以看出,Redis的过期Key清理策略和强制淘汰策略都会遍历各个db。将key分布在不同的db有助于过期Key的及时清理。另外不同业务使用不同db也有助于问题排查和无用数据的及时下线.
5.Redis集群原理
参考:
https://blog.csdn.net/truelove12358/article/details/79612954
https://blog.csdn.net/yejingtao703/article/details/78484151
https://segmentfault.com/p/1210000009708869/read
redis的2种持久化方式 aof(文件追加) rdb(快照)
aof 类似数据库的binlog,把每次写操作以追加形式记录在其中,以文件形式刷到磁盘
可以使用不同的fsync策略,每秒fsync,每次写时fsync或无fsync
默认使用:每秒fsync策略(fsync由后台线程进行处理,主线程会尽力处理客户端请求),故障时,最多丢失1秒的数据
rdb快照机制
redis工作在内存中,rdb就是每隔一段时间,对内存中的数据做一次快照,保存在rdb文件中
redis的主从同步可以实现异步,由于rdb的机制,它会在做快照时,fork出一个子进程,由子进程来做快照,父进程继续处理请求,适合数据备份
redis-cluster集群原理
使用去中心化思想,使用hash slot方式将 16348个hash slot覆盖到所有节点上,
对于存储的每个key,使用crc16(key)&16348 = slot,得到key对应的hash slot并在访问key时,去找它的hash slot在哪个节点上,
然后由当前访问节点从实际被分配了这个hash slot的节点去取数据.
节点间使用轻量级协议通信,性能很高,自动实现负载均衡与高可用,自动实现failover,并支持动态扩展
去中心化思想免去了proxy代理的消耗
实现原理如下:
1.所有redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽
2.节点的fail是通过急群众超过半数的节点检测失效时才生效
3.客户端与redis节点直连,不需要中间proxy层,客户端不需要连接集群所有节点,连接集群中任一节点即可,
连接的节点,根据 crc16(key)&16348 = slot,得到slot所在的节点,把请求转发给对应的节点进行处理
4.redis-cluster把所有的物理节点映射到[0-16383]slot上,cluster 负责维护node<->slot<->value
Redis集群中内置了16348个哈希槽,当需要在 Redis 集群中放置一个 key-value 时,redis 先对key 使用 crc16 算法算出一个结果,然后把结果对 16384 求余数,
这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,redis 会根据节点数量大致均等的将哈希槽映射到不同的节点
1.集群中任何两个节点之间时相互连通的,客户端可以与任一一个节点连接,然后就可访问集群中的任何一个节点,对其进行存取和其他操作
实现:
插槽 slot:一个可以存储两个数值的一个变量,该变量取值范围是 0~16383
存取的key到达时,redis根据crc16(key)算法得到一个结果,然后对16384取余,
这个key会对应一个在 0-16383 之间的哈希槽,通过这个值,去找到对应的插槽所对应的节点,然后直接自动跳转到对应的节点上进行存取操作
eg:
集群有3个节点 集群搭建时,给每个节点分配哈希槽
node1 0~5000
node2 5001~10000
node3 10001~16383
在node1上执行 set a a
1.使用crc16(key)计算,然后对结果进行16384求余
crc16(a) = 26384
26384%16484=10000
2.查找包含10000哈希槽的节点,找到node2,自动跳转到node2节点
3.在node2上执行 set a a 命令
node3上执行查询 get a
crc16(a) = 26384
26384%16484=10000
跳转到node2
在node2上执行 get a
为保证高可用,理论上应该给集群中每个master节点至少一个备用的redis节点服务
集群如何判断某个节点是否挂掉?
1.每个节点都存有这个集群中所有主节点及从节点的信息
2.它们自己相互通过ping-pong判断是否节点可以连接上,如果有一半以上的节点去ping某个节点时没有回应,
集群就认为这个节点宕机了,然后去连接它的备用slave节点
3.如果某个节点及其所有从节点全部挂掉,集群就进入fail状态
集群容错机制:投票
1.投票过程是集群中所有master参数,超过半数以上master节点与某个master节点通信超时(cluster-node-timeout),认为该master节点挂掉
2.什么时候整个集群不可用?
集群中的slot映射[0-16383]不完整时,进入fail状态
1.如果集群任意master挂掉,且当前master没有slave,集群进入fail状态,即:集群的slot映射[0-16383]不完整时进入fail状态.
2.如果集群超过半数以上master挂掉,无论是否有slave,集群进入fail状态
集群中的主从复制
集群中的每个节点都有1个至N个复制品,其中一个为主节点,其余的为从节点,如果主节点下线了,集群就会把这个主节点的一个从节点设置为新的主节点,继续工作。这样集群就不会因为一个主节点的下线而无法正常工作
注意:
1、如果某一个主节点和他所有的从节点都下线的话,redis集群就会停止工作了。redis集群不保证数据的强一致性,在特定的情况下,redis集群会丢失已经被执行过的写命令
2、使用异步复制(asynchronous replication)是redis 集群可能会丢失写命令的其中一个原因,有时候由于网络原因,如果网络断开时间太长,redis集群就会启用新的主节点,之前发给主节点的数据就会丢失。
redis主从复制过程原理
原理:
主要是通过master server持久化的rdb文件实现的。
master server 先dump出内存快照文件,然后将rdb文件传给slave server,slave server 根据rdb文件重建内存表。
过程:
1.slave启动连接到master server后,slave主动发送Sync命令给master
2.master接收SYNC命令后,判断是否有正在进行内存快照的子进程,如果有,则等待其结束,否则fork一个子进程,子进程把内存数据保存为文件,发送给slave
3.master子进程做数据快照时,拂霓裳继续接收请求写数据,此时,父进程把新写入的数据放到 待发送缓存队列 中
4.slave接收内存快照文件后,清空内存数据,根据接收的快照文件,重建内存表数据结构
5.master把快照文件发送完后,发送 缓存队列 中保存的 子进程快照期间改变的数据,给slave,slave做相同处理,保证数据一致性
6.master后续接收的数据,都会通过步骤1建立的连接,把数据发送到slave
特别注意:
slave如果因网络或其他原因,断开与master的连接,当slave重新连接时,需要重新获取master的内存快照文件,slave的数据会自动全部清空,然后再重建内存表
集群目标,达到线性可扩展性、可用性、数据一致性。
线程扩展
官方推荐最大节点数量为1000,Master与Slave之间使用异步replication
数据一致性
客户端容忍一定程度的数据丢失,集群尽可能保存Client write操作的数据,保证数据一致性
可用性
redis集群通过Partition提供给一定程度可用性,当集群中一部分节点失效或无法通讯时,集群仍可继续通过服务
1.只要集群中大多数Master可达、且失效的Master至少有一个Slave可达,即集群非Fail状态,集群都是可用的
2.Redis集群的replicas migration机制可以将拥有多个Slave的Master的某个Slave,迁移到没有Slave的Master下,即Slave分布相对平衡,确保Master都有一定数量的Slave备份。
6.数据库分库分表策略的具体实现方案
参考:https://blog.csdn.net/xlgen157387/article/details/53976153
分库分表:
https://blog.csdn.net/bluishglc/article/details/7970268
https://blog.csdn.net/everlasting_188/article/details/53232367?utm_source=blogxgwz1
水平分库如何做平滑扩展?
https://baijiahao.baidu.com/s?id=1584744472767329273&wfr=spider&for=pc
随着业务规模的不断扩大,需要选择合适的方案去应对数据规模的增长,以应对逐渐增长的访问压力和数据量
MySQL扩展具体的实现方式:
业务拆分、主从复制+读写分离,数据库分库与分表
业务拆分:
集中式架构,改成服务式架构,进行业务拆分,每个模块使用独立数据库,不同业务访问不同DB,将原本对一个数据库的依赖拆分为对4个数据库的依赖,这样的话就变成了4个数据库同时承担压力,系统的吞吐量自然就提高了
eg:
电商平台,包含了用户、商品、评价、订单等几大模块,最简单的做法就是在一个数据库中分别创建users、shops、comment、order四张表
主从复制:
原理:
数据复制的实际就是Slave从Master获取Binary log文件,然后再本地镜像的执行日志中记录的操作。
由于主从复制的过程是异步的,因此Slave和Master之间的数据有可能存在延迟的现象,此时只能保证数据最终的一致性。
数据库分库分表:
目的:
机器通过线性增长就能满足业务逻辑不断增长的需求
1.分表实现策略:
用户ID、表容量
大部分数据库的设计和业务的操作,级别都与用户ID相关,使用用户ID是最常用的分库的路由策略
表容量:
当数据比较大的时候,对数据进行分表操作,首先要确定需要将数据平均分配到多少张表中,即:表容量
eg:
order表分成100张表存储,
首先,对用户ID进行取模操作, user_id%100获取对应的表进行存储查询操作
user_id%100 = 0
user_id%100 = 1
user_id%100 = 2
。。。。。。
查询:
user_id = 101 那么,我们在获取值的时候的操作,可以通过下边的sql语句:
select * from order_1 where user_id= 101
其中,order_1是根据 101%100 计算所得,表示分表之后的第一张order表。
MyBatis,支持数据库分表功能的SQL
/**
* 获取用户相关的订单详细信息
* @param tableNum 具体某一个表的编号
* @param userId 用户ID
* @return 订单列表
*/
public List
xml的Mapper映射
select * from order_${tableNum}
where user_id = #{userId}
注意:
1.${tableNum} 直接让参数表名加入到sql中
2.在实际的开发中,我们的用户ID更多的可能是通过UUID生成的,我们可以首先将UUID进行hash获取到整数值,然后在进行取模操作。
2.分库实现策略
数据库分表能够解决单表数据量很大的时候数据查询的效率问题,但是无法给数据库的并发操作带来效率上的提高,
因为分表的实质还是在一个数据库DB上进行的操作,容易受到数据库IO性能和连接数的限制
分库:
将数据库IO性能问题平均分配处理,解决单台数据库的性能问题
用户ID、库容量
eg:
对order进行分库,分成100个库,简单取模
分库策略:user_id%100
user_id%100 = 0
user_id%100 = 1
user_id%100 = 2
......
如果用户ID是uuid,先hash(userId),再进行取模
3.分库分表实现策略
数据库分表可以解决单表海量数据的查询性能问题!!!
分库可以解决单台数据库的并发访问压力问题!!!
分库分表:
同时考虑这两个问题,因此,我们既需要对单表进行分表操作,还需要进行分库操作,以便同时扩展系统的并发处理能力和提升单表的查询性能
分库分表的依据 --- 字段选择
参考:http://825635381.iteye.com/blog/2368838
常用:主键ID、用户ID、时间、商户ID、产品ID、业务类型等
原则:
按使用频率最高的维度字段,去分库分表,尽量保证高可使用维度下只查询单表
分库分配策略,
主要原理:
分区、取模、数据路由表
常见的路由策略:
中间变量 = user_id%(库数量*表数量)
库序号 = 取整(中间变量/每个库的表数量)
表序号 = 中间变量%每个库的表数量
eg:
数据库有256个,每个库中有1024个数据表,用户user_id = 262145 根据路由策略计算如下:
中间变量 = 262145%(256*1024) = 1
库序号 = 取整(1/1024) = 0
表序号 = 1%1024 = 1
即:
对于user_id=262145,将被路由到第0个数据库的第1个表中
总结:
1.根据用户ID应该是比较简单的一种。其他方式比如使用号段进行分区或者直接使用hash进行路由等
2.如果用户的ID是通过UUID的方式生成的话,我们需要单独的进行一次hash操作,然后在进行取模操作等,其实hash本身就是一种分库分表的策略
hash路由策略
优点:
数据分布均匀
缺点:
数据库迁移时麻烦,不能按照机器性能分摊数据
根据二八法则, 20%的租户可能占用了80%的存储资源。如果使用hash算法,很可能导致数据分布不均匀。
3.分库分表之后,如果我们需要对系统进行进一步的扩阵容(路由策略变更),将变得非常不方便,
需要我们重新进行数据迁移
分库分表策略 --- 按时间区间
基本原理:
一定区间时间内产生的数据放到一张表中,多个时间区间的表,放到一个库中
eg:
单库多表
1.按月分表
user_201601,user_201602,...,user_201612
2.按年分表
user_2016,user_2017,...
多库多表
按天分表,每天一张表,当单库超过100张表时,进行分库到下一张表
假如第一张报表在库BD0,表名是user_20160201。从DB0.user_20160201,..到DB0.user_20160511就100张表了,
接下来就要进行分库了,进入20160512,就是DB1.user_20160512
拆分算法:
算法就是上线的时候定一个上线日期,具体算法如下
库ID = (当前日期 - 上线日期)/ 100
表ID = user_yyyyMMdd
注:好处是可以直接根据时间经过简单计算定位到哪个库和哪个表
还有一种算法:
库ID = (当前日期 - 上线日期)/ 100
表ID = (当前日期 - 上线日期) % 100
表名如下: DB0.user_0001, user_0002,....,user_01000。
注:表名和库名都要经过计算,比较麻烦
4)按月分表,每个月一张表;
这种情况,一般就不用分库了,一年12张表说明量也不会特别大,
如果量特别大,或者是热点数据,可以一年分一个库,具体算法和上面差不多
分库分表策略 --- 按主键ID区间
对于自增主键ID,可按照ID区间进行分表,以1000万为分界线,对线性的ID进行切割分表,每涨到1000万数据,分到下一张表,超过一定数据的表(eg:100),进行分开库
算法:单表1000万,每个库分成100个表
库ID = 主键ID/1000万/100
表ID = 主键ID/1000万%100
eg:
DB0.user_0000,...,DB0.user_0099, DB1.user_0000,...,DB1.user_0099
分库分表策略 --- 按指定字段hash后再取模
如果要取模的字段不是整数型,要先hash后,再通过取模算法,算出在哪个库和那个表。
具体算法,参照下面的按用户ID取模
分库分表策略 --- 按照用户ID取模 --- 使用场景最多
参考:
http://825635381.iteye.com/blog/2368838
很多时候都是用户相关数据量最大,需要分库分表,查询维度更多也是按照用户来查询,所以对用户取模,让同一个用户的数据落到一张表里面
案例:
用户ID时整数型的,库数量分为4个,每个库表数量为8,一共32张表
原理:
4库,8表,共32张表,用户ID要平均分配到1~32张表
两种分法:
1.1到8是第一个库,9到16第二个库,17到24第三个库,25到32是第四个库,每个库里面表的编号都是0到3,
原则:一个库里面一个一个分,分完再下一个库一个一个分,保证不重复,不漏
2.1、5、9、13每隔4个一个库,2开头隔4个一个库,
原则:一个库分一个,再分下一个库,一圈走完,再在第一个库没分到的表继续分,保证了不重复,不漏
算法:
库ID = userId % 库数量4
表ID = userId / 库数量4 % 表数量8
或
库ID = userId / 表数量4 % 库数量4
表ID = userId % 表数量8
分库分表策略 --- 数据路由表
如果分库分表的算法很复杂,可以通过路由表+程序算法,来存储和计算分库分表规则,
不过一般不建议,分库分表搞得太复杂,不便于维护和查询问题
总结:
1.分区算法
优点:线性扩容,平滑扩容,不需要数据迁移
缺点:存在热点数据,非时间维度查询多的情况,聚合复杂
建议:冷数据,用户维度查询少,且数据量大的情况用分区算法
2.取模算法
优点:同一个热点的数据可以做到一个表里面,查询方便
缺点:扩容不是很方便,需要数据迁移
建议:用户维度查询多,热点维度查询多的情况,建议使用
3.实际应用
分库分表的核心是未来数据量的预估,根据预估和实际使用情况来确定分库分表方案
eg:
订单类数据以用户维度取模分表最好,
商品数据以商户取模最好,
签到等活动流水或日志数据时间分区最好。
数据迁移、扩容方案:
参考:
https://www.cnblogs.com/firstdream/p/7090524.html
https://www.cnblogs.com/baimingqian/p/7599850.html
迁移方案:
1.全量迁移 + 停机增量迁移 : 停机是为了保证不会在迁移期间产生新的增量数据 不推荐
2.双写法、非停机迁移:旧库修改 变为 新旧库同时修改,在此期间迁移数据到新库,最终对比数据,然后切换过去
扩容方案:
1.全量迁移 + 停机增量迁移 不推荐
2.双主同步方案 --- 完美方案
步骤:
1.假设,现有A、B两台, x%2 = 0路由到A,x%2 = 1路由到B
2.现在为A、B 建立双主同步库,A对应C,B对应D
3.修改配置项,建立新的链接, %4=0对应A、%4=1对应B、%4=2对应C、%4=3对应D
4.撤销双主,A与C、B与D不再建立关系
5.数据缩容, eg:将A中%4=2的全部干掉,C中%4=0的全部干掉。完美结束
数据迁移效率:
新表不建立索引,迁移完成再建立索引。有实践为证,亿级表无索引迁移需要约10分钟,有索引迁移约需要50个小时;
hint语句:加上append、nologging、parallel等能极大优化效率;
如果是分区表,可单张分区表独立迁移。
分裂法扩容 --- 升级从库
参考:
https://baijiahao.baidu.com/s?id=1584744472767329273&wfr=spider&for=pc
https://mp.weixin.qq.com/s/BLOneOs-cPxP_9b5eH8oQA
线上数据库,我们为了保持其高可用,一般都会每台主库配一台从库,读写在主库,然后主从同步到从库。如下,A,B是主库,A0和B0是从库
当需要扩容的时候,我们把A0和B0升级为新的主库节点,如此由2个分库变为4个分库。同时在上层的分片配置,做好映射,规则如下:
uid%4=0和uid%4=2的分别指向A和A0,
即:之前指向uid%2=0的数据,分裂为uid%4=0和uid%4=2
uid%4=1和uid%4=3的指向B和B0,
即:之前指向uid%2=1的数据,分裂为uid%4=1和uid%4=3
因为A和A0库的数据相同,B和B0数据相同,所以此时无需做数据迁移即可。只需要变更一下分片配置即可,通过配置中心更新,无需重启
由于之前uid%2的数据分配在2个库里面,此时分散到4个库中,
由于老数据还存在(uid%4=0,还有一半uid%4=2的数据),所以需要对冗余数据做一次清理。
这个清理,不会影响线上数据的一致性,可是随时随地进行
处理完成以后,为保证高可用,以及下一步扩容需求。可以为现有的主库再次分配一个从库
具体步骤:
1.修改分片配置,做好新库和旧库的映射
2.同步配置,从库升级为主库
3.解除主从关系
4.冗余数据清理
5.为扩容后新的数据节点搭建新的从库
Mysql数据库分库和分表方式(常用)
1 分库
1.1 按照功能分库
按照功能进行分库。常见的分成6大库:
1 用户类库:用于保存了用户的相关信息。例如:db_user,db_system,db_company等。
2 业务类库:用于保存主要业务的信息。比如主要业务是笑话,用这个库保存笑话业务。例如:db_joke,db_temp_joke等。
3 内存类库:主要用Mysql的内存引擎。前台的数据从内存库中查找,速度快。例如:heap。
4 图片类库:主要保存图片的索引以及关联。例如:db_img_index,db_img_res。
5 日志类库:记录点击,刷新,登录等日志信息。例如:db_log_click,db_log_fresh,db_log_login。
6 统计类库:对业务的统计,比如点击量,刷新量等等。例如db_stat。
1.2 按照城市站分库
如果业务遍布全国,在按照功能分库库,每一个城市复制一份一模一样的库,只是库后缀都是城市名称。
eg:db_log_click_bj,db_log_click_tj,db_log_click_sh;
2 分表
2.1 按照用户或业务的编号分表
对与用户或业务可以按照编号%n,进行分成n表。
eg:笑话表。
tb_joke_01,tb_joke_02,tb_joke_03,tb_joke_04........
2.2 按照日期分表
对于日志或统计类等的表。可以按照年,月,日,周分表。
eg: 点击量统计。
tb_click_stat_201601,tb_click_stat_201602,tb_click_stat_201603
3 Mysql数据库常用架构
核心:一主多从,读写分离。
分布式数据库架构,排序、分组、分页问题及解决思路:
水平切分 --- 分库 垂直拆分 --- 分表
1.多分片(水平切分)返回结果合并(排序) --- 排序
思路:
1.对各分片结果去重后合并,再排序
2.对各分片结果求max、min值,然后最得到的值集合,再取 max、min值
3.
2.多分片(水平切分)返回结果分页 --- 分页
思路:
合并各分片返回结果,逻辑分页 --- 把分配结果集,放到内存中再进行分页
3.多分片(水平切分)查询有分组语法的合并 --- 分组
DB中间件:MyCat、cobar 或 应用层中间层 sharding-jdbc
数据异构 --- 分布式数据库架构
1.Hadoop + Hive --- 适合离线非实时查询操作
思路:使用Hadoop HDFS来存储数据,通过Hdoop MapReduce完成数据计算,通过Hive HQL语言使用部分与RDBBS一样的表格查询特性和分布式存储计算特性。
优点: 可以解决问题
具有并发处理能力
可以离线处理
缺点: 实时性不能保证
网络延迟会增加
异常捕获难度增加
Web应用起来比较复杂
2.分布式存储、总库集中查询
ES集群、MongoDB集群