中间件笔记


数据库和redis、es等的双写可用 canal + kafka 实现

代码中双写会出现一致性问题

mysql ACID靠什么保证

A原子性由undo log日志保证,它记录了需要回滚的日志信息,事务回滚时撤销已经执行成功的sql

C一致性一般由代码层面来保证

I隔离性由MVCC来保证

D持久性由内存+redo log来保证,mysql修改数据同时在内存和redo log记录这次操作,事务提交的时候通过redo log刷盘,宕机的时候可以从redo log恢复

数据库读写分离,解决主从延迟的方法

数据库同步写(不用修改业务层)
选择性强制读主(需要改动业务层)
中间件 / 缓存路由(写入时缓存 key,查询时根据 key是否存在选择主库或从库)

对于类似亿级数据操作的场景,可以优先考虑分而治之

redis 的 keys 和 scan 命令

使用keys指令可以扫出指定模式的key列表。

keys指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。这个时候可以使用scan指令,scan指令可以无阻塞的提取出指定模式的key列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用keys指令长。

es 提升查询效率

给 filesystem cache 留足够大的空间、减少不用来搜索的字段,可以用 mysql/hbase 做存储、定期预热、冷热数据分离、避免join和nest等操作、不允许深度分页,可以用 scroll 或 search after 替代

mysql limit 数字大时的优化

select * from table where col = 1 limit 100000,1

select a.* from table a join (select id from table where col = 1 limit 100000,1) b on a.id = b.id
##如果是一页页翻,可以记录max id
select * from table where id > @maxId limit 1

Redis 哨兵和 Cluster 区别

哨兵模式监控权交给了哨兵系统,集群模式中是工作节点自己做监控

哨兵模式发起选举是选举一个leader哨兵节点来处理故障转移,集群模式是在从节点中选举一个新的主节点,来处理故障的转移

哨兵模式缺点是不能水平扩展。如果你的数据量很少,主要是承载高并发高性能的场景,比如你的缓存一般就几个 G,单机就足够了,可以使用 replication,一个 master 多个 slaves,要几个 slave 跟你要求的读吞吐量有关,然后自己搭建一个 sentinel 集群去保证 Redis 主从架构的高可用性。

Redis cluster,主要是针对海量数据+高并发+高可用的场景。Redis cluster 支撑 N 个 Redis master node,每个 master node 都可以挂载多个 slave node。这样整个 Redis 就可以横向扩容了。如果你要支撑更大数据量的缓存,那就横向扩容更多的 master 节点,每个 master 节点就能存放更多的数据了。集群模式内置了哨兵逻辑,无需再部署哨兵节点。

Redis Cluster 数据分区

一致性哈希算法将 整个哈希值空间 组织成一个虚拟的圆环,范围是 [0 , 232-1],对于每一个数据,根据 key 计算 hash 值,确数据在环上的位置,然后从此位置沿顺时针行走,找到的第一台服务器就是其应该映射到的服务器:

与哈希取余分区相比,一致性哈希分区将 增减节点的影响限制在相邻节点。以上图为例,如果在 node1 和 node2 之间增加 node5,则只有 node2 中的一部分数据会迁移到 node5;如果去掉 node2,则原 node2 中的数据只会迁移到 node4 中,只有 node4 会受影响。

一致性哈希分区的主要问题在于,当 节点数量较少 时,增加或删减节点,对单个节点的影响可能很大,造成数据的严重不平衡。还是以上图为例,如果去掉 node2,node4 中的数据由总数据的 1/4 左右变为 1/2 左右,与其他节点相比负载过高。

方案三:带有虚拟节点的一致性哈希分区

该方案在 一致性哈希分区的基础上,引入了 虚拟节点 的概念。Redis 集群使用的便是该方案,其中的虚拟节点称为 槽(slot)。槽是介于数据和实际节点之间的虚拟概念,每个实际节点包含一定数量的槽,每个槽包含哈希值在一定范围内的数据。

