……

第一章、redis入门

1,redis是什么

redis是一种支持Key-Value等多种数据结构的存储系统。可用于缓存,事件发布或订阅,高速队列等场景。该数据库使用ANSI C语言编写,支持网络,提供字符串,哈希,列表,队列,集合结构直接存取,基于内存,可持久化。

 

2,支持的语言

img

 

3,redis的应用场景有哪些

1,会话缓存(最常用) 2,消息队列, 比如支付3,活动排行榜或计数 4,发布,订阅消息(消息通知) 5,商品列表,评论列表等

 

4,redis数据类型

*Redis*一共支持五种数据类:string(字符串),hash(哈希),list(列表),set(集合)和zset(sorted set有序集合)。

(1)字符串(字符串)

它是redis的最基本的数据类型,一个键对应一个值,需要注意是一个键值最大存储512MB。

img

(2)hash(哈希)

redis hash是一个键值对的集合,是一个string类型的field和value的映射表,适合用于存储对象

img

(3)表(列表)

是redis的简单的字符串列表,它按插入顺序排序

img

(4)组(集合)

是字符串类型的无序集合,也不可重复

img

(5)zset(sorted set有序集合)

是string类型的有序集合,也不可重复 有序集合中的每个元素都需要指定一个分数,根据分数对元素进行升序排序,如果多个元素有相同的分数,则以字典序进行升序排序,sorted set因此非常适合实现排名

img

 

5,redis的服务相关的命令

img

slect#选择数据库(数据库编号0-15) 退出#退出连接 信息#获得服务的信息与统计 monitor#实时监控 config get#获得服务配置 flushdb#删除当前选择的数据库中的key flushall#删除所有数据库中的键

 

6,redis的发布与订阅

redis的发布与订阅(发布/订阅)是它的一种消息通信模式,一方发送信息,一方接收信息。 下图是三个客户端同时订阅同一个频道

img

下图是有新信息发送给频道1时,就会将消息发送给订阅它的三个客户端 img

 

 

7,redis的持久化

redis持久有两种方式:快照(快照),仅附加文件(AOF)

快照(快照)

1,将存储在内存的数据以快照的方式写入二进制文件中,如默认dump.rdb中 2,保存900 1

#900秒内如果超过1个Key被修改,则启动快照保存 3,保存300 10

#300秒内如果超过10个Key被修改,则启动快照保存 4,保存60 10000

#60秒内如果超过10000个重点被修改,则启动快照保存

仅附加文件(AOF)

1,使用AOF持久时,服务会将每个收到的写命令通过写函数追加到文件中(appendonly.aof) 2,AOF持久化存储方式参数说明 appendonly yes

#开启AOF持久化存储方式 appendfsync always

#收到写命令后就立即写入磁盘,效率最差,效果最好 appendfsync everysec

#每秒写入磁盘一次,效率与效果居中 appendfsync no

#完全依赖操作系统,效率最佳,效果没法保证

 

8,redis的性能测试

自带相关测试工具

img

实际测试同时执行100万的请求

img

第二章、redis实战

1、redis简介

Redis 是一个开源的 使用 ANSI C 语言编写、支持网络、可基于内存亦可持久化的日志 型、Key-Value 数据库。

2、存储操作

redis的key-value需要特别说明下,key只能为字符串类型,但value可以为字符串,hash表,列表(list),集合(set),有序集合(sorted set)等多种数据类型,这也是redis与memcached的区别。memcached也是key-value数据库,但是key,value都只能是字符串类型。

1、redis的key

Redis 的 key 是字符串类型,但是 key 中不能包括边界字符 ,由于 key 不是 binary safe 的字符串,所以像"my key"和"mykey\n"这样包含空格和换行的 key 是不允许的。

2、key的相关操作
  • exits key检测指定key是否存在,返回1表示存在,0不存在

  • del key1key2…keyN删除给定key,返回删除key的数目,0表示给定key都不存在

  • type key 返回给定 key 值的类型。返回 none 表示 key 不存在,string 字符类型,list 链表 类型 set 无序集合类型…

  • keys pattern 返回匹配指定模式的所有 key。如:keys * 返回所有key

  • randomkey 返回从当前数据库中随机选择的一个 key,如果当前数据库是空的,返回空串 rename oldkey

  • newkey 重命名一个 key,如果 newkey 存在,将会被覆盖,返回 1 表示成功, 0 失败。可能是 oldkey 不存在或者和 newkey 相同。

  • renamenx oldkey newkey 同上,但是如果 newkey 存在返回失败。 expire key seconds 为 key 指定过期时间,单位是秒。返回 1 成功,0 表示 key 已经设置过过 期时间或者不存在。

  • ttl key 返回设置过过期时间key的剩余过期秒数。-1表示key不存在或者未设置过期时间。 select db-index 通过索引选择数据库,默认连接的数据库是 0,默认数据库数是 16 个。返回 1 表示成功,0 失败。

  • movekeydb-index将key从当前数据库移动到指定数据库。返回 1表示成功。0表示key 不存在或者已经在指定数据库中 。 key的大小最大为512MB。

3、字符串

string 是最基本的类型,而且 string 类型是二进制安全的。意思是 redis 的 string 可以包含任何数据。比如 jpg 图片或者序列化的对象。从内部实现来看其实 string 可以看作 byte 3数组,最大上限是 1G 字节。

4、String操作命令
  • set key value 设置 key 对应 string 类型的值,返回 1 表示成功,0 失败。

  • setnx key value 如果 key 不存在,设置 key 对应 string 类型的值。如果 key 已经存在,返 回0。

  • get key 获取 key 对应的 string 值,如果 key 不存在返回 nil

  • getset key value 先获取 key 的值,再设置 key 的值。如果 key 不存在返回 nil。

  • mget key1 key2 … keyN 一次获取多个 key 的值,如果对应 key 不存在,则对应返回 nil。

  • mset key1 value1 … keyN valueN 一次设置多个 key 的值,成功返回 1 表示所有的值都设置 了,失败返回 0 表示没有任何值被设置。

  • msetnx key1 value1 … keyN valueN 一次设置多个 key 的值,但是不会覆盖已经存在的 key

  • incr key 对 key 的值做++操作,并返回新的值。注意 incr 一个不是 int 的 value 会返回错误,incr一个不存在的 key,则设置 key 值为 1。

  • decr key 对 key 的值做–操作,decr 一个不存在 key,则设置 key 值为-1。

  • incrby key integer 对 key 加上指定值 ,key 不存在时候会设置 key,并认为原来的 value 是0。

  • decrby key integer 对key减去指定值。decrby完全是为了可读性,我们完全可以通过incrby 一个负值来实现同样效果,反之一样。 set说明

    - //设置值和获取值
    127.0.0.1:7000> set b 1234
    OK
    127.0.0.1:7000> get b
    "1234"

    //再次设置值,之前的值会被修改
    127.0.0.1:7000> set b 1dfff
    OK
    127.0.0.1:7000> get b
    "1dfff"

    //nx如果b已经存在,则直接返回失败,不存在则设置成功。
    127.0.0.1:7000> set b fsdf nx
    (nil)
    127.0.0.1:7000> get b
    "1dfff"

    //xx如果b存在,则设置成功,如果b不存在则设置失败,这相当于修改一个值。
    127.0.0.1:7000> set b ffff xx
    OK
    127.0.0.1:7000> get b
    "ffff"
    127.0.0.1:7000>

    //设置过期时间,多次设置与最后一次为准。相当于修改
    127.0.0.1:7000> set f ffff ex 10
    OK
    //获取过期还剩多少
    127.0.0.1:7000> ttl f
    (integer) 6
    127.0.0.1:7000> ttl f
    (integer) 3
    127.0.0.1:7000>

    原子操作 incr 如果遇到需要i++这类型的操作我们代码里面一般会如下:

伪代码如下:

int a= get key
a= a + 1
set key a;
这样是不安全的,因为多个线程同事操作时,会导致结果不一致。

上面分析了,i++ 操作,那么redis为我们提供了原子性的i++操作。

127.0.0.1:7000> set a 1
OK
127.0.0.1:7000> INCR a
(integer) 2
127.0.0.1:7000> incr a
(integer) 3
127.0.0.1:7000> get a
"3"

设置一个a=1,现在需要将a+1,那么调用incr a会自动为我们做++操作,decr a会自动做减减操作。incrby a 2 这个表示a直接加2操作,而incr为每次自动+1,incrby为指定加多少。

bitmap Redis从2.2.0版本开始新增了setbit,getbit,bitcount等几个bitmap相关命令。虽然是新命令,但是并没有新增新的数据类型,因为setbit等命令只不过是在set上的扩展,所以bigmap还是一种字符串类型

在一台2010MacBook Pro上,offset为232-1(分配512MB)需要~300ms,offset为230-1(分配128MB)需要~80ms,offset为228-1(分配32MB)需要~30ms,offset为226-1(分配8MB)需要8ms

指令 SETBIT key offset value 复杂度 O(1) 设置或者清空key的value(字符串)在offset处的bit值(只能只0或者1)。

offset的最大值为10亿,2的23-1方

5、hash(散列)

hash是一个string类型的field和value的映射表。添加,删除操作都是O(1)(平均)。hash特别适合用于存储对象。相对于将对象的每个字段存成单个string类型。将一个对象存储在hash类型中会占用更少的内存,并且可以更方便的存取整个对象。省内存的原因是新建一个hash对象时开始是用zipmap(又称为small hash)来存储的。这个zipmap其实并不是hash table,但是zipmap相比正常的hash实现可以节省不少hash本身需要的一些元数据存储开销。尽管zipmap的添加,删除,查找都是O(n),但是由于一般对象的field数量都不太多,所以使用zipmap也是很快的,也就是说添加删除平均还是O(1)。如果field或者value的大小超出一定限制后,redis会在内部自动将zipmap替换成正常的hash实现.这个限制可以在配置文件中指定。 hash-max-zipmap-entries64#配置字段最多64个 hash-max-zipmap-value512#配置value最大为512字节

