常用组件
2022-04-05 22:54:24 0 举报
AI智能生成
常用组件包括Redis、Zookeeper、Kafka等
作者其他创作
大纲/内容
ZooKeeper是为分布式应用提供高性能协调服务的Apache项目
是什么?
配置信息维护
分布式同步(分布式锁)
统一命名服务
集群管理
队列管理
作用
一次数据更新要么成功(半数以上),要么失败,不存在中间状态
原子性
每个server保存一份数据副本,client从每个server获取的数据都是一致的
一致性
如果消息被一个server接收,则所有的server都接收
可靠性
包括全局有序和偏序两种:全局有序是指如果在一台服务器上消息 a 在消息 b 前发布,则在所有 Server 上消息 a 都将在消息 b 前被发布;偏序是指如果一个消息 b 在消息 a 后被同一个发送者发布,a 必将排在 b 前面。
顺序性
Zookeeper 保证客户端将在一个时间间隔范围内获得服务器的更新信息,或者服务器失效的信息。但由于网络延时等原因,Zookeeper 不能保证两个客户端能同时得到刚更新的数据,如果需要最新数据,应该在读数据之前调用 sync() 接口
实时性
特性
在ZooKeeper集群中只有一个节点作为集群的领导者,由各Follower通过ZooKeeper Atomic Broadcast(ZAB)协议选举产生,主要负责接收和协调所有写请求,并把写入的信息同步到Follower和Observer。
Leader(领导者)
当leader发生故障时,进行投票选举leader
处理读请求,并配合Leader一起进行写请求处理
Follower(跟随者)
Observer不参与选举和写请求的投票,只负责处理读请求、并向Leader转发写请求,避免系统处理能力浪费。
Observer(观察者)
Learner(学习者)
读写请求发起方
Client(客户端)
架构
zkServer.sh start
启动
zkServer.sh status
查看状态
zkServer.sh stop
停止
zkServer
create path \"data\"
创建带数据持久节点
有序节点
-s
临时节点
-e
create
ls -s path
查看path节点详细数据
ls
zkCli.sh -server 本机ip:port
客户端连接
zkCli
命令
ZK服务器与客户端通信心跳时间,单位ms。session的最小超时时间是2*tickTime
tickTime
集群中的Follower跟随者服务器与Leader领导者服务器之前初始连接时能容忍的最多心跳(tickTime)数量,用来限定集群中的Zookeeper服务器连接到Leader的时限
LF初始通信时限
initLimit
集群中的Leader与Follower之间的最大响应时间单位,假如响应超过syncLimit*tickTime,Leader认为Follower死掉,从服务器列表中删除Follower
LF同步通信时限
syncLimit
主要保存Zookeeper的snapshot等数据
数据文件目录+数据持久化路径
dataDir
客户端连接的端口
clientPort
日志存储文件
dataLogDir
是否启用扩展功能
extendedTypesEnabled
A 是一个数字,表示这个是第几号服务器;集群模式下配置一个文件 myid,这个文件在 dataDir 目录下,这个文件里面有一个数据就是 A 的值,Zookeeper 启动时读取此文件,拿到里面的数据与 zoo.cfg 里面的配置信息比较从而判断到底是哪个 server。
B 是这个服务器的地址;
C 是这个服务器 Follower 与集群中的 Leader 服务器交换信息的端口;
D 是万一集群中的 Leader 服务器挂了,需要一个端口来重新进行选举,选出一个新的 Leader,而这个端口就是用来执行选举时服务器相互通信的端口。
server.A=B:C:D
配置参数
单点部署
伪集群部署
安装部署
Paxos算法
ZAB协议
实现机制
Curator版本与Zookeeper版本对应关系
子主题
Curator
持久节点,一旦创建成功不会被删除,除非客户端主动发起删除请求
PERSISTENT
久顺序节点,会在用户路径后面拼接一个不会重复的字增数字后缀,其他同上
PERSISTENT_SEQUENTIAL
临时节点,当创建该节点的客户端链接断开后自动被删除
EPHEMERAL
临时顺序节点,基本同上,也是增加一个数字后缀
EPHEMERAL_SEQUENTIAL
容器节点,一旦子节点被删除完就会被服务端删除
CONTAINER
带过期时间的持久节点,带有超时时间的节点,如果超时时间内没有子节点被创建,就会被删除
PERSISTENT_WITH_TTL
带过期时间的持久顺序节点,基本同上,多了一个数字后缀
PERSISTENT_SEQUENTIAL_WITH_TTL
节点类型(3.6.2版本)
配置启用扩展功能:extendedTypesEnabled=true
数据模型和分层命名空间
ZooKeeper
<dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-slf4j-impl</artifactId> <version>2.14.1</version></dependency>
maven依赖
StaticLoggerBinder
LoggerFactory.getLogger()
slf4j适配log4j2
指定配置的名称
name
指定log4j2自身的打印日志的级别
status
指定log4j2自动重新配置的监测时间间隔,单位是s,最小是5s
monitorInterval
属性
指定Appender的名字
SYSTEM_OUT或SYSTEM_ERR,一般只设置默认SYSTEM_OUT
target
输出格式,不设置默认为:%m%n.
pattern
PatternLayout
子节点
Console
指定输出日志的目的文件带全路径的文件名
filename
File
filename.%i
指定新建日志文件的名称格式
filepattern
日志文件权限
filePermissions
指定多久滚动一次,默认是1 hour
interval
modulate=true用来调整时间:比如现在是早上3am,interval是4,那么第一次滚动是在4am,接着是8am,12am...而不是7am.
modulate
TimeBasedTriggeringPolicy基于时间的滚动策略
属性用来定义每个日志文件的大小.
size
SizeBasedTriggeringPolicy基于指定文件大小的滚动策略
Policies指定滚动日志的策略,就是什么时候进行新建日志文件输出日志
文件夹下最多日志文件数量
max
DefaultRolloverStrategy用来指定同一个文件夹下最多有几个日志文件时开始删除最旧的,创建新的
RollingFile
根据所有注册的Lookups进行评估并将结果用于选择路由
每个route都有一个key属性用来匹配前面pattern的筛选结果,只能有一个route可以没有key,配置为默认
key
用来指定该日志输出到哪个Appender.
ref
Route
span style=\
Script
Routing
Appenders
日志输出级别,共有8个级别,按照从低到高为:All < Trace < Debug < Info < Warn < Error < Fatal < OFF.
level
AppenderRef
Root用来指定项目的根日志,如果没有单独指定Logger,那么就会默认使用该Root日志输出
日志是否在父Logger中输出
additivity
Logger
Loggers
Configuration
log4j2配置
如果不带参数,则输出完整的logger名称;如果参数是整数n(只支持正整数),则先将logger名称依照小数点(.)分割成n段,然后取右侧的n段;如果参数不是整数,则除了最右侧的一段,其他整段字符都用这个字符代替,保留小数点;
com.logdemo.stdout.LogStdout
%c
LogStdout
%c{1}
stdout.LogStdout
c.l.s.LogStdout
%c{1.}
c.!.!.LogStdout
%c{1.1.!}
固定类名+4个点
....LogStdout
%c{.}
示例
%c{参数}或%logger{参数}
输出类名,参数同上
%C{参数}或%class{参数}
第二个大括号中的参数是java.util.TimeZone.getTimeZone的值,可以设定时区。
%d{DEFAULT}
%d{ABSOLUTE}
20210722191255040
%d{COMPACT}
%d{DATE}
%d{ISO8601}
%d{ISO8601_BASIC}
2021-07-22 19:18:44.637
%d{yyyy-MM-dd HH:mm:ss.SSS}
2021-07-22 19:18:44.637 +0800
%d{yyyy-MM-dd HH:mm:ss.SSS Z}{GMT+8}
%d{参数}{时区te{参数}{时区}
输出文件名,不包含路径只有文件名如:LogStdout.java
%F|%file
使用该参数会影影响日志输出的性能。
输出完整的错误位置
%l
输出错误行号
%L
输出错误信息,即logger.error(String msg)中的msg
%m或%msg或%message
输出方法名
%M或%method
换行符
%n
创建logging事件的线程名
%t或%thread
输出日志信息优先级,即All < Trace < Debug < Info < Warn < Error < Fatal < OFF
输出info日志信息
%-5p
%p
[%d{yyyy-MM-dd HH:mm:ss.SSS Z}] [%-5p] [%t] [%c %L] %m%n
常用pattern
paterrn详解
Lookups可以添加在log4j2配置的任意地方,是实现了StrLookup的特殊Plugin
ContextMapLookup允许使用log4j2提供的Map存储结构ThreadContext类,可以在pattern中使用key值查找在ThreadContext存储的value值。在初始配置处理期间,第一个’$’将被删除
$${ctx:loginId} 或者 %X{loginId}
Context Map Lookup
DateLookup与其他查找有些不同,因为它不使用键来定位值。 相反,该键可用于指定对SimpleDateFormat有效的日期格式字符串。当前日期或与当前日志事件关联的日期将按指定格式进行格式化
$${date:MM-dd-yyyy}
Date Lookup
DockerLookup可用于从运行应用程序的Docker容器中查找属性
容器属性访问
Docker Lookup
EnvironmentLookup允许系统配置环境变量,无论是在/etc/profile之类的全局文件中,还是在应用程序的启动脚本中,然后从日志配置中检索这些变量。下面的示例在应用程序日志中包含当前登录用户的名称,此lookup还支持默认值
$${env:JAVA_HOME}
当环境变量JAVA_HOME不存在时,使用默认值\\user\\bin
$${env:JAVA_HOME:\\user\\bin}
Environment Lookup
EventLookup 提供对配置中日志事件内字段的访问
日志事件字段
EventLookup
JavaLookup允许使用java:前缀在方便的预格式化字符串中检索Java环境信息
Java Lookup
JndiLookup 允许通过 JNDI 检索变量。默认情况下,键的前缀为 java:comp/env /,但是,如果键包含“:”,则不会添加前缀
Jndi Lookup
使用 JMXMapJVMImporting 参数(而不是主参数)来获取 JVM 参数
JVMImporting
KubernetesLookup 可用于从 Kubernetes 环境中查找运行应用程序的容器的属性
Kubernetes Lookup
Log4j 配置属性。表达式${log4j:configLocation}和${log4j:configParentLocation}分别提供了 log4j 配置文件及其父文件夹的绝对路径使用此查找将日志文件放置在相对于 log4j 配置文件的目录中
Log4j Configuration Location Lookup
LowerLookup 将传入的参数转换为小写。大概该值将是嵌套查找的结果
Lower Lookup
LowerLookup 将传入的参数转换为大写。大概该值将是嵌套查找的结果
Upper Lookup
Lookups详解
slf4j+log4j2
日志框架
简单动态字符串SDS
底层实现
整数值用long保存,浮点数或者字符串用embstr或raw编码
embstr编码比raw编码少一次内存分配
redisobject和数据是一块连续的空间,发挥缓存优势
长度小于32字节用embstr编码
长度大于32字节用raw编码
编码
512M
redis一个字符串值最大是多少
题目
字符串String
双向链表
列表保存字符串元素小于64字节且元素个数小于512个
使用list-max-ziplist- value和list-max- ziplist-entries修改上面值
压缩列表
列表List
键值对紧挨在一起,先放入键再放入值
哈希表
哈希表Hash
每个元素使用两个节点保存,一个保存元素值一个保存分值
列表元素长度小于64字节且元素个数小于512个
zet-max-ziplist-value和zset-max-ziplist-entries
哈希表能够降低查找复杂度为O(1),跳表又有利于维护范围操作的排序
跳表 +哈希表
有序集合Sorted Set
列表元素都是整数值且元素个数小于512个
使用set-max-inset-entries
整数数组
集合Set
type字段实现类型检查,检查命令是否与键类型相符
refcount实现引用计数内存回收和对象共享
lru记录被最后一次访问的时间,用于计算空转时长,除此之外还可以用于内存超时回收机制
redisobject对象头作用
数据类型
O(1)复杂度获取字符串长度
杜绝缓冲区溢出
长度小1M,每次扩展2倍
长度大于1M,每次扩展1M
空间预分配
惰性空间释放
减少修改字符串时带来的内存重分配次数
二进制安全
兼容c语言字符串函数
与C语言字符串区别
链表长度信息与前后指针
双向链表Linked List
结构
链地址法,且最新数据被放入链表头
解决哈希冲突方法
MurmurHash
哈希算法
为了解决哈希表负载因子,维持一个合理范围,尽量减小冲突
没有执行bgsave或者bgrewriteaof,且负载因子大于等于1
正在执行bgsave或者bgrewriteaof,且负载因子大于等于5
扩容条件
第一个大于等于h[0].used * 2且为2^n的数
扩容大小
扩容
第一个大于等于h[0].used的2^n数
缩容
利用两个map(h[0]和h[1])来完成rehash操作,跟是否扩缩容以及当前map的键值对有关(h[0].used)
在rehash时,redis的增删改查会触发一次rehash,将ht[0]的rehashIndex索引上的哈希表rehash到ht[1]
渐进式rehash的采取分而治之的方法,将rehash操作均摊到每个增删改查操作,避免了集中rehash带来的庞大工作量
rehash时数据访问先去查找ht[0],找不到再去查找ht[1]。rehash期间新增加的数据只放入ht[1]中,保证ht[0]只减不增,最终变为空表,然后将ht[1]赋值给ht[0],rehashIndex置为-1
渐进式rehash
rehash
哈希表Map
记录前一节点的字节长度,1字节或者5字节(5字节以0xFE开头)
previous_entry_length
encoding
content
每个节点
数据结构
长度小于等于2^6 -1的字节数组
长度小于等于2^14-1的字节数组
长度小于等于2^32-1的字节数组
字节数组
4bit无符号整数0-12
1字节长有符号整数
3字节长有符号整数
int16_t
int32_t
int64_t
整数值
每一个压缩列表节点都可以保存一个字节数组或者整数值
多个连续长度介于250-253的节点
在增加或者删除节点都可能存在
连锁更新
压缩列表Ziplist
按分值大小进行排序,分值相同的按值的字典序排序
每个节点的层高是1-32的随机数
节点的分值可以相同,但节点的值必须不同
跳表Skip List
整数数组由编码方式决定底层数组存储的数据类型是int8_t、int16_t、int32_t或int64_t
新元素的长度大于所有现有元素长度
提升数组灵活性,可以根据长度变换数组类型
节约内存
升级的好处
升级
一旦升级完后不支持降级,所以当数组中存储大部分数据比较短,突然来了一个长数据,redis会对其升级,而当这个数据被删除后,愿数组长度无法降级
数组内的元素是有序的
时间复杂度
底层数据类型
redis数据类型与底层数据结构关系
Redis使用了一个哈希表来保存所有键值对,全局哈希表
全局哈希表的问题是哈希表的冲突问题和rehash可能带来的操作阻塞
redis的组织结构
数据类型与底层数据结构
redis的网络I/O与键值对读写是单线程的,redis还有其他线程用来重写日志等
redis的单线程主要指的是什么
不用解决多线程的并发访问共享资源控制问题
为什么redis要用单线程
redis主要操作内存,且有哈希表、跳表等高效数据结构
基于epoll的网络I/O多路复用机制
redis为什么那么快
一个线程处理多个网络I/O,即同时存在多个监听套接字和已连接套接字,如select/epoll机制。它提供了基于事件的回调机制,因此redis不会阻塞于一个客户端的连接,而且可以处理多个客户端提高并发性
什么是多路复用机制
select句柄数受限,默认是1024;epoll没有
select采用轮询机制监听套接字,epoll维护一个队列,基于事件驱动机制,即有事件发生的fd才会回调,性能不会随着fd增加而降低
epoll使用了mmap文件映射内存加速了与内核空间消息传递
select与epoll的区别
常规操作为了提高读写效率和保护磁盘使用了页缓存机制。页缓存存在内核空间用户不能直接调用,使用时需要将页缓存数据拷贝至内存中。这样一次数据访问需要内存->页缓存->磁盘两次拷贝。mmap直接建立磁盘地址与内存地址的映射,只用一次拷贝就可以将磁盘数据读取到内存
为什么mmap能加速文件访问
防止应用程序随意访问导致系统崩溃,用户空间与内核空间有助于保护系统
为什么会存在用户空间和内存空间
redis的单线程
持久化的是命令记录
命令被执行后,将命令放入aof_buf缓冲区
命令追加
文件写入与同步
AOF实现
命令压缩
bgrewriteaof子线程执行
AOF日志重写
AOF(append only file)日志
redis使用bgsave执行快照可以正常处理请求。bgsave是fork的子线程,不会影响redis的主线程处理读请求;redis依赖写时复制技术,bgsave子线程共享主线程内存数据,当主线程接收到写请求时,会将被修改部分的数据生成一份副本,主线程在这个副本上修改,子线程继续使用原来的数据。
快照时redis能不能正常处理请求?
频繁对全量数据执行快照会对磁盘带来很大压力
虽然bgsave子线程不会阻塞主线程,但是fork线程时会阻塞主线程,且主线程内存越大,阻塞时间越长
redi频繁执行快照带来的问题
redis4.0支持混用AOF与RDB。即不用很频繁地执行RDB快照,在两次快照时间用AOF记录所有操作日志。
这样既避免了频繁执行快照阻塞主线程的风险,又可以减少AOF日志的大小,避免日志重写时开销,还可以保证数据不丢失。
redis如何实现增量快照?
只有在AOF未开启时会用rdb导入数据库,否则优先使用AOF
导入时会阻塞主线程,直到导入完成
启动时自动检测,存在rdb文件就导入
SAVE和 BGSAVE生成rdb,SVAE会阻塞主进程,BGSAVE由fork子进程执行,不会阻塞主进程
rdb的导入导出
在A秒内至少进行B次修改
save A B
满足save选项设置的条件
自动间隔执行BGSAVE
持久化的是二进制数据
RDB(Redis Database)快照
AOF是redis的操作日志,RDB是redis某一时刻全量数据
AOF日志在命令之后执行,在主线程中;RDB可以由主线程也可以由bgsave子线程执行,推荐子线程
AOF不会阻塞当前命令(命令之后执行)如果AOF文件过大,由于是主线程执行可能会影响下一个命令;RDB由fork的bgsave子线程执行,一般不会阻塞主线程,但是如果频繁执行RDB,fork 子进程时可能阻塞主进程。
由于redis是单线程,命令一条条执行,AOF日志恢复数据时很慢;而redis直接把RDB文件读入内存,可以实现很快恢复数据
AOF与RDB对比
AOF与RDB混用
数据不能丢失
可以只使用RDB
允许分钟级别的丢失
选择everysec配置每秒由内存缓冲区写回磁盘
只使用AOF
实战选择
BGSAVE执行期间SAVE不允许执行,因为主进程与子进程会产生竞争条件
BGSAVE执行期间不能再执行BGSAVE因为也会产生竞争条件
BGSAVE执行期间收到BGREWRITEAOF命令,BGREWRITEAOF将被延迟执行;反之,BGREWRITEAOF执行期间不允许执行BGSAVE,避免同时写入磁盘,降低性能。
SVAE、BGSAVE、BGREWRITEAOF三个命令执行关系
redis持久化
读操作主库和从库都可以执行,写操作先到主库,主库再同步到其他从库
runID,是每个 Redis 实例启动时都会自动生成的一个随机 ID,用来唯一标记这个实例。当从库和主库第一次复制时,因为不知道主库的 runID,所以将 runID 设为“?”。
offset,此时设为 -1,表示第一次复制。
从库给主库发送 psync 命令,表示要进行数据同步,主库根据这个命令的参数来启动复制。psync 命令包含了主库的 runID 和复制进度 offset 两个参数。主库收到 psync 命令后,会用 FULLRESYNC 响应命令带上两个参数:主库 runID 和主库目前的复制进度 offset,返回给从库。从库收到响应后,会记录下这两个参数。FULLRESYNC 响应表示第一次复制采用的全量复制,也就是说,主库会把当前所有的数据都复制给从库
建立连接,协商请求
主库将所有数据同步给从库。从库收到数据后,在本地完成数据加载。这个过程依赖于内存快照生成的 RDB 文件。
从库接收到RDB文件后会先清空本地数据,然后再加载RDB文件避免了数据不一致之前数据影响
主库在进行数据同步时不会阻塞,仍能正常处理请求。但是,这些请求中的写操作并没有记录到刚刚生成的 RDB 文件中。为了保证主从库的数据一致性,主库会在内存中用专门的 replication buffer,记录 RDB 文件生成后收到的所有写操作。
主库同步数据给从库
当主库完成 RDB 文件发送后,就会把此时 replication buffer 中的修改操作发给从库,从库再重新执行这些操作。这样一来,主从库就实现同步了。
主库同步replication buffer
生成全量数据 RDB 文件和传输 RDB 文件
主库的2个耗时操作
如果从库数量很多,而且都要和主库进行全量复制的话,就会导致主库忙于 fork 子进程生成 RDB 文件,进行数据全量同步。fork 这个操作会阻塞主线程处理正常请求,
多实例的主从同步可能带来的问题
可以手动选择一个从库(比如选择内存资源配置较高的从库),用于级联其他的从库。然后,我们可以再选择一些从库(例如三分之一的从库),在这些从库上执行如下命令,让它们和刚才所选的从库,建立起主从关系。replicaof 所选从库的IP 6379
通过“主 - 从 - 从”模式将主库生成 RDB 和传输 RDB 的压力,以级联的方式分散到从库上
主从级联模式
主从库第一次同步(全量复制)
主从同步全量复制之后,主库将后续命令通过长连接同步给从库
redis2.8之前会重新进行全量复制
redis2.8后实现增量复制
主从库间的网络断连怎么办?
基于长连接的命令传播
主库偏移量master_repl_offset
从库偏移量slave_repl_offset
只要有从库存在,这个repl_backlog_buffer就会存在。主库的所有写命令除了传播给从库之外,都会在这个repl_backlog_buffer中记录一份,缓存起来,只有预先缓存了这些命令,当从库断连后,从库重新发送psync $master_runid $offset,主库才能通过$offset在repl_backlog_buffer中找到从库断开的位置,只发送$offset之后的增量数据给从库即可
环形缓冲区repl_backlog_buf
如果从库的读取速度比较慢,就有可能导致从库还未读取的操作被主库新写的操作覆盖了,这会导致主从库间的数据不一致
可以调整 repl_backlog_size 这个参数。这个参数和所需的缓冲空间大小有关。缓冲空间的计算公式是:缓冲空间大小 = 主库写入命令速度 * 操作大小 - 主从库间网络传输命令速度 * 操作大小。在实际应用中,考虑到可能存在一些突发的请求压力,我们通常需要把这个缓冲空间扩大一倍,即 repl_backlog_size = 缓冲空间大小 * 2
环形缓冲区repl_backlog_buf大小如何设置
增量复制
当从库或客户端连接redis后,redis都会分配一个buf来进行数据交互Redis先把数据写到这个buffer中,然后再把buffer中的数据发到client socket中再通过网络发送出去,这样就完成了数据交互。所以主从在增量同步时,从库作为一个client,也会分配一个buffer,只不过这个buffer专门用来传播用户的写命令到从库,保证主从数据一致,我们通常把它叫做replication buffer。
如果主从在传播命令时,因为某些原因从库处理得非常慢,那么主库上的这个buffer就会持续增长,消耗大量的内存资源,甚至OOM。所以Redis提供了client-output-buffer-limit参数限制这个buffer的大小,如果超过限制,主库会强制断开这个client的连接,也就是说从库处理慢导致主库内存buffer的积压达到限制后,主库会强制断开从库的连接,此时主从复制会中断,中断后如果从库再次发起复制请求,那么此时可能会导致恶性循环,引发复制风暴
redis从库可能对主库造成的影响
redis主从模式
主从模式解决了redis分摊压力,数据不一致问题。但是没有解决主库故障问题,主库一旦故障,redis便无法写入数据。而且也会影响从库之间的数据同步
在Redis主从集群中,哨兵机制是实现主从库自动切换的关键机制,哨兵主要职责:监控、选主和通知
redis为什么要引入哨兵?
哨兵周期性ping主从库判断网络连接情况,如果响应超时则判断为主观下线
主观下线
单个哨兵由于主库压力或者网络一时阻塞容易产生误判,因此哨兵节点也会以集群方式进行部署,成为哨兵集群
当有N个哨兵实例时,最好要有N/2 + 1个实例判断主库为“主观下线”,才能最终判定主库为“客观下线”
客观下线能降低哨兵误判的概率,减少因误判而不必要的主从切换与数据同步
客观下线
哨兵集群模式下有超过一半的哨兵判断主库主观下线,则主库为客观下线
哨兵机制如何判断主库下线
监控
筛选+打分机制
除了要检查从库的当前在线状态,还要判断它之前的网络连接状态。如果从库总是和主库断连,而且断连次数超出了一定的阈值,就可以判断从库网络状态不好,就可以筛掉这个从库
使用配置项down-after-milliseconds * 10可以筛选从库,down-after-milliseconds是主从库断连的最大连接超时时间,如果发生断连的次数超过了10次,就说明这个从库的网络状况不好,不适合作为新主库
筛选
第一轮从库优先级最高的打分高,优先级都相同则进入下一轮
slave-priority配置项,越小优先级越高。如果为0表示此slave为\"观察者\
从库优先级
根据环形缓冲区的slave_repl_offset与master_repl_offset差距,差距越小说明与主库数据差距最小
从库复制进度
每个实例都会有一个ID,在优先级和复制进度都相同的情况下,ID号最小的从库得分最高,会被选为新主库
从库ID
打分
选主
哨兵会把新主库的连接信息发给其他从库,让它们执行replicaof命令,和新主库建立连接,并进行数据复制
哨兵会把新主库的连接信息通知给客户端,让它们把请求操作发到新主库上
通知
哨兵机制
从库或哨兵与主库连接后,可以在主库发布消息,其余从库也可以订阅消息,实现信息交换
为了区分不同应用的消息,Redis会以频道的形式,对这些消息进行分门别类的管理,频道即为redis的消息类别
只有订阅了同一个频道的应用,才能通过发布的消息进行信息交换。
Redis的发布/订阅机制
部署哨兵集群:sentinel monitor <master-name> <ip> <redis-port> <quorum>只配置了主库的ip和port,未配置其余哨兵的ip,那么哨兵之前如何互相发现?
哨兵服务发现基于Redis的发布/订阅机制,通过__sentinel__:hello频道互相交换哨兵信息建立哨兵集群
哨兵服务发现
哨兵与主库连接,建立哨兵集群,监测主库
哨兵向主库发送INFO命令,主库会返回从库列表
哨兵与从库连接,监测从库历史连接状态,选主
哨兵与客户端连接,实现事件通知机制
任何一个实例只要自身判断主库“主观下线”后,就会给其他实例发送is-master-down-by-addr命令。接着,其他实例会根据自己和主库的连接情况,做出Y或N的响应,Y相当于赞成票,N相当于反对票。
拿到半数以上的赞成票
拿到的票数同时还需要大于等于哨兵配置文件中的quorum值
两个条件
如果不产生Leader,则哨兵集群会等待一段时间(也就是哨兵故障转移超时时间的2倍),再重新选举
如果哨兵集群只有2个实例,此时,一个哨兵要想成为Leader,必须获得2票,而不是1票。所以,如果有个哨兵挂掉了,那么,此时的集群是无法进行主从库切换的。因此,通常我们至少会配置3个哨兵实例。
Leader选举
哨兵Leader
哨兵集群
切片集群,也叫分片集群,就是指启动多个Redis实例组成一个集群,然后按照一定的规则,把收到的数据划分成多份,每一份用一个实例来保存。Redis Cluster是Redis官方用于实现切片集群的方案
Redis Cluster方案采用哈希槽(Hash Slot,接下来我会直接称之为Slot),来处理数据和实例之间的映射关系。在Redis Cluster方案中,一个切片集群共有16384个哈希槽,这些哈希槽类似于数据分区,每个键值对都会根据它的key,被映射到一个哈希槽中。
首先根据键值对的key,按照CRC16算法计算一个16 bit的值;然后,再用这个16bit值对16384取模,得到0~16383范围内的模数,每个模数代表一个相应编号的哈希槽。创建切片集群时,Redis会自动把这些槽平均分布在集群实例上,即每个实例上的槽个数为16384/N个。也可以使用cluster meet命令手动建立实例间的连接,形成集群,再用cluster addslots命令,指定每个实例上的哈希槽个数。在手动分配哈希槽时,需要把16384个槽都分配完,否则Redis集群无法正常工作
MOVED命令:数据已经由一个实例迁移到另一个实例
ASK命令:正在迁移到数据如何访问。ASK命令并不会更新客户端缓存的哈希槽分配信息
redis重定向机制
redis集群建立以后会把各个实例对应的哈希槽信息同步,客户端访问任意一个实例都可以知道数据对应的哈希槽的实例
redis哈希槽并不会一直不变,当实例增删或者负载均衡时,都会重新分配哈希槽信息
Redis Cluster方案
切片
redis集群
MULTI、EXEC、WATCH实现事务的功能
将多个命令打包成一个请求,顺序执行。事务执行期间服务器不会中断去执行客户端其他请求
MULTI命令标志着事务开始
事务开始
处于事务状态时,MULTI、EXEC、DISCARD、WATCH会立即执行,其余命令会放入事务队列并返回QUEUED
每个客户端都有一个事务状态,每个事务状态维护一个FIFO队列和已入队计数器
命令入队
收到EXEC命令后redis执行队列的左右请求,最后再将所有结果返回
事务执行
事务实现三个阶段
EXEC之前使用WATCH监视任意键,EXEC执行时被监视的键的值被修改后,事务失败并返回nil空回复
使用字典watched_key,键是被WATCH命令监视的键,值是一个链表,里面保存了监视该键的客户端
当有watched_key中键被修改时,其链表上的客户端对应的REDIS_DIRTY_CAS标识被置位,表示客户端的事务安全性被破坏
当收到EXEC命令后,会判断对应客户端的REDIS_DIRTY_CAS,如果置位则拒绝事务
实现
WATCH乐观锁
redis的ACID
redis事务
Redis
每一个发布订阅的对象就是一个主题
Topic
向主题发布消息的客户端
Producer
订阅消息的客户端
Consumer
kafka的服务端,负责接受和处理客户端请求并持久化
Broker
对客户端提供服务
Leader Replica
与Leader进行同步
Follower Replica
数据副本
Replica
一个有序不变的消息队列
主题-消息-分区三层架构
消息分区
Partition
基本概念
指定了Broker需要使用的若干个文件目录路径
log.dirs
指定单个路径
log.dir
磁盘
单套Kafka集群
多套Kafka集群
Zookeeper的连接
zookeeper.connect
ZK连接
SSL: //localhost:9092
PLAINTEXT://localhost:9092
<协议名称,主机名,端口号>
外部连接者要通过什么协议访问指定主机名和端口开放的Kafka服务
listeners
Broker用于对外发布的监听器
advertised.listeners
指定协议底层使用了哪种安全协议
listener.security.protocol.map
监听器
建议置为false
是否允许自动创建Topic
auto.create.topics.enable
是否允许数据落后的副本参与选举,建议置为false,否则可能会引起脏数据
是否允许Unclean Leader选举
unclean.leader.election.enable
定期切换leader,但是切换一次代价大,置为false
auto.leader.rebalance.enable
Topic管理
log.retention.hours=168
控制一条消息数据被保存多长时间。从优先级上来说ms设置最高、minutes次之、hours最低
log.retention.{hours|minutes|ms}
log.retention.bytes = -1
指定Broker为消息保存的总磁盘容量大小,默认是-1表示不限制
log.retention.bytes
message.max.bytes=5242880
控制Broker能够接收的最大消息大小,默认是1000012不足1M,应该设置的大一些。
message.max.bytes
default.replication.factor=2
kafka保存消息的副本数,如果一个副本失效了,另一个还可以继续提供服务
default.replication.factor
default.replication.factor>min.insync.replicas,否则一个副本挂掉后,便无法提交消息。建议default.replication.factor=min.insync.replicas+1
控制消息写入多少个副本后算已提交,默认为1,实际环境建议大于1
min.insync.replicas
数据留存
bin/kafka-topics.sh --bootstrap-server localhost:9092 --create --topic transaction --partitions 1 --replication-factor 1 --config retention.ms=15552000000 --config max.message.bytes=5242880
创建Topic时进行设置
修改Topic时设置
Topic级别参数会覆盖全局Broker参数的值
规定了该Topic消息被保存的时长。默认是7天
retention.ms
规定了要为该Topic预留多大的磁盘空间,默认值是-1,表示可以无限使用磁盘空间
retention.bytes
Kafka Broker能够正常接收该Topic的最大消息大小
max.message.bytes
Topic级别参数
acks=all
消息被多少个broker接受算是提交,Kafka只对已提交的数据持久化
acks
发送失败支持重试次数
retries
消息提交
参数配置
服务部署
每个节点执行各自分区的读写请求,实现负载均衡的能力
Kafka为什么要分区
负载均衡最好的策略,也是默认分区策略
轮询策略
随机存放数据,负载均衡性能比轮询略差
随机策略
相同的key被放入一个相同的partition
按消息键排序策略
实现producer.Partition接口,接口有一个方法
自定义策略
Kafka分区策略
生产者分区
compression.type
Procurer端压缩
compression.type默认配置为producer,即与生产端压缩算法一致
producer与broker的压缩算法配置不一致,导致broker先解压再压缩,性能低
broker端消息格式不一致存在v1和v2版本的Kafka消息格式
Broker压缩的条件
Broker端压缩
利用mmap内存映射将网络数据直接写入磁盘
网络数据直接到磁盘
利用Linux内核的sendfile,直接将磁盘数据通过DMA拷贝到socket buf无需内核到用户态copy
磁盘数据直接到网络
Broker零拷贝技术
吞吐量LZ4最好
压缩比zstd最好
压缩算法对比
消息压缩
消息必须被N个broker接受
broker中必须有一个存活
Kafka消息对已提交的消息做有限度的持久化
acks=all表示所有broker都收到数据才算提交
Kafka是异步消息机制,生产者发送消息后会立刻返回,但可能由于网络波动或者被broker拒收导致消息没有提交到Kafka
要使用带有回调的Kafka消息发送
Producer消息丢失
enable.auto.commit=false关闭
consumer先更新消息位移再消费数据可能导致消息丢失,应该是先消费消息再更新位移,不过这会导致重复消费
consumer多线程自动提交更新位移,可能导致消息丢失,建议关闭自动提交更新位移
Consumer消息丢失
从最早消费,消费者先启动等待消息到来
producer先于consumer感知到分区消息丢失
消息丢失场景
无消息丢失
客户端实战与原理分析
Kafka
常用组件
0 条评论
回复 删除
下一页