序号 | 地址 |
1 | 计算机网络核心 |
2 | 数据库相关 |
3 | Redis |
4 | Linux相关 |
5 | JVM的内容 |
6 | GC相关的 |
7 | Java多线程与并发 |
8 | Java多线程与并发-原理 |
9 | Java常用类库与技巧 |
10 | Java框架-Spring |
1、缓存中间件
Memcache:代码层次类似于Hash。
- 支持简单数据类型
- 不支持数据持久化存储
- 不支持主从
- 不支持分片
Redis:
- 数据类型丰富
- 支持数据磁盘持久化存储
- 支持主从
- 支持分片
2、为什么Redis能这么快
10万+QPS(QPS即query per second,每秒内查询次数)
- 完全基于内存,绝大部分请求是纯粹的内存操作,执行效率高。
- 数据结构简单,对数据操作也很简单。
- 采用单线程,单线程也能处理高并发请求,想多核也可启动多实例。
- 使用多路I/O复用模型,非阻塞IO。
- 单进程,单线程的K-V数据库,由C语言编写。
- 主线程是单线程的,多个客户端对同一个key进行写操作,就不会有数据问题,避免了上下文切换和锁竞争。
3、多路I/O复用模型
FD:File Descriptor,文件描述符
一个打开的文件通过唯一的描述符进行引用,该描述符是打开文件额元数据到文件本身的映射。
传统的阻塞I/O模型:
前面的没有处理完成,后面的则进行阻塞。等待前面的读写完成以后,才会执行后面的方法。
多路I/O复用模型
Select系统调用
同时监视多个文件是否可读可写,系统把读写交给Selector,自己执行别的操作。
Redis采用的IO多路复用函数:epoll/kqueue/export/select?
- 因地制宜
- 优先选择时间复杂度为O(1)的I/O多路复用函数作为底层实现
- 以时间复杂度为o(n)的 select作为保底
- 基于react设计模式监听I/O事件
4、Redis的数据类型
- String :最基本的数据类型,二进制安全。
String可以存储任何类型,JPG,以及序列化的内容。
Redis是原子性的。
- Hash:String元素组成的字典,适合用于存储对象
hmset lilei name "LiLei" age 25 title"Senior" hget lilei age hget lilei title hset lilei title "Pricipal"
- List:列表,按照String元素插入顺序排序(后进先出)(40亿)
lpush myList aaa lpush myList bbb lpush myList ccc lrange myList 0 10 #从左往后取,"ccc","bbb","aaa"
- Set:String元素组成的无序集合,通过哈希表实现,不允许重复
sadd myset 111 sadd myset 222 smembers myset "111" "222"
- Sorted Set:通过分数来为集合中的成员进行从小到大的排序
元素不可以重复,但是分数是可以重复的。
zadd myzset 3 aaa zadd myzset 2 bbb zrangbyscore myzset 0 10//分数越小,越靠前 "bbb" "aaa"
Redis底层数据类型基础
- 简单动态字符串
- 链表
- 字典
- 跳跃表
- 整数集合
- 压缩列表
- 对象
5、从海量Key里查询出某一固定前缀的Key
摸清数据规模,即问清楚边界。
KEYS pattern:査找所有符合给定模式 pattern的key
keys一次返回所有的key,对性能消耗很严重。key过大,系统会卡顿。
dbsize //查看redis中数量 keys k1* //查找以k1开头key的数据
SCAN
SCAN cursor [MATCH pattern] [COUNT] count SCAN 0 match k1* count 10 0:开始的游标位置 k1*:以k1开头的key 10:建议返回数量10(只是建议,不一定为10) "1330812" "k183843" "k183831" 会返回cursor的值(1330812),用于下次的查询
- 基于游标的迭代器,需要基于上一次的游标延续之前的迭代过程。
- 以0作为游标开始一次新的迭代,直到命令返回游标0完成一次遍历。
- 不保证每次执行都返回某个给定数量的元素,支持模糊查询。
- 一次返回的数量不可控,只能是大概率符合 count参数。
6、如何通过Redis实现分布式锁
分布式锁需要解决的问题
- 互斥性:(任意时刻只能有一个客户端获取)
- 安全性:(只能被持有该锁的客户端释放)
- 死锁:(获取锁的客户端宕机后)
- 容错:(部分节点宕机后,客户端仍能获取到锁)
SENTX Key value:如果key不存在,则创建并复制
- 时间复杂度:O(1)
- 返回值:设置成功,返回1;设置失败,返回0。
setnx locknx test 加入key(locknx)。如果存在返回0(设置失败),不存在返回1(设置成功)
如何解决SENTNX长期有效的问题
EXPIRE key seconds
设置key的生存时间,当key过期时(生存时间为0),会被自动删除。
expire locknx 2 设置locknx过期时间为2秒
一个设置分布式锁的代码,错误的,2个原子性操作,分布执行,整体不一定为原子性。
RedisService redisService= SpringUtils.getBean(RedisService class); long status=redisService.setnx(key,1"): if(status= 1)[ redisService expire(key,expire); //执行独占资源逻辑 doocuppiedwork() }
Set key value[EX seconds][PX milliseconds][NX|XX]
- EX second:设置键的过期时间为 second秒
- PX millisecond:设置键的过期时间为 millisecond毫秒
- NⅩ:只在键不存在时,才对键进行设置操作
- ⅩX:只在键已经存在时,才对键进行设置操作
- SET操作成功完成时,返回OK,否则返回nil
# 添加lock-1234 数据,过期时间10s。NX不存在,才进行操作 set lock 1234 ex 10 nx
7、大量的key同时过期的注意事项
集中过期,由于清除大量的key很耗时,会出现短暂的卡顿现象
- 解放方案:在设置key的过期时间的时候,给每个key加上随机值
8、如何使用Redis做异步队列
使用List作为队列,Rpush生产消息,Lpop消费消息。
- 缺点:没有等待队列里有值就直接消费
- 弥补:可以通过在应用层引入Seep机制去调用LPOP重试
BLPOP key[key…]timeout:阻塞直到队列有新消息或者超时
- 缺点:只能供一个消费者消费
rpush testList aaa rpop testList "aaa" blpop testList 30 //等待30秒,等待添加数据
pub/sub:主题订阅者模式
- 发送者(pub)发送消息,订阅者(sub)接收消息
- 订阅者可以订阅任意数量的频道
subscribe myTopic //订阅myTopic频道 publish myTopic "Hello" //向频道myTopic发布消息
消息的发布是无状态的,无法保证可达,(发布时不在线,则无法接收)
9、Redis如何做持久化
RDB(快照)持久化:保存某个时间点的全量数据快照。
- SAVE:阻塞 Redis的服务器进程,直到RDB文件被创建完毕。
- BGSAVE:Fork出一个子进程来创建RDB文件,不阻塞服务器进程。
vim redis.conf save 900 1 save 300 10 //300秒内有10条写入,就触发一次快照,没有的话 save 60 10000 // //备份进程失败的时候,主线程会停止接受新的写入操作,保证持久化数据一致性,(一般默认开启的) stop-writes-on-bgsave-error yes //RDB文件压缩相关,在备份的时候,将文件压缩才进行保存(默认no) rdbcompression yes //禁用RDB的配置 save ""
手动进行RDB备份命令
//返回上次RDB持久化的时间 lastsave //阻塞主线程,进行持久化 save //开辟子线程,进行持久化。 bgsave
自动化触发RDB持久化的方式
- 根据 redis.conf配置里的 SAVE m n定时触发(用的是 BGSAVE)。
- 主从复制时,主节点自动触发。
- 执行 Debug reload。
- 执行 Shutdown且没有开启AOF持久化。
BGSAVE原理
系统调用fork( ):创建进程,实现了Copy-on-Write(写实复制)(COW)
如果有多个调用者同时要求相同资源(如内存或磁盘上的数据存储),他们会共同获取相同的指针指向相同的资源,直到某个调用者试图修改资源的内容时,系统才会真正复制份专用副本给该调用者,而其他调用者所见到的最初的资源仍然保持不变。
- 父子调用的是同一个地址空间。
- 如果调用者修改数据,才会创建一个新的地址空间,创建数据副本。
- fork出子进程后,主进程有数据更改,则为主线程创建一新的地址空间,子进程操作的是fork时候的空间内容。
- RDB是全量备份,备份当前的全部数据。
RDB持久化缺点:
- 内存数据的全量同步,数据量大会由于IO而严重影响性能。
- 可能会因为 Redis 挂掉而丢失从当前至最近一次快照期间的数据。
10、AOF(Append-only-File)持久化:保存写状态(默认关闭)
- 记录下除了查询以外的所有变更数据库状态的指令。
- 以 append的形式追加保存到AOF文件中(增量)。
appendonly no //默认关闭AOF持久化方式 appendfilename "appendonly.aof //AOF持久化文件 //AOF持久化策略 appendtsync always //及时写入(有更新就写入) appendtsync everysec //每秒写入 appendtsync no //写入由操作系统决定(一般是缓存区填满以后)
日志重写解决AOF文件大小不断增大的问题,原理如下:
- 调用fork( ),创建一个子进程。
- 子进程把新的AOF写到一个临时文件里,不依赖原来的AOF文件。
- 主进程持续将新的变动同时写到内存和原来的AOF里。
- 主进程获取子进程重写AOF的完成信号,往新AOF同步增量变动。
- 使用新的AOF文件替换掉旧的AOF文件
# AOF文件大小较上次重写超过100%时进行重写 auto-aof-rewrite-percentage 100 # aof文件大小超过64m时重写 auto-aof-rewrite-min-size 64mb
Redis数据的恢复
RDB和AOF文件共存情况下的恢复流程
RDB和AOF的优缺点:
- RDB优点:全量数据快照,文件小,恢复快。
- RDB缺点:无法保存最近一次快照之后的数据。
- AOF优点:可读性高,适合保存增量数据,数据不易丢失。
- AOF缺点:文件体积大,恢复时间长。
RDB-AOF混合持久化方式(4.0)(默认配置)
- BGSAVE做镜像全量持久化,AOF做增量持久化
11、使用Pipeline的好处
- Pipeline和Linux的管道类似
- Redis基于请求/响应模型,单个请求处理需要一 一应答
- Pipeline批量执行指令,节省多次I/O往返的时间
- 有顺序依赖的指令建议分批发送
主从同步原理
Master:进行写操作
Sync:进行读操作
最后一致性。
全量同步过程:
- Salve发送syn命令到 Master。
- Master启动一个后台进程,将 Redis中的数据快照保存到文件中。
- Master将保存数据快照期间接收到的写命令缓存起来。
- Master完成写文件操作后,将该文件发送给 Salve。
- 使用新的AOF文件替换掉旧的AOF文件。
- Master将这期间收集的增量写命令发送给 Salve端。
增量同步过程:
- Master接收到用户的操作指令,判断是否需要传播到 Slave。
- 将操作记录追加到AOF文件。
- 将操作传播到其他 Slave:1、对齐主从库;2、往响应缓存写入指令。
- 将缓存中的数据发送给Slave。
Redis Sentinel(哨兵机制)
Redis官方提供的集群管理工具,监控M-S集群,发现Master宕机后,能进行切换。
解决主从同步 Master宕机后的主从切换问题:
- 监控:检查主从服务器是否运行正常。
- 提醒:通过API向管理员或者其他应用程序发送故障通知。
- 自动故障迁移:主从切换。
将一个S提升为M,通过流言协议来接收主服务器是否下线通知,通过投票协议执行故障迁移,哪台服务器作为新的主服务器。
流言协议(Gossip)
在杂乱无章中寻求一致
- 每个节点都随机地与对方通信,最终所有节点的状态达成一致。
- 种子节点定期随机向其他节点发送节点列表以及需要传播的消息。
- 不保证信息一定会传递给所有节点,但是最终会趋于—致。
如何从海量数据里快速找到所需?
- 分片:按照某种规则去划分数据,分散存储在多个节点上
- 常规的按照哈希划分无法实现节点的动态增减
一致性哈希算法:
- 对2^32取模,将哈希值空间组织成虚拟的圆环
- 将数据key使用相同的函数Hash计算出哈希值(确定出在哈希环的唯一位置)
假定Node C宕机:
Object C本来应该存到Node C,但是Node C宕机,则会存到Node D中,只需要重新定位环中的一小部分问题,最小化的有损服务。
假定新增节点(Node F)
只要其周边的一个回定位到新增的,只会影响新增节点与前一节点之间的数据。
有较好的容错性和扩展性。
遇到的问题:Hash环的数据倾斜问题
数据运算后与B节点很近,则会存储到节点B中,造成数据倾斜。把服务器撑爆。
解决:引入虚拟节点解决数据倾斜的问题
只有2个实际节点,虚拟出6个虚拟节点
12、Redis的主从复制
为了避免单点故障,我们需要将数据复制多份部署在多台不同的服务器上,即使有一台服务器出现故障其他服务器依然可以继续提供服务。
这就要求当一台服务器上的数据更新后,自动将更新的数据同步到其他服务器上,这时候就用到了Redis的主从复制。