[Java复习] 缓存Cache part1
1. 在项目中是如何使用缓存的?为什么要用?不用行不行?用了可能会有哪些不良后果?
结合项目业务,主要两个目的:高性能和高并发。缓存走内存,天然支持高并发。
不良后果:
- 缓存与DB双写不一致
- 缓存雪崩,缓存穿透
- 缓存并发竞争
2. Redis的线程模型是什么?
Redis内部使用文件事件处理器(file event handler),这个处理器是单线程,所有Redis才叫单线程模型。
采用IO 多路复用机制同时监听多个 socket,将产生事件的 socket 压入内存队列中,事件分派器根据 socket 上的事件类型来选择对应的事件处理器进行处理。
文件处理器包含4个部分:
- 多个 socket
- IO 多路复用程序
- 文件事件分派器
- 事件处理器(连接应答处理器、命令请求处理器、命令回复处理器)
socket并发不同操作,对应不同文件事件处理器,IO多路复用监听多个socket,将产生事件的socket放入队列排队,
文件事件分发器每次从队列中取出一个socket,根据socket事件类型交给对应的事件处理器进行处理。
I/O多路复用:
在同一个线程里面, 通过拨开关的方式,来同时传输多个I/O流。
Redis 通信过程:
3. 为什么Redis单线程模型也能效率这么高?
- 纯内存操作。
- 核心是基于非阻塞的 IO 多路复用机制。
- C 语言实现,一般来说,C 语言实现的程序“距离”操作系统更近,执行速度相对会更快。
- 单线程反而避免了多线程的频繁上下文切换问题,预防了多线程可能产生的竞争问题。
4. Redis 都有哪些数据类型?分别在哪些场景下使用比较合适?
数据类型:
string:最基本。普通Set get,简单K-V缓存
set name zhangsan
hash:类map结构。可以把结构化数据(对象)缓存。把简单对象缓存,后续操作可以只修改对象中某个字段。
key=user
value={
"id": 100,
"name":"zhangsan",
"age",20
}
hset user id 100
hset user name zhangsan
hset user age 20
list:有序列表。微博大V粉丝以list格式放Redis缓存。
key=某大V value=[zhangsan, li, wangwu]
list的lrange命令:从某个元素开始读取多少个元素,可以基于list实现简单分页。
lrange mylist 0 -1
0开始位置,-1结束位置,结束位置为-1时,表示列表的最后一个位置,即查看所有。
lpush, lpop //栈(FILO)
set: 无序,自动去重。
JVM的hashset可以去重,但是多台机器的呢?这个Redis的set适用于分布式全集去重。可以基于set做交集,并集,差集。查看大V共同好友等等。
sadd myset 1 // 添加元素1
smembers myset // 查看全部元素
sismember myset 2 // 判断是否包含某个元素
srem myset 1 // 删除元素1
srem myset 1 3 // 删除某些元素
scrad myset // 查看元素个数
spop myset // 随机删除一个元素
smove testset myset abc // 将testset的元素abc移到myset
sinter testset myset // 求两个set交集
sunion testset myset // 求两个元素并集
sdiff testset myset //求差集 在testset中而不宅myset的元素
sorted set: 排序的set,去重还可以排序。例如写入元素带分数,自动根据分数排序。
zadd board 85 zhangsan
zadd board 72 lisi
zadd board 96 wangwu
zadd board 63 zhaoliu
zrevrange board 0 3 // 获取排名前三 rev 改降序
zrank board zhaoliu
5. Redis的过期策略都有哪些?内存淘汰机制都有哪些?手写一下 LRU 代码实现?(往Redis写入数据怎么会没了?)
缓存基于内存,内存有限,写入超过内存容量,肯定有数据失效。要么设置过期时间,要么被redis干掉。
设置过期时间:
如果设置一批key只能存活1个小时,1个消息后,redis是怎么对这批数据进行删除的?
redis过期策略是:定期删除 + 惰性删除。
定期删除:随机抽取一些过期key来检查和删除。
为什么?如果很多key,10W个key设了过期时间,每隔几百毫秒,去检查,会造成高CPU负载。所以实际上redis时随机抽取key来删除。
惰性删除:并不是key到时间就被删除,而是过期后查询这个key时,redis查询下这key过期了,删除。不会返回值。
数据明明过期了,怎么还占用着内存?
但是实际上这还是有问题的,如果定期删除漏掉了很多过期 key,然后你也没及时去查,也就没走惰性删除,此时会怎么样?
如果大量过期 key 堆积在内存里,导致 redis 内存块耗尽了,咋整?
解决方案是:走内存淘汰机制。
内存淘汰机制:
Redis内存淘汰机制如下:
- noeviction: 当内存不足时,新写入操作会报错。 (不会用)
- allkeys-lru:当内存不足时,移除最近最少使用的 key(最常用的)。
- allkeys-random:当内存不足时,随机删除。
- volatile-lru:当内存不足时,设置了过期时间的键中,移除最近最少使用的 key(这个一般不太合适)。
- volatile-random:当内存不足时,设置了过期时间的key中,随机移除某个 key。
- volatile-ttl:当内存不足时,设置了过期时间的key中,有更早过期时间的 key 优先移除。
手写一个 LRU(Least Recent Used) 算法:
利用JDK实现一个Java版LRU.
class LRUCache<K, V> extends LinkedHashMap<K, V> {
private final int CACHE_SIZE; // 传递进来最多能缓存多少数据 public LRUCache(int cacheSize) {
// 设置一个hashmap的初始大小
// true 表示让 linkedHashMap 按照访问顺序来进行排序,最近访问的放在头部,最老访问的放在尾部
super((int) Math.ceil(cacheSize / 0.75) + 1, 0.75f, true);
CACHE_SIZE = cacheSize;
} @Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
// 当 map中的数据量大于指定的缓存个数的时候,就自动删除最老的数据。
return size() > CACHE_SIZE;
}
}
用到的LinkedHashMap的构造函数:
public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder)
{
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
参数说明:
initialCapacity: 初始容量大小,使用无参构造方法时,此值默认是16
loadFactor: 负载因子,使用无参构造方法时,此值默认是 0.75f
accessOrder: false: 基于插入顺序 true: 基于访问顺序
重点看看accessOrder的作用,使用无参构造方法时,此值默认是false。
accessOrder = true: 基于访问的顺序,get一个元素后,这个元素被加到最后(使用了LRU 最近最少被使用的调度算法)
6. 如何保证 redis 的高并发和高可用?redis 的主从复制原理能介绍一下么?redis 的哨兵原理能介绍一下么?
Redis实现高并发:
主从架构,一主多从,读写分离,主节点负责写,将数据同步到其他从节点,从节点负责读。
好处:轻松水平扩容,支撑高并发。
Redis Replication 的核心机制:
Master节点异步复制到Slave节点。
注意点:1. Master节点必须使用持久化。
2. Master的各种备份方案。如从备份中挑一份RDB去恢复Master,才能确保Master启动时,是有数据的。
Redis 主从复制的核心原理:
Slave第一次连Master, 发送PSYNC给Master, 触发full resynchronization全量复制。
Master生产快照RDB文件,同时在内存缓存最新数据(从客户端接收最新写命令),发送RDB给Slave。
Slave先把RDB写磁盘,再加载到内存。然后Master再把内存中缓存的写命令发到Slave,Slave再同步这些写命令。
如果Slave与Master因网络原因断开后,再连接时,Master只会发缺少的部分数据到Slave。
主从复制的断点续传:
master内存中维护一个backlog,master和slave都有一个replica offset,还有一个master run id.
master run id:是一个节点的唯一id, Host + IP有可能能变更。
如果连接断开,再连上后slave就从上次replica offset开始复制,如果没有找到,就全量复制。
无磁盘化复制:
master在内存中创建RDB,不写磁盘,发给Slave。
repl-diskless-sync yes
# 等待 5s 后再开始复制,因为要等更多 slave 重新连接过来
repl-diskless-sync-delay 5
复制的流程:
全量复制:
如果在复制期间,内存缓冲区持续消耗超过 64MB,或者一次性超过 256MB,那么停止复制,复制失败。
client-output-buffer-limit slave 256MB 64MB 60
heartbeat:
master默认每隔10秒发送一次heartbeat,slave每隔1秒发送一个 heartbeat。
Redis如何做到高可用:
redis的高可用架构,叫做failover故障转移,也可以叫做主备切换。
master在故障时,自动检测,并且将某个slave自动切换为master的过程,叫做主备切换。
Redis高可用,做主从架构,加上哨兵机制,实现主备切换。
Redis 哨兵集群实现高可用:
sentinel(哨兵)主要功能:
- 集群监控:监控master和slave进程是否正常
- 消息通知:实例有故障时,发送消息给管理员
- 故障转移:master挂了,自动转移到slave
- 配置中心:如果故障转移,通知客户端新的master地址
哨兵至少需要 3 个实例,来保证自己的健壮性。
经典的 3 节点哨兵集群:
配置 quorum=2,如果 M1 所在机器宕机了,那么三个哨兵还剩下 2 个,S2 和 S3 可以一致认为 master 宕机了,然后选举出一个来执行故障转移,同时3个哨兵的majority是2,所以还剩下的2个哨兵运行着,就可以允许执行故障转移。
redis 哨兵主备切换的数据丢失问题:
两种情况和导致数据丢失:
1. 异步复制导致的数据丢失
有部分数据还没有来得及复制到slave,master就挂了,这部分数据就丢失。
2. 脑裂导致的数据丢失
脑裂,指某个master突然脱离正常网络,无法与其他slave连接,但master还在运行,
此时哨兵认为master挂了,开启选举,将其他slave选为master。这个时候集群内有2个mster, 就叫脑裂。
此时,客户端会向旧master写数据,当旧master恢复成一个新的slave挂到新master时,新master并没有这段时间数据,就丢失了。
数据丢失问题的解决方案:
min-slaves-to-write 1
min-slaves-max-lag 10
要求至少有 1 个 slave(min-slaves-to-write),数据复制和同步的延迟不能超过 10 秒,如果超过了,master不再接收请求。
有了 min-slaves-max-lag 这个配置,减少异步复制数据的丢失量。
如果不能继续给指定数量slave发送数据,且slave超过10秒没有给自己发ack,则拒绝客户端写请求,最多丢10秒数据。
sdown 和 odown 转换机制:
sdown 是主观宕机,就一个哨兵如果自己觉得一个 master 宕机了,那么就是主观宕机。
odown 是客观宕机,如果 quorum 数量的哨兵都觉得一个 master 宕机了,那么就是客观宕机。
sdown: 如果一个哨兵 ping 一个 master,超过了 is-master-down-after-milliseconds 指定的毫秒数之后,就主观认为 master 宕机了
哨兵集群的自动发现机制:
哨兵之前通过Redis的pub/sub实现互相发现。每隔两秒,每个哨兵会往自己监控的master+slave对应的__sentinel__:hello这个channel里发送消息,
消息包括自己的host,ip, run id还有对master的监控配置。每个哨兵都监听自己监控的channel, 消费其他哨兵的消息,感知其他哨兵的存在。
slave 配置的自动纠正:
当一个slave成为master时,哨兵确保其余slave连接到新的master上。
slave -> master选举算法:
slave选举考虑因素:
跟master断开连接的时长,slave优先级,复制数据的offset,run id
如果一个 slave 跟 master 断开连接的时间已经超过了 down-after-milliseconds 的 10 倍,外加 master 宕机的时长,那么 slave 就被认为不适合选举为 master。
(down-after-milliseconds * 10) + milliseconds_since_master_is_in_SDOWN_state
选举排序:
1. slave priority越低,优先级越高。默认配置中slave-priority=100
2. slave priority相同时,看replica offset,哪个复制的数据多,offset靠后,优先级高
3. 以上相同时,选run id小的slave
quorum和majority:
哨兵主备切换时,需要quorum数量的哨兵认为sdown(主观down),才能转换为odown。
这个时候选一个哨兵来做切换,这个哨兵还需要得到majority哨兵的授权,才能正式执行切换。
configuration epoch(version):
哨兵会对一套redis的 master + slave 进行监控,有相应的监控配置。
执行切换的哨兵,会从新master(slave->master)得到一个configuration epoch,是一个version号,每次切换的version号必须唯一。
如果选举出的哨兵进行切换失败,则其他哨兵等待failover-timeout时间后,接替做切换,重新获取一个新的configuration epoch作为新的version。
configuration传播:
哨兵完成切换后,在自己本地更新生成最新master配置,然后通过hello channel同步给其他哨兵。
新的master配置是跟着新的version号,其他哨兵都是根据版本号的大小来更新自己的master配置。
Part1总结:
redis高并发:主从架构,一主多从,一般项目足够,一主写入数据,单机几W的QPS,多从用来查询数据,多个实例可以提供10W的QPS。
比如Redis主只有8G内存,其实最多只能容纳8G的数据量。如果要容量的数据量更大,就需要redis集群,用集群后可以提供每秒几十万的读写并发。
redis高可用:如果主从架构,加上哨兵集群就可以实现。一个实例宕机,自动进行主备切换。
[Java复习] 缓存Cache part1的更多相关文章
- [Java复习] 缓存Cache part2
7. Redis持久化有哪几种方式?不同的持久化机制都有什么优缺点?持久化机制具体底层是如何实现的? 为什么要持久化? 如果只是存在内存里,如果redis宕机再重启,内存数据就丢失了,所以要用持久化机 ...
- Java 中常用缓存Cache机制的实现
所谓缓存,就是将程序或系统经常要调用的对象存在内存中,一遍其使用时可以快速调用,不必再去创建新的重复的实例.这样做可以减少系统开销,提高系统效率. 所谓缓存,就是将程序或系统经常要调用的对象存在内存中 ...
- Java 中常用缓存Cache机制的实现《二》
所谓缓存,就是将程序或系统经常要调用的对象存在内存中,一遍其使用时可以快速调用,不必再去创建新的重复的实例.这样做可以减少系统开销,提高系统效率. AD: Cache 所谓缓存,就是将程序或系统经常要 ...
- Java中常用缓存Cache机制的实现
缓存,就是将程序或系统经常要调用的对象存在内存中,一遍其使用时可以快速调用,不必再去创建新的重复的实例. 这样做可以减少系统开销,提高系统效率. 缓存主要可分为二大类: 一.通过文件缓存,顾名思义文件 ...
- Java中经常使用缓存Cache机制的实现
缓存,就是将程序或系统常常要调用的对象存在内存中,一遍其使用时能够高速调用,不必再去创建新的反复的实例. 这样做能够降低系统开销.提高系统效率. 缓存主要可分为二大类: 一.通过文件缓存,顾名思义文件 ...
- 5个强大的Java分布式缓存框架推荐
在开发中大型Java软件项目时,很多Java架构师都会遇到数据库读写瓶颈,如果你在系统架构时并没有将缓存策略考虑进去,或者并没有选择更优的 缓存策略,那么到时候重构起来将会是一个噩梦.本文主要是分享了 ...
- java 复习001
java 复习001 比较随意的记录下我的java复习笔记 ArrayList 内存扩展方法 分配一片更大的内存空间,复制原有的数据到新的内存中,让引用指向新的内存地址 ArrayList在内存不够时 ...
- java 开源缓存框架--转载
原文地址:http://www.open-open.com/13.htm JBossCache/TreeCache JBossCache是一个复制的事务处理缓存,它允许你缓存企业级应用数据来更好的 ...
- Java分布式缓存框架
http://developer.51cto.com/art/201411/457423.htm 在开发中大型Java软件项目时,很多Java架构师都会遇到数据库读写瓶颈,如果你在系统架构时并没有将缓 ...
随机推荐
- git命令——git commit
功能 将暂存区中的更改记录到仓库. 加到staging area里面的文件,是表示已经准备好commit的.所以在commit修改之前,务必确定所有修改文件都是staged的.对于unstaged的文 ...
- CUDA中确定你显卡的thread和block数
CUDA中确定你显卡的thread和block数 在进行并行计算时, 你的显卡所支持创建的thread数与block数是有限制的, 因此, 需要自己提前确定够用, 再进行计算, 否则, 你需要改进你的 ...
- Java中实现图片的上传
这边直接存放在c盘的指定目录,在property中指定了一个目录 没有花时间写用户操作的上传页面,直接用swagger2插件,可以上传 默认图片大小超过1mb就不可以上传,可以如下更改 server. ...
- 无法访问此网站 ERR_CONTENT_DECODING_FAILED
这个错误挺少见的. 百度了下: 1,说是文件编码格式不正确: 2,说是Nginx的压缩和tomcat的压缩冲突了,关闭gzip压缩: 3,……………… 不管是哪一种情况,都是编码问题,所以逐一排查,根 ...
- Codeforces Round #519 D - Mysterious Crime
题目 题意: 在m组数,每组有n个数(数的范围1-n)中,找到某些序列 使它是每组数的一个公共子序列,问这样的某些序列的个数? 思路: 不难想出答案ans是≥n的. 创立一个next数组,使每组中第i ...
- SPOJ LCS Longest Common Substring 和 LG3804 【模板】后缀自动机
Longest Common Substring 给两个串A和B,求这两个串的最长公共子串. no more than 250000 分析 参照OI wiki. 给定两个字符串 S 和 T ,求出最长 ...
- vue1 监听数据变化
- Python之Linux命令
1.查看当前文件路径 : pwd LangYingdeMacBook-Pro:Users langying$ pwd /Users 2.切换目录 cd 例如:切换到根目录 : cd / 回到 ...
- 中检测到有潜在危险的 Request.Form 值。”
添加富文本时 如果出现" 中检测到有潜在危险的 Request.Form 值.” 却不知道怎么排错时,就在HTML 或Web表格头部添加 ValidateRequest=&qu ...
- MongoDB 副本集的常用操作及原理
本文是对MongoDB副本集常用操作的一个汇总,同时也穿插着介绍了操作背后的原理及注意点. 结合之前的文章:MongoDB副本集的搭建,大家可以在较短的时间内熟悉MongoDB的搭建和管理. 下面的操 ...