1.volile关键字
什么时volitile关键字?
修饰变量以保证变量的可见性(不同线程)和有序性(禁止指令重排序)。
那么什么是可见性和有序性呢?这就不得不聊一下JMM(java内存模型)了。
JMM是什么呢?
JMM也就是java内存模型,是为了解决各自硬件和操作系统的内存访问差异,让java程序在各种平台上都能达到一致得到内存访问效果。简单来说就是为了解决cpu快内存访问慢的问题。JMM规定所有的变量都是存在主存中的,类似于普通内存,每个线程又有自己的工作内存,所有的线程操作都是以工作内存为主,他们只能访问自己的工作内存,且工作前后都要把值同不会主内存。

线程执行时先从主存read变量值,load到工作内存中,然后传给处理器进行操作,执行完毕后将结果赋值给工作内存,工作内存在将值同步给主存。即使执行速度再快也会出现一个问题,那就是对线程情况下,对同一个变量操作,主存中的数据可能不是我们预期的值。为什么会出现这个问题呢?这就涉及到JMM围绕解决的三个问题 原子性、可见性和有序性,解决了这三个问题就可以解决缓存不一致问题。下面我们先来展开说说这三个特性。
原子性:原子性就是操作不可以中断,要么不做要么做完。
可见性:java通过volitile提供可见性,使用volitile修饰的变量,他的修改会直接刷新到主存,其他线程需要该变量时需要重主存中读取,它会把本地内存中的对象去掉,这样只能重新读取了。
有序性:JMM允许编译器和处理器对指令重排序,但是规定了as-if-serial语义,也就是不管怎样重排序,程序的执行结果不能改变,被volitile修饰的变量,禁止重排序。可以保证程序的额有序性。
上面的可见性和有序性提到了volitile,但是没有说原子性,是不是它不能保证原子性呢?
是的!对于复合类操作时不可以保证原子性的,它最多只能对volitile修饰的变量的读写具有原子性,想要实现原子性,之恶能通过syn,lock锁或者基本数据类型的自增自减等操作实现。
那么Volitile是如何实现可见性可有序性的呢?
volitile的底层代码中使用了lock前缀指令,lock前缀指令相当于一个内存屏障,就是靠这个内存屏障实现的。内存屏障是作用:重排序时不可以把后面的指令重排序到屏障之前。使得本cpu的cache写入到主存。写入操作时会硬气别的cpu或者内核无效化其cache(工作内存),也就相当于新写入的值对别的线程可见。
常见的对volitile使用例子:
状态量标记、单例模式双重检查锁定
2.redis
redist是什么呢?
本质上是一个key-value类型的内存数据库,会把整个数据库统统加载到内存中进行操作,定期通过异步将数据库数据刷新到硬盘中进行保存。
我们为什么要使用redis呢?
因为快!为啥快呢?首先它是操作内存的,其次他是单线程避免了上下文的切换,最后它采用了多路复用的网络模型!(网络模型随后再说)
redis支持哪些数据类型呢?
包含常见的五大基本类型
String:二进制安全字符串,也就是简单动态字符串,Rdis的字符串并不是直接使用C语言中的原生字符串进行存储,而是使用了一个成为简单动态字符串的数据结构。这种数据结构为Redis带来了很多的优势。
SDS的数据结构大致如下
struct sdshdr{
//记录buf中已使用的字节数量,等于sds锁保存字符串的长度
int len;
//记录buf数组中未使用的字节数量
int free;
//字节数组,用于保存字符串,这里没有指定数组长度面试一个柔性数组
char buf[]
}