6、hash 类型数据操作指令简介
  • hset key field value 设置 hash field 为指定值,如果 key 不存在,则创建

  • hget key field 获取指定的hash field。

  • hmget key filed1…fieldN 获取全部指定的 hash filed。

  • hmset key filed1 value1 … filedN valueN 同时设置 hash 的多个 field。

  • hincrby key field integer 将指定的 hash filed 加上指定值。成功返回 hash filed 变更后的 值。

  • hexists key field 检测指定 field 是否存在。

  • hdel key field 删除指定的 hash field。

  • hlen key 返回指定 hash 的 field 数量。

  • hkeys key 返回 hash 的所有 field。 hvals key 返回 hash 的所有 value。 hgetall 返回hash的所有filed和value

//使用hash来存储数据。user是key,由于value是hash,所以还有一个key,value健值对。
127.0.0.1:7000> hset user id 1
(integer) 1
127.0.0.1:7000> hset user userName xiaobao
(integer) 1
127.0.0.1:7000> hset user password ssssss
(integer) 1
//获取key为user的hash表中的id的值。
127.0.0.1:7000> hget user id
"1"
//获取hash表中的所有key
127.0.0.1:7000> hkeys user
1) "id"
2) "userName"
3) "password"
//获取hash表中所有的value
127.0.0.1:7000> HVALS user
1) "1"
2) "xiaobao"
3) "ssssss"
127.0.0.1:7000>
7、list(列表)

list是一个链表结构,可以理解为每个子元素都是string类型的双向链表。主要功能是push、pop获取一个范围的所有值等。操作中 key理解为链表的名字。

8、List 类型数据操作指令简介

lpush key string 在key对应list的头部添加字符串元素,返回 1表示成功,0表示key存 在且不是 list 类型。 rpush key string 在 key 对应 list 的尾部添加字符串元素。 llen key 返回 key 对应 list 的长度,如果 key 不存在返回 0,如果 key 对应类型不是 list 返回错误。 lrange key start end 返回指定区间内的元素,下标从 0 开始,负值表示从后面计算,-1 表示 倒数第一个元素 ,key 不存在返回空列表。 ltrimkeystartend 截取list指定区间内元素,成功返回1,key不存在返回错误。 lset key index value 设置 list 中指定下标的元素值,成功返回 1,key 或者下标不存在返回 错误。 lremkeycountvalue从 List 的头部(count正数)或尾部(count负数)删除一定数量(count) 匹配 value 的元素,返回删除的元素数量。count为0时候删除全部。 lpop key 从list的头部删除并返回删除元素。如果key对应list不存在或者是空返回nil, 如果 key 对应值不是 list 返回错误。 rpop key从list的尾部删除并返回删除元素。 blpop key1 … keyN timeout 从左到右扫描,返回对第一个非空 list 进行 lpop 操作并返回, 比如 blpop list1 list2 list3 0 ,如果 list 不存在 list2,list3 都是非空则对 list2 做 lpop 并返回从 list2 中删除的元素。如果所有的 list 都是空或不存在,则会阻塞 timeout 秒,timeout为0表示一直阻塞。当阻塞时,如果有 client对key1…keyN中的任意key 进行 push 操作,则第一在这个 key 上被阻塞的 client 会立即返回。如果超时发生,则返回 nil。有点像 unix 的 select 或者 poll。 brpop 同blpop,一个是从头部删除一个是从尾部删除。

//从key为list的左边push一个1.
127.0.0.1:7000> lpush list 1
(integer) 1
127.0.0.1:7000> lpush list 2
(integer) 2
//查看list队列长度
127.0.0.1:7000> llen list
(integer) 2

//从右边连续push 3 4 5 6
127.0.0.1:7000> rpush list 3 4 5 6
(integer) 6
//返回列表从0到6个元素
127.0.0.1:7000> LRANGE list 0 6
1) "2"
2) "1"
3) "3"
4) "4"
5) "5"
6) "6"
//从队列头弹出值并删除
127.0.0.1:7000> lpop list
"2"
127.0.0.1:7000> lpop list
"1"
127.0.0.1:7000> lpop list
"3"
127.0.0.1:7000> lpop list
"4"
整个集合就是一个入栈出栈的操作,记住集合它是一个双向链表就OK了。
9、set(集合)

set是无序集合,最大可以包含(2 的 32 次方-1)个元素,set 是不能有重复元素,如果存入重复元素不会报错,但是set里面还是只有一个。 set 的是通过 hash table 实现的, 所以添加,删除,查找的复杂度都是 O(1)。 hash table 会随着添加或者删除自动的调整大小,需要注意的是调整hash table大小时候需要同步(获取写锁)会阻塞其他读写操作,可能不久后就会改用跳表(skip list)来实现。跳表已经在sorted sets 中使用了。关于set集合类型除了基本的添加删除操作,其它有用的操作还包含集合的取并集 (union),交集(intersection), 差集(difference)。通过这些操作可以很容易的实现 SNS 中的好友推荐和 blog 的 tag 功能。

10、set 类型数据操作指令简介

sadd key member 添加一个 string 元素到 key 对应 set 集合中,成功返回1,如果元素以及 在集合中则返回 0,key 对应的 set 不存在则返回错误。 srem key member 从 key 对应 set 中移除指定元素,成功返回 1,如果 member 在集合中不 存在或者 key 不存在返回 0,如果 key 对应的不是 set 类型的值返回错误。 spop key count 删除并返回key对应set中随机的一个元素,如果set是空或者key不存在返回nil。 srandmember key count 同spop,随机取set中的一个元素,但是不删除元素。 smembers key 返回key对应set的所有元素,结果是无序的。 smove srckey dstkey member 从srckey对应set中移除member并添加到dstkey对应set中,整个操作是原子的。成功返回 1,如果 member 在 srckey 中不存在返回 0,如果 key 不是 set 类型返回错误。 scard key返回set的元素个数,如果set是空或者key不存在返回 0。 sismember key member 判断member是否在set中,存在返回1,0表示不存在或者key不存在。 sinter key1 key2 … keyN 返回所有给定 key 的交集。 sinterstore dstkey key1 … keyN 返回所有给定 key 的交集,并保存交集存到 dstkey 下。 sunion key1 key2 … keyN 返回所有给定 key 的并集。 sunionstore dstkey key1 … keyN 返回所有给定 key 的并集,并保存并集到 dstkey 下。 sdiff key1 key2 … keyN 返回所有给定 key 的差集。 sdiffstore dstkey key1 … keyN 返回所有给定 key 的差集,并保存差集到 dstkey 下。

//存入元素
127.0.0.1:7000> sadd set 1
(integer) 1
127.0.0.1:7000> sadd set 2
(integer) 1
//1已经存在了,再存入1则返回0了。
127.0.0.1:7000> sadd set 1
(integer) 0
//弹出key为set的总的2个元素,弹出后删除元素 
127.0.0.1:7000> spop set 2
1) "1"
2) "2"
//没有元素的情况下返回空
127.0.0.1:7000> spop set 1
(empty list or set)

127.0.0.1:7000> sadd set 1 2 3 4 5 6 6 6
(integer) 6
//随机弹出10个元素,不删除
127.0.0.1:7000> srandmember set 10
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
6) "6"
127.0.0.1:7000> srandmember set 1
1) "6"

//返回set的所有元素,无序的
127.0.0.1:7000> smembers set3
1) "2"
2) "3"
3) "5"
127.0.0.1:7000> srandmember set 10
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
6) "6"
127.0.0.1:7000> sadd set1 2 3 5 10 20
(integer) 5
//求set,set2两个key的并集
127.0.0.1:7000> sunion set set1
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
6) "6"
7) "10"
8) "20"

//求set set1的交集
127.0.0.1:7000> sinter set set1
1) "2"
2) "3"
3) "5"

//求两个交集,返回的结果存储到set3里面
127.0.0.1:7000> sinterstore set3 set set1
(integer) 3
127.0.0.1:7000> SRANDMEMBER set3 10
1) "2"
2) "3"
3) "5"
11、有序集合

sorted set 是有序集合,它在 set 的基础上增加了一个顺序属性,这一属性在添加修 改元素的时候可以指定 ,每次指定后,会自动重新按新的值调整顺序 。可以理解了有两列 的 mysql 表,一列存 value,一列存顺序。操作中key理解为sorted set的名字。

12、Sorted Set 类型数据操作指令简介

zadd key score member 添加元素到集合,元素在集合中存在则更新对应 score。 zrem key member 删除指定元素,1 表示成功,如果元素不存在返回 0。 zincrby key incr member 增加对应 member 的 score 值,然后移动元素并保持 skip list 保持有 序。返回更新后的 score 值。 zrank key member 返回指定元素在集合中的排名(下标 ),集合中元素是按 score 从小到大 排序的。 zrevrank key member 同上,但是集合中元素是按 score 从大到小排序。 zrange key start end 类似 lrange 操作从集合中去指定区间的元素。返回的是有序结果 zrevrange key start end 同上,返回结果是按 score 逆序的。 zrangebyscore key min max 返回集合中 score 在给定区间的元素。 zcount key min max 返回集合中 score 在给定区间的数量。 zcard key 返回集合中元素个数。 zscore key element 返回给定元素对应的 score。 zremrangebyrank key min max 删除集合中排名在给定区间的元素 。 zremrangebyscore key min max 删除集合中 score 在给定区间的元素。

