> @Author:songxiankun2008@126.com
### Redis
---
###### EHcache
- 轻量级缓存
- 他是运行在单机内存里的 方便简单
- 缺点:容量非常有限 应用场合主要是单机应用 单机范围内
###### Mem cache
- 内容存储很单一 就是字符串
###### redis
- 存储类型丰富 性能非常高 可靠性很高
- Redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,zset,hash等数据结构的存储。memcache支持简单的数据类型,String。
- Redis支持数据的备份,即master-slave模式的数据备份。
- Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用。
###### Memcache & redis
- 1. 由于Redis只使用单核,而Memcached可以使用多核,所以在比较上,平均每一个核上 Redis在存储小数据时Memcached性能更高。而在100k以上的数据中,Memcached性能要高于Redis,虽然Redis最近也在存储 大数据的性能上进行优化,但是比起Memcached,还是稍有逊色。说了这么多,结论是,无论你使用哪一个,每秒处理请求的次数都不会成为瓶颈。
- 2. 如果要说内存使用效率,使用简单的key-value存储的话,Memcached的内存利用率更高,而如果Redis采用hash结构来做key-value存储,由于其合组式的压缩,其内存利用率会高于Memcached。
- 3. 如果你对数据持久化和数据同步有所要求,那么推荐你选择Redis,因为这两个特性Memcached都不具备。即使你只是希望在升级或者重启系统后缓存数据不会丢失,选择Redis也是明智的。
- Redis和Memcache都是将数据存放在内存中,都是内存数据库。不过memcache还可用于缓存其他东西,例如图片、视频等等。
- 虚拟内存--Redis当物理内存用完时,可以将一些很久没用到的value 交换到磁盘
- 过期策略--memcache在set时就指定,例如set key1 0 0 8,即永不过期。Redis可以通过例如expire 设定,例如expire name 10
- 分布式--设定memcache集群,利用magent做一主多从;redis可以做一主多从。都可以一主一从
- 存储数据安全--memcache挂掉后,数据没了;redis可以定期保存到磁盘(持久化)
- 灾难恢复--memcache挂掉后,数据不可恢复; redis数据丢失后可以通过aof恢复
- Memcached是多线程,非阻塞IO复用的网络模型;Redis使用单线程的IO复用模型。
![image](https://user-gold-cdn.xitu.io/2018/4/18/162d7773080d4570?imageslim)
##### 1. redis在Linux上的安装
1. 安装redis编译的c环境,yum install gcc-c++
2. 将redis-2.6.16.tar.gz上传到Linux系统中
3. 解压到/usr/local下 tar -xvf redis-2.6.16.tar.gz -C /usr/local
4. 进入redis-2.6.16目录 使用make命令编译redis
5. 在redis-2.6.16目录中 使用make PREFIX=/usr/local/redis install命令安装 redis到/usr/local/redis中
6. 拷贝redis-2.6.16中的redis.conf到安装目录redis中
7. 启动redis 在bin下执行命令redis-server redis.conf
8. 如需远程连接redis,需配置redis端口6379在linux防火墙中开发
/sbin/iptables -I INPUT -p tcp --dport 6379 -j ACCEPT
/etc/rc.d/init.d/iptables save
9. 将redis.conf文件中的daemonize从false修改成true表示后台启动
10. 若要远程连接的话,1.<code>bind 127.0.0.1改为 #bind 127.0.0.1</code>,2.<code>protected-mode yes 改为 protected-mode no</code>
##### 2. Redis的常用命令
###### redis是一种高级的key-value的存储系统
其中的key是字符串类型,尽可能满足如下几点:
1. key不要太长,最好不要操作1024个字节,这不仅会消耗内存还会降低查找 效率
2. key不要太短,如果太短会降低key的可读性
3. 在项目中,key最好有一个统一的命名规范(根据企业的需求)
4. 其中value 支持五种数据类型:
- 字符串型 string
- 字符串列表 lists
- 字符串集合 sets
- 有序字符串集合 sorted sets
- 哈希类型 hashs
##### 3. 存储字符串string
- [x] 字符串类型是Redis中最为基础的数据存储类型,它在Redis中是二进制安全的,这便意味着该类型可以接受任何格式的数据,如JPEG图像数据或Json对象描述信息等。 在Redis中字符串类型的Value最多可以容纳的数据长度是512M
![image](https://sxk.s3.didiyunapi.com/pic/redis/string.png)
- 1)set key value:设定key持有指定的字符串value,如果该key存在则进行覆盖 操作。总是返回”OK”
- 2)get key:获取key的value。如果与该key关联的value不是String类型,redis 将返回错误信息,因为get命令只能用于获取String value;如果该key不存在,返 回null
![image](https://sxk.s3.didiyunapi.com/pic/redis/1.png)
- 3)getset key value:先获取该key的值,然后在设置该key的值。
- 4)incr key:将指定的key的value原子性的递增1.如果该key不存在,其初始值 为0,在incr之后其值为1。如果value的值不能转成整型,如hello,该操作将执 行失败并返回相应的错误信息。
- 5)decr key:将指定的key的value原子性的递减1.如果该key不存在,其初始值 为0,在incr之后其值为-1。如果value的值不能转成整型,如hello,该操作将执 行失败并返回相应的错误信息。
- 6)incrby key increment:将指定的key的value原子性增加increment,如果该 key不存在,器初始值为0,在incrby之后,该值为increment。如果该值不能转成 整型,如hello则失败并返回错误信息
- 7)decrby key decrement:将指定的key的value原子性减少decrement,如果 该key不存在,器初始值为0,在decrby之后,该值为decrement。如果该值不能 转成整型,如hello则失败并返回错误信息
- 8)append key value:如果该key存在,则在原有的value后追加该值;如果该 key 不存在,则重新创建一个key/value
##### 4. 存储lists类型
- [x] 在Redis中,List类型是按照插入顺序排序的字符串链表。和数据结构中的普通链表 一样,我们可以在其头部(left)和尾部(right)添加新的元素。在插入时,如果该键并不存在,Redis将为该键创建一个新的链表。与此相反,如果链表中所有的元素均被移 除,那么该键也将会被从数据库中删除。
![image](https://sxk.s3.didiyunapi.com/pic/redis/2.png)
- 1)==lpush key value1 value2...==:在指定的key所关联的list的头部插入所有的 values,如果该key不存在,该命令在插入的之前创建一个与该key关联的空链 表,之后再向该链表的头部插入数据。插入成功,返回元素的个数。
- 2)==rpush key value1、value2…==:在该list的尾部添加元素
- 3)lrange key start end:获取链表中从start到end的元素的值,start、end可 为负数,若为-1则表示链表尾部的元素,-2则表示倒数第二个,依次类推…
![image](https://sxk.s3.didiyunapi.com/pic/redis/3.png)
- 4)lpushx key value:仅当参数中指定的key存在时(如果与key管理的list中没 有值时,则该key是不存在的)在指定的key所关联的list的头部插入value。
- 5)rpushx key value:在该list的尾部添加元素
![image](https://sxk.s3.didiyunapi.com/pic/redis/4.png)
- 6)lpop key:返回并弹出指定的key关联的链表中的第一个元素,即头部元素。
- 7)rpop key:从尾部弹出元素
- 8)rpoplpush resource destination:将链表中的尾部元素弹出并添加到头部
- 9)llen key:返回指定的key关联的链表中的元素的数量。
- 10)lset key index value:设置链表中的index的脚标的元素值,0代表链表的头元 素,-1代表链表的尾元素。
- 11)lrem key count value:删除count个值为value的元素,如果count大于0,从头向尾遍历并删除count个值为value的元素,如果count小于0,则从尾向头遍历并删除。如果count等于0,则删除链表中所有等于value的元素。
![image](https://sxk.s3.didiyunapi.com/pic/redis/5.png)
- 12)linsert key before|after pivot value:在pivot元素前或者后插入value这个元素
##### 5. 存储sets类型
- [x] 在Redis中,我们可以将Set类型看作为没有排序的字符集合,和List类型一样,我们也可以在该类型的数据值上执行添加、删除或判断某一元素是否存在等操作。需要说明的是,这些操作的时间是常量时间。Set可包含的最大元素数是4294967295。
- [x] 和List类型不同的是,Set集合中不允许出现重复的元素。和List类型相比,Set类 型在功能上还存在着一个非常重要的特性,即在服务器端完成多个Sets之间的聚合计 算操作,如unions、intersections和differences。由于这些操作均在服务端完成, 因此效率极高,而且也节省了大量的网络IO开销
- 1)==sadd key value1、value2…==:向set中添加数据,如果该key的值已有则不会 重复添加
- 2)==smembers key==:获取set中所有的成员
- 3)scard key:获取set中成员的数量
- 4)sismember key member:判断参数中指定的成员是否在该set中,1表示存 在,0表示不存在或者该key本身就不存在
- 5)srem key member1、member2…:删除set中指定的成员
- 6)srandmember key:随机返回set中的一个成员
- 7)sdiff sdiff key1 key2:返回key1与key2中相差的成员,而且与key的顺序有 关。即返回差集。
- 8)sdiffstore destination key1 key2:将key1、key2相差的成员存储在 destination上
- 9)sinter key[key1,key2…]:返回交集。
- 10)sinterstore destination key1 key2:将返回的交集存储在destination上
- 11)sunion key1、key2:返回并集。
- 12)sunionstore destination key1 key2:将返回的并集存储在destination上
##### 6. 存储sortedset
- [x] Sorted-Sets和Sets类型极为相似,它们都是字符串的集合,==都不允许重复的成员出现在一个Set中==。它们之间的主要差别是Sorted-Sets中的每一个成员都会有一个分数(score)与之关联,Redis正是通过分数来为集合中的成员进行从小到大的排序。然而需要额外指出的是,尽管Sorted-Sets中的成员必须是唯一的,但是分数(score) 却是可以重复的。
在Sorted-Set中添加、删除或更新一个成员都是非常快速的操作,其时间复杂度为 集合中成员数量的对数。由于Sorted-Sets中的成员在集合中的位置是有序的,因此, 即便是访问位于集合中部的成员也仍然是非常高效的。事实上,Redis所具有的这一 特征在很多其它类型的数据库中是很难实现的,换句话说,在该点上要想达到和Redis 同样的高效,在其它数据库中进行建模是非常困难的。
- [ ] 例如:游戏排名、微博热点话题等使用场景。
- 1)==zadd key score member score2 member2 …== :将所有成员以及该成员的 分数存放到sorted-set中
- 2)zcard key:获取集合中的成员数量
- 3)zcount key min max:获取分数在[min,max]之间的成员
- zincrby key increment member:设置指定成员的增加的分数。
- zrange key start end [withscores]:获取集合中脚标为start-end的成员,[withscores]参数表明返回的成员包含其分数。
- zrangebyscore key min max [withscores] [limit offset count]:返回分数在[min,max]的成员并按照分数从低到高排序。[withscores]:显示分数;[limit offset count]:offset,表明从脚标为offset的元素开始并返回count个成员。
- zrank key member:返回成员在集合中的位置。
- zrem key member[member…]:移除集合中指定的成员,可以指定多个成员。
- zscore key member:返回指定成员的分数
##### 7. 存储hash
- [x] Redis中的Hashes类型可以看成具有String Key和String Value的map容器。所以该类型==非常适合于存储值对象的信息==。如Username、Password和Age等。如果 Hash中包含很少的字段,那么该类型的数据也将仅占用很少的磁盘空间。每一个Hash 可以存储4294967295个键值对。
![image](https://sxk.s3.didiyunapi.com/pic/redis/6.png)
- 1)==hset key field value==:为指定的key设定field/value对(键值对)。
- 2)==hgetall key==:获取key中的所有filed-vaule
![image](https://sxk.s3.didiyunapi.com/pic/redis/7.png)
- 3)==hget key field==:返回指定的key中的field的值
- 4)hmset key fields:设置key中的多个filed/value
- 5)hmget key fileds:获取key中的多个filed的值
- 6)hexists key field:判断指定的key中的filed是否存在
- 7)hlen key:获取key所包含的field的数量
- 8)hincrby key field increment:设置key中filed的值增加increment,如:age 增加20
![image](https://sxk.s3.didiyunapi.com/pic/redis/11.png)
---
#### 8.数据类型String 分布式锁
- 设置过期时间:==expire== key time 单位s
- 分布式锁:==setnx== key value
- 当key不存在时就新建这个key(只有这个key不存在的时候才会成功)
- set 设置/添加
- get 获取
- incr 自增
- decr 自减
- incrby 自定义增加
- decrby 自定义减少
- del key 删除
- flushall或者flushdb可以用来清空redis
#### 9. 数据类型 Hash List Set SortedSet 排行榜
Hash <String,values>
- hmset key field value //存储Hash
- eg: hmset laowang name "laowang" age "28" look "handsome"
- hgetall key //获取全部
- eg: hgetall laowang //此时会将上面存储的全部get出来
- hget key field //获取Hash中的单个
- eg: hget laowang age //输出28
- hset laowang age "88" //设 值 - 此处即覆盖
List <单列集合>
- lpush 可以当做集合也可以当做堆栈
- eg: lpush laowang aaa bbb ccc
- 再lpush一下,是数值叠加,不是覆盖
- lrange key start stop // 范围获取,从哪个值到哪个值的范围之中
- lrange laowang 0 2 //表示取出前面三个元素
- 输出为:ccc bbb aaa //因为是堆栈,先进后出 后进先出
- lrange laowang 0 100 //只要尾数大于容量数就可以获取全部的值
- lindex key index // 获取专门哪个值,索引脚标对应的值
- lindex laowang 1 //输出bbb
- lpop key //表示将这个key中的栈顶的值弹出(即get到后删除)
- eg: lpop laowang //会输出"ccc" 再获取下全部就没有了"ccc"
- rpop key // 表示将 栈底 的那个值给弹出 (r:reverse 倒着)
- eg: rpop laowang //会输出"aaa"
- lset key index value// 将index值修改,即覆盖
- eg: lset laowang 0 xxx //原理laowang下的首个元素值被改成了"xxx"
- llen key //查看key的长度
- eg: llen laowang
- lrem key count value //删除哪些元素
- eg: count是指从顶自下几个元素删除
set <不能有重复>
###### 没有索引
- sadd key value1 value2 value3... //往这个key里面存值
- eg: sadd laowang aaa bbb ccc //返回"3" 表示成功操作了3个数据
- smembers key //获取这个key下面的所有成员值(Members)
- eg:smembers laowang (取出来没得重复的元素)
- 若此时再添加aaa: sadd laowang aaa 则会返回"0"表示没有数据操作,再获取所有:smembers laowang 输出的是 aaa,bbb,ccc;并不会出现两个aaa
- spop key [count] //[]表示这个值可有可无;如果没有则表示默认移除栈顶元素;若有指定的值,就从顶向下数到count值进行移除
- eg: spop laowang //移除 栈顶元素
- eg: spop laowang 2 //移除上面两个元素
- srem key member [member ...] //删除key下那个元素(名称)
- eg: srem laowang aaa bbb //将laowang中的aaa,bbb给删除
- sinter key [key ...] // 取出两个key之中 一样的情况
- sdiff key [key ...] // 取出两个key之中 不一样的情况
- sinter laowang laowang2 取出这两个集合的相同点;
- sdiff laowang laowang2 对这两个集合进行求差值
SortedSet <有序的set>
- zadd laowang 3 ccc 2 aaa 5 fff
- 存入(根据sort进行排序)
- zrange key start stop [withScores] //取出,可选伴随分数
- zrange laowang 0 5 取出
- 输出后是,分数较小的在上面;即:"aaa" "ccc" "fff"
- zrange laowang 0 10 withscores
- 输出是: "aaa" "2" "ccc" "3" "fff" "5"
- 此时若插入一个相同的值,不同的分数;eg:zadd laowang 99 aaa;此时会返回"0"表示没有数据执行成功,即没有第二个aaa;但是原先那个aaa的分数由原来的2变成了现在的99;那么在获取全部输出的时候aaa将会排在最后输出(值按照分数从小到大排列)
普通代码:
public class Main {
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1", 6379, 60);
jedis.select(9);
jedis.set("java", "123");
jedis.setnx("java", "789");
jedis.set("c", "c++");
System.out.println(jedis.get("java");
System.out.println(jedis.mget("java","c"));
Map<String, String> map = new HashMap<String, String>();
map.put("one", "first");
map.put("one", "2");
map.put("two", "WW");
map.put("3", "Three");
jedis.hmset("LaLa", map);
System.out.println(jedis.hmget("LaLa","one"));
System.out.println(jedis.hgetAll("LaLa"));
}
}
![image](https://sxk.s3.didiyunapi.com/pic/redis/8.jpg)
#### 10. Redis发布订阅
###### Redis发布订阅的模型
![image](https://sxk.s3.didiyunapi.com/pic/redis/9.jpg)
- 订阅频道:所有的redis客户端执行命令<code>subscribe</code>跟频道名称;
- eg: <code>subscribe laowang</code>
- 发布消息:一个没有订阅这个ChannelId的客户端去推送消息发布
- publish ChannelId Message
- publish laowang Shot-Him
![image](https://sxk.s3.didiyunapi.com/pic/redis/10.png)
###### 用代码实现消息订阅与发布:
①消息订阅的实现:
public class RedisSubscribe extends JedisPubSub {
public RedisSubscribe() {
super();
}
@Override
public void onMessage(String channel, String message) {
System.out.println(channel + " " + message);
}
②消息的订阅(监听):
public class subscribe {
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1", 6379, 60);
// subscribe 订阅/订购
jedis.subscribe(new RedisSubscribe(),"laowang");
}
}
//消息订阅 是一个阻塞线程;一直都处于等待状态,有消息就才处理
③消息的发布: ( 上下的②跟③是在两个项目中 )
public class publish {
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1", 6379, 60);
jedis.publish("laowang", "这里是要发布的消息!");
}
}
- [x] redis的发布订阅是 异步、解耦到其他的系统里面去;独立起来各与各之间没得关联;一处有问题不会影响其他地方的执行;
#### 11. redis事务
- multi //开启事务(执行的第一行代码)
- set key values //装值 (执行的第二行代码)
- eg: set aaa aaaa 会返回"QUEUED"表示进入队列成功
- 可以再继续 set bb bbb ;同样会返回"QUEUED"(执行的第三行代码)
- get key //通过键获取值 (执行的第四行代码)
- eg: get aaa
- exec //执行 (第五行代码即最后一行——执行!)
###### 第一行代码是事务开始,中间3行代码是执行数据的代码,最后一行是启动“执行”这一效果来按顺序执行上面的3行数据代码;此时返回的值是OK OK "aaaa"
- 如果上面的exec执行失败;会有两种报错:
- 1. 事务中途出错(或语法出错),exec后不会操作数据,如果刚刚有添加数据,那么此时获取这个数据是获取不到的;只会回滚
- 2. 事务过程中没有出错,在exec执行的时候才出错;是里面插入的数据类型不符合但是redis没有报错没有检测出来在exec的时候才检测出来,那么在这个应该检测出来的这一行命令之前的命令已经执行成功了;虽然此时exec是执行失败了但是前面已经执行成功了的数据依然是执行成功了的并不会数据回滚;这样的话这些脏数据只能自己手动清理
- watch key [keys ...] // 监控
###### 如果是在multi事务中要涉及到上面监控的这个key的值,如果要修改这个key的值那么exec执行后,会显示执行无效; 因为这个 key 是在被监控的状态是不能被修改的;除非是执行了 unwatch 取消监控的命令后再重复这一套mutil->exec事务才能执行成功
###### 相当于是加了锁;(出了watch这个事务外,在其他事务或者操作中要操作我这儿的东西可能会被拒绝或者一直等待)
unwatch 取消监控
#### 12. redis持久化
- 1.快照(一般不用)
- redis的快照是默认配置
- 优点: 能够随便保存,数据保存的比较全
- 缺点:随便保存如果数据量很大的话磁盘大小会受不了,并且读写磁盘的效率比较低,大大的浪费了效率;只是900s改变了一个key然而把好多G的数据全部down在了磁盘造成了数据的浪费
###### redis持久化策略(默认快照,快照的默认配置)
• 1. RDB:基于特定的时间间隔将数据 “全量快照”,生成 RDB 文件并落地
SNAPSHOTTING (快照)
# Save the DB on disk:
#
# save <seconds> <changes>
save 900 1 //如果900秒内发生了一次key的改变就生成一次快照
save 300 10 //如果300秒内发生了10次改变就生成一次快照
save 60 10000 //如果60s内...
// 1 跟 2 的配置文件是 redis.windows.conf
- 2.AOF (append only file)(公司多用这个)
- 仅添加被改变的;将被改变的添加到某持久化的磁盘中
###### redis持久化策略(Append only mode)
此模式默认状态下是关闭的;如果要开启aop这个持久化策略的话,需要将上面的3个“save”全部用“#”关闭;
再将
appendonly no
置为
appendonly yes
策略:
# appendfsync always //有改变就持久化到磁盘
appendfsync everysec //每秒钟同步一次(非常建议用 好用)
# appendfsync no //完全依赖于操作系统或者redis,不能认为控制
##### RDB 与 AOF 优缺点和选择
###### RDB
- 非常适合于备份以及灾难恢复的场景
- 能够最大化 Redis 性能
- 相对于 AOF,RDB 文件在 Redis 启动时能够更快加载
- 若期望将数据丢失的可能性最小化,RDB 并不适用
###### AOF
- 基于 “追加” 和 “文件同步” 的特性,AOF 具有更佳的 “持久化” 表现
- 对于相同的数据,AOF 文件大小通常将超过 RDB
- [x] 综合而言,如果能够承担一定程度的数据丢失风险,仅启用 RDB 持久化即可。但并不建议只启用 AOF 持久化,毕竟 RDB 文件更适合于数据备份。
- [x] 若 RDB 持久化和 AOF 持久化同时启用,Redis 启动时,将加载 AOF 文件,毕竟 AOF 具有更佳的 “持久化” 表现。
##### redis淘汰算法
- redis可以持久化,所以内存容量会越来越小
- 于是就有了淘汰算法
# maxmemory <bytes> //最大能存多大
# maxmemory-policy noeviction //有了上面那个就必有这个(最大内存策略);即超过了这个最大内存会怎么办
- 此时就引入了一个新的算法:LRU
###### LRU: 最近使用算法 很长时间不同的 进行淘汰 从redis删除
- redis中lru的策略机制:(每个key的使用都会有一个时间戳,越历史的时间的越容易被删除)
- ①:
```
# maxmemory-policy noeviction
//noeviction
表示没有设置过期的key虽然永久不过期但是还是要用,所以不要把这个key给删除
```
- ②:
# maxmemory-policy volatile-lru
//volatile-lru表示将会删除永久不过期的key
- 其他:
```
# volatile-lru -> remove the key with an expire set using an LRU algorithm
# allkeys-lru -> remove any key according to the LRU algorithm
# volatile-random -> remove a random key with an expire set
# allkeys-random -> remove a random key, any key
# volatile-ttl -> remove the key with the nearest expire time (minor TTL)
# noeviction -> don't expire at all, just return an error on write operations
1. volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
2. volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
3. volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
4. allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
5. allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
6. no-enviction(驱逐):禁止驱逐数据
```
#### 13. redis 集群 ...
- redis 3.0 之后才能支持集群;
- 在redis3.0出来之前要想搭集群是有一套“哨兵”机制:
![image](https://sxk.s3.didiyunapi.com/pic/redis/12.jpg)
###### redis2.0 +
- redis的“哨兵”机制与Nginx的“KeepAlive”心跳存活检测是相似的道理;检测到哪一个挂了就用另一个顶替上去
- “Sentinel”即哨兵系统是一套主从分离系统;从图中可以看到是有一个主要的server1在工作,下面的server234都是在复制server1的数据
- 当这个主服务器Server1挂掉后,server1就立刻下线,让server2来当这个Master服务器
- 缺点:Master服务器与Slave服务器一直都在保持一致的数据,导致数据重复数据浪费数据占据了大量的内存空间
###### redis3.0 +
![image](https://sxk.s3.didiyunapi.com/pic/redis/13.jpg)
- redis的集群一般都是6台(6/8/10)三主三从
- 安装Ruby环境
- 新建6个文件夹,分别在6个目录中新建6个配置文件例redis.6379.conf
- 配置内容:
```
不配置了,这就是一个鸡肋..现在没得用的
```
#### Redis “主 - 从” 机制
---
Redis 提供 “主 - 从” 的数据复制:“从” Redis 即作为 “主” Redis 的数据副本。“从” Redis,既能够用于读性能的扩展,亦能够作为数据备份的一种手段。
同时,Redis 支持 Redis Sentinel,实现 “主 - 从” 监控、故障迁移,限于篇幅,本文不予以展开。
工作机制
###### “主 - 从” 数据复制的基本工作机制
- 已建立的 “主” - “从” 连接,“主” Redis 不断地将命令发送到 “从” Redis
- 若连接中断(例如:网络问题),“从” Redis 将尝试重新建立连接,并尝试 “半 - 重新同步”
- 若无法进行 “半 - 重新同步”,“从” Redis 将尝试进行 “重新同步”(“主 - 从” 连接首次建立,亦执行 “重新同步”)
关于 “半 - 重新同步” & “重新同步”
- “半 - 重新同步”:“从” Redis 将尝试获取连接中断期间于 “主” Redis 执行的命令(存储于 backlog)
- “重新同步”
- “主” Redis 创建数据快照(RDB 文件)、同步到 “从” Redis,开始将 “主” Redis 执行的命令发送到 “从” Redis
- “从” Redis 丢弃当前数据,加载 “主” Redis 的 RDB 文件,开始执行 “主” Redis 发送的命令
“数据复制” 对于 “主” Redis 全部是异步的;对于 “从” Redis,大部分是异步的,但 “重新同步” 涉及 “丢弃当前数据,加载 RDB 文件”,将引起 “短暂中断”。
“主 - 从” 配置
```
slaveof master_ip master_port
# “从” Redis 配置:“主” Redis - IP & port
masterauth master_password # “从” Redis 配置:“主” Redis - 密码
slave-serve-stale-data yes
# “从” Redis 配置:当 “主 - 从” 连接中断或 “从” Redis 正在进行初始化同步,“从” Redis 是否提供服务:
# yes: 默认,以 “从” Redis 当前数据提供服务
# no: 对于接收到的命令,“从” Redis 返回 “SYNC in progress”(INFO、SLAVEOF命令除外)
slave-read-only yes
# “从” Redis 配置:是否 “只读”,默认 yes
“主” Redis 配置:根据已连接的 “从” Redis 情况,“主” Redis 是否接收 “写命令”
min-slaves-to-write 3
min-slaves-max-lag 10
表示:最少有 3 个已连接的 “从” Redis,且延迟小于等于 10 秒
min-slaves-to-write 3 # 默认 0,即无论 “从” Redis 的连接情况,始终接收 “写命令”
min-slaves-max-lag 10
```
以上的代码,仅列出了部分关键的配置。其他类似于:diskless 复制、backlog 配置,限于篇幅,未能列出,详情内容请参考 redis.conf for Redis 2.8。
“主 - 从” 命令
###### 1. SLAVEOF host port
将 Redis 配置作为 “从” Redis,其 “主” Redis 位置即为 host:port。
###### 2. SLAVEOF NO ONE
终止 “从” Redis 自 “主” Redis 的数据同步。
特别说明:SLAVEOF NO ONE 包含了 Redis
```
设计之初,关于 “自由” 的思想:“If slavery is not wrong, nothing is wrong. -- Abraham Lincoln”。
```
“主 - 从” 链
“从” Redis 能够作为其他 Redis的“主”Redis,由此构建级联结构的“主 - 从”链。并且,“主” Redis能够与多个“从”Redis建立连接,建立“树状”结构。
![image](https://s3.didiyunapi.com/sxk/pic/redis/17.png)
图中所示,对于扩展读性能,非常有益。
#### 优化 Redis 内存使用
合理的 Redis 实例,内存的占有量不应当超过 60%,当内存使用率过高时,应该予以清理及优化。
##### 使用 ziplist & intset
- ziplist 优化机制
- ziplist 实现了 “紧凑” 的数据结构,通过尽可能减少非数据节点的占用,以提供内存密度。
![image](https://s3.didiyunapi.com/sxk/pic/redis/18.png)
###### 图中所示,ziplist 整体结构:
• zl-bytes:整个 ziplist 占用内存的字节数
• zl-tail:ziplist 尾节点距离起始地址的字节数
• zl-len:ziplist 包含的节点数量
• entry:节点
• zl-end:ziplist 末端标记,固定 0xFF
ziplist 节点结构:
• previous-entry-length:前一个节点占用内存的字节数
• encoding:节点编码,明确节点存储内容属于 “字节数组” 或整数,并明确长度(即占用的字节数)
• content:节点存储内容
“散列表”、“链表”、“有序集合”,使用 ziplist,受益于其 “紧凑” 的数据结构,相较于 hashtable、linkedlist、skiplist,能够有效减少内存占用。
然而,受限于 “紧凑” 的数据结构,随着节点数量增长和节点大小膨胀,基于 ziplist 实现的 “散列表”、“链表”、“有序集合”,性能将显著下降。
intset 优化机制
intset 使用整型数组作为存储的数据结构。通常,hashtable 实现的 Redis 集合,其成员以 “字符串” 结构进行存储,intset 由此能够显著降低内存使用。
类似于 ziplist,同样受限于其整型数组,“集合” 成员数量的增长将引起 “集合” 性能的下降。
涉及配置
```
“散列表” 使用 ziplist 的限制条件:
- 成员数量不超过 hash-max-ziplist-entries
- 最大内存占用的成员,内存占用不超过 hash-max-ziplist-value (字节)
两者必须同时具备,任意条件不满足,即无法使用 ziplist
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
“链表” 使用 ziplist 的限制条件
list-max-ziplist-entries 512
list-max-ziplist-value 64
“有序集合” 使用 ziplist 的限制条件
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
“集合” 使用 intset 的限制条件:
- 成员全部为 64 位有符号整数
- 成员数量不超过 set-max-intset-entries
set-max-intset-entries 512
```
附加说明:ziplist & intset 的限制条件,是基于内存占用和性能的综合考虑。
数据分片
###### 分布式 “数据分片”
分布式 “数据分片”:选取合适的方式将 Redis 数据分布于不同的实例,由此降低单实例的内存使用,实现优化。请参考 “扩展 Redis 的容量和性能” 章节。
###### 单实例 “数据分片”
通常而言,单实例 “数据分片”,并不能直接降低 Redis 内存使用,需要结合 ziplist 等内存优化方式,以 “散列表” 为例:
• 以散列表的键作为 “数据分片” 的 “路由”,将单个内存占用量大的 “散列表” 分片到多个内存占有量小的 “散列表”
• 内存占有量小的 “散列表”(例如:“散列表” 成员数量少) 能够以 ziplist 方式减少内存占用
由此,有效地实现内存使用的优化。
基于业务进行优化
基于业务,通常能够取得良好的 Redis 内存优化效果,例如:
• 尽可能短的 Redis 键,例如:以 “u_178” 替代 “user_id_178”
• 选择合适的 Redis 数据结构,例如:合理地选择 “散列表” 替代 “字符串”,若 “字符串” 数量较少,使用一个 “散列表” 替代,通常能够减少内存使用
• 减少存储于 Redis 的业务数据量
#### 扩展 Redis 的容量和性能
Redis 作为任何单实例的数据服务,最终会遇到容量和性能瓶颈。前文阐述的 Redis “主 - 从”,即为常见且有效的扩展 Redis 读性能的方案。
本章节,主要关注基于 “数据分片” 构建 Redis 集群。常用的方案包括:Redis Cluster、twemproxy、Codis。
##### “数据分片” - 使用 Codis
###### 选择 Codis 的原因
Codis 来自于 “豌豆荚”,相对于 twemproxy,选择 Codis 的原因:
• twemproxy 无法实现动态水平扩展
• Codis 运行于多核机器能够获得更好的应用
相对于 Redis Cluster,选择 Codis 的原因:
• Redis Cluster 必须使用 Redis 3.0 以上版本的客户端
• Redis Cluster 无法支持 pipeline
##### Codis 架构
![image](https://s3.didiyunapi.com/sxk/pic/redis/19.png)
图中所示,Codis 架构中引入了 codis-proxy,由 codis-proxy 基于 Redis key 计算分片,将命令转发到 codis-group,因此:对于绝大多数的命令,客户端对于 Codis 的接入是透明的。
Codis 针对 Redis key 计算 CRC32,默认分为 1024 个 Slot,进而路由到特定的 codis-group,实现分片。
除了 “数据分片”,Codis 的特性还包括:
• 提供了 codis-fe & codis-dashborad 作为集群管理工具
• 允许多个 codis-proxy,实现 proxy 层的高可用
• codis-group 支持 “主 - 从”,引入 redis-sentinel 实现 “主 - 从” 故障迁移
必须说明的是:“数据分片” 扩展容量和性能的同时,亦限制了 Redis 若干方便的能力,例如:Codis 不支持事务、部分命令不支持。
#### 基于 Lua 的 Redis “存储过程”
Redis 内置了 Lua 解释器(Redis 2.6.0 起),允许使用 Lua(版本 5.1)进行服务器端脚本编程,即类似于 “存储过程” 相似。
##### Lua 服务器脚本编程涉及的 Redis 命令
###### 1. EVAL script numkeys key [key ...] arg [arg ...]
于 Redis 服务端上下文:
• 执行 script 脚本
• 命令参数 numkeys key [key ...] 即为 key 集合(numkeys 即为集合大小)
• 命令参数 arg [arg ...] 即为传递到脚本的附加参数集合。
特别说明:建议将脚本可能会读取或写入的 key 全部通过 EVAL 命令的 numkeys key [key ...] 参数传递(于 Redis 集群非常有益)。
###### 2. SCRIPT LOAD script
将 script 脚本载入到 Redis 服务端,并返回脚本的 SHA1 摘要。
###### 3. EVALSHA sha1 numkeys key [key ...] arg [arg ...]
与 EVAL 命令相似,通过 SCRIPT LOAD 命令获得的 sha1 明确需要执行的脚本。
##### Lua 与 Redis 交互
###### Lua 脚本获取 EVAL & EVALSHA 命令的参数
通过 Lua 脚本的全局变量 KEYS 和 ARGV,能够访问 EVAL 和 EVALSHA 命令的 key [key ...] 参数和 arg [arg ...] 参数。
作为 Lua Table,能够将 KEYS 和 ARGV 作为一维数组使用,其下标从 1 开始。
###### Lua 脚本内部执行 Redis 命令
Lua 脚本内部允许通过内置函数执行 Redis 命令:
• redis.call()
• redis.pcall()
函数的第 1 个参数即为 Redis 命令,命令的参数即为函数的其他参数。
两者非常相似,区别在于:若 Redis 命令执行错误,redis.call() 将错误抛出(即 EVAL & EVALSHA 执行出错);redis.pcall() 将错误内容返回。
##### Lua 与 Redis 数据类型
Redis 与 Lua 脚本的交互,必然涉及 Lua 和 Redis 的数据类型转换。限于篇幅,本文仅列出若干关键点。完整的数据类型转换请参阅 redis.io。
##### 1. Lua 提供 boolean 数据类型,Redis 无 boolean 数据类型
• “true” (Lua) 转换为 “整数 1” (Redis)
• “false” (Lua) 与 “nil” (Redis) 互相转换
##### 2. Lua 仅提供单一的数值类型 number,不区分浮点数和整数
• number (Lua) 转换为 “整数”(Redis),舍弃 number 的小数部分
• 若需要传递 “浮点数”,必须使用字符串
##### 3. Lua Table 与 Redis “multi bulk reply” 相互转换
若 Table (Lua) 的某一成员被转换为 nil (Redis),Table (Lua) 后续成员的转换即终止
###### 示例: “接口调用延时” 统计分析的组件
使用 Lua,除了能够简化代码、减少与 Redis 的交互,一个重要的收益:Lua 脚本、单个 Redis 命令、“MULTI / EXEC” 事务,都是原子操作。
《如何基于 Redis 构建应用程序组件》 阐述了 “接口调用延时” 统计分析的组件。本文章使用 Lua 脚本编程,重新实现 “接口调用延时” “上报” 接口。
```
获取 Redis 连接
def get_connection():
return redis.Redis.from_url('redis://127.0.0.1:6379/')
“接口调用延时” 数据上报 - Lua 脚本初始化
def init_report(conn = None):
if conn is None:
conn = get_connection()
return conn.script_load('''
local time_delay = ARGV[1]
local temp_key_for_max = KEYS[1]
local temp_key_for_min = KEYS[2]
local statistics_key_for_max = KEYS[3]
local statistics_key_for_min = KEYS[4]
local statistics_key_for_sum_count = KEYS[5]
redis.call('zadd', temp_key_for_max, time_delay, 'max')
redis.call('zunionstore', statistics_key_for_max, 2, statistics_key_for_max, temp_key_for_max, 'AGGREGATE', 'MAX')
redis.call('zadd', temp_key_for_min, time_delay, 'min')
redis.call('zunionstore', statistics_key_for_min, 2, statistics_key_for_min, temp_key_for_min, 'AGGREGATE', 'MIN')
redis.call('zincrby', statistics_key_for_sum_count, 1, 'count')
redis.call('zincrby', statistics_key_for_sum_count, time_delay, 'sum')
redis.call('del', temp_key_for_max, temp_key_for_min)
''')
```
“接口调用延时” 数据上报
```
def report(interface, time_delay, conn = None, function = None):
if conn is None:
conn = get_connection()
if function is None:
function = init_report(conn)
temp_key_for_max = str(uuid.uuid4())
temp_key_for_min = str(uuid.uuid4())
raw_key = 'statistics_%s_%d' % (interface, (int(time.time()) / 60))
statistics_key_for_max = '%s_for_max' % (raw_key, )
statistics_key_for_min = '%s_for_min' % (raw_key, )
statistics_key_for_sum_count = '%s_for_sum_count' % (raw_key, )
conn.evalsha(function, 5, temp_key_for_max, temp_key_for_min, statistics_key_for_max, statistics_key_for_min, statistics_key_for_sum_count, time_delay)
```
基于 Redis 特性,Lua 脚本执行过程中,Redis 不能接收其他命令,假如执行时间过长(超过 Redis 配置项 lua-time-limit),此时:
• 若 Lua 脚本没有进行 “写” 操作,能够通过 SCRIPT KILL 安全地终止 Lua 脚本;
• 否则,为了确保 Lua 脚本的 “原子性” ,仅允许通过 SHUTDOWN NOSAVE 强制终止 Redis 进程
---
##### Redis Cluster分区实现原理
- 槽(solt)概念
- [x] Redis Cluster中有一个16384长度的槽的概念,他们的编号为0、1 、2.....16382 、16383。这个槽是一个虚拟的槽,并不是真正存在的。正常工作的时候,Redis Cluster中每个Master节点都会负责一部分的槽,当有某个key被映射到某个Master负责的槽,那么这个Master负责为这个key提供服务,至于哪个Master节点负责哪个槽,这是可以由用户指定的,也可以在初始化的时候自动生成(redis-trib.rn脚本)。这里值得一提的是,在 Redis Cluster 中,只有Master才拥有槽的所有权,如果是某个Master的slave,这个slave只负责槽的使用,但是没有所有权。redis Cluster怎么知道哪些槽是由哪些节点负责的呢?某个Master又怎么知道某个槽自己是不是拥有呢?
- 位序列结构
- [x] Master节点维护着一个16384/8字节的位序列,Master节点用bit来标识对于某个槽自己是否拥有。比如对于编号为1的槽,Master只要判断序列的第二位(索引从0开始)是不是为1即可。如果在当下找不到key 返回给客户端
- 一致性哈希
- 之前的哈希算法是 HashCode%6=一个数;这个数就是一个key值,6表示有6台集群机器;如果某一台机器宕机了就是 HashCode%5=另一个数;这两个数完全不一样即Key值不一样;导致在各个片区上面存储的东西也有了变化;后来就衍生出了“一致性哈希”
![image](https://sxk.s3.didiyunapi.com/pic/redis/14.jpg)
###### 一致性哈希:将所有的key值映射到一个圆环上面去;对圆环分成几个片区,在这每一个片区上面又分布几个key值;这样key并没有完全分布在同一个地方,而且,如果其中的某一个片区(机器)挂掉后,这个片区上面的key值并不会消失而是将这些key值就近分配到左右周围片区上面去,与其他片区上面的key值融合在一起;
- 单调性(Monotonicity),单调性是指如果已经有一些请求通过哈希分派到了相应的服务器进行处理,又有新的服务器加入到系统中时候,应保证原有的请求可以被映射到原有的或者新的服务器中去,而不会被映射到原来的其它服务器上去。 这个通过上面新增服务器ip5可以证明,新增ip5后,原来被ip1处理的user6现在还是被ip1处理,原来被ip1处理的user5现在被新增的ip5处理。
- 分散性(Spread):分布式环境中,客户端请求时候可能不知道所有服务器的存在,可能只知道其中一部分服务器,在客户端看来他看到的部分服务器会形成一个完整的hash环。如果多个客户端都把部分服务器作为一个完整hash环,那么可能会导致,同一个用户的请求被路由到不同的服务器进行处理。这种情况显然是应该避免的,因为它不能保证同一个用户的请求落到同一个服务器。所谓分散性是指上述情况发生的严重程度。好的哈希算法应尽量避免尽量降低分散性。 一致性hash具有很低的分散性
- 平衡性(Balance):平衡性也就是说负载均衡,是指客户端hash后的请求应该能够分散到不同的服务器上去。一致性hash可以做到每个服务器都进行处理请求,但是不能保证每个服务器处理的请求的数量大致相同
![image](https://sxk.s3.didiyunapi.com/pic/redis/15.jpg)
redis 是单进程 单线程
![image](https://sxk.s3.didiyunapi.com/pic/redis/16.jpg)
###### Redis的并发竞争问题如何解决?
```
Redis为单进程单线程模式,采用队列模式将并发访问变为串行访问。Redis本身没有锁的概念,Redis对于多个客户端连接并不存在竞争,但是在Jedis客户端对Redis进行并发访问时会发生连接超时、数据转换错误、阻塞、客户端关闭连接等问题,这些问题均是由于客户端连接混乱造成。对此有2种解决方法:
1.客户端角度,为保证每个客户端间正常有序与Redis进行通信,对连接进行池化,同时对客户端读写Redis操作采用内部锁synchronized。
2.服务器角度,利用setnx实现锁。
注:对于第一种,需要应用程序自己处理资源的同步,可以使用的方法比较通俗,可以使用synchronized也可以使用lock;第二种需要用到Redis的setnx命令,但是需要注意一些问题。
```
###### Redis 大量数据插入
```
有些时候,Redis实例需要装载大量用户在短时间内产生的数据,数以百万计的keys需要被快速的创建,我们称之为大量数据插入(mass insertion)
方案:使用Luke协议
```
#### ++缓存世界中的三大问题及解决方案++
##### 1. 缓存穿透
###### 1. 当业务系统发起某一个查询请求时,首先判断缓存中是否有该数据;
###### 2. 如果缓存中存在,则直接返回数据;
###### 3. 如果缓存中不存在,则再查询数据库,然后返回数据。
##### 1.1 什么是缓存穿透?
业务系统要查询的数据根本就存在!当业务系统发起查询时,按照上述流程,首先会前往缓存中查询,由于缓存中不存在,然后再前往数据库中查询。由于该数据压根就不存在,因此数据库也返回空。这就是缓存穿透。
综上所述:业务系统访问压根就不存在的数据,就称为缓存穿透。
##### 1.2 缓存穿透的危害
如果存在海量请求查询压根就不存在的数据,那么这些海量请求都会落到数据库中,数据库压力剧增,可能会导致系统崩溃(你要知道,目前业务系统中最脆弱的就是IO,稍微来点压力它就会崩溃,所以我们要想种种办法保护它)。
##### 1.3 为什么会发生缓存穿透?
发生缓存穿透的原因有很多,一般为如下两种:
1. 恶意攻击,故意营造大量不存在的数据请求我们的服务,由于缓存中并不存在这些数据,因此海量请求均落在数据库中,从而可能会导致数据库崩溃。
2. 代码逻辑错误。这是程序员的锅,没啥好讲的,开发中一定要避免!
##### 1.4 缓存穿透的解决方案
下面来介绍两种防止缓存穿透的手段。
###### 1.4.1 缓存空数据
之所以发生缓存穿透,是因为缓存中没有存储这些空数据的key,导致这些请求全都打到数据库上。
那么,我们可以稍微修改一下业务系统的代码,将数据库查询结果为空的key也存储在缓存中。当后续又出现该key的查询请求时,缓存直接返回null,而无需查询数据库。
###### 1.4.2 BloomFilter
第二种避免缓存穿透的方式即为使用BloomFilter。
它需要在缓存之前再加一道屏障,里面存储目前数据库中存在的所有key,当业务系统有查询请求的时候,首先去BloomFilter中查询该key是否存在。若不存在,则说明数据库中也不存在该数据,因此缓存都不要查了,直接返回null。若存在,则继续执行后续的流程,先前往缓存中查询,缓存中没有的话再前往数据库中的查询。
###### 1.4.3 两种方案的比较
这两种方案都能解决缓存穿透的问题,但使用场景却各不相同。
对于一些恶意攻击,查询的key往往各不相同,而且数据贼多。此时,第一种方案就显得提襟见肘了。因为它需要存储所有空数据的key,而这些恶意攻击的key往往各不相同,而且同一个key往往只请求一次。因此即使缓存了这些空数据的key,由于不再使用第二次,因此也起不了保护数据库的作用。
因此,**对于空数据的key各不相同、*key重复请求概率低***的场景而言,应该选择第二种方案。而对于**空数据的key数量有限、*key重复请求概率较高***的场景而言,应该选择第一种方案。
##### 2. 缓存雪崩
###### 2.1 什么是缓存雪崩?
通过上文可知,缓存其实扮演了一个保护数据库的角色。它帮数据库抵挡大量的查询请求,从而避免脆弱的数据库受到伤害。
如果缓存因某种原因发生了宕机,那么原本被缓存抵挡的海量查询请求就会像疯狗一样涌向数据库。此时数据库如果抵挡不了这巨大的压力,它就会崩溃。
这就是缓存雪崩。
###### 2.2 如何避免缓存雪崩?
###### 2.2.1 使用缓存集群,保证缓存高可用
也就是在雪崩发生之前,做好预防手段,防止雪崩的发生。
###### 2.2.2 使用Hystrix
Hystrix是一款开源的“防雪崩工具”,它通过 熔断、降级、限流三个手段来降低雪崩发生后的损失。
Hystrix就是一个Java类库,它采用命令模式,每一项服务处理请求都有各自的处理器。所有的请求都要经过各自的处理器。处理器会记录当前服务的请求失败率。一旦发现当前服务的请求失败率达到预设的值,Hystrix将会拒绝随后该服务的所有请求,直接返回一个预设的结果。这就是所谓的“**熔断**”。当经过一段时间后,Hystrix会放行该服务的一部分请求,再次统计它的请求失败率。如果此时请求失败率符合预设值,则完全打开限流开关;如果请求失败率仍然很高,那么继续拒绝该服务的所有请求。这就是所谓的“**限流**”。而Hystrix向那些被拒绝的请求直接返回一个预设结果,被称为“**降级**”。
##### 3. 热点数据集中失效
###### 3.1 什么是热点数据集中失效?
我们一般都会给缓存设定一个失效时间,过了失效时间后,该数据库会被缓存直接删除,从而一定程度上保证数据的实时性。
但是,对于一些请求量极高的热点数据而言,一旦过了有效时间,此刻将会有大量请求落在数据库上,从而可能会导致数据库崩溃。
如果某一个热点数据失效,那么当再次有该数据的查询请求[req-1]时就会前往数据库查询。但是,从请求发往数据库,到该数据更新到缓存中的这段时间中,由于缓存中仍然没有该数据,因此这段时间内到达的查询请求都会落到数据库上,这将会对数据库造成巨大的压力。此外,当这些请求查询完成后,都会重复更新缓存。
##### 3.2 解决方案
###### 3.2.1 互斥锁
我们可以使用缓存自带的锁机制,当第一个数据库查询请求发起后,就将缓存中该数据上锁;此时到达缓存的其他查询请求将无法查询该字段,从而被阻塞等待;当第一个请求完成数据库查询,并将数据更新值缓存后,释放锁;此时其他被阻塞的查询请求将可以直接从缓存中查到该数据。
当某一个热点数据失效后,只有第一个数据库查询请求发往数据库,其余所有的查询请求均被阻塞,从而保护了数据库。但是,由于采用了互斥锁,其他请求将会阻塞等待,此时系统的吞吐量将会下降。这需要结合实际的业务考虑是否允许这么做。
互斥锁可以避免**某一个**热点数据失效导致数据库崩溃的问题,而在实际业务中,往往会存在一批热点数据同时失效的场景。那么,对于这种场景该如何防止数据库过载呢?
###### 3.3.2 设置不同的失效时间
当我们向缓存中存储这些数据的时候,可以将他们的缓存失效时间错开。这样能够避免同时失效。如:在一个基础时间上加/减一个随机数,从而将这些缓存的失效时间错开。
评论区