这样的结构优势:
动态字符串中存在预分配字段,也就是free记录的剩余字节,当需要追加内容时,就可以不用重新分配空间了。
因为len记录了字符串长度,所以获取字符串长度的操作会很快,而且C语言原生的字符串不支持存空,因为它以控字符串作为结束标志,我们现在记录长度后就可以存空字符串了。
使用场景:微博数,粉丝数等常规计数
list
由双向链表和压缩列表组成,使用那总结构取决于列表的大小和元素的特性。
双向链表:当列表元素数量较多,或者元素较大时使用,双向链表的每个节点会保存前一个结点和后一个节点的指针。这使得插入和删除相对容易。
压缩列表:当列表中元素数量较少且元素较小时使用压缩列表,压缩列表是一个紧凑的连续的内存块,它按顺序存储列表中的元素
当向一个压缩列表中添加一个新元素时,如果仍满足压缩列表的条件(元素数量和大小没有超出阈值)那么直接在压缩列表中添加,否则将压缩列表转换为双向链表然后在尾部添加
使用场景:粉丝列表,发最新消息排行等
hash
哈希的底层有压缩列表和哈希表,哈希类型允许用户在单个键中存储多个字段和对应的值。
在hash使用时同样字段大小和字段数量较少较小时使用压缩列表,否则使用哈希表
使用场景:用户信息,商品信息等
set
redis的sets是一个无序元素不重复的集合,底层由hash表和整数集合组成
整数集合的使用限制:长度小于512且元素只能是整数
可以根据tag求交集,大于某个值就可以推荐
zset
有序集合的底层由压缩列表和跳表组成,数据量小且元素较小时使用压缩列表。
跳表时一种维护多层指针的有序列表,可以实现快速查询,插入和删除
使用场景:直播系统中,一些排行榜,延迟任务等实现

以及少见的
bitMap(位图)
不管是哪种结构存储,大小都是有上限的,那么如何删除过期的键值呢?
常见的有三种策略:
定时删除:在设置键的过期时间的同时设置一个定时器,在过期时间到达时执行删除操作。
定期删除:每隔一段时间,对数据库进行一次检查,删除其中过期的键
惰性删除:在读取时检查过期时间,若过期则删除
但是即使我们会删除过期的键,有时候空间依旧不够用!那么这种情况下继续存储有什么策略呢?
有六种!
在设置过期时间的数据集中,淘汰距离过期时间最近的
在设置过期时间的数据集中,淘汰最不常用的
在设置过期时间的数据集中,淘汰随便一个
在所有的数据集中,淘汰最不常用的一个
在所有的数据集中,淘汰随便一个
不淘汰,直接返回错误
在使用时还需要尽量规范一些,注意不要出现大key,另外对热点key也要注意处理!
在聊聊redis可能出现的问题缓存穿透,缓存击穿和缓存雪崩。
这三个是啥意思,如何避免呢?
缓存穿透:就是查询一个一定不存在的数据,缓存中肯定没有,然后就会去查询数据库,这样就会导致每次都查询数据库了,进而给数据库带来压力。
解决方案:1.将空数据也进行缓存,查询返回数据为空时也将返回值缓存
2.使用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被过滤器拦截。
缓存击穿: 就是热点数据失效的时候大量请求直接访问数据库。
解决方案:热点数据永不失效。
加锁(jvm锁或分布式锁setnx)
缓存雪崩:大量数据同时失效,导致数据库压力大,(也有可能是redis宕机导致的)即使重启也会再次被请求访问到崩溃
解决方案:从问题触发解决,首先大量数据同时失效那么我们就在原来的失效时间上加一个随机值,避免同一时间失效,极端一点我们甚至可以直接将热点数据设置为永不失效。其次数据库压力太大,那么我们可以使用分库分表,读写分离的方式来降低数据库的压力。另外可以使用分布式的方式将热点数据均匀分布,同时分布式集群也可以有效避免redis宕机的情况。最终手段就是使用熔断机制,当流量达到阈值时直接返回“系统拥挤”!
Redis如何解决hash冲突?
使用rehash操作,增加hush桶,Redis默认使用两个全局哈希表,一个用于当前使用,称为主hash表,一个用于扩容,称为备用哈希表
Redis集群使用哈希槽进行管理。
为啥不用一致性hash
CRC16(key)%16384 也就是对2的14次方取模 也很均匀且对于节点的增删操作比较方便
持久化方式:
AOF:追加文件,redis处理的每一个命令都会记录在aof中
RDB:redis数据快照,会把内存中所有的数据都记录到磁盘中。当Redis实例故障重启后可以从磁盘读取快照文件,恢复数据
缓存预热:
系统上线后之江将缓存数据加载到redis中。可以加一个小按钮或定时刷新等
数据库缓存一致性问题
延迟双删:删除缓存==》修改数据库==》延时删除缓存 不能保证强一致性
redisson 可重入锁
集群后续再说。

被折叠的 条评论
为什么被折叠?



