数据库基础
主流的数据库
SqlServer数据库
是微软,.net程序员最爱,中型和大型项目,性能高
Oracle数据库
是甲骨文的,java程序员(必学),大型项目,特点是适合处理复杂业务逻辑。
Mysql数据库
是sun公司,属于甲骨文公司,中型大型项目,特点:并发性好。对简单的sql处理效率高。
db2数据库
是ibm公司,处理海量数据,大型项目。很强悍。
Informix数据库
是ibm公司。在银行系统,安全性高
Sybase数据库
事务想必大家并不陌生,至于什么是 ACID,也是老生常谈了。不过暖男为了保证文章的完整性确保所有人都听得懂,我还是得先说说 ACID,然后再来介绍下什么是分布式事务和常见的分布式事务包括 2PC、3PC、TCC、本地消息表、消息事务、最大努力通知。
什么是事务?举个生活中的例子:你去小卖铺买东西,“一手交钱,一手交货”就是一个事务的例子,交钱和交货必须全部成功,事务才算成功,任一个步骤失败,事务将撤销所有已成功的步骤。
明白上述例子,再来看事务的定义:事务可以看做是一次大的活动,它由不同的小活动组成,这些活动要么全部成功,要么全部失败。
严格意义上的事务实现应该是具备原子性、一致性、隔离性和持久性,简称 ACID。
ACID理论:
总结:通俗意义上事务就是为了使得一些更新操作要么都成功,要么都失败。
看到 ACID 理论,可能有人会说,不对啊 Redis 的事务不能保证所有操作要么都执行要么都不执行,为什么它也叫事务啊?
我们来看看 Redis 怎么说的:
步骤1:redis 官网解释了为什么不支持回滚,他们说首先如果命令出错那都是语法使用错误,是你们自己编程出错,而且这种情况应该在开发的时候就被检测出来,不应在生产环境中出现。
步骤2:然后 Redis 就是为了快!不需要提供回滚。
下面还有一段话我就不截图了,就是说就算提供回滚也没用,你这代码都写错了,回滚并不能使你免于编程错误。而且一般这种错也不可能进入到生产环境,所以选择更加简单、快速的方法,我们不支持回滚。
步骤3:你看看这说的好像很有道理,我们不提供回滚,因为我们不需要为你的编程错误买单!但好像哪里不对劲?角度、立场不同,大家自己品。
分布式事物在什么时候会产生?

单体系统访问多个数据库实例 当单体系统需要访问多个数据库(实例)时就会产生分布式事务。 比如:用户信息和订单信息分别在两个MySQL实例存储,用户管理系统删除用户信息,需要分别删除用户信息及用户的订单信 息,由于数据分布在不同的数据实例,需要通过不同的数据库链接去操作数据,此时产生分布式事务。 简言之:跨数据库实例产生分布式事务。

典型的场景就是微服务架构 微服务之间通过远程调用完成事务操作。比如:订单微服务和库存微服务,下单的 同时订单微服务请求库存微服务减库存。简言之:跨JVM进程产生分布式事务。
这里不讲解了,自己去了解即可。
2PC(Two-phase commit protocol),中文叫两阶段提交协议。两阶段提交协议是一种强一致性设计。
2PC 引入一个事务协调者的角色来协调管理各参与者(也可称之为各本地资源)的提交和回滚,2PC将事务分成两阶段,两阶段分别是指:准备阶段(Prepare phase)、提交阶段(commit phase)。

2PC实现方案有:
3PC 的出现是为了解决 2PC 的一些问题,相比于 2PC 它在参与者中也引入了超时机制,并且新增了一个阶段使得参与者可以利用这一个阶段统一各自的状态。
3PC 包含了三个阶段,分别是准备阶段、预提交阶段和提交阶段,对应的英文就是:CanCommit、PreCommit 和 DoCommit。

