单库模式
特点:简单粗暴、适合数据量千万以下小型应用、企业网站、创业公司首选、不具备可用性与并发性。
读写分离集群模式
概念:适用于读多写少的场景,读从库、写主库,使用分片中间件将读路由到从数据库,写路由到主数据库,并使用binlog日志进行主从同步。
特点:
1.架构复杂度提升,成本提高。
2.所有节点数据均保持同步。
3.适用于读多写少,单表不过千万的互联网应用。
4.配合MHA中间件方案实现高可用性(主挂了会自动把从提升为主)。
分库分表(分片)集群模式
简要架构图:
特点:
1.架构复杂度提升,成本提高。
2.每个节点数据是所有数据的子集。
3.适用于十亿级数据总量大型应用。
4.不具备高可用特性,可以配合主从架构提升可用性。
分片算法
Hash分片:
1.Hash法分为取模与一致性Hash。
2.数据分配均衡。
3.节点扩展复杂,数据迁移难度大。
4.建议提前部署足够的节点。
5.适用于预算充足的大型互联网应用。
范围分片:
1.容易造成尾部热点效应,即大量的读写集中在尾部某个节点。
2.范围法结构简单,扩展容易。
3.适合范围检索。
4.数据分布不均匀,局部负载压力大。
5.适用于流水账应用。
时间分片:
按日期范围分片。
概括:通过将重要字段单独剥离出一张小表,让每一页能够容纳更多的行,进而缩小数据扫描的范围,达到提高执行效率的目的。
解释:mysql从磁盘中加载数据是一页一页来加载的(每页16K),再查询一条完整的记录时,先去小表中查找某条记录对应的id,然后再拿着这个id去大表查快速定位得到一条完整的记录。这样做可以显著提升查找效率,因为先查小表的话,由于小表里的字段少,所占空间小, mysql读取的每一页也就可以存储更多的行数据。
什么时候需要做垂直分表?
单表数据量未来可能千万,字段超过20个,且包含了超长的Varchar、CLOB、BLOB等字段。
什么样的字段放在小表?
1.数据查询、排序时需要的字段,如分类编号、商户id、品牌编号、逻辑删除标志位等。
2.高频访问的小字段,如商品名称、子标题、价格、厂商基本信息等。
什么样的字段放在大表?
1.低频访问字段:配送信息、售后声明、最后更新时间等。
2.大字段:商品图文详情、图片BLOB、JSON元数据等。
解决方案描述:
服用端缓存分为进程内缓存(如Mybatis的二级缓存)和进程外缓存(如Redis),互联网的应用场景一般都是读多写少,写数据时直接往Mysql里写,读的时候先从进程内缓存读,进程内读不到数据再去进程外缓存读(如Redis),如果还读不到就只能去Mysql读了,然后进行缓存的双写更新(更新Redis、更新Mybatis的缓存)。如果写的时候修改了Mysql中的某条数据,肯定会导致缓存和Mysql的数据不一致,这时候可以引入Kafka或RocketMQ之类的消息队列,将变更消息写入消息队列,然后消息队列自动往缓存组件推送变更消息,从而实现Mysql和缓存数据的一致性。
用消息队列保证缓存一致性的原理如图:
有哪些情况有必要使用缓存?
1.缓存的数据是稳定的。
2.瞬时可能会产生极高并发的场景。
3.一定程度上允许数据不一致。
原因:
1.如果你只有一个主数据库,那么自增id是不会重复的,但这个地方会成为系统的瓶颈,而且也容易成为一个单点故障。
如果你是主/从数据库,这可以解决单点的问题,但不会解决生成id瓶颈的问题。而且在主数据库挂掉,进行主从切换的时候,这个自增id是可能出问题的。
2.从数据库表的设计来看,用自增id作为主键是存在一些问题的。特别是在分布式架构中,多个实例中要保持一个表的主键的唯一性,而普通单表的自增id主键就满足不了这点。通俗点说,在分布式架构中,在一个高并发的环境下,会有两个或者多个数据同时生成且他们的ID相同,这就不符合主键的要求了。
使用UUID可以替代自增主键吗?
不可以,如果使用有序递增的主键,新的记录只需要插入在B+树的尾部,重排发生的机率校小。而UUID是无序的,插入记录时可能插到一个已放满索引的B+树节点,从而导致大量索引重排(分链或B+树调整)。
什么是雪花算法?
雪花算法(Snowflake)是 Twitter 公司分布式项目采用的 ID 生成算法。
实现雪花算法时要注意时间回拨带来的影响。因为雪花算法是按照时间顺序生成id的,时间回拨可能导致生成相同的id。
布隆过滤器原理:将一个key经过若干次Hash映射到一个位数组中的某几位上。
如何减少误判
1.增加二进制数组位数。
2.二进制数组位数够长的情况下增加Hash次数。
删除一条记录怎么办
布隆过滤器因为某一位二进制可能被多个编号Hash引用,因此布隆过滤器无法直接处理删除数据的情况。解决方案:
1.定时异步重建布隆过滤器。
2.使用计数布隆过滤器,写入数据时对应比特位计数加1,删除数据时对应比特位减1。
IP直连有什么问题?
存在强耦合问题,例如把访问数据库的ip写在项目的配置文件中,万一数据库换机器了,就要重新修改配置文件,然后重新将项目打包上线。而大厂项目的上线是非常麻烦的,需要提单并经过多级审批,所以IP直连带来的强耦合会造成很多麻烦。
解决方案1: 引入内部DNS
将ip存储在DNS域名解析器中,每次访问服务时通过域名先从DNS中获取一个ip,然后访问ip对应的服务。
特点:
1.简单粗暴。
2.没有故障发现与转移,如果某个ip对应的服务挂掉了,DNS是发现不了的,客户端依然可能得到一个不可用的ip去访问服务。
3.多IP只有轮询规则,DNS中如果一个域名配置了多个ip,只能使用轮询,不支持丰富的负载均衡策略。
解决方案2:加入注册中心
1.注册中心采用心跳检测机制,从而支持故障发现与故障转移。
2.支持多种负载均衡规则。
3.架构复杂度增加。
概念:
一致性C代表更新操作成功后,所有节点在同一时间的数据完全一致。
可用性A代表用户访问数据时,系统是否能在正常响应时间返回预期的结果。
分区容错性P代表分布式系统在遇到某节点或网络分区故障的时候,仍然能够对外提供满足一致性或可用性的服务。
使用场景:
AP(满足可用性和分区容错性,常用在互联网应用)表现为订单创建后不等待库存减少直接返回处理结果,即使出现分区故障也直接返回处理结果,具体库存减少可以在分区故障修复后经过一系列补救措施进行库存的减少。
CP(满足一致性和分区容错性,常用在银行机构)表现为订单创建后一直等待库存减少后才返回结果,如果出现分区故障,会一直等待故障修复后才返回结果。
AC(满足可用性和一致性,常用在小型单体应用)表现为不再拆分数据系统即不分区,在一个数据库的一个事务中完成操作,也就是单体应用。
CAP(要快、要一致、还要能分区,做不到)。
负载均衡器的种类有哪些
从软硬件层面可分为硬件负载均衡和软件负载均衡
硬件负载均衡器成本高,一般是十万或上百万一台,软件负载均衡器成本较低,如nginx可以部署在普通的机器上。
从网络模型可以分为四层代理(TCP)和七层代理(HTTP)。
Nginx的五种负载均衡策略
轮询策略(默认):排排坐吃果果,你一口我一口。
权重策略:我比你胖,我要吃十个。
IP_HASH:一个萝卜(IP)一个坑。相同IP的每次请求都会落在同一台机器上,如果某个IP占用的服务资源比其他IP多,长期积累后会导致不同机器之间的负载出现巨大差异。
URL_HASH:一个白菜(URL)一个坑。URL的粒度比IP更细,但是如果某个URL占用的服务资源比其他URL多,长期积累后也会导致不同机器之间的负载出现巨大差异。
FAIR:你们谁闲着,出来挑粪去。通过心跳机制选择一台延迟较低或闲置的机器。(工业上较少使用)
【阿里JAVA规范】不得使用外键与级联,一切外键概念必须在应用层解决。
1.每次做DELETE 或者UPDATE都必须考虑外键约束,会导致开发的时候很痛苦,测试数据极为不方便。
2.性能问题,额外的数据一致性校验查询。
3.并发问题,在从表写入时会检查外键在主表中是否存在,如果这时主表有写入操作,那么会在主表加写锁,这会导致从表无法检查主表,从而从表写入进入阻塞。
4.级联删除问题,多层级联删除会让数据变得不可控(出现问题也不知道到底是哪里的数据被删除引起的),触发器也严格被禁用。
5.数据耦合,数据库层面数据关系产生耦合,数据迁移维护困难(当数据量过大要将某一张表迁移到非关系型数据库,外键约束不好处理)。
Canal是阿里巴巴旗下的一款开源项目,纯Java开发。基于数据库增量日志解析,提供增量数据订阅&消费,目前主要支持了MySQL(也支持mariaDB)。
Canal是一个中间件,同步数据和MySQL主从同步类似,相当于把下图从库部分换成了Canal,然后Canal侦听MySQL的日志,再把日志交给其他类型的数据库进行同步。
MySQL主从同步:
Canal侦听:
如何实现异构数据库同步的解藕?可以引入消息队列:
MHA架构在MySQL原有主从架构的基础上增加了MHA manager,并在master和应用之前增加了VIP虚拟IP,同时在MySQL每个节点中增加了MHA node模块,master也自带了binlog server,用于记录master上的所有数据:
MHA故障发现与转移过程
MHA manager每隔几秒就会ping一下mater,如果连续3次都无法ping通,就通知slave看能不能ping通,如果所有Node连接不上Master就认定master实例挂了:
接下来MHA manager终止所有Slave与Master同步管道,并断开Master与VIP的绑定:
找到同步了最新数据的slave,这个slave将差异部分发送到其他slave,直到所有slave的数据都相同:
MHA manager将master binlog的差异部分发到各个slave,直到所有slave的日志和旧主相同:
MHA manager选主,选主方式有三种,1.指定谁是主;2。看哪个slave日志最新;3.按注册实例列表向后选择;
VIP虚拟IP进行IP飘移,将IP指向新的master:
在旧master恢复后,可以作为slave自动从new master同步:
MHA的优缺点
不过,尽管MHA具有诸多缺点,但它具有良好的兼容性,主流的MySQL版本它几乎都支持,所以也是目前传统行业应用以及金融领域使用到的主要的高可用方案。
分布式事务的通用解决方案(两阶段提交):
Seata的解决方案
分布式事务体系的三个重要角色:
事务管理器(TM):决定什么时候全局提交/回滚(司令官)。
事务协调者(TC):负责通知命令的中间件Seata-Server(传令官)。
资源管理器(RM):做具体事儿的工具人(大头兵)。
三个角色的工作图示:
Seata AT模式下如何实现数据自动提交、回滚
Seata在第一阶段提交本地事务时会直接把数据写入数据库,但是Seata会在所有的资源管理器(RM)数据库中增加一张额外的回滚日志表,每写入一条数据就在回滚表中插入一条相反操作的日志,如果第二阶段全局事务正常提交了就把回滚日志表对应的数据删除,如果第二阶段全局事务提交失败,则执行回滚日志表里记录相反操作的SQL。
Seata如何避免并发场景的脏读与脏写
Seata使用事务协调者(TC)自带的分布式锁,在执行写入时将该条数据加锁,防止其他事务写入:
最简单的模型是一台Nginx挂载多台Tomcat,但这个架构存在单点故障,如果Nginx挂了,那整个服务就挂了
:
此时使用虚拟IP技术(VIP),这个虚拟IP指向反向代理层,反向代理层改为使用两台Nginx,每台Nginx绑定一个keepalived组件,这个组件监听Nginx的运行状态,如果Nginx挂了,就把虚拟IP飘移并指向另一台运行正常的Nginx,但这个架构也存在问题,就是反向代理层只有一台Nginx在工作,另一台Nginx闲置了
:
此时可以使用DNS,使用两个虚拟IP,分别指向两台Nginx,这样两台Nginx都可以工作了,如果某台Nginx挂了,依然进行IP飘移,只是两个虚拟IP都会指向同一台Nginx而已:
那为什么不直接使用DNS指向不同的Tomcat呢?原因如下:
1.只负责IP轮询获取,不保证节点可用。
2.DNS IP列表变更有延时。
3.外网IP占用严重(外网IP本来就是稀缺资源)。
4.安全性降低(Tomcat直接暴露给外网不够安全,所以使用了DNS在安全层面还要做好DNS劫持的预防)。
Cluster模式是Redis3.0开始推出:
采用无中心结构,每个节点保存数据和整个集群状态, 每个节点都和其他所有节点连接。
官方要求:至少6个节点才可以保证高可用,即3主3从;扩展性强、更好做到高可用。
各个节点会互相通信,采用gossip协议交换节点元数据信息。
数据分散存储到各个节点上。
Redis Cluster 集群 与 Redis Sentinel 有什么不同?
Redis Cluster 集群如何将数据分散存储?
为什么是16384?
在Redis 集群中槽分配的元数据会不间断的在Redis集群中分发,以保证所有节点都知晓槽的分配情况
16384=16k,在发送心跳包时使用char进行bitmap压缩后是2k(2 * 8 (8 bit) * 1024(1k) = 16K)
通常我们不同部署超过10000个Redis主节点,因此16384就够用了。
MySQL的隔离级别:
MVCC的概念:
在MySQL InnoDB存储引擎下,RC、RR基于MVCC(多版本并发控制)进行并发事务控制。
MVCC是基于”数据版本”对并发事务进行访问。
可重复读和读已提交图示
RR级别(Read Repeat): Select1=张三 Select2=张三
RC级别(Read Commit):Select1=张三 Select2=张小三
RC级别下出现了“不可重复读”:
MVCC是基于undo_log版本链来实现的:
UNDO_LOG不是会被删除吗?
中间数据万一被删了版本链不就断了?
UNDO_LOG版本链不是立即删除,
MySQL确保版本链数据不再被“引用”后再进行删除。
ReadView是什么
ReadView是“快照读”SQL执行时MVCC提取数据的依据
快照读就是最普通的Select查询SQL语句
当前读指代执行下列语句时进行数据读取的方式
Insert、Update、Delete、
Select…for update
Select…lock in share mode
ReadView是一个数据结构,包含4个字段:
m_ids:当前活跃的事务编号集合
min_trx_id:最小活跃事务编号
max_trx_id:预分配事务编号,当前最大事务编号+1
creator_trx_id:ReadView创建者的事务编号
读已提交(RC):在每一次执行快照读时生成ReadView:
可重复读(RR):仅在第一次执行快照读时生成ReadView,后续快照读复用(但有例外,见下面能否完全避免幻读的分析):
RR级别下使用MVCC能避免幻读吗?能,但不完全能!
连续多次快照读,ReadView会产生复用,没有幻读问题。
特例:同一个事务中,当两次快照读之间存在当前读,ReadView会重新生成,导致产生幻读。
蓝绿发布:
应用一开始部署在了两个集群上,首先断开网关与集群B的连接,只让集群A承接流量,升级集群B;然后断开集群A并连上集群B,把流量接入升级后的集群B;升级集群A,重新连上集群A,让集群A和集群B都承接流量。
蓝绿发布的缺点:
在断开一半的集群后如果突然出现了高并发,由于可承接流量的资源减半了,一半的集群可能无法承受高并发,导致整个系统瘫痪。
红黑发布:
升级的时候开辟一个全新的集群,在新集群里部署新的应用,然后将流量从旧集群转移到新集群。
与红黑发布的对比:
与蓝绿部署相比,红黑部署可以充分利用了云计算的弹性伸缩优势,从而获得了两个收益:一是,简化了流程;二是,避免了在升级的过程中,由于只有一半的服务器提供服务,而可能导致的系统过载问题。
灰度发布:
灰度发布,也被叫作金丝雀发布。与蓝绿部署、红黑部署不同的是,灰度发布属于增量发布方法。也就是说,服务升级的过程中,新旧版本会同时为用户提供服务。就像小米手机系统更新时先让一部分用户参与体验一样,如果新版本没发现什么问题再逐步将用户流量迁移到新版本。
灰度发布遇到的挑战1:
考虑数据库变更对旧版本的兼容性影响
例如:某数据表有ab两个字段
程序猿小甲的SQL是: insert into t values(‘a’,’b’);
但v1.1版本中在数据表增加了c字段,就版本运行就会报错
因此在考虑未来灰度发布的情况,要求团队成员写SQL必须明确字段
insert into t(a,b) values(‘a’,’b’);
TIPS:任何删除、更新字段信息的操作都要格外谨慎
对于新旧版本无法协同作业的情况:
方案一:
放弃灰度,采用红黑方式全量发布
方案二(了解一下即可,项目几乎不会考虑):
可以考虑独立部署数据源进行迁移
为新旧版本分配独立的数据源
但新旧数据源之间数据同步会更考验架构师与DBA的智慧
灰度发布遇到的挑战2:
灰度发布用户群的选择
不能直接采用类似于Nginx的权重Weight
会导致一个用户不同请求在新旧版本间反复横跳,出现无法预期的Bug
解决方法:利用Nginx + Lua脚本化
基于IP或者UA等用户稳定特性然后Hash取模来决定访问新旧版本
Hash(192.168.31.102) % 10 = 7 #送给旧版本
Hash(192.168.31.108) % 10 = 9 #送给新版本
灰度发布遇到的挑战3:
什么时候才可以提升分配比例
在发布过程中,我们应该注意监测用户请求失败率、用户请求处理时长和异常出现数量这几个信息,以保证快速发现问题并及时回滚。
在灰度发布的时候,可以部署相对较小的集群,让集群保持在高压力确认新版应用的性能情况,之后再酌情进行扩容。
TIPS:推荐了解下SkyWalking,国产开源监控系统,简单粗暴
不严谨解释:命中的索引值超过总量25%就可能产生索引选择性陷阱,导致全表扫描。
PS:一切以Explain执行计划为准。
索引选择性差的案例:
在业务表只查询逻辑删=0数据select * from t where is_delete = 0或1。
查询格力集团月薪<=5万的人员工资。
医院护士表查询性别=女的工作人员。
索引选择性差解决办法
通过组合索引提高选择性(业务相关):
select * from 护士表 where 科室=’妇科’ and 性别=’女’
引入搜索引擎:如Es或者Solr(更换数据源):
例如:将护士表导入ElasticSearch,Es基于分片多线程检索,解决查询慢的问题
强制使用索引(有时会有奇效,以实际运行效果为准)
explain select * from question force index(answer) where answer = ‘A’
增加缓存,提高全表扫描速度(超能力)
innodb_buffer_pool_size=16G
innodb_buffer_pool_instances=2
使用redis等缓存型数据库
MQ中间件的通用消息投递过程:
发送阶段:遇到高延迟,Producer会多次重发消息,直到Broker ack确认,过程中Broker会自动去重,超时Producer产生异常,应用进行捕获提示。
存储阶段:Broker先刷盘再ack确认,即便ack失败消息不会丢失,多次重试直到Producer接收,会导致消息积压。
消费阶段:Broker向Consumer发数据,一段时间未接收,自动重发,直到Consumer Ack确认,Consumer注意幂等处理。
实际应用时的建议:
1.异步刷盘(NSYNC_FLUSH),改同步刷盘
2.存储介质损坏,建议采用RAID10或分布式存储
3.不要启用自动Ack,RabbitMQ存在此问题
4.避开都市传说ActiveMQ
RAID10存储架构:
Raid 10其实结构非常简单,首先创建2个独立的Raid1,然后将这两个独立的Raid1组成一个Raid0,当往这个逻辑Raid中写数据时,数据被有序的写入两个Raid1中。磁盘1和磁盘2组成一个Raid1,磁盘3和磁盘4又组成另外一个Raid1;这两个Raid1组成了一个新的Raid0。如写在硬盘1上的数据1、3、5、7,写在硬盘2中则为数据1、3、5、7,硬盘3中的数据为0、2、4、6,硬盘4中的数据则为0、2、4、6,因此数据在这四个硬盘上组合成Raid10,且具有raid0和raid1两者的特性。
本篇笔记的编写主要参考B站“IT老齐”
相关的视频讲解。