//存储一个有序set,sortset为key,1表示分数,a是我们的值
127.0.0.1:7000> zadd sortset 1 a
(integer) 1
127.0.0.1:7000> zadd sortset 2 b
(integer) 1
//查看b的下标
127.0.0.1:7000> zrank sortset b
(integer) 1

//返回0,1个元素
127.0.0.1:7000> zrange sortset 0 1
1) "a"
2) "b"
3、redis运维
1、统计生产上比较大的key
./redis-cli -p 7000 --bigkeys
2、查看key的详细信息

//查看key为d的详细信息,查看key的详细信息(从这里可以看出key占用的存储空间挺大的,需要存储很多信息)

127.0.0.1:7000> DEBUG OBJECT d
Value at:0x7fb7cf526240 refcount:1 encoding:embstr serializedlength:5 lru:6748523 lru_seconds_idle:537
3、分页查看redis中的key
//查看key
127.0.0.1:7000> SCAN 0
1) "17"
2) 1) "d"
   2) "kye"

127.0.0.1:7000> SCAN 17
1) "0"
2) 1) "dfff"
   2) "ff" 
   3) "ddd"  

第一次迭代使用 0 作为游标, 表示开始一次新的迭代。 第二次迭代使用的是第一次迭代时返回的游标, 也即是命令回复第一个元素的值17. 从上面的示例可以看到, SCAN 命令的回复是一个包含两个元素的数组, 第一个数组元素是用于进行下一次迭代的新游标, 而第二个数组元素则是一个数组, 这个数组中包含了所有被迭代的元素。 在第二次调用 SCAN 命令时, 命令返回了游标 0 , 这表示迭代已经结束, 整个数据集(collection)已经被完整遍历过了。

以 0 作为游标开始一次新的迭代, 一直调用 SCAN 命令, 直到命令返回游标 0 , 我们称这个过程为一次完整遍历(full iteration)。

4、模糊查找key
127.0.0.1:7000> scan 0 match t*
1) "0"
2) 1) "t"
   2) "ttt"

127.0.0.1:7000> scan 0 match t* count 2
1) "3"
2) 1) "t"   

scan 0 match t* 与keys t* 还是有差别的,因为scan 是带分页的,而keys 是没有分页的,遍历了所有的key。

在线上不建议使用keys来查询key。

5、性能查询
redis-benchmark -n 10000
6、密码登陆

链接后没有权限操作:(error) NOAUTH Authentication required

redis-cli -h 127.0.0.1 -p 70000 -a password
如果忘记输入密码,可以在进入的时候使用
auth password
4、redis缓存淘汰策略

redis淘汰策略配置,在redis.conf文件查看:maxmemory-policy voltile-lru 支持热配置

1、redis 提供 6种数据淘汰策略:
  • voltile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰

  • volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰

  • volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰

  • allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰

  • allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰

  • no-enviction(驱逐):禁止驱逐数据(默认策略) 选择策略规则

如果数据呈现幂律分布,也就是一部分数据访问频率高,一部分数据访问频率低,则使用allkeys-lru 如果数据呈现平等分布,也就是所有的数据访问频率都相同,则使用allkeys-random volatile-lru策略和volatile-random策略适合我们将一个Redis实例既应用于缓存和又应用于持久化存储的时候,然而我们也可以通过使用两个Redis实例来达到相同的效果, 将key设置过期时间实际上会消耗更多的内存,因此我们建议使用allkeys-lru策略从而更有效率的使用内存

5、主从,哨兵模式
1、主从设置

进入到将要设置的从的客户端或者在配置文件里面下入

$ redis-cli -p 7000
127.0.0.1:7000> slaveof 192.168.110.101 6379

即可将当前7000这个redis服务设置为一个从服务。

2、取消从服务
127.0.0.1:7000> SLAVEOF no one

redis设置了主从服务器后,从会自动备份主的数据,但是从服务器对外提供只能读,不能写。

3、sentinel

哨兵是分布式应用,可以启动多个,指定不同端口即可。哨兵与哨兵之间不需要互相配置,哨兵会根据自己监控的主服务做判断,如果是监控的是同一个主服务,则他们会互相自动建立连接,互相通信。

哨兵配置监控时只需要配置master地址即可,但是不代表它不监控从节点。sentinel连接上主节点后,会自动从主节点获取到该节点的从节点信息然后进行监控。

对于redis-sentinel 程序, 你可以用以下命令来启动 Sentinel 系统:

redis-sentinel /path/to/sentinel.conf

对于 redis-server 程序, 你可以用以下命令来启动一个运行在 Sentinel 模式下的 Redis 服务器:

redis-server /path/to/sentinel.conf --sentinel

sentinel.conf配置如下:

sentinel monitor mymaster 127.0.0.1 7001 2
sentinel config-epoch mymaster 1
port 17000

创建了一个监控monitor, 自定义一个名称为mymaster,redis主服务器的IP地址,端口号,需要投票的数量为2. 设置哨兵的端口为17000 sentinel操作流程:

如果主服务器挂了,哨兵在指定的时间内检测到,会在从服务器中选一个出来做为主节点,然后把其他所有节点全部设置为从节点。 然后在把之前挂掉的主给从sentinel中剔除去。 注意:

第一行最后面的那个数字为2,需要至少启动2个sentinel,才能完成投票,如果只启动一个sentinel,但是配置为2,当主redis挂掉的时候,sentinel会发起投票,因为只有一个sentinel,也就只有一票,这个时候sentinel就不能做主从切换了。 对于客户端来说,是不能直接连接sentinel只能连接主或者从。 Java客户端连接sentinel对redis操作。

@Configuration
@EnableConfigurationProperties(RedisProperties.class)
public class RedisConfig {

    @Autowired
    private RedisProperties redisProperties;
    
    @Bean
    public RedisConnectionFactory jedisConnectionFactory() {
        RedisSentinelConfiguration sentinelConfig = new RedisSentinelConfiguration();
        sentinelConfig.master(redisProperties.getSentinel().getMaster());
        sentinelConfig.setSentinels(createSentinels(redisProperties.getSentinel()));
        JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(sentinelConfig);
        jedisConnectionFactory.setPassword(redisProperties.getPassword());
        return jedisConnectionFactory;
    }
    
    @Bean
    @DependsOn("jedisConnectionFactory")
    public StringRedisTemplate redisTemplate(JedisConnectionFactory jedisConnectionFactory) {
        return new StringRedisTemplate(jedisConnectionFactory);
    }
    
    private List<RedisNode> createSentinels(RedisProperties.Sentinel sentinel) {
        List<RedisNode> nodes = new ArrayList();
        for (String node : StringUtils
                .commaDelimitedListToStringArray(sentinel.getNodes())) {
            try {
                String[] parts = StringUtils.split(node, ":");
                Assert.state(parts.length == 2, "Must be defined as 'host:port'");
                nodes.add(new RedisNode(parts[0], Integer.valueOf(parts[1])));
            } catch (RuntimeException ex) {
                throw new IllegalStateException(
                        "Invalid redis sentinel " + "property '" + node + "'", ex);
            }
        }
        return nodes;
    }

}
可以看出来,操作sentinel时,不是直接操作sentinel的,而是通过sentinel找到主服务的IP地址和端口,然后再根据IP地址和端口去连接reids。
6、持久化

通常 Redis 将数据存储在内存中或虚拟内存中,它是通过以下两种方式实现对数据的持久化。 持久化即使存储到硬盘上,如果存储到内存中,电脑重启后内存就会被清空。

1、RDB(快照模式)

这种方式就是将内存中数据以快照的方式写入到二进制文件中,默认的文件名为dump.rdb。 客户端也可以使用save或者bgsave命令通知redis做一次快照持久化。save操作是在主线程中保存快照的,由于redis是用一个主线程来处理所有客户端的请求,这种方式会阻塞所有客户端请求,所以不推荐使用,bgsave是fork一个子进程来做的,就是后台执行,不阻塞执行。另一点需要注意的是,每次快照持久化都是将内存数据完整写入到磁盘一次,并不是增量的只同步增量数据。如果数据量大的话,写操作会比较多,必然会引起大量的磁盘IO操作,可能会严重影响性能。

redis会自动备份,而不需要我们自己手动的去调用save来保存备份。 通过配置文件可以看出:vi /usr/local/etc/redis.conf

save 900 1 //900秒里面发生1次操作则执行一次save。
save 300 10 //300秒了发生了10次操作则执行一次save
save 60 10000 //60秒里面发生了1万次操作则执行一次save。

注意:由于快照方式是在一定间隔时间做一次的,所以如果 redis意外当机的话,就会丢失最后一次快照后的所有数据修改。

2、AOF(日志追加)

这种方式redis会将每一个收到的写命令都通过write函数追加到文件中(默认appendonly.aof)。当redis重启时会通过重新执行文件中保存的写命令来在内存中重建整个数据库的内容。当然由于操作系统会在内核中缓存write做的修改,所以可能不是立即写到磁盘上。这样的持久化还是有可能会丢失部分修改。不过我们可以通过配置文件告诉redis我们想要通过fsync函数强制操作系统写入到磁盘的时机。有三种方式如下(默认是:每秒fsync一次)

appendonly yes //启用日志追加持久化方式
//appendfsync always //每次收到写命令就立即强制写入磁盘,最慢的,但是保证完全的持久化,不推荐使用
appendfsync everysec //每秒钟强制写入磁盘一次,在性能和持久化方面做了很好的折中,推荐
//appendfsync no //完全依赖操作系统,性能最好,持久化没保证
3、AOF重写

AOF会自动优化文件。 比如:

set key aaa set key bbb set key ddd 上面三个操作最终库里面数据为:key=ddd,那就简单了额,AOF会只需要存储set key ddd命令就OK了,其他两条命令可以忽略了,这样就可以达到优化存储的效果。

