Redis
·什么是NoSQL
●NoSQL最常见的解释是"non-relational", 很多人也说它是"Not Only SQL"
●NoSQL仅仅是一个概念,泛指非关系型的数据库
●区别于关系数据库,它们不保证关系数据的ACID特性
●NoSQL是-项全新的数据库革命性运动,提倡运用非关系型的数据存储,相对于铺天盖地的关系型数据库
运用,这一-概念无疑是一一种全新的思维的注入
· NoSQL的特点
应用场景
●高并发的读写
●海量数据读写
●高可扩展性
●速度快
·不适用场景
●需要事务支持
●基于sq|的结构化查询存储,处理复杂的关系,需要即席查询(用户自定义查询条件的查询)
·redis介绍
●几乎覆盖了Memcached的绝大部分功能
●数据都在内存中,支持持久化,主要用作备份恢复
●除了支持简单的key-value模式,还支持多种数据结构的存储,比如list、set、 hash、 zset等。
●一般是作为缓存数据库辅助持久化的数据库
●现在市面上用得非常多的-款内存数据库
·Redis的基本介绍
●Redis是当前比较热门]的NoSQL系统之一
●它是一个开源的、使用ANSI C语言编写的key-value存储系统(区别于MySQL的二维表格形式存储)
●和Memcache类似,但很大程度补偿了Memcache的不足,Redis数据都是缓存在计算机内存中,不同的是,Memcache只能将数据缓存到内存中,无法自动定期写入硬盘,这就表示,一断电或重启,内存清空,数据丢失
·Redis的应用场景
·取最新N个数据的操作
比如典型的取网站最新文章,可以将最新的5000条评论ID放在Redis的List集合中,并将超出集合部分从数据库获取。
·排行榜应用,取TOP N操作
这个需求与上面需求的不同之处在于,前面操作以时间为权重,这个是以某个条件为权重,比如按顶的次数
排序,可以使用Redis的sorted set,将要排序的值设置成sorted set的score,将具体的数据设置成相应的value,每次只需要执行一条ZADD命令即可。
· Redis的特点
·高效性(内存)
o Redis读取的速度是30w次/s,写的速度是81000次/s
·原子性
o Redis的所有操作都是原子性的,同时Redis还支持对几个操作全并后的原子性执行。
·支持多种数据结构
o string (字符串)
o list(列表)
o hash(哈希)
o set(集合),去重
o zset(有序集合)
·稳定性:持久化,主从复制(集群)
·其他特性:支持过期时间,支持事务,消息订阅。
·主逻辑:单线程!
安装
·运行服务端
·运行客户端
连接redis客户端
·命令操作
- keys *:查看所有
- set (key xxx):添加值
- exists (key)判断某个key是否存在
- type (key)查看你的key是什么类型,
- del(key)删除指定的key数据·
- unlink(key)根据value选择非阻塞删除,仅将keys 从Keyspace元数据中删除,真正的删除会在后续异步操作。
- expire(key) 10 10秒钟:为给定的 key设置过期时间
- ttl (key)查看还有多少秒过期,-1表示永不过期,-2表示已过期w
- select(0,1,2,3...)命令切换redis数据库
- dbsize查看当前数据库的key的数量
- flushdb清空当前库·
- flushall通杀全部库。
·支持多种数据结构
o string (字符串)
·数据结构
<key,value>
key:键
value:值
不像,list,hash,set,zset结构有集合名称,string没有集合名称,只有一个key,一个value
String 的数据结构为简单动态字符串(Simple Dynamic String,缩写SDS)。是可以修改的字符串,内部结构实现上类似于Java的 ArrayList采用预分配冗余空间的方式来减少内存的频繁分配.
如图中所示,内部为当前字符串实际分配的空间capacity一般要高于实际字符串长度len。当字符串长度小于1M时,扩容都是加倍现有的空间,如果超过1M,扩容时一次只会多扩1M的空间。需要注意的是字符串最大长度为512M。
·常用命令
·set<key><value> :加入值,有相同的可以会覆盖
·get<key> :查询对应键值
·append <key><value>将给定的<value>追加到原值的末尾
·strlen <key>获得值的长度;
·setnx <key><value>只有在key 不存在时设置key的值才能成功
·inc <key>将key中储存的数字值增1,只能对数字值操作,如果为空,新增值为1
·decr <key>将key中储存的数字值减1,只能对数字值操作,如果为空,新增值为-1.
·incrby / decrby <key><步长>将key中储存的数字值增减。自定义步长。
·mset....key<1><value1><key2><value2> ...同时设置一个或多个key-value对。有原子性
·mget...key1><key2><key3> ....同时获取一个或多个value。有原子性
·msetnx <key1><value1><key2><value2> ....同时设置一个或多个key-value对,当且仅当所有给定key都不存在。
有原子性,有一个失败则都失败。
·getrange <key><起始位置><结束位置>:获得value值的范围数据
·setrange <key><起始位置><value>:用<value>覆写<key>所储存的字符串值,从<起始位置>开始(索引从0开始)。
·setex <key><过期时间><value>:设置键值的同时,设置过期时间,单位秒。
·getset <key><value>:以新换旧,设置了新值同时获得旧值。
o list(列表)
·单键多值:<key>,<<value1>,<value2>,<value3>...>
key:集合名称
<<value1>,<value2>,<value3>:list结构
Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)。它的底层实际是个双向链表,对两端的操作性能很高,通过索引下标的操作中间的节点性能会较差。
·数据结构
List的数据结构为快速链表quickList。
- 首先在列表元素较少的情况下会使用一块连续的内存存储,这个结构是ziplist,也即是压缩列表。它将所有的元素紧挨着一起存储,分配的是一块连续的内存。
2)当数据量比较多的时候才会改成quicklist。
因为普通的链表需要的附加指针空间太大,会比较浪费空间。比如这个列表里存的只是int类型的数据,结构上还需要两个额外的指针prev和next。
·常用命令
·lpush/push….key><value1><value2><value3>...,从左边/右边插入一个或多个值。
·lrange <key><start><stop>按照索引下标获得元素(从左到右),start=0,stop=-1表示去所有值
·lpop/rpop <key>从左边/右边吐出一个值,并且这个让值死亡。
·rpoplpush <key1><key2>从<key1>列表右边吐出一个值,插到<key2>列表左边。
·lindex <key><index>按照索引下标获得元素(从左到右)llen <key>获得列表长度
·llen <key>获得列表长度。
·linsert <key> before/after <value><newvalue>在<value>的后面插入<newvalue>插入值。
·lrem <key><n><value>从左边删除n个value(从左到右)
·lset<key><index><value>将列表key下标为index的值替换成valueu
o hash(哈希)
·Redis hash是一个键值对集合。
<key>,<<key1,value1>,<key2,value2>,<key3,value3>...>
key:集合名称
<<key1,value1>,<key2,value2>,<key3,value3>...>:键值对集合
·Redis hash是一个string类型的field和value的映射表,hash 特别适合用于存储对象类似Java里面的Map<String,Object>。并且保障元素value的唯一性
·用户D为查找的key,存储的value用户对象包含姓名,年龄,生日等信息。
如果用普通的key/value结构来存储。
·数据结构
Hash类型对应的数据结构是两种: ziplist (压缩列表),hashtable (哈希表)。当
field-value长度较短且个数较少时,使用zipRjst,否则使用hashtable。
·常用命令
·hset <key><field><value> 给<key>集合中的 <figld>键赋值 <value>
·hget <key1><field> 从<key1>集合<field>取出 value
解读:key=user:01,(key格式带“ :”)
field=name 或者 field=age ,field相当于内部key
value=dahuang 或者value= 11
·hmset <key1><field1><value1><field2><value2>... 批量设置hash的值。(现在这个被淘汰,hset 也能完成上述功能)
·hexists<key1><field>查看哈希表key 中,给定域field 是否存在。
·hkeys <key>列出该hash集合的所有field
·hvals <key>列出该hash集合的所有value
·hincrby <key><field><increment>为哈希表 key 中的域field 的值加上增量(可以正数,负数)
·hsetnx <key><field><value>将哈希表key中添加<field,value>, 当field已经存在时失败,不存在时添加成功。
o set(集合),去重
Redis set对外提供的功能与list类似是一个列表的功能,特殊之处在于set是可以自动排重的,当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择,并且set提供了判断某个成员是否在一个set集合内的重要接口,这个也是list所不能提供的。
Redis的set是string类型的无序集合。它底层其实是一个value为null的hash表,所以添加,删除,查找的复杂度都是O(1)。
一个算法,随着数据的增加,执行时间的长短,如果是O(1),数据增加,查找数据的时间不变。
·数据结构
<key,value1,value2,....>
key:集合名称
value:成员,(不能重复,如果想加入重复的value,将不生效)
set数据结构是dict字典,字典是用哈希表实现的.
Java中 HashSet的内部实现使用的是 HashMap,只不过所有的value都指向同一个对象。Redis的set结构也是一样,它的内部也使用hash结构,所有的value都指向同一个内部值。
·常用命令
·sadd <key><value1><value2> ....
将一个或多个member元素加入到集合key中,已经存在的member元素将被忽略
·smembers <key>取出该集合的所有值。
·sismember <key><value>判断集合<key>是否为含有该<value>值,有1,没有0
·scard<key>返回该集合的元素个数。
·srem <key><value1><value2>....删除集合中的某个元素。
·vspop<key>随机从该集合中吐出一个值。并且删除
·srandmember <key><n>随机从该集合中取出n个值。但是不会从集合中删除。
·smove <source><destination>value把集合中一个值从一个集合添加移动到另一个集合。重复的覆盖。
·sinter <key1><key2>返回两个集合的交集元素。
·sunion <key1><key2>返回两个集合的并集元素。
·sdiff <key1><key2>返回两个集合的差集元素(key1有的,不包含key2中的)
o zset(有序集合)
·Redis有序集合zset与普通集合set非常相似,是一个没有 重复元素的字符串集合。
·不同之处是有序集合的每个成员都关联了一个评分 ( score) ,这个评分( score )被用来按照从最低分到最高分的方式排序集合中的成员。集合的成员是唯一的,但是评分可以是重复了。
·因为元素是有序的,所以你也可以很快的根据评分( score )或者次序( position )来获取一个范围的元素。
·访问有序集合的中间元素也是非常快的,因此你能够使用有序集合作为一个没有重复成员的智能列表。
·数据结构
|
SortedSet (zset)是Redis提供的一个非常特别的数据结构,一方面它等价于Java的数据结构Map<String, Double> ,可以给每一个元素 value赋予一个权重score ,另一方面它又类似于TreeSet,内部的元素会按照权重score进行排序,可以得到每元素的名次,还可以通过score的范围来获取元素的列表。
·zset底层使用了两个数据结构。
( 1 ) hash , hash的作用就是关联元素value和权重score ,保障元素value的唯一性,可以通过元素value找到相应的score值。。
(2 )跳跃表,跳跃表的目的在于给元素value排序,根据score的范围获取元素列表。
1、跳跃表简介。
有序集合在生活中比较常见,例如根据成绩对学生排名,根据得分对玩家排名等。对于有序集合的底层实现,可以用数组、平衡树、链表等。数组不便元素的插入、删除;平衡树或红黑树虽然效率高但结构复杂;链表查询需要遍历所有效率低。Redis采用的是跳跃表。跳跃表效率堪比红黑树,实现远比红黑树简单。
2、实例:对比有序链表和跳跃表,从链表中查询出51
(1)有序链表
要查找值为51的元素,需要从第一个元素开始依次查找、比较才能找到。共需要6次比较。。
(2)跳跃表
从第2层开始,1节点比51节点小,向后比较。
21节点比51节点小,继续向后比较,后面就是NULL了,所以从21节点向下到第1层。
在第1层,41节点比51节点小,继续向后, 61节点比51节点大,所以从41向下。
在第0层, 51节点为要查找的节点,节点被找到,共查找4次。
·常用命令
·zadd <key> <score1> <value1> <score2> <value2> ..
将一个或多个member元素及其score; 值加入到有序集key当中。
·zrange <key> <start> < stop>I WITHSCORES] 。
返回有序集key中,下标在<start><stop>之间的元素。带withscores,可以让分数一起和值返回到结果集。
·zrangebyscore key min max [withscores] [limit offset count]
返回有序集key中,所有score值介于min和max之间(包括等于min或max )的成员。
有序集成员按score值递增(从小到大)次序排列。
·zrevrangebyscore key max min [withscores] [imit offset count]同上,改为从大到小排列。
·zincrby <key> <increment> <value>为元素的score加上增量。
·zrem <key> <value>删除该集合下,指定值的元素
·zcount <key> <min> <max>统计该集合,分数区间内的元素个数。
·zrank <key> <value>返回该值在集合中的排名,从0开始。
案例:如何利用zset实现- -个文章访问量的排行榜?
·Redis的发布和订阅
·什么是发布和订阅
Redis发布订阅(pub/sub)是一种消息通信模式:发送者(pub) 发送消息,订阅者(sub)接收消息。
Redis客户端可以订阅任意数量的频道。
如:
·Jedis操作Redis6
·关闭防火墙
·实例:手机验证码
要求:
1、输入手机号,点击发送后随机生成6位数字码, 2分钟有效
2、输入验证码,点击验证,返回成功或失败
3、每个手机号每天只能输入3次
public class PhoneCode {
public static void main(String[] args) {
//发送到redis
verify("15814813692");
//验证,验证码
verifycode("15814813692","334152");
}
//随机生成6位数字,作为验证码
public static String getCode(){
Random random = new Random();
String code="";
for (int i = 0; i < 6; i++) {
int rand = random.nextInt();
code+= rand;
}
return code;
}
//只能每天3次
public static void verify(String phone){
//连接redis,放入验证码
Jedis jedis = new Jedis("192.168.52.128",6379);
//拼接key
//手机发送次数key
String countKey="verify"+phone+"count";
//验证码key
String codeKey="verify"+phone+"code";
//手机只能发3次
String count = jedis.get(countKey);
if (count==null){
//没有发送,
//设置发送次数为1
jedis.setex(codeKey, 24 * 60 * 60, "1");
}else{
System.out.println("今天发送次数超过3次");
jedis.close();
return;
}
//放入redis
String mycode = getCode();
jedis.setex(codeKey,120,mycode);
jedis.close();
}
// 校验验证码的准确性
public static void verifycode(String phone,String code){
连接redis,取出验证码
Jedis jedis = new Jedis("192.168.52.128",6379);
//验证key
String codeKey="verify"+phone+"code";
String s = jedis.get(codeKey);
//判断
if (s.equals(code)){
System.out.println("成功");
}else System.out.println("失败");
}
}
·验证码从Xshell7上查看。
输入
·Redis事务
Redis事务是一个单独的隔离操作∶事务中的所有命令都会序列化、按顺序地执行事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
Redis事务的主要作用就是串联多个命令防止别的命令插队。
·Mulit
·Exec
·discard
从输入Multi命令开始,输入的命令都会依次进入命令队列中,但不会执行, 直到输入Exec后, Redis会将之前的命令队列中的命令依次执行。组队的过程中可以通过discard来放弃组队。
·情况一
组队中某个命令出现了报告错误,执行时整个的所有队列都会被取消。
·情况二
如果执行阶段某个命令报出了错误,则只有报错的命令不会被执行,而其他的命令都会执行,不会回滚。
·悲观锁VS乐观锁
·乐观锁
在执行multi之前,先执行watch key,可以监视一个(或多个) key,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。
1先执行,2再执行,因为检测到版本号变化了
·unwatch :取消WATCH命令对所有key 的监视。
如果在执行WATCH命令之后, EXEC命令或DISCARD命令先被执行了的话,那么就不需要再执行UNWATCH了。
·Redis事务三特性.
➢单独的隔离操作:
事务中的所有 命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
➢没有隔离级别的概念:
队列中的命令没有提交之前都不会实际被执行,因为事务提交前任何指令都不会被实际执行。
➢不保证原子性:
事务中如果有一条命令执行失败,后的命令仍然会被执行,没有回滚。
·redis持久化
·RDB:
在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是行话讲的 Snapshot快照,它恢复时是将快照文件直接读到内存里。
·备份是如何执行的?
Redis.会单独创建( fork )一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失。
·Fork
1)Fork的作用是复制一个与当前进程一样的进程。新进程的所有数据(变量、环境变量、程序计数器等)数值都和原进程一致,但是是一个全新的进程,并作为原进程的子进程。
2)在Linux程序中,fork(会产生一个和父进程完全相同的子进程,但子进程在此后多会exec系统调用,出于效率考虑,Linx 中引入了“写时复制技术”。
3)一般情况父进程和子进程会共用同一段物理内存,只有进程空间的各段的内容要发生变化时,才会将父进程的内容复制一份给子进程。
·劣势
·Fork 的时候,内存中的数据被克隆了一份,大致2倍的膨胀性需要考虑
·虽然 Redis.,在 fork时使用了写时拷贝技术,但是如果数据庞大时还是比较消耗性能。
·在备份周期在一定间隔时间做一次备份,所以如果Redis.,意外down掉的话,就会丢失最后一次快照后的所有修改。
·AOF (Append Only File) .
是什么?
以日志的形式来记录每个写操作(增量保存),将Redis执行过的所有写指令记录下来(读操作不记录),只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis重启的话就根据日志文件的内容将写指令从前到后执行次以完成数据的恢复工作
·AOF同步频率设置。
·appendfsync always
始终同步,每次Redis的写入都会立刻记入日志;性能较差但数据完整性比较好
·appendfsync everysec
每秒同步,每秒记入日志一次,如果宕机,本秒的数据可能丢失。
·appendfsync nov
redis不主动进行同步,把同步时机交给操作系统。
·AOF持久化流程.
- 户端的请求写命令会被append追加到AOF缓冲区内
- AOF缓冲区根据AOF持久化策略[ always,everysec,nq]将操作sync同步到磁盘的AOF文件中;
- AOF文件大小超过重写策略或手动重写时,会对AOF文件rewrite重写,压缩AOF文件容量;
(4) Redis服务重启时,会重新load加载AQF文件中的写操作达到数据恢复的目的;
·劣势。
·比起RDB占用更多的磁盘空间。
·恢复备份速度要慢。
·每次读写都同步的话,有-定的性能压力。
·存在个别Bug ,造成恢复不能。
●RDB持久化方式能够在指定的时间间隔能对你的数据进行快照存储。
●AOF持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以redis协议追加保存每次写的操作到文件末尾.。
●Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大
●只做缓存: 如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持玖化方式。
●同时开启两种持久化方式,
●在这种情况下,当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整.
●RDB的数据不实时,同时使用两者时服务器重启也只会找AOF文件。那要不要只使用AOF呢?。
●建议不要, 因为RDB更适合用于备份数据库(AOF在不断变化不好备份),快速重启,而且不会有AOF可能潜在的bug ,留着作为一个万一的手段。
●性能建议。
·Redis主从复制
·一主多从
- 读写分离
1)当从连接上主服务器之后,从服务器主动向主服务发送进行数据同步消息。
2)主服务器接到从服务器发送过来同步消息,把主服务器数据进行持久化rdb文件,把rdb文件发送从服务器,从服务器拿到rdb进行读取。
3)每次主服务器进行写操作之后,主服务器主动和从服务器进行数据同步。
2、容灾快速恢复
·薪火相传模式
一个主服务器---->多个一级从服务器----->多个二级从服务器--->.....
·反客为主:从服务器--变-->主服务器
·哨兵模式
反客为主的自动版,能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库。旧主机恢复也只能当从机
·复制延时
由于所有的写操作都是先在Master.上操作,然后同步更新到Slave.上,所以从Master同步到Slave机器有一定的延迟,当系统很繁的时候,延迟问题会更加严重, Slave机器数量的增加也会使这个问题更加严重。
·Redis集群。
容量不够, redis如何进行扩容?
并发写操作,redis 如何分摊?
另外,主从模式,薪火相传模式,主机宕机,导致ip地址发生变化,应用程序中配置需要修改对应的主机地址、端口等信息。
之前通过代理主机来解决,但是redil3.0中提供了解决方案。就是无中心化集群配置。
·什么是集群。
Redis集群实现了对Redis的水平扩容,即启动N个redis节点,将整个数据库分布存储在这N个节点中,每个节点存储总数据的1/N。
Redis集群通过分区( partition )来提供一定程度的可用性( availability ) :即使集群中有一部分节点失效或者无法进行通讯,集群也可以继续处理命令请求。
·分配原则尽量保证每个主数据库运行在不同的IP地址,每个从库和主库不在一个IP地址上。
·[OK] AIl 16384 slots covered.s
·一个Redis集群包含16384 个插槽( hashslot),数据库中的每个键都属于这16384个插槽的其中一个。
·集群使用公式CRC16(key) % 16384来计算键key属于哪个槽,中CRC16(key)语句用于计算键key的CRC16校验和。
·集群中的每个节点负责处理一部分插槽。 举个例子, 如果一个集群可以有主节点,其中:
节点A负责处理0号至5460号插槽。
节点B负责处理5461号至10922 号插槽。
节点C负责处理10923号至16383号插槽。
作用:按照格式散列到不同<key-value>的主服务器上面,分担压力
集群添加<key-value>数据时,有两种方式,
·一种是:普通的单个添加,
·如果批量添加会报错,可以使用分组解决
·在终端如果要查找<key-value>要找到对应的主服务器
·如果所有某一段插槽的主从节点都宕掉, redis 服务是否还能继续?。
1)如果某一段插槽的主从都挂掉,而cluster-require-full-coverage为yes , 那么, 整个集群都挂掉
2)如果某- -段插槽的主从都挂掉,而cluster-require-full-coverage为no , 那么,该插槽数据全都不能使用,也无法存储。
redis.conf中的参数cluster-require-full-coverage.
·Redis集群提供了以下好处。
实现扩容
分摊压力,
无中心配置相对简单。
·Redis集群的不足.
多键操作是不被支持的。
多键的Redis事务是不被支持的。lua 脚本不被支持。
由于集群方案出现较晚,很多公司已经采用了其他的集群方案,而代理或者客户端分片的方案想要迁移至redis cluster ,需要整体迁移而不是逐步过渡,复杂度较大。
·redis缓存穿透
·解决方案。
一个一定不存在缓存及查询不到的数据,由于缓存是不命中时被动写的,并且出于容错考虑,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。
解决方案:
(1) 对空值缓存:如果一个查询返回的数据为空(不管是数据是否不存在) ,我们仍然把这个空结果( null )进行缓存,设置空结果的过期时间会很短,最长不超过五分钟。如:<key1,null>这种的给它设置极短生存时间。
(2) 设置可访问的名单(白名单) :使用bitmaps类型定义一个可以访问的名单,名单id作为bitmaps的偏移量,每次访问和bitmap里面的id进行比较,如果访问id不在bitmaps里面,进行拦截,不允许访问。
(3)采用布隆过滤器: (布隆过滤器( Bloom Filter )是1970年由布隆提出的。它实际上是一个很长的二进制向量(位图)和一系列随机映射函数(哈希函数)。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有-定的误识别率和删除困难)。将所有可能存在的数据哈希到一个足够大的bitmaps中,一个定不存在的数据会被这个bitmaps拦截掉,从而避免了对底层存储系统的查询压力。
(4)进行实时监控:当发现Redis的命中率开始急速降低,需要排查访问对象和访问的数据,和运维人员配合,可以设置黑名单限制服务。
·redis缓存击穿
·解决方案。
key可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。这个时候,需要考虑一个问题:缓存被“击穿”的问题。
解决问题:。
(1)预先设置热门数据:在redis 高峰访问之前,把一些热门数据提前存入到redis里面,加大这些热]数据key的时长。
(2)实时调整:现场监控哪些数据热门,实时调整key的过期时长。
(3)使用锁:。
1)就是在缓存失效的时候(判断拿出来的值为空) , 不是立即去load db。
2)先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX )去set一个mutex keys
·redis缓存雪崩
问题描述。
key对应的数据存在,但在redis中过期,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。
缓存雪崩与缓存击穿的区别在于这里针对很多key缓存,前者则是某一个key。正常访问。
·解决方案。
缓存失效时的雪崩效应对底层系统的冲击非常可怕!。
解决方案:
(1) 构建多级缓存架构: nginx缓存+ redis缓存+其他缓存( ehcache等)
(2) 使用锁或队列:
用加锁或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并 发请求落到底层存储系统上。不适用高并发情况。
(3)设置过期标志更新缓存:
记录缓存数据是否过期(设置提前量) , 如果过期会触发通知另外的线程在后台去更新实际key的缓存。
(4) 将缓存失效时间分散开:
比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5 分钟随机,这样每一个缓存的过期时间的 重复率就会降低,就很难引发集体失效的事件。
分布式锁
·问题描述
随着业务发展的需要,原单体单机部署的系统被演化成分布式集群系统后,由于分布式系统多线程、多进程并且分布在不同机器上,这将使原单机部署情况下的并发控制锁策略失效,单纯的Java API不能提供分布式锁的能力。为了解决这个问题就需要一种跨JVM的互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题。
分布式锁主流的实现方案:
1.基于数据库实现分布式锁。
2.基于缓存( Redis等)。
3.基于Zookeepers
每一种分布式锁解决方案都有各自的优缺点:
1.性能: redis最高
2.可靠性: zookeeper最高
这里,我们就基于redis 实现分布式锁。。
·redis的主节点存放的是总数据的1/N,所以锁只能在一个主redis的一个插槽里。
·同级的Redis从机也只复制同级的主机。