超时机制:如果在预提交阶段超时,则中断事务;如果提交阶段超时,则事务参与者提交事物。
优点:
缺点:
数据不一致问题依然存在,即在参与者收到PreCommit请求后等待最终指令,如果此时协调者无法与参与者正常通信,会导致参与者继续提交事务,造成数据不一致。
增加了一个询问阶段,询问阶段可以确保尽可能早的发现无法执行操作而需要中止的行为,但是它并不能发现所有的这种行为,只会减少这种情况的发生在准备阶段以后,协调者和参与者执行的任务中都增加了超时,一旦超时,协调者和参与者都继续提交事务,默认为成功,这也是根据概率统计上超时后默认成功的正确性最大。
三阶段提交协议与两阶段提交协议相比,具有如上的优点,但是一旦发生超时,系统仍然会发生不一致,只不过这种情况很少见罢了,好处就是至少不会阻塞导致永远锁定资源。
TCC是Try、Confirm、Cancel三个词语的缩写。
TCC 要求每个分支事务实现三个操作:预处理(Try),确认(Confirm),撤销(Cancel)。
2PC 和 3PC 都是数据库层面的,而 TCC 是业务层面的分布式事务。

分为三个阶段:
TCC和2PC/3PC很像,不过TCC的事务控制都是业务代码层面的,而2PC/3PC则是资源层面的。
TCC又可以被称为两阶段补偿事务,第一阶段try只是预留资源,第二阶段要明确的告诉服务提供者,这个资源你到底要不要,对应第二阶段的confirm/cancel,用来清除第一阶段的影响,所以叫补偿型事务。
前提:假设库存数量本来是50,那么可销售库存也是50。账户余额为50,可用余额也为50。
用户下单,买了1个单价为1元的商品。流程如下:
因为Try阶段检查并预留了资源,所以confirm阶段一般都可以执行成功。
资源锁定都是在业务代码中完成,不会block住DB,可以做到对db性能无影响。
TCC的实时性较高,所有的DB写操作都集中在confirm中,写操作的结果实时返回(失败时因为定时程序执行时间的关系,略有延迟)。
从流程分析中可以看到,因为事务状态管理,将产生多次DB操作,这将损耗一定的性能,并使得整个TCC事务时间拉长。
事务涉及方越多,Try、Confirm、Cancel中的代码就越复杂,可复用性就越底(这一点主要是相对最终一致性方案而言的)。另外涉及方越多,这几个阶段的处理时间越长,失败的可能性也越高。
TCC实现方案有:
参考博客:
https://www.cnblogs.com/zhangliwei/p/9984129.html
https://blog.csdn.net/weixin_40533111/article/details/85069536
本地消息表这个方案最初是 eBay 提出的。
eBay 的完整方案:https://queue.acm.org/detail.cfm?id=1394128。

消息生产方:
需要额外建一个消息表,并记录消息发送状态。消息表和业务数据要在一个事务里提交,也就是说它们要在一个数据库里面。然后消息会经过MQ发送到消息的消费方。如果消息发送失败,会进行重试发送。(会有定时任务)
消息消费方:
需要处理这个消息,并完成自己的业务逻辑。
如果本地事务处理成功,则发送消息到消息生产方删除对应消息数据,表明已经处理成功了。
如果处理失败(运行期异常,非业务失败),那么就会重试执行。如果是业务上面的失败,可以给生产方发送一个业务补偿消息,通知生产方进行回滚等操作。
生产方和消费方定时扫描本地消息表,把还没处理完成的消息或者失败的消息再发送一遍。如果有靠谱的自动对账补账逻辑,这种方案还是非常实用的。
优点:一种非常经典的实现,避免了分布式事务,实现了最终一致性。
缺点:消息表会耦合到业务系统中,如果没有封装好的解决方案,会有很多杂活需要处理。
RocketMQ 就很好的支持了消息事务,让我们来看一下如何通过消息实现事务。
第一步先给 Broker 发送事务消息即半消息,半消息不是说一半消息,而是这个消息对消费者来说不可见,然后发送成功后发送方再执行本地事务。
再根据本地事务的结果向 Broker 发送 Commit 或者 RollBack 命令。
并且 RocketMQ 的发送方会提供一个反查事务状态接口,如果一段时间内半消息没有收到任何操作请求,那么 Broker 会通过反查接口得知发送方事务是否执行成功,然后执行 Commit 或者 RollBack 命令。
如果是 Commit 那么订阅方就能收到这条消息,然后再做对应的操作,做完了之后再消费这条消息即可。
如果是 RollBack 那么订阅方收不到这条消息,等于事务就没执行过。
可以看到通过 RocketMQ 还是比较容易实现的,RocketMQ 提供了事务消息的功能,我们只需要定义好事务反查接口即可。