AOF 重写和 RDB 创建快照一样,都巧妙地利用了写时复制机制。 以下是 AOF 重写的执行步骤:

Redis 执行 fork() ,现在同时拥有父进程和子进程。 子进程开始将新 AOF 文件的内容写入到临时文件。 对于所有新执行的写入命令,父进程一边将它们累积到一个内存缓存中,一边将这些改动追加到现有 AOF 文件的末尾: 这样即使在重写的中途发生停机,现有的 AOF 文件也还是安全的。 当子进程完成重写工作时,它给父进程发送一个信号,父进程在接收到信号之后,将内存缓存中的所有数据追加到新 AOF 文件的末尾。 现在 Redis 原子地用新文件替换旧文件,之后所有命令都会直接追加到新 AOF 文件的末尾。

4、恢复模式

数据恢复是内存数据被清空了,需要从硬盘中的备份文件重新加载到内存中。 在电脑重启后,如果两种持久化模式都开启了,则会优先从AOF中恢复数据,然后才是RDB模式。

5、两种方式的优缺点

RDB优点:

  1. RDB 可以最大化 Redis 的性能:父进程在保存 RDB 文件时唯一要做的就是 fork 出一个子进程,然后这个子进程就会处理接下来的所有保存工作,父进程无须执行任何磁盘 I/O 操作。

  2. RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。

  3. RDB 非常适用于灾难恢复(disaster recovery):它只有一个文件,并且内容都非常紧凑,可以(在加密后)将它传送到别的数据中心 RDB缺点:

  4. 如果你需要尽量避免在服务器故障时丢失数据,那么 RDB 不适合你。 虽然 Redis 允许你设置不同的保存点(save point)来控制保存 RDB 文件的频率, 但是, 因为RDB 文件需要保存整个数据集的状态, 所以它并不是一个轻松的操作。 因此你可能会至少 5 分钟才保存一次 RDB 文件。 在这种情况下, 一旦发生故障停机, 你就可能会丢失好几分钟的数据。

  5. 每次保存 RDB 的时候,Redis 都要 fork() 出一个子进程,并由子进程来进行实际的持久化工作。 在数据集比较庞大时, fork() 可能会非常耗时,造成服务器在某某毫秒内停止处理客户端; 如果数据集非常巨大,并且 CPU 时间非常紧张的话,那么这种停止时间甚至可能会长达整整一秒。 虽然 AOF 重写也需要进行 fork() ,但无论 AOF 重写的执行间隔有多长,数据的耐久性都不会有任何损失。 AOF优点

  6. 使用 AOF 持久化会让 Redis 变得非常耐久(much more durable):你可以设置不同的 fsync 策略,比如无 fsync ,每秒钟一次 fsync ,或者每次执行写入命令时 fsync 。 AOF 的默认策略为每秒钟 fsync 一次,在这种配置下,Redis 仍然可以保持良好的性能,并且就算发生故障停机,也最多只会丢失一秒钟的数据( fsync 会在后台线程执行,所以主线程可以继续努力地处理命令请求)。

  7. AOF 文件是一个只进行追加操作的日志文件(append only log), 因此对 AOF 文件的写入不需要进行 seek , 即使日志因为某些原因而包含了未写入完整的命令(比如写入时磁盘已满,写入中途停机,等等), redis-check-aof 工具也可以轻易地修复这种问题。

  8. Redis 可以在 AOF 文件体积变得过大时,自动地在后台对 AOF 进行重写: 重写后的新 AOF 文件包含了恢复当前数据集所需的最小命令集合。 整个重写操作是绝对安全的,因为 Redis 在创建新 AOF 文件的过程中,会继续将命令追加到现有的 AOF 文件里面,即使重写过程中发生停机,现有的 AOF 文件也不会丢失。 而一旦新 AOF 文件创建完毕,Redis 就会从旧 AOF 文件切换到新 AOF 文件,并开始对新 AOF 文件进行追加操作

  9. AOF 文件有序地保存了对数据库执行的所有写入操作, 这些写入操作以 Redis 协议的格式保存, 因此 AOF 文件的内容非常容易被人读懂, 对文件进行分析(parse)也很轻松。 导出(export) AOF 文件也非常简单: 举个例子, 如果你不小心执行了 FLUSHALL 命令, 但只要 AOF 文件未被重写, 那么只要停止服务器, 移除 AOF 文件末尾的 FLUSHALL 命令, 并重启 Redis , 就可以将数据集恢复到 FLUSHALL 执行之前的状态。 AOF缺点

  10. 对于相同的数据集来说,AOF 文件的体积通常要大于 RDB 文件的体积。

  11. 根据所使用的 fsync 策略,AOF 的速度可能会慢于 RDB 。 在一般情况下, 每秒 fsync 的性能依然非常高, 而关闭 fsync 可以让 AOF 的速度和 RDB 一样快, 即使在高负荷之下也是如此。 不过在处理巨大的写入载入时,RDB 可以提供更有保证的最大延迟时间(latency)。

  12. AOF 在过去曾经发生过这样的 bug : 因为个别命令的原因,导致 AOF 文件在重新载入时,无法将数据集恢复成保存时的原样。 (举个例子,阻塞命令 BRPOPLPUSH 就曾经引起过这样的 bug 。) 测试套件里为这种情况添加了测试: 它们会自动生成随机的、复杂的数据集, 并通过重新载入这些数据来确保一切正常。 虽然这种 bug 在 AOF 文件中并不常见, 但是对比来说, RDB 几乎是不可能出现这种 bug 的。

7、事务

Redis 事务可以一次执行多个命令, 并且带有以下两个重要的保证:

  • 批量操作在发送 EXEC 命令前被放入队列缓存。

  • 收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行。

  • 在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中。 一个事务从开始到执行会经历以下三个阶段:

  • 开始事务。

  • 命令入队。

  • 执行事务。

redis 127.0.0.1:6379> MULTI
OK

redis 127.0.0.1:6379> SET book-name "Mastering C++ in 21 days"
QUEUED

redis 127.0.0.1:6379> GET book-name
QUEUED

redis 127.0.0.1:6379> SADD tag "C++" "Programming" "Mastering Series"
QUEUED

redis 127.0.0.1:6379> SMEMBERS tag
QUEUED

redis 127.0.0.1:6379> EXEC
1) OK
2) "Mastering C++ in 21 days"
3) (integer) 3
4) 1) "Mastering Series"
   2) "C++"
   3) "Programming"

DISCARD :取消事务,放弃执行事务块内的所有命令。

8、redis keyspace键通知

keyspace有啥用? 请看下面回答

  1. 设置了生存时间的Key,在过期时能不能给一个通知?

  2. 如何使用 Redis 来实现定时任务?

  3. key监听事件,key的删除,添加.之类等等.

    在 Redis 里面有一些事件,比如键到期、键被删除等。然后我们可以通过配置一些东西来让 Redis 一旦触发这些事件的时候就往特定的 Channel 推一条消息。 大致的流程就是我们给 Redis 的某一个 db 设置过期事件,使其键一旦过期就会往特定频道推消息,我在自己的客户端这边就一直消费这个频道就好了。 以后一来一条定时任务,我们就把这个任务状态压缩成一个键,并且过期时间为距这个任务执行的时间差。那么当键一旦到期,就到了任务该执行的时间,Redis 自然会把过期消息推去,我们的客户端就能接收到了。这样一来就起到了定时任务的作用。

第一步:需要开启事件通知

开启所有的事件
redis-cli> config set notify-keyspace-events KEA

开启keyspace Events
redis-cli> config set notify-keyspace-events KA

开启keyspace 所有List 操作的 Events
redis-cli> config set notify-keyspace-events Kl

redis-cli> config set notify-keyspace-events Ex  // 其中Ex表示键事件通知里面的key过期事件,每当有过期键被删除时,会发送通知

或者在redis.conf配置文件中,找到notify-keyspace-events “”表示什么都不做,后面加上值即为开启.修改后需要重启redis服务

注意:默认情况下config在生产环境是不开启的,所以在生产上使用config会提示不存在的问题. config在redis.conf配置文件里面,去掉config前面的注解就可以使用了.

1589167101871 第二步:订阅通知事件

127.0.0.1:7000> psubscribe __keyevent@0__:expired
1

0表示第0个数据库,expired表示订阅过期事件. keyevent@0:expired 整个值为过期事件的topic. 订阅到的数据只有key,没有value哦.

9、集群

Redis 集群是一个提供在多个Redis间节点间共享数据的程序集。 Redis集群并不支持处理多个keys的命令,因为这需要在不同的节点间移动数据,从而达不到像Redis那样的性能,在高负载的情况下可能会导致不可预料的错误.

Redis 集群通过分区来提供一定程度的可用性,在实际环境中当某个节点宕机或者不可达的情况下继续处理命令. Redis 集群的优势:

  • 自动分割数据到不同的节点上。

  • 整个集群的部分节点失败或者不可达的情况下能够继续处理命令。

    10、Redis 集群的数据分片

    Redis 集群没有使用一致性hash, 而是引入了哈希槽的概念.

Redis 集群有16384个(2的14次方)哈希槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽.集群的每个节点负责一部分hash槽,举个例子,比如当前集群有3个节点,那么:

  • 节点 A 包含 0 到 5500号哈希槽.

  • 节点 B 包含5501 到 11000 号哈希槽.

  • 节点 C 包含11001 到 16384号哈希槽. 这种结构很容易添加或者删除节点. 比如如果我想新添加个节点D, 我需要从节点 A, B, C中得部分槽到D上. 如果我像移除节点A,需要将A中得槽移到B和C节点上,然后将没有任何槽的A节点从集群中移除即可. 由于从一个节点将哈希槽移动到另一个节点并不会停止服务,所以无论添加删除或者改变某个节点的哈希槽的数量都不会造成集群不可用的状态. 为了使得集群在一部分节点下线或者无法与集群的大多数(majority)节点进行通讯的情况下, 仍然可以正常运作, Redis 集群对节点使用了主从复制功能: 集群中的每个节点都有 1 个至 N 个复制品(replica), 其中一个复制品为主节点(master), 而其余的 N-1 个复制品为从节点(slave)。

