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