其实我觉得本地消息表也可以算最大努力,事务消息也可以算最大努力。
就本地消息表来说会有后台任务定时去查看未完成的消息,然后去调用对应的服务,当一个消息多次调用都失败的时候可以记录下然后引入人工,或者直接舍弃。这其实算是最大努力了。
事务消息也是一样,当半消息被commit了之后确实就是普通消息了,如果订阅者一直不消费或者消费不了则会一直重试,到最后进入死信队列。其实这也算最大努力。
所以最大努力通知其实只是表明了一种柔性事务的思想:我已经尽力我最大的努力想达成事务的最终一致了。
适用于对时间不敏感的业务,例如短信通知。
可以看出 2PC 和 3PC 是一种强一致性事务,不过还是有数据不一致,阻塞等风险,而且只能用在数据库层面。
而 TCC 是一种补偿性事务思想,适用的范围更广,在业务层面实现,因此对业务的侵入性较大,每一个操作都需要实现对应的三个方法。
本地消息、事务消息和最大努力通知其实都是最终一致性事务,因此适用于一些对时间不敏感的业务。
Redis是c语言编写的,是开源免费的。是一个高性能的分布式内存数据库(存储结构为key/value键值对)。
Redis是基于内存运行的,并支持持久化的NoSQL数据库。是单线程的。
缓存穿透:是指查询一个数据库不存在的数据。
Redis正常使用流程:
数据查询先进行缓存查询,如果key不存在或者key已经过期,再对数据库进行查询,并把查询到的对象,放进缓存。如果数据库查询对象为空,则不放进缓存中,直接向外返回空。
问题引出:
想象一下这个情况,如果传入的参数为-1,会是怎么样?这个-1,就是不存在的对象。就会每次都去查询数据库,而每次查询都是空,每次又都不会进行缓存。假如有恶意攻击,就可以利用这个漏洞,对数据库造成压力,甚至压垮数据库。即便是采用UUID,也是很容易找到一个不存在的KEY,进行对数据库的攻击。
1)redis缓存空值(推荐方案)
对上诉所讲传入一个不存在的key,对数据库进行攻击,我们可以将这个不存在的key缓存一个null值放入到redis中,给null值得缓存设置一个比平常缓存短的过期时间即可。当这个不存在的key存在时,即可将null值删除,将数据存入key中即可。
2)判断key的数据格式(有局限,列如key没有任何规则)
网关判断客户端传入对应的key规则,如果不符合数据库的查询规则直接返回null值。
缓存雪崩:指在某一个时间段,缓存集中过期失效。
举例:
马上就要到双十二零点,很快就会迎来一波抢购,这波商品时间比较集中的放入了缓存,假设缓存一个小时。那么到了凌晨一点钟的时候,这批商品的缓存就都过期了。而对这批商品的访问查询,都落到了数据库上,对于数据库而言,就会产生周期性的压力波峰。
1)使用二级缓存(推荐)
一级缓存使用ehcache,二级缓存使用redis。
一级缓存为原始缓存,二级缓存为拷贝缓存,一级缓存失效时,可以访问二级缓存。一级缓存失效时间设置为短期,二级缓存设置为长期。
2)均摊分配redis key的失效时间(推荐)
不同的key,设置不同的过期时间,让缓存失效的时间点不要过于集中。这种方式需要根据业务需求和正式场景看情况设置失效时间。
缓存击穿:热点数据突然过期。
举例:一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。
1)设置热点数据永远不过期。(推荐)
2)使用锁(不推荐,效率低)
单点项目使用本地锁,分布式项目使用分布式锁。
对于某种规则key中,在缓存失效后,通过加锁来控制读数据库写缓存的线程数量。也就是同一时间只允许一个线程查询数据和写缓存,其他线程等待。

CAP 理论是一个已经经过证实的理论,指出在一个分布式系统中最多只能同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance)这三项中的两项。

BASE 理论是对 CAP 中的一致性和可用性进行一个权衡的结果,理论的核心思想就是:我们无法做到强一致性(CAP的一致性就是强一致性),但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性。

2PC,两阶段性提交协议:
2PC存在的问题:

3PC,三阶段性提交协议,是基于2PC的,添加了一个阶段,并添加超时机制和:
3PC优点:
3PC缺点:数据不一致问题依然存在,即在参与者收到PreCommit请求后等待最终指令,如果此时协调者无法与参与者正常通信,会导致参与者继续提交事务,造成数据不一致。
增加了一个询问阶段,询问阶段可以确保尽可能早的发现无法执行操作而需要中止的行为,但是它并不能发现所有的这种行为,只会减少这种情况的发生在准备阶段以后,协调者和参与者执行的任务中都增加了超时,一旦超时,协调者和参与者都继续提交事务,默认为成功,这也是根据概率统计上超时后默认成功的正确性最大。
三阶段提交协议与两阶段提交协议相比,具有如上的优点,但是一旦发生超时,系统仍然会发生不一致,只不过这种情况很少见罢了,好处就是至少不会阻塞导致永远锁定资源。