注意:节点 A 、B 、C 的例子中, 如果节点 B 下线了, 那么集群将无法正常运行, 因为集群找不到节点来处理 5501 号至 11000号的哈希槽。A,C节点也将不可用了哦。 为了解决上面说的B下线了, 导致整个集群不可以用了,我们需要使用集群的另外一个功能,主从复制功能。 为A,B,C,添加一个或者多个从节点A1,B1,C1,这样B节点挂了,B1节点会自动升级为主节点,接替B的工作,为集群工作。这个很像哨兵模式,其实就是哨兵模式。 在redis的集群模块中,添加了哨兵模式,主节点挂了从节点会自动切换为主节点。

整个集群模式如图:

img

架构细节:

  1. 所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽.

  2. 节点的fail是通过集群中超过半数的节点检测失效时才生效.

  3. 客户端与redis节点直连,不需要中间proxy层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可

  4. redis-cluster把所有的物理节点映射到[0-16383]slot上,cluster 负责维护node<->slot<->value

    10、Redis 一致性保证

    Redis 并不能保证数据的强一致性. 这意味这在实际中集群在特定的条件下可能会丢失写操作.

第一个原因是因为集群是用了异步复制. 写操作过程:

  • 客户端向主节点B写入一条命令.

  • 主节点B向客户端回复命令状态.

  • 主节点将写操作复制给他得从节点 B1, B2 和 B3. 主节点对命令的复制工作发生在返回命令回复之后, 因为如果每次处理命令请求都需要等待复制操作完成的话, 那么主节点处理命令请求的速度将极大地降低 —— 我们必须在性能和一致性之间做出权衡。 注意:Redis 集群可能会在将来提供同步写的方法。 Redis 集群另外一种可能会丢失命令的情况是集群出现了网络分区, 并且一个客户端与至少包括一个主节点在内的少数实例被孤立。

举个例子 假设集群包含 A 、 B 、 C 、 A1 、 B1 、 C1 六个节点, 其中 A 、B 、C 为主节点, A1 、B1 、C1 为A,B,C的从节点, 还有一个客户端 Z1 假设集群中发生网络分区,那么集群可能会分为两方,大部分的一方包含节点 A 、C 、A1 、B1 和 C1 ,小部分的一方则包含节点 B 和客户端 Z1 .

Z1仍然能够向主节点B中写入, 如果网络分区发生时间较短,那么集群将会继续正常运作,如果分区的时间足够让大部分的一方将B1选举为新的master,那么Z1写入B中得数据便丢失了.

注意, 在网络分裂出现期间, 客户端 Z1 可以向主节点 B 发送写命令的最大时间是有限制的, 这一时间限制称为节点超时时间(node timeout), 是 Redis 集群的一个重要的配置选项:

port 7000   //redis端口
cluster-enabled yes   //开启集群
cluster-config-file nodes.conf   //集群配置文件
cluster-node-timeout 5000   //集群节点超时设置
appendonly yes   //开启aof持久化
10、mac集群搭建

要让集群正常工作至少需要3个主节点,在这里我们要创建6个redis节点,其中三个为主节点,三个为从节点,对应的redis节点的ip和端口对应关系如下(为了简单演示都在同一台机器上面)

1、安装redis
brew install redis

由于配置了.bash_profile变量,所以可以在任何地方直接启动redis 1.直接启动

redis-server

2.带配置文件启动

redis-server /Users/baowenwei/service/redis/7000/redis7000.conf

创建6个文件夹7000,7001,…; 在每个文件夹里面创建一个配置文件,redis7000.conf,redis7001.conf 在每个redis.conf里面配置如下: 7000.conf

port 7000
dbfilename dump.rdb
dir /Users/baowenwei/service/redis/7000
cluster-enabled yes

7001.conf

port 7001
dbfilename dump.rdb
dir /Users/baowenwei/service/redis/7001
cluster-enabled yes
…

由于集群的脚本是使用ruby写的,所以还需要安装ruby。

brew install ruby.

安装ruby后,就可以使用ruby安装ruby操作redis的redis包了。

gam install redis -v 3.2 //安装完ruby后,gam命令就自己有了。

由于我使用的是brew在mac上面安装的redis,所以没有源码包,现在需要下载源码包,从官网下载源码包到本地。 进入源码包里面cd ~/service/redis/redis-3.2.11(根据个人自己的路径来看),执行

make //编译打包的意思。
编译后可以从src里面看到redis-trib.rb这个文件了。
使用cp redis-trib.rb /usr/local/bin/redis-trib ///usr/local/bin这个目录已经配置到环境变量的pash下面了, 这样就可以在任何地方直接使用redis-trib了。

以此启动7000—7005节点。

redis-server /Users/baowenwei/service/redis/7000/redis7000.conf

创建集群

redis-trib create --replicas 1 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005

create表示创建,选项 --replicas 1 表示我们希望为集群中的每个主节点创建一个从节点。。

create表示创建,选项 --replicas 1 表示我们希望为集群中的每个主节点创建一个从节点。。

进入客户端操作了。

$ redis-cli -c -p 7002
127.0.0.1:7002> set 777 ddd
-> Redirected to slot [6787] located at 127.0.0.1:7001
OK
127.0.0.1:7001> set ffff sddd
-> Redirected to slot [14956] located at 127.0.0.1:7002
OK
127.0.0.1:7002> set ffff sddd
OK
127.0.0.1:7002>

可以看到777这个key计算后应该存放到7001下面的卡曹,这时候会切换到7001节点上去,然后你需要重新执行一边命令,之前的并没有执行成功哦。

注意事项:在第一次创建集群前,不能有dump.rdb文件

11、redis穿透

缓存穿透是指查询一个根本不存在的数据,缓存层和存储层都不会命中,但是出于容错的考虑,如果从存储层查不到数据则不写入缓存层。

1、防穿透

穿透过程

  • 缓存层不命中

  • 存储层不命中,所以不将空结果写回缓存

  • 返回空结果 缓存穿透将导致不存在的数据每次请求都要到存储层去查询,失去了缓存保护后端存储的意义,这就是redis穿透。

2、解决方案1:

缓存空对象会有两个问题: 第一,空值做了缓存,意味着缓存层中存了更多的键,需要更多的内存空间 (如果是攻击,问题更严重 ),比较有效的方法是针对这类数据设置一个较短的过期时间,让其自动剔除。 第二,缓存层和存储层的数据会有一段时间窗口的不一致,可能会对业务有一定影响。例如过期时间设置为5分钟,如果此时存储层添加了这个数据,那此段时间就会出现缓存层和存储层数据的不一致,此时可以利用消息系统或者其他方式清除掉缓存层中的空对象。

img

3、解决方案2:

布隆过滤器拦截

4、缓存热点 key 重建优化

开发人员使用缓存 + 过期时间的策略既可以加速数据读写,又保证数据的定期更新,这种模式基本能够满足绝大部分需求。但是有两个问题如果同时出现,可能就会对应用造成致命的危害:

当前 key 是一个热点 key( 例如一个热门的娱乐新闻),并发量非常大。 重建缓存不能在短时间完成,可能是一个复杂计算,例如复杂的 SQL、多次 IO、多个依赖等。

在缓存失效的瞬间,有大量线程来重建缓存,造成后端负载加大,甚至可能会让应用崩溃。

热点 key 失效后大量线程重建缓存要解决这个问题也不是很复杂,但是不能为了解决这个问题给系统带来更多的麻烦,所以需要制定如下目标:

  • 减少重建缓存的次数

  • 数据尽可能一致

  • 较少的潜在危险

12、互斥锁 (mutex key)

此方法只允许一个线程重建缓存,其他线程等待重建缓存的线程执行完,重新从缓存获取数据即可

img

(1) 从 Redis 获取数据,如果值不为空,则直接返回值,否则执行下面的步骤 (2) 如果 set(nx 和 ex) 结果为 true,说明此时没有其他线程重建缓存,那么当前线程执行缓存构建逻辑。 (2.2) 如果 setnx(nx 和 ex) 结果为 false,说明此时已经有其他线程正在执行构建缓存的工作,那么当前线程将休息指定时间 ( 例如这里是 50 毫秒,取决于构建缓存的速度 ) 后,重新执行函数,直到获取到数据。

13、redis雪崩

雪崩是指如果redis突然挂掉了,所有请求都打到了db上面,这个时候有可能会把db给打垮掉。

