[TOC]
1 Kafka 核心概念详解
1.1 Kafka(MQ) 的应用场景
1.1.1 Kafka(MQ)之异步化、服务解耦、削峰填谷
异步化
服务解耦、削峰填谷
1 | server.servlet.context-path=/ |
1 | import java.util.Map; |
1 | server.servlet.context-path=/ |
1 | @Component |
1 | public class Sender4DLXExchange { |
1 | public class Receiver4DLXtExchange { |
1 | public class Sender { |
1 | public class Receiver { |
I/O是指网络I/O,多路指多个TCP连接(即socket或者channel),复用指复用一个或几个线程。意思说一个或一组线程处理多个TCP连接。最大优势是减少系统开销小,不必创建过多的进程/线程,也不必维护这些进程/线程。 IO多路复用使用两个系统调用(select/poll/epoll和recvfrom),blocking IO只调用了recvfrom;select/poll/epoll 核心是可以同时处理多个connection,而不是更快,所以连接数不高的话,性能不一定比多线程+阻塞IO好,多路复用模型中,每一个socket,设置为non-blocking,阻塞是被select这个函数block,而不是被socket阻塞的。
基本原理: 客户端操作服务器时就会产生这三种文件描述符(简称fd):writefds(写)、readfds(读)、和exceptfds(异常)。select会阻塞住监视3类文件描述符,等有数据、可读、可写、出异常 或超时、就会返回;返回后通过遍历fdset整个数组来找到就绪的描述符fd,然后进行对应的IO操作。
优点:几乎在所有的平台上支持,跨平台支持性好
缺点:由于是采用轮询方式全盘扫描,会随着文件描述符FD数量增多而性能下降。 每次调用 select(),需要把 fd 集合从用户态拷贝到内核态,并进行遍历(消息传递都是从内核到用户空间) 默认单个进程打开的FD有限制是1024个,可修改宏定义,但是效率仍然慢。
基本原理与select一致,也是轮询+遍历;唯一的区别就是poll没有最大文件描述符限制(使用链表的方式存储fd)。
基本原理:没有fd个数限制,用户态拷贝到内核态只需要一次,使用时间通知机制来触发。通过epoll_ctl注册fd,一旦fd就绪就会通过callback回调机制来激活对应fd,进行相关的io操作。epoll之所以高性能是得益于它的三个函数 1)epoll_create()系统启动时,在Linux内核里面申请一个B+树结构文件系统,返回epoll对象,也是一个fd 2)epoll_ctl() 每新建一个连接,都通过该函数操作epoll对象,在这个对象里面修改添加删除对应的链接fd, 绑定一个callback函数 3)epoll_wait() 轮训所有的callback集合,并完成对应的IO操作
优点:没fd这个限制,所支持的FD上限是操作系统的最大文件句柄数,1G内存大概支持10万个句柄 效率提高,使用回调通知而不是轮询的方式,不会随着FD数目的增加效率下降 内核和用户空间mmap同一块内存实现(mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间)
例子:100万个连接,里面有1万个连接是活跃,我们可以对比 select、poll、epoll 的性能表现 select:不修改宏定义默认是1024,l则需要100w/1024=977个进程才可以支持 100万连接,会使得CPU性能特别的差。 poll: 没有最大文件描述符限制,100万个链接则需要100w个fd,遍历都响应不过来了,还有空间的拷贝消耗大量的资源。 epoll: 请求进来时就创建fd并绑定一个callback,主需要遍历1w个活跃连接的callback即可,即高效又不用内存拷贝。
多个 socket 可能会并发产生不同的操作,每个操作对应不同的文件事件,但是 IO 多路复用程序会监听多个 socket,会将 socket 产生的事件放入队列中排队,事件分派器每次从队列中取出一个事件,把该事件交给对应的事件处理器进行处理。
客户端socket01请求redis的server scoket建立连接,此时server socket生成AE_READABLE
事件,IO多路复用程序监听到server socket产生的事件,并将该事件压入队列。
文件事件分派器从队列中拉取事件交给连接应答处理器,处理器同时生成一个与客户端通信的socket01,并将该scoket01的AE_READABLE事件与命令请求处理器关联
此时客户端scoket01发送一个”set key value“的请求,redis的scoket01接收到AE_READABLE事件,IO多路复用程序监听到事件,将事件压入队列,文件分派器取到事件,由于scoket01已经
和命令请求处理器关联,所以命令请求处理器开始”set key value”,完毕后会将redis的scoket01的AE_WRITABLE
事件关联到命令回复处理器
如果此时客户端准备好接收返回结果了,向redis中的socket01发起询问请求,那么 redis 中的 socket01 会产生一个 AE_WRITABLE
事件,同样压入队列中,事件分派器找到相关联的命令回复处理器,由命令回复处理器对 socket01 输入本次操作的一个结果,比如 ok
,之后解除 socket01 的 AE_WRITABLE
事件与命令回复处理器的关联。
这样便完成了redis的一次通信。
redis 将所有数据放在内存中,内存的响应时长大约为 100 纳秒,这是 redis 的 QPS 过万的重要基础。
什么是阻塞式 IO
当我们调用 Scoket 的读写方法,默认它们是阻塞的。
read() 方法要传递进去一个参数 n,表示读取这么多字节后再返回,如果没有读够 n 字节线程就会阻塞,直到新的数据到来或者连接关闭了, read 方法才可以返回,线程才能继续处理。
write() 方法会首先把数据写到系统内核为 Scoket 分配的写缓冲区中,当写缓存区满溢,即写缓存区中的数据还没有写入到磁盘,就有新的数据要写道写缓存区时,write() 方法就会阻塞,直到写缓存区中有空闲空间。
什么是非阻塞式 IO
非阻塞 IO 在 Scoket 对象上提供了一个选项Non_Blocking
,当这个选项打开时,读写方法不会阻塞,而是能读多少读多少,能写多少写多少。
能读多少取决于内核为 Scoket 分配的读缓冲区的大小,能写多少取决于内核为 Scoket 分配的写缓冲区的剩余空间大小。读方法和写方法都会通过返回值来告知程序实际读写了多少字节数据。
有了非阻塞 IO 意味着线程在读写 IO 时可以不必再阻塞了,读写可以瞬间完成然后线程可以继续干别的事了。
白话举例:模拟一个tcp服务器处理30个客户socket
假设你是一个老师,让30个学生解答一道题目,然后检查学生做的是否正确,你有下面几个选择:
单线程能带来几个好处:
单线程的问题:对于每个命令的执行时间是有要求的。如果某个命令执行过长,会造成其他命令的阻塞,所以 redis 适用于那些需要快速执行的场景。
string 字符串
string: 最简单的字符串类型键值对缓存,也是最基本的
key相关
keys *:查看所有的key (不建议在生产上使用,有性能影响)
type key:key的类型
string类型
get/set/del:查询/设置/删除set rekey data:设置已经存在的key,会覆盖setnx rekey data:设置已经存在的key,不会覆盖
set key value ex time:设置带过期时间的数据expire key:设置过期时间ttl:查看剩余时间,-1永不过期,-2过期
append key:合并字符串strlen key:字符串长度
incr key:累加1decr key:类减1incrby key num:累加给定数值decrby key num:累减给定数值
getrange key start end:截取数据,end=-1 代表到最后setrange key start newdata:从start位置开始替换数据
mset:连续设值mget:连续取值msetnx:连续设置,如果存在则不设置
其他
select index:切换数据库,总共默认16个flushdb:删除当前下边db中的数据flushall:删除所有db中的数据
hash
hash:类似map,存储结构化数据结构,比如存储一个对象(不能有嵌套对象)
使用
hset key property value:> hset user name imooc> 创建一个user对象,这个对象中包含name属性,name值为imooc
hget user name:获得用户对象中name的值
hmset:设置对象中的多个键值对> hset user age 18 phone 139123123hmsetnx:设置对象中的多个键值对,存在则不添加> hset user age 18 phone 139123123
hmget:获得对象中的多个属性> hmget user age phone
hgetall user:获得整个对象的内容
hincrby user age 2:累加属性hincrbyfloat user age 2.2:累加属性
hlen user:有多少个属性
hexists user age:判断属性是否存在
hkeys user:获得所有属性hvals user:获得所有值
hdel user:删除对象
list
list:列表,[a, b, c, d, …]
使用
lpush userList 1 2 3 4 5:构建一个list,从左边开始存入数据rpush userList 1 2 3 4 5:构建一个list,从右边开始存入数据lrange list start end:获得数据
lpop:从左侧开始拿出一个数据rpop:从右侧开始拿出一个数据
pig cow sheep chicken duck
llen list:list长度lindex list index:获取list下标的值
lset list index value:把某个下标的值替换
linsert list before/after value:插入一个新的值
lrem list num value:删除几个相同数据
ltrim list start end:截取值,替换原来的list
sorted set:
sorted set:排序的set,可以去重可以排序,比如可以根据用户积分做排名,积分作为set的一个数值,根据数值可以做排序。set中的每一个memeber都带有一个分数
使用
zadd zset 10 value1 20 value2 30 value3:设置member和对应的分数
zrange zset 0 -1:查看所有zset中的内容zrange zset 0 -1 withscores:带有分数
zrank zset value:获得对应的下标zscore zset value:获得对应的分数
zcard zset:统计个数zcount zset 分数1 分数2:统计个数
zrangebyscore zset 分数1 分数2:查询分数之间的member(包含分数1 分数2)zrangebyscore zset (分数1 (分数2:查询分数之间的member(不包含分数1 和 分数2)zrangebyscore zset 分数1 分数2 limit start end:查询分数之间的member(包含分数1 分数2),获得的结果集再次根据下标区间做查询
zrem zset value:删除member
[toc]
RDB:每隔一段时间,把内存中的数据写入磁盘的临时文件,作为快照,恢复的时候把快照文件读进内存。如果宕机重启,那么内存里的数据肯定会没有的,那么再次启动redis后,则会恢复。
内存备份 –> 磁盘临时文件临时文件 –> 恢复到内存
保存位置,可以在redis.conf自定义:/user/local/redis/working/dump.rdb
保存机制:
1 | save 900 1 |
1 | 如果1个缓存更新,则15分钟后备份 |
stop-writes-on-bgsave-error
◦ yes:如果save过程出错,则停止写操作
◦ no:可能造成数据不一致
rdbcompression
◦ yes:开启rdb压缩模式
◦ no:关闭,会节约cpu损耗,但是文件会大,道理同nginx
rdbchecksum
◦ yes:使用CRC64算法校验对rdb进行数据校验,有10%性能损耗
◦ no:不校验
RDB适合大量数据的恢复,但是数据的完整性和一致性可能会不足。
RDB会丢失最后一次备份的rdb文件,但是其实也无所谓,其实也可以忽略不计,毕竟是缓存,丢了就丢了,但是如果追求数据的完整性,那就的考虑使用AOF了。
1 | # AOF 默认关闭,yes可以开启 |