TCC 是业务层面的分布式事务,分为三个阶段:
TCC和2PC/3PC很像,不过TCC的事务控制都是业务代码层面的,而2PC/3PC则是资源层面的。
TCC又可以被称为两阶段补偿事务,第一阶段try只是预留资源,第二阶段要明确的告诉服务提供者,这个资源你到底要不要,对应第二阶段的confirm/cancel,用来清除第一阶段的影响,所以叫补偿型事务。

Seata 中有三大模块,分别是 TM、RM 和 TC。

在 Seata 中,分布式事务的执行流程:
AT模式是两阶段提交协议(2PC)的演变:
AT模式主要流程:
核心概念:
主从复制:主机数据更新后根据配置和策略,自动同步到备机的master/slaver机制,Master以写为主,Slave以读为主。
从节点开启主从复制,有三种方式。
分别是:配置文件,启动服务命令,客户端命令。
在从服务器的配置文件中加入:
1 | slaveof <masterip> <masterport> |
redis-server启动命令后加入:
1 | redis-server -slaveof <masterip> <masterport> |
Redis服务器启动后,直接通过客户端执行命令
1 | slaveof <masterip> <masterport> |
客户端执行命令:
1 | slaveof no one |
注意:主从复制的开启,完全是在从节点发起的,不需要我们在主节点做任何事情。
// TODO

该阶段的主要作用是在主从节点之间建立连接,为数据同步做好准备。

步骤1:设置master的地址和端口,保存master信息
步骤2:建立socket连接
步骤3:发送ping命令(定时器任务)
步骤4:身份验证
步骤5:发送slave端口信息
至此,主从连接成功!
主从节点之间的连接建立以后,便可以开始进行数据同步,该阶段可以理解为从节点数据的初始化。具体执行的方式是:从节点向主节点发送psync命令(Redis2.8以前是sync命令),开始同步。
数据同步阶段是主从复制最核心的阶段,根据主从节点当前状态的不同,可以分为全量复制和部分复制,下面会有一章专门讲解这两种复制方式以及psync命令的执行过程,这里不再详述。
需要注意的是:在数据同步阶段之前,从节点是主节点的客户端,主节点不是从节点的客户端;而到了这一阶段及以后,主从节点互为客户端。原因在于:在此之前,主节点只需要响应从节点的请求即可,不需要主动发请求,而在数据同步阶段和后面的命令传播阶段,主节点需要主动向从节点发送请求(如推送缓冲区中的写命令),才能完成复制。

步骤1:请求同步数据
步骤2:创建RDB同步数据
步骤3:恢复RDB同步数据
步骤4:请求部分同步数据
步骤5:恢复部分同步数据
至此,数据同步工作完成!
注意:先看[全量复制和部分复制] + [心跳机制],再看这节内容。
数据同步阶段完成后,主从节点进入命令传播阶段;在这个阶段主节点将自己执行的写命令发送给从节点,从节点接收命令并执行,从而保证主从节点数据的一致性。
在命令传播阶段,除了发送写命令,主从节点还维持着心跳机制:PING和REPLCONF ACK。由于心跳机制的原理涉及部分复制,因此将在介绍了部分复制的相关内容后单独介绍该心跳机制。
repl-disable-tcp-nodelay no