预防和解决缓存雪崩问题,可以从以下三个方面进行着手。

  1. 保证缓存层服务高可用性。 和飞机都有多个引擎一样,如果缓存层设计成高可用的,即使个别节点、个别机器、甚至是机房宕掉,依然可以提供服务,例如前面介绍过的 Redis Sentinel 和 Redis Cluster 都实现了高可用。

  2. 依赖隔离组件为后端限流并降级。 无论是缓存层还是存储层都会有出错的概率,可以将它们视同为资源。作为并发量较大的系统,假如有一个资源不可用,可能会造成线程全部 hang 在这个资源上,造成整个系统不可用。降级在高并发系统中是非常正常的:比如推荐服务中,如果个性化推荐服务不可用,可以降级补充热点数据,不至于造成前端页面是开天窗。

  3. 在实际项目中,我们需要对重要的资源 ( 例如 Redis、 MySQL、 HBase、外部接口 ) 都进行隔离,让每种资源都单独运行在自己的线程池中,即使个别资源出现了问题,对其他服务没有影响。但是线程池如何管理,比如如何关闭资源池,开启资源池,资源池阀值管理,这些做起来还是相当复杂的,这里推荐一个 Java 依赖隔离工具 Hystrix(https://github.com/Netflix/Hystrix),如下图所示。

    img

    Hystrix不再处于主动开发中,并且当前处于维护模式。

    Hystrix(1.5.18版)足够稳定,可以满足Netflix现有应用程序的需求。同时,我们的重点已转向对应用程序的实时性能做出反应的自适应性实现,而不是预先配置的设置(例如,通过自适应并发限制)。对于像Hystrix这样有意义的情况,我们打算继续将Hystrix用于现有应用程序,并为新的内部项目利用诸如resilience4j之类的开放式活动项目。我们开始建议其他人也这样做。

    Netflix Hystrix现在正式处于维护模式,对整个社区具有以下期望:Netflix将不再主动审查问题,合并请求并发布新版本的Hystrix。我们已经根据1891年的最终版本发布了Hystrix(1.5.18),以便Maven Central中的最新版本与Netflix内部使用的最新已知稳定版本(1.5.11)保持一致。如果社区成员有兴趣获得Hystrix的所有权并将其移回活动模式,请联系hystrixoss@googlegroups.com

    多年来,Hystrix为Netflix和社区提供了良好的服务,向维护模式的过渡绝不表示Hystrix的概念和想法不再有价值。相反,Hystrix启发了许多伟大的想法和项目。我们感谢Netflix和整个社区中的每个人多年来为Hystrix所做的所有贡献。

    介绍

    Hystrix是一个延迟和容错库,旨在隔离对远程系统,服务和第三方库的访问点,停止级联故障,并在不可避免发生故障的复杂分布式系统中实现弹性。

    完整文件

    有关完整的文档,示例,操作详细信息和其他信息,请参见Wiki

    有关API,请参见Javadoc

    通讯

    它有什么作用?

    1)延迟和容错

    停止级联故障。后备和正常降级。无法快速快速恢复。

    使用断路器隔离线程和信号量。

    2)实时操作

    实时监控和配置更改。监视服务和财产变更在整个机队中扩散后立即生效。

    在几秒钟内收到警报,做出决定,影响更改并查看结果。

    3)并发

    并行执行。并发请求缓存。通过请求折叠自动进行批处理。

    你好,世界!

    要隔离的代码包装在HystrixCommand的run()方法内,类似于以下代码:

    公共 类 CommandHelloWorld  扩展 HystrixCommand < String > {
    
        私有 最终 字符串名称;
    
        公共 CommandHelloWorld(字符串 名称){
             超(HystrixCommandGroupKey 。工厂。阿斯基(“ ExampleGroup ”));
            这个。名称=名称
        }
    
        @Override 
        受保护的 字符串 run(){
             返回 “ Hello ”  +名称+  “!”;
        }
    }
    

    该命令可以这样使用:

    字符串 s =  new  CommandHelloWorld(“ Bob ”)。执行();
    Future < String > s =  新的 CommandHelloWorld(“ Bob ”)。队列();
    Observable < String > s =  新的 CommandHelloWorld(“ Bob ”)。观察();
    

    有关更多示例和信息,请参见“ 如何使用”部分。

    可以在hystrix-examples模块中找到示例源代码。

    二进制文件

    可以在http://search.maven.org上找到有关Maven,Ivy,Gradle等的二进制文件和依赖项信息。

    更改历史记录和版本号=> CHANGELOG.md

    Maven的示例:

    < 依存关系 >
        < groupId > com.netflix.hystrix </ groupId >
        < artifactId > hystrix-core </ artifactId >
        < 版本 > xyz </ 版本 >
    </ 依赖 >
    

    对于常春藤:

    < 依赖 org = “ com.netflix.hystrix ” 名称 = “ hystrix核心”  rev = “ xyz ” />
    

    如果您需要下载jar而不是使用构建系统,请使用所需的版本创建一个Maven pom文件,如下所示:

    <?xml 版本 = “ 1.0 ”?>
    < project  xmlns = “ http://maven.apache.org/POM/4.0.0 ”  xmlns :xsi = “ http://www.w3.org/2001/XMLSchema-instance ”  xsi :schemaLocation = “ http:/ /maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd “ >
    	< modelVersion > 4.0.0 </ modelVersion >
    	< groupId > com.netflix.hystrix.download </ groupId >
    	< artifactId > hystrix下载</ artifactId >
    	< 版本 > 1.0-SNAPSHOT </ 版本 >
    	< name >用于下载hystrix-core和依赖项的简单POM </ name >
    	< url > http://github.com/Netflix/Hystrix </ url >
    	< 依赖项 >
    		< 依存关系 >
    			< groupId > com.netflix.hystrix </ groupId >
    			< artifactId > hystrix-core </ artifactId >
    			< 版本 > xyz </ 版本 >
    			< 范围 />
    		</ 依赖 >
    	</ 依赖 >
    </ project >
    

    然后执行:

    mvn -f download-hystrix-pom.xml dependency:copy-dependencies
    

    它将hystrix-core-*。jar及其依赖项下载到./target/dependency/中。

    您需要Java 6或更高版本。

    建立

    建立:

    $ git clone git@github.com:Netflix/Hystrix.git
    $ cd Hystrix/
    $ ./gradlew build
    

    有关构建的更多详细信息,请参见Wiki的“ 入门”页面。

    运行演示

    要运行演示应用程序,请执行以下操作:

    $ git clone git@github.com:Netflix/Hystrix.git
    $ cd Hystrix/
    ./gradlew runDemo
    

    您将看到类似于以下内容的输出:

    Request => GetUserAccountCommand[SUCCESS][8ms], GetPaymentInformationCommand[SUCCESS][20ms], GetUserAccountCommand[SUCCESS, RESPONSE_FROM_CACHE][0ms]x2, GetOrderCommand[SUCCESS][101ms], CreditCardCommand[SUCCESS][1075ms]
    Request => GetUserAccountCommand[FAILURE, FALLBACK_SUCCESS][2ms], GetPaymentInformationCommand[SUCCESS][22ms], GetUserAccountCommand[FAILURE, FALLBACK_SUCCESS, RESPONSE_FROM_CACHE][0ms]x2, GetOrderCommand[SUCCESS][130ms], CreditCardCommand[SUCCESS][1050ms]
    Request => GetUserAccountCommand[FAILURE, FALLBACK_SUCCESS][4ms], GetPaymentInformationCommand[SUCCESS][19ms], GetUserAccountCommand[FAILURE, FALLBACK_SUCCESS, RESPONSE_FROM_CACHE][0ms]x2, GetOrderCommand[SUCCESS][145ms], CreditCardCommand[SUCCESS][1301ms]
    Request => GetUserAccountCommand[SUCCESS][4ms], GetPaymentInformationCommand[SUCCESS][11ms], GetUserAccountCommand[SUCCESS, RESPONSE_FROM_CACHE][0ms]x2, GetOrderCommand[SUCCESS][93ms], CreditCardCommand[SUCCESS][1409ms]
    
    #####################################################################################
    # CreditCardCommand: Requests: 17 Errors: 0 (0%)   Mean: 1171 75th: 1391 90th: 1470 99th: 1486 
    # GetOrderCommand: Requests: 21 Errors: 0 (0%)   Mean: 100 75th: 144 90th: 207 99th: 230 
    # GetUserAccountCommand: Requests: 21 Errors: 4 (19%)   Mean: 8 75th: 11 90th: 46 99th: 51 
    # GetPaymentInformationCommand: Requests: 21 Errors: 0 (0%)   Mean: 18 75th: 21 90th: 24 99th: 25 
    #####################################################################################
    
    Request => GetUserAccountCommand[SUCCESS][10ms], GetPaymentInformationCommand[SUCCESS][16ms], GetUserAccountCommand[SUCCESS, RESPONSE_FROM_CACHE][0ms]x2, GetOrderCommand[SUCCESS][51ms], CreditCardCommand[SUCCESS][922ms]
    Request => GetUserAccountCommand[SUCCESS][12ms], GetPaymentInformationCommand[SUCCESS][12ms], GetUserAccountCommand[SUCCESS, RESPONSE_FROM_CACHE][0ms]x2, GetOrderCommand[SUCCESS][68ms], CreditCardCommand[SUCCESS][1257ms]
    Request => GetUserAccountCommand[SUCCESS][10ms], GetPaymentInformationCommand[SUCCESS][11ms], GetUserAccountCommand[SUCCESS, RESPONSE_FROM_CACHE][0ms]x2, GetOrderCommand[SUCCESS][78ms], CreditCardCommand[SUCCESS][1295ms]
    Request => GetUserAccountCommand[FAILURE, FALLBACK_SUCCESS][6ms], GetPaymentInformationCommand[SUCCESS][11ms], GetUserAccountCommand[FAILURE, FALLBACK_SUCCESS, RESPONSE_FROM_CACHE][0ms]x2, GetOrderCommand[SUCCESS][153ms], CreditCardCommand[SUCCESS][1321ms]
    

    该演示模拟了4种不同的HystrixCommand实现,这些实现在多线程环境中具有故障,延迟,超时和重复调用。

    它从HystrixCommandMetrics记录HystrixRequestLog的结果和指标。

    仪表板

    该项目的hystrix-dashboard组件已弃用,移至Netflix-Skunkworks / hystrix-dashboard。请参阅自述文件以获取更多详细信息,包括重要的安全注意事项。

    错误和反馈

    有关错误,问题和讨论,请使用GitHub Issues

    执照

    版权所有2013 Netflix,Inc.

    根据Apache许可版本2.0(“许可”)许可;除非遵守许可,否则不得使用此文件。您可以在以下位置获得许可的副本:

    http://www.apache.org/licenses/LICENSE-2.0

    除非适用法律要求或书面同意,否则根据“许可”分发的软件将按“原样”分发,没有任何明示或暗示的保证或条件。请参阅许可证,以了解许可证下管理权限和限制的特定语言。