在使用了槽的一致性哈希分区中,槽是数据管理和迁移的基本单位。槽 解耦 了 数据和实际节点 之间的关系,增加或删除节点对系统的影响很小。仍以上图为例,系统中有 4 个实际节点,假设为其分配 16 个槽(0-15);

槽 0-3 位于 node1;4-7 位于 node2;以此类推…
如果此时删除 node2,只需要将槽 4-7 重新分配即可,例如槽 4-5 分配给 node1,槽 6 分配给 node3,槽 7 分配给 node4;可以看出删除 node2 后,数据在其他节点的分布仍然较为均衡。

Redis 哨兵模式


上图 展示了一个典型的哨兵架构图,它由两部分组成,哨兵节点和数据节点:

哨兵节点: 哨兵系统由一个或多个哨兵节点组成,哨兵节点是特殊的 Redis 节点,不存储数据;
数据节点: 主节点和从节点都是数据节点;
在复制的基础上,哨兵实现了 自动化的故障恢复 功能,下方是官方对于哨兵功能的描述:

监控(Monitoring): 哨兵会不断地检查主节点和从节点是否运作正常。
自动故障转移(Automatic failover): 当 主节点 不能正常工作时,哨兵会开始 自动故障转移操作,它会将失效主节点的其中一个 从节点升级为新的主节点,并让其他从节点改为复制新的主节点。
配置提供者(Configuration provider): 客户端在初始化时,通过连接哨兵来获得当前 Redis 服务的主节点地址。
通知(Notification): 哨兵可以将故障转移的结果发送给客户端。
其中,监控和自动故障转移功能,使得哨兵可以及时发现主节点故障并完成转移。而配置提供者和通知功能,则需要在与客户端的交互中才能体现。

Redis 两种持久化对比

是的,持久化的话是Redis高可用中比较重要的一个环节,因为Redis数据在内存的特性,持久化必须得有,我了解到的持久化是有两种方式的。

RDB:RDB 持久化机制,是对 Redis 中的数据执行周期性的持久化。

AOF:AOF 机制对每条写入命令作为日志,以 append-only 的模式写入一个日志文件中,因为这个模式是只追加的方式,所以没有任何磁盘寻址的开销,所以很快,有点像Mysql中的binlog。

两种方式都可以把Redis内存中的数据持久化到磁盘上,然后再将这些数据备份到别的地方去,RDB更适合做冷备,AOF更适合做热备,比如我杭州的某电商公司有这两个数据,我备份一份到我杭州的节点,再备份一个到上海的,就算发生无法避免的自然灾害,也不会两个地方都一起挂吧,这灾备也就是异地容灾,地球毁灭他没办法。

tip:两种机制全部开启的时候,Redis在重启的时候会默认使用AOF去重新构建数据,因为AOF的数据是比RDB更完整的。

那这两种机制各自优缺点是啥?
我先说RDB吧

优点:
他会生成多个数据文件,每个数据文件分别都代表了某一时刻Redis里面的数据,这种方式,有没有觉得很适合做冷备,完整的数据运维设置定时任务,定时同步到远端的服务器,比如阿里的云服务,这样一旦线上挂了,你想恢复多少分钟之前的数据,就去远端拷贝一份之前的数据就好了。

RDB对Redis的性能影响非常小,是因为在同步数据的时候他只是fork了一个子进程去做持久化的,而且他在数据恢复的时候速度比AOF来的快。

缺点:
RDB都是快照文件,都是默认五分钟甚至更久的时间才会生成一次,这意味着你这次同步到下次同步这中间五分钟的数据都很可能全部丢失掉。AOF则最多丢一秒的数据,数据完整性上高下立判。

还有就是RDB在生成数据快照的时候,如果文件很大,客户端可能会暂停几毫秒甚至几秒,你公司在做秒杀的时候他刚好在这个时候fork了一个子进程去生成一个大快照,哦豁,出大问题。

我们再来说说AOF