主节点心跳流程:
主节点发送命令ping,判断从节点是否超时。
从节点心跳流程:
步骤1:从节点发送命令replconf ack offset
步骤2:主节点接受命令,判断从节点复制偏移量(offset)是否在复制缓冲区
步骤3:如果在,主节点复制偏移量(offset)和从节点复制偏移量(offset)相同,则忽略
步骤3:如果在,但主节点复制偏移量(offset)和从节点复制偏移量(offset)不同,则执行部分复制
步骤3:如果不在,则执行全量复制
在Redis2.8以前,从节点向主节点发送sync命令请求同步数据,此时的同步方式是全量复制;在Redis2.8及以后,从节点可以发送psync命令请求同步数据,此时根据主从节点当前状态的不同,同步方式可能是全量复制或部分复制。
注意:后文介绍以Redis2.8及以后版本为例。
Redis通过psync命令进行全量复制的过程如下:
通过全量复制的过程可以看出,全量复制是非常重型的操作:
由于全量复制在主节点数据量较大时效率太低,因此Redis2.8开始提供部分复制,用于处理网络中断时的数据同步。
部分复制的实现,依赖于三个重要的概念:服务器运行ID,复制积压缓存区,复制偏移量。
概念:每个Redis节点(无论主从),在启动时都会自动生成一个随机ID(每次启动都不一样),由40个随机的十六进制字符组成。
作用:运行id被用于在服务器间进行传输,识别身份。
主从节点初次复制时,主节点将自己的runid发送给从节点,从节点将这个runid保存起来,当断线重连时,从节点会将这个runid发送给主节点;
主节点根据runid判断能否进行部分复制:
概念:复制积压缓冲区,又名复制缓冲区,是由主节点维护的、固定长度的、先进先出(FIFO)队列,默认大小1MB;
作用:当主节点开始有从节点时创建,是备份主节点最近发送给从节点的数据。(用于存储服务器执行过的命令,每次传播命令,master都会将传播的命令记录下来,并存储在复制缓冲区)
组成:偏移量 + 字节值
注意:无论主节点有一个还是多个从节点,都只需要一个复制积压缓冲区。


在命令传播阶段,主节点除了将写命令发送给从节点,还会发送一份给复制积压缓冲区,作为写命令的备份;除了存储写命令,复制积压缓冲区中还存储了每个字节对应的复制偏移量(offset)。由于复制积压缓冲区定长且是先进先出,所以它保存的是主节点最近执行的写命令;时间较早的写命令会被挤出缓冲区。
由于该缓冲区长度固定且有限,因此可以备份的写命令也有限,当主从节点offset的差距过大超过缓冲区长度时,将无法执行部分复制,只能执行全量复制。反过来说,为了提高网络中断时部分复制执行的概率,可以根据需要增大复制积压缓冲区的大小(通过配置repl-backlog-size);例如如果网络中断的平均时间是60s,而主节点平均每秒产生的写命令(特定协议格式)所占的字节数为100KB,则复制积压缓冲区的平均需求为6MB,保险起见,可以设置为12MB,来保证绝大多数断线情况都可以使用部分复制。
从节点将offset发送给主节点后,主节点根据offset和缓冲区大小决定能否执行部分复制:
概念:主节点和从节点分别维护一个复制偏移量(offset),代表的是主节点向从节点传递的字节数;
作用:主节点每次向从节点传播N个字节数据时,主节点的offset增加N;从节点每次收到主节点传来的N个字节数据时,从节点的offset增加N。
offset用于判断主从节点的数据库状态是否一致:
如果二者offset相同,则一致;如果offset不同,则不一致,此时可以根据两个offset找出从节点缺少的那部分数据。例如,如果主节点的offset是1000,而从节点的offset是500,那么部分复制就需要将offset为501-1000的数据传递给从节点。
在了解了节点运行id、复制积压缓冲区、复制偏移量之后,总结一下部分复制的流程:
psync2 runid offset。+CONTINUE,保存master的复制偏移量,并执行命令,完成同步 在了解了节点运行id、复制积压缓冲区、复制偏移量之后,本节将介绍psync命令的参数和返回值,从而说明psync命令执行过程中,主从节点是如何确定使用全量复制还是部分复制的。


slaveof no one,则从节点发送命令为psync ? -1,向主节点请求全量复制;psync <runid> <offset>,其中runid为上次复制的主节点的runid,offset为上次复制截止时从节点保存的复制偏移量。在命令传播阶段,除了发送写命令,主从节点还维持着心跳机制:PING和REPLCONF ACK。心跳机制对于主从复制的超时判断、数据安全等有作用。
每隔指定的时间,主节点会向从节点发送PING命令,这个PING命令的作用,主要是为了让从节点进行超时判断。。
master心跳:
slave心跳:
指令:在命令传播阶段,从节点会向主节点发送REPLCONF ACK命令,命令格式为REPLCONF ACK {offset},其中offset指从节点保存的复制偏移量
周期:频率是每秒1次
作用:
min-slaves-to-write 2min-slaves-max-lag 8https://www.cnblogs.com/kismetv/p/9236731.html