第三章、redis面试

1、Redis 持久化机制

Redis是一个支持持久化的内存数据库,通过持久化机制把内存中的数据同步到硬盘文件来保证数据持久化。当Redis重启后通过把硬盘文件重新加载到内存,就能达到恢复数据的目的。 实现:单独创建fork()一个子进程,将当前父进程的数据库数据复制到子进程的内存中,然后由子进程写入到临时文件中,持久化的过程结束了,再用这个临时文件替换上次的快照文件,然后子进程退出,内存释放。

RDB是Redis默认的持久化方式。按照一定的时间周期策略把内存的数据以快照的形式保存到硬盘的二进制文件。即Snapshot快照存储,对应产生的数据文件为dump.rdb,通过配置文件中的save参数来定义快照的周期。( 快照可以是其所表示的数据的一个副本,也可以是数据的一个复制品。) AOF:Redis会将每一个收到的写命令都通过Write函数追加到文件最后,类似于MySQL的binlog。当Redis重启是会通过重新执行文件中保存的写命令来在内存中重建整个数据库的内容。 当两种方式同时开启时,数据恢复Redis会优先选择AOF恢复。

2、缓存雪崩、缓存穿透、缓存预热、缓存更新、缓存降级等问题
一、缓存雪崩

缓存雪崩我们可以简单的理解为:由于原有缓存失效,新缓存未到期间 (例如:我们设置缓存时采用了相同的过期时间,在同一时刻出现大面积的缓存过期),所有原本应该访问缓存的请求都去查询数据库了,而对数据库CPU和内存造成巨大压力,严重的会造成数据库宕机。从而形成一系列连锁反应,造成整个系统崩溃。 解决办法: 大多数系统设计者考虑用加锁( 最多的解决方案)或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上。还有一个简单方案就是将缓存失效时间分散开

二、缓存穿透

缓存穿透是指用户查询数据,在数据库没有,自然在缓存中也不会有。这样就导致用户查询的时候,在缓存中找不到,每次都要去数据库再查询一遍,然后返回空(相当于进行了两次无用的查询)。这样请求就绕过缓存直接查数据库,这也是经常提的缓存命中率问题。 解决办法; 最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。 另外也有一个更为简单粗暴的方法,如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。通过这个直接设置的默认值存放到缓存,这样第二次到缓冲中获取就有值了,而不会继续访问数据库,这种办法最简单粗暴。 5TB的硬盘上放满了数据,请写一个算法将这些数据进行排重。如果这些数据是一些32bit大小的数据该如何解决?如果是64bit的呢?

对于空间的利用到达了一种极致,那就是Bitmap和布隆过滤器(Bloom Filter)。 Bitmap: 典型的就是哈希表 缺点是,Bitmap对于每个元素只能记录1bit信息,如果还想完成额外的功能,恐怕只能靠牺牲更多的空间、时间来完成了。

布隆过滤器(推荐) 就是引入了k(k>1)k(k>1)个相互独立的哈希函数,保证在给定的空间、误判率下,完成元素判重的过程。 它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。 Bloom-Filter算法的核心思想就是利用多个不同的Hash函数来解决“冲突”。 Hash存在一个冲突(碰撞)的问题,用同一个Hash得到的两个URL的值有可能相同。为了减少冲突,我们可以多引入几个Hash,如果通过其中的一个Hash值我们得出某元素不在集合中,那么该元素肯定不在集合中。只有在所有的Hash函数告诉我们该元素在集合中时,才能确定该元素存在于集合中。这便是Bloom-Filter的基本思想。 Bloom-Filter一般用于在大数据量的集合中判定某元素是否存在。 受提醒补充:缓存穿透与缓存击穿的区别 缓存击穿:指一个key非常热点,大并发集中对这个key进行访问,当这个key在失效的瞬间,仍然持续的大并发访问就穿破缓存,转而直接请求数据库。 解决方案;在访问key之前,采用SETNX(set if not exists)来设置另一个短期key来锁住当前key的访问,访问结束再删除该短期key。

三、缓存预热

缓存预热这个应该是一个比较常见的概念,相信很多小伙伴都应该可以很容易的理解,缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据! 解决思路: 1、直接写个缓存刷新页面,上线时手工操作下; 2、数据量不大,可以在项目启动的时候自动进行加载; 3、定时刷新缓存;

四、缓存更新

除了缓存服务器自带的缓存失效策略之外(Redis默认的有6中策略可供选择),我们还可以根据具体的业务需求进行自定义的缓存淘汰,常见的策略有两种: (1)定时去清理过期的缓存; (2)当有用户请求过来时,再判断这个请求所用到的缓存是否过期,过期的话就去底层系统得到新数据并更新缓存。 两者各有优劣,第一种的缺点是维护大量缓存的key是比较麻烦的,第二种的缺点就是每次用户请求过来都要判断缓存失效,逻辑相对比较复杂!具体用哪种方案,大家可以根据自己的应用场景来权衡。

五、缓存降级

当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然需要保证服务还是可用的,即使是有损服务。系统可以根据一些关键数据进行自动降级,也可以配置开关实现人工降级。 降级的最终目的是保证核心服务可用,即使是有损的。而且有些服务是无法降级的(如加入购物车、结算)。 以参考日志级别设置预案: (1)一般:比如有些服务偶尔因为网络抖动或者服务正在上线而超时,可以自动降级; (2)警告:有些服务在一段时间内成功率有波动(如在95~100%之间),可以自动降级或人工降级,并发送告警; (3)错误:比如可用率低于90%,或者数据库连接池被打爆了,或者访问量突然猛增到系统能承受的最大阀值,此时可以根据情况自动降级或者人工降级; (4)严重错误:比如因为特殊原因数据错误了,此时需要紧急人工降级。

服务降级的目的,是为了防止Redis服务故障,导致数据库跟着一起发生雪崩问题。因此,对于不重要的缓存数据,可以采取服务降级策略,例如一个比较常见的做法就是,Redis出现问题,不去数据库查询,而是直接返回默认值给用户。

热点数据和冷数据是什么 热点数据,缓存才有价值 对于冷数据而言,大部分数据可能还没有再次访问到就已经被挤出内存,不仅占用内存,而且价值不大。频繁修改的数据,看情况考虑使用缓存 对于上面两个例子,寿星列表、导航信息都存在一个特点,就是信息修改频率不高,读取通常非常高的场景。 对于热点数据,比如我们的某IM产品,生日祝福模块,当天的寿星列表,缓存以后可能读取数十万次。再举个例子,某导航产品,我们将导航信息,缓存以后可能读取数百万次。 数据更新前至少读取两次,缓存才有意义。这个是最基本的策略,如果缓存还没有起作用就失效了,那就没有太大价值了。 那存不存在,修改频率很高,但是又不得不考虑缓存的场景呢?有!比如,这个读取接口对数据库的压力很大,但是又是热点数据,这个时候就需要考虑通过缓存手段,减少数据库的压力,比如我们的某助手产品的,点赞数,收藏数,分享数等是非常典型的热点数据,但是又不断变化,此时就需要将数据同步保存到Redis缓存,减少数据库压力。

3、Memcache与Redis的区别都有哪些?

1)、存储方式 Memecache把数据全部存在内存之中,断电后会挂掉,数据不能超过内存大小。 Redis有部份存在硬盘上,redis可以持久化其数据 2)、数据支持类型 memcached所有的值均是简单的字符串,redis作为其替代者,支持更为丰富的数据类型 ,提供list,set,zset,hash等数据结构的存储 3)、使用底层模型不同 它们之间底层实现方式 以及与客户端之间通信的应用协议不一样。 Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求。 4). value 值大小不同:Redis 最大可以达到 512M;memcache 只有 1mb。 5)redis的速度比memcached快很多 6)Redis支持数据的备份,即master-slave模式的数据备份。

4、单线程的redis为什么这么快

(一)纯内存操作 (二)单线程操作,避免了频繁的上下文切换 (三)采用了非阻塞I/O多路复用机制

5、redis的数据类型,以及每种数据类型的使用场景

回答:一共五种

(一)String

这个其实没啥好说的,最常规的set/get操作,value可以是String也可以是数字。一般做一些复杂的计数功能的缓存。

(二)hash

这里value存放的是结构化的对象,比较方便的就是操作其中的某个字段。博主在做单点登录的时候,就是用这种数据结构存储用户信息,以cookieId作为key,设置30分钟为缓存过期时间,能很好的模拟出类似session的效果。

(三)list

使用List的数据结构,可以做简单的消息队列的功能。另外还有一个就是,可以利用lrange命令,做基于redis的分页功能,性能极佳,用户体验好。本人还用一个场景,很合适—取行情信息。就也是个生产者和消费者的场景。LIST可以很好的完成排队,先进先出的原则。

(四)set

因为set堆放的是一堆不重复值的集合。所以可以做全局去重的功能。为什么不用JVM自带的Set进行去重?因为我们的系统一般都是集群部署,使用JVM自带的Set,比较麻烦,难道为了一个做一个全局去重,再起一个公共服务,太麻烦了。 另外,就是利用交集、并集、差集等操作,可以计算共同喜好,全部的喜好,自己独有的喜好等功能。

(五)sorted set

sorted set多了一个权重参数score,集合中的元素能够按score进行排列。可以做排行榜应用,取TOP N操作。

6、Redis 内部结构