优点:
上面提到了,RDB五分钟一次生成快照,但是AOF是一秒一次去通过一个后台的线程fsync操作,那最多丢这一秒的数据。

AOF在对日志文件进行操作的时候是以append-only的方式去写的,他只是追加的方式写数据,自然就少了很多磁盘寻址的开销了,写入性能惊人,文件也不容易破损。

缺点:
一样的数据,AOF文件比RDB还要大。

AOF开启后,Redis支持写的QPS会比RDB支持写的要低,他不是每秒都要去异步刷新一次日志嘛fsync,当然即使这样性能还是很高,我记得ElasticSearch也是这样的,异步刷新缓存区的数据去持久化,为啥这么做呢,不直接来一条怼一条呢,那我会告诉你这样性能可能低到没办法用的,大家可以思考下为啥哟。

Redis 通信过程


Redis 服务端进程初始化的时候,会将 server socket 的 AE_READABLE 事件与连接应答处理器关联。

客户端 socket01 向 Redis 进程的 server socket 请求建立连接,此时 server socket 会产生一个 AE_READABLE 事件,IO 多路复用程序监听到 server socket 产生的事件后,将该 socket 压入队列中。文件事件分派器从队列中获取 socket,交给连接应答处理器。连接应答处理器会创建一个能与客户端通信的 socket01,并将该 socket01 的 AE_READABLE 事件与命令请求处理器关联。

假设此时客户端发送了一个 set key value 请求,此时 Redis 中的 socket01 会产生 AE_READABLE 事件,IO 多路复用程序将 socket01 压入队列,此时事件分派器从队列中获取到 socket01 产生的 AE_READABLE 事件,由于前面 socket01 的 AE_READABLE 事件已经与命令请求处理器关联,因此事件分派器将事件交给命令请求处理器来处理。命令请求处理器读取 socket01 的 key value 并在自己内存中完成 key value 的设置。操作完成后,它会将 socket01 的 AE_WRITABLE 事件与命令回复处理器关联。

如果此时客户端准备好接收返回结果了,那么 Redis 中的 socket01 会产生一个 AE_WRITABLE 事件,同样压入队列中,事件分派器找到相关联的命令回复处理器,由命令回复处理器对 socket01 输入本次操作的一个结果,比如 ok ,之后解除 socket01 的 AE_WRITABLE 事件与命令回复处理器的关联。

Redis做异步队列

一般使用list结构作为队列,rpush生产消息,lpop消费消息。当lpop没有消息的时候,要适当sleep一会再重试。

Redis的同步机制

Redis可以使用主从同步,从从同步。第一次同步时,主节点做一次 bgsave,并同时将后续修改操作记录到内存buffer,待完成后将RDB文件全量同步到复制节点,复制节点接受完成后将RDB镜像加载到内存。加载完成后,再通知主节点将期间修改的操作记录同步到复制节点进行重放就完成了同步过程。后续的增量数据通过AOF日志同步即可,有点类似数据库的binlog。

Zookeeper 选举

Zxid是极为重要的概念,它是一个long型(64位)整数,分为两部分:纪元(epoch)部分和计数器(counter)部分,是一个全局有序的数字。

epoch代表当前集群所属的哪个leader,leader的选举就类似一个朝代的更替,你前朝的剑不能斩本朝的官,用epoch代表当前命令的有效性,counter是一个递增的数字。

选举规则:

 return ((newEpoch > curEpoch)
                || ((newEpoch == curEpoch)
                    && ((newZxid > curZxid)
                        || ((newZxid == curZxid)
                            && (newId > curId)))));

当其他节点的纪元比自身高投它,如果纪元相同比较自身的zxid的大小,选举zxid大的节点,这里的zxid代表节点所提交事务最大的id,zxid越大代表该节点的数据越完整。

最后如果epoch和zxid都相等,则比较服务的serverId,这个Id是配置zookeeper集群所配置的,所以我们配置zookeeper集群的时候可以把服务性能更高的集群的serverId配置大些,让性能好的机器担任leader角色。