dict 本质上是为了解决算法中的查找问题(Searching)是一个用于维护key和value映射关系的数据结构,与很多语言中的Map或dictionary类似。 本质上是为了解决算法中的查找问题(Searching) sds sds就等同于char * 它可以存储任意二进制数据,不能像C语言字符串那样以字符’\0’来标识字符串的结 束,因此它必然有个长度字段。 skiplist (跳跃表) 跳表是一种实现起来很简单,单层多指针的链表,它查找效率很高,堪比优化过的二叉平衡树,且比平衡树的实现, quicklist ziplist 压缩表 ziplist是一个编码后的列表,是由一系列特殊编码的连续内存块组成的顺序型数据结构, redis的过期策略以及内存淘汰机制 redis采用的是定期删除+惰性删除策略。

7、为什么不用定时删除策略?

定时删除,用一个定时器来负责监视key,过期则自动删除。虽然内存及时释放,但是十分消耗CPU资源。在大并发请求下,CPU要将时间应用在处理请求,而不是删除key,因此没有采用这一策略.

8、定期删除+惰性删除是如何工作的呢?

定期删除,redis默认每个100ms检查,是否有过期的key,有过期key则删除。需要说明的是,redis不是每个100ms将所有的key检查一次,而是随机抽取进行检查(如果每隔100ms,全部key进行检查,redis岂不是卡死)。因此,如果只采用定期删除策略,会导致很多key到时间没有删除。 于是,惰性删除派上用场。也就是说在你获取某个key的时候,redis会检查一下,这个key如果设置了过期时间那么是否过期了?如果过期了此时就会删除。

9、采用定期删除+惰性删除就没其他问题了么?

不是的,如果定期删除没删除key。然后你也没即时去请求key,也就是说惰性删除也没生效。这样,redis的内存会越来越高。那么就应该采用内存淘汰机制 在redis.conf中有一行配置

maxmemory-policy volatile-lru 1

10、该配置就是配内存淘汰策略的(什么,你没配过?好好反省一下自己)

volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰 volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰 volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰 allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰 allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰 no-enviction(驱逐):禁止驱逐数据,新写入操作会报错 ps:如果没有设置 expire 的key, 不满足先决条件(prerequisites); 那么 volatile-lru, volatile-random 和 volatile-ttl 策略的行为, 和 noeviction(不删除) 基本上一致。

11、Redis 为什么是单线程的

官方FAQ表示,因为Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且CPU不会成为瓶颈,那就顺理成章地采用单线程的方案了(毕竟采用多线程会有很多麻烦!)Redis利用队列技术将并发访问变为串行访问 1)绝大部分请求是纯粹的内存操作(非常快速)

2)采用单线程,避免了不必要的上下文切换和竞争条件 3)非阻塞IO优点: 1.速度快,因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1)

  1. 支持丰富数据类型,支持string,list,set,sorted set,hash 3.支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行

4、丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除如何解决redis的并发竞争key问题

同时有多个子系统去set一个key。这个时候要注意什么呢? 不推荐使用redis的事务机制。因为我们的生产环境,基本都是redis集群环境,做了数据分片操作。你一个事务中有涉及到多个key操作的时候,这多个key不一定都存储在同一个redis-server上。因此,redis的事务机制,十分鸡肋。 (1)如果对这个key操作,不要求顺序: 准备一个分布式锁,大家去抢锁,抢到锁就做set操作即可 (2)如果对这个key操作,要求顺序: 分布式锁+时间戳。 假设这会系统B先抢到锁,将key1设置为{valueB 3:05}。接下来系统A抢到锁,发现自己的valueA的时间戳早于缓存中的时间戳,那就不做set操作了。以此类推。 (3) 利用队列,将set方法变成串行访问也可以redis遇到高并发,如果保证读写key的一致性 对redis的操作都是具有原子性的,是线程安全的操作,你不用考虑并发问题,redis内部已经帮你处理好并发的问题了。

12、Redis 集群方案应该怎么做?都有哪些方案?

1.twemproxy,大概概念是,它类似于一个代理方式, 使用时在本需要连接 redis 的地方改为连接 twemproxy, 它会以一个代理的身份接收请求并使用一致性 hash 算法,将请求转接到具体 redis,将结果再返回 twemproxy。 缺点: twemproxy 自身单端口实例的压力,使用一致性 hash 后,对 redis 节点数量改变时候的计算值的改变,数据无法自动移动到新的节点。

2.codis,目前用的最多的集群方案,基本和 twemproxy 一致的效果,但它支持在 节点数量改变情况下,旧节点数据可恢复到新 hash 节点

3.redis cluster3.0 自带的集群,特点在于他的分布式算法不是一致性 hash,而是 hash 槽的概念,以及自身支持节点设置从节点。具体看官方文档介绍。

13、有没有尝试进行多机redis 的部署?如何保证数据一致的?

主从复制,读写分离 一类是主数据库(master)一类是从数据库(slave),主数据库可以进行读写操作,当发生写操作的时候自动将数据同步到从数据库,而从数据库一般是只读的,并接收主数据库同步过来的数据,一个主数据库可以有多个从数据库,而一个从数据库只能有一个主数据库。

14、对于大量的请求怎么样处理

redis是一个单线程程序,也就说同一时刻它只能处理一个客户端请求; redis是通过IO多路复用(select,epoll, kqueue,依据不同的平台,采取不同的实现)来处理多个客户端请求的

15、Redis 常见性能问题和解决方案?

(1) Master 最好不要做任何持久化工作,如 RDB 内存快照和 AOF 日志文件 (2) 如果数据比较重要,某个 Slave 开启 AOF 备份数据,策略设置为每秒同步一次 (3) 为了主从复制的速度和连接的稳定性, Master 和 Slave 最好在同一个局域网内 (4) 尽量避免在压力很大的主库上增加从库 (5) 主从复制不要用图状结构,用单向链表结构更为稳定,即: Master <- Slave1 <- Slave2 <- Slave3…

16、讲解下Redis线程模型

文件事件处理器包括分别是套接字、 I/O 多路复用程序、 文件事件分派器(dispatcher)、 以及事件处理器。使用 I/O 多路复用程序来同时监听多个套接字, 并根据套接字目前执行的任务来为套接字关联不同的事件处理器。当被监听的套接字准备好执行连接应答(accept)、读取(read)、写入(write)、关闭(close)等操作时, 与操作相对应的文件事件就会产生, 这时文件事件处理器就会调用套接字之前关联好的事件处理器来处理这些事件。 I/O 多路复用程序负责监听多个套接字, 并向文件事件分派器传送那些产生了事件的套接字。 工作原理: 1)I/O 多路复用程序负责监听多个套接字, 并向文件事件分派器传送那些产生了事件的套接字。 尽管多个文件事件可能会并发地出现, 但 I/O 多路复用程序总是会将所有产生事件的套接字都入队到一个队列里面, 然后通过这个队列, 以有序(sequentially)、同步(synchronously)、每次一个套接字的方式向文件事件分派器传送套接字: 当上一个套接字产生的事件被处理完毕之后(该套接字为事件所关联的事件处理器执行完毕), I/O 多路复用程序才会继续向文件事件分派器传送下一个套接字。如果一个套接字又可读又可写的话, 那么服务器将先读套接字, 后写套接字.

在这里插入图片描述

17、为什么Redis的操作是原子性的,怎么保证原子性的?

对于Redis而言,命令的原子性指的是:一个操作的不可以再分,操作要么执行,要么不执行。 Redis的操作之所以是原子性的,是因为Redis是单线程的。 Redis本身提供的所有API都是原子操作,Redis中的事务其实是要保证批量操作的原子性。 多个命令在并发中也是原子性的吗? 不一定, 将get和set改成单命令操作,incr 。使用Redis的事务,或者使用Redis+Lua==的方式实现.

18、Redis事务

Redis事务功能是通过MULTI、EXEC、DISCARD和WATCH 四个原语实现的 Redis会将一个事务中的所有命令序列化,然后按顺序执行。 1.redis 不支持回滚“Redis 在事务失败时不进行回滚,而是继续执行余下的命令”, 所以 Redis 的内部可以保持简单且快速。 2.如果在一个事务中的命令出现错误,那么所有的命令都不会执行; 3.如果在一个事务中出现运行错误,那么正确的命令会被执行。 注:redis的discard只是结束本次事务,正确命令造成的影响仍然存在.

1)MULTI命令用于开启一个事务,它总是返回OK。 MULTI执行之后,客户端可以继续向服务器发送任意多条命令,这些命令不会立即被执行,而是被放到一个队列中,当EXEC命令被调用时,所有队列中的命令才会被执行。 2)EXEC:执行所有事务块内的命令。返回事务块内所有命令的返回值,按命令执行的先后顺序排列。 当操作被打断时,返回空值 nil 。 3)通过调用DISCARD,客户端可以清空事务队列,并放弃执行事务, 并且客户端会从事务状态中退出。 4)WATCH 命令可以为 Redis 事务提供 check-and-set (CAS)行为。 可以监控一个或多个键,一旦其中有一个键被修改(或删除),之后的事务就不会执行,监控一直持续到EXEC命令。

19、Redis实现分布式锁

Redis为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对Redis的连接并不存在竞争关系Redis中可以使用SETNX命令实现分布式锁。 将 key 的值设为 value ,当且仅当 key 不存在。 若给定的 key 已经存在,则 SETNX 不做任何动作

在这里插入图片描述

解锁:使用 del key 命令就能释放锁 解决死锁: 1)通过Redis中expire()给锁设定最大持有时间,如果超过,则Redis来帮我们释放锁。 2) 使用 setnx key “当前系统时间+锁持有的时间”和getset key “当前系统时间+锁持有的时间”组合的命令就可以实现

 

 posted on 2020-10-13 14:49  大码王  阅读(183)  评论(0编辑  收藏  举报
复制代码