在这里插入图片描述

24. ZooKeeper 能保证单调写一致性,所有请求会按到达顺序依次处理。但不保证写后读一致性,也就不保证强一致性(写入只保证过半的节点)

25. ZooKeeper 写入流程

在这里插入图片描述

  • Leader收到客户端的写请求,生成一个事务(Proposal),其中包含了zxid;
  • Leader开始广播该事务,需要注意的是所有节点的通讯都是由一个FIFO的队列维护的;
  • Follower接受到事务之后,将事务写入本地磁盘,写入成功之后返回Leader一个ACK;
  • Leader收到过半的ACK之后,开始提交本事务,并广播事务提交信息
  • 从节点开始提交本事务。

bigInt(20) 、 tinyint(2) 、varchar(32) 这种后面带数字与不带数字有何区别

bigint 和 int 加不加数字都不影响能存储的值。
bigint 能存储2^64-1 范围内的值,int是2^32-1。只是有些前端会根据括号里来截取显示而已。建议不加。

varchar()就必须带,因为varchar()括号里的数字代表能存多少字符。假设varchar(2),就只能存两个字符,不管是中文还是英文。varchar() 这个值可以设得稍稍大点,因为内存是按照实际的大小来分配内存空间的,不是按照值来预分配的。需要注意的是255这个边界。小于255都需要一个字节记录长度,超过255就需要两个字节。

SQL逻辑相同 性能差异较大的 可能情况

一.字段发生了转换,导致本该使用索引而没有用到索引
1.条件字段函数操作
2.隐式类型转换
3.隐式字符编码转换
(如果驱动表的字符集比被驱动表得字符集小,关联列就能用到索引,如果更大,需要发生隐式编码转换,则不能用到索引,latin<gbk<utf8<utf8mb4)

二.嵌套循环,驱动表与被驱动表选择错误
1.连接列上没有索引,导致大表驱动小表,或者小表驱动大表(但是大表走的是全表扫描) --连接列上建立索引
2.连接列上虽然有索引,但是驱动表任然选择错误。–通过straight_join强制选择关联表顺序
3.子查询导致先执行外表在执行子查询,也是驱动表与被驱动表选择错误。
–可以考虑把子查询改写为内连接,或者改写内联视图(子查询放在from后组成一个临时表,在于其他表进行关联)
4.只需要内连接的语句,但是写成了左连接或者右连接。比如select * from t left join b on t.id=b.id where b.name='abc’驱动表被固定,大概率会扫描更多的行,导致效率降低.
–根据业务情况或sql情况,把左连接或者右连接改写为内连接

三.索引选择不同,造成性能差异较大
1.select * from t where aid= and create_name>’’ order by id limit 1;
选择走id索引或者选择走(aid,create_time)索引,性能差异较大.结果集都有可能不一致
–这个可以通过where条件过滤的值多少来大概判断,该走哪个索引

四.其它一些因素
1.比如之前学习到的是否有MDL X锁
2.innodb_buffer_pool设置得太小,innodb_io_capacity设置得太小,刷脏速度跟不上
3.是否是对表做了DML语句之后,马上做select,导致change buffer收益不高
4.是否有数据空洞
5.select选取的数据是否在buffer_pool中
6.硬件原因,资源抢占

MySQL在read-commited隔离级别下,update语句的semi-consistent read优化

如果update语句碰到一个已经被锁了的行,会读入最新的版本,然后判断一下是不是满足查询条件,
a)如果不满足,就直接跳过;
b) 如果满足,才进入锁等待

这个策略,只对update有效,delete无效

缓存穿透、缓存击穿、缓存雪崩

穿透是缓存和数据库都不存在
击穿是缓存不存在,数据库存在,一条数据失效时的高并发查询
雪崩是缓存不存在,数据库存在,大量数据同时失效时的高并发查询

相关推荐
©️2020 CSDN 皮肤主题: 深蓝海洋 设计师:CSDN官方博客 返回首页