Redis是当下最火爆流行的内存数据库之一,是典型的非关系型数据库,通过在内存中读写数据极大地提高了读写速度。
Redis为什么这么快?
特点:
客户端与Redis的通信过程:
String类型
例如:热点数据缓存(例如报表、明星出轨),对象缓存、全页缓存、可以提升热点数据的访问数据。
String 类型,因为 Redis 是分布式的独立服务,可以在多个应用之间共享
例如:分布式Session
<dependency>
<groupId>org.springframework.sessiongroupId>
<artifactId>spring-session-data-redisartifactId>
dependency>
String 类型setnx方法,只有不存在时才能添加成功,返回true
public static boolean getLock(String key) {
Long flag = jedis.setnx(key, "1");
if (flag == 1) {
jedis.expire(key, 10);
}
return flag == 1;
}
public static void releaseLock(String key) {
jedis.del(key);
}
int类型,incrby,利用原子性
incrby userid 1000
分库分表的场景,一次性拿一段
int类型,incr方法
例如:文章的阅读量、微博点赞数、允许一定的延迟,先写入Redis再定时同步到数据库
int类型,incr方法
以访问者的ip和其他信息作为key,访问一次增加一次计数,超过次数则返回false
String类型的bitcount(1.6.6的bitmap数据结构介绍)
字符是以8位二进制存储的
set k1 a
setbit k1 6 1
setbit k1 7 0
get k1
/* 6 7 代表的a的二进制位的修改
a 对应的ASCII码是97,转换为二进制数据是01100001
b 对应的ASCII码是98,转换为二进制数据是01100010
因为bit非常节省空间(1 MB=8388608 bit),可以用来做大数据量的统计。
*/
例如:在线用户统计,留存用户统计
setbit onlineusers 01
setbit onlineusers 11
setbit onlineusers 20
支持按位与、按位或等等操作
BITOPANDdestkeykey[key...] ,对一个或多个 key 求逻辑并,并将结果保存到 destkey
BITOPORdestkeykey[key...] ,对一个或多个 key 求逻辑或,并将结果保存到 destkey
BITOPXORdestkeykey[key...] ,对一个或多个 key 求逻辑异或,并将结果保存到 destkey
BITOPNOTdestkeykey ,对给定 key 求逻辑非,并将结果保存到 destkey
计算出7天都在线的用户
BITOP "AND" "7_days_both_online_users" "day_1_online_users" "day_2_online_users" ... "day_7_online_users"
String 或hash。所有String可以做的hash都可以做
list,双向链表,直接作为timeline就好了。插入有序
List提供了两个阻塞的弹出操作:blpop/brpop,且可以设置超时时间
上面的操作,其实就是java的阻塞队列
自带一个随机获得值
spop myset
假如上面的微博ID是t1001,用户ID是u3001
用 like:t1001 来维护 t1001 这条微博的所有点赞用户:
用 tags:i5001 来维护商品所有的标签:
// 获取差集sdiff set1 set2// 获取交集(intersection )sinter set1 set2// 获取并集sunion set1 set2
假如:iPhone11 上市了
sadd brand:apple iPhone11sadd brand:ios iPhone11sad screensize:6.0-6.24 iPhone11sad screentype:lcd iPhone11
赛选商品,苹果的、ios的、屏幕在6.0-6.24之间的,屏幕材质是LCD屏幕
sinter brand:apple brand:ios screensize:6.0-6.24 screentype:lcd
follow 关注 fans 粉丝
相互关注:
sadd 1:follow 2sadd 2:fans 1sadd 1:fans 2sadd 2:follow 1
我关注的人也关注了他(取交集):
sinter 1:follow 2:fans
可能认识的人:
sdiff 2:follow 1:follow # 用户1可能认识的人(差集)sdiff 1:follow 2:follow # 用户2可能认识的人
id 为6001 的新闻点击数加1:
zincrby hotNews:20190926 1 n6001
获取今天点击最多的15条:
zrevrange hotNews:20190926 0 15 withscores
安装
进入解压路径后,执行如下命令:
redis-server redis.windows.conf
服务启动与停止
常用命令:
卸载服务redis-server --service-uninstall开启服务redis-server --service-start停止服务resid-server --service-stop
进入redis目录下运行
redis-cli.exe -h 127.0.0.1 -p 6379
先执行search命令,以redis为关键字搜索docker hub,查看是否有合适版本的redis镜像:
docker search redis
拉取docker镜像
docker pull redis:latest
新建docker容器
docker run -itd --name redis --port 6379:6379 redis:latest
启动docker容器
docker exec -d redis /bin/bash
在Redis的根目录下有一个配置文件(redis.conf),当然也可以通过Redis CONFIG命令获取和设置所有的Redis配置。
CONFIG命令的基本语法:
CONFIG GET CONFIG_SETTING_NAME
示例:
CONFIG GET loglevel1) "loglevel" 2) "notice"
要获取所有配置设置,请使用*
代替CONFIG_SETTING_NAME
如果要更新配置,可以直接编辑redis.conf文件,也可以通过CONFIG set命令更新配置。
以下是CONFIG SET
命令的基本语法:
CONFIG SET CONFIG_SETTING_NAME NEW_CONFIG_VALUE
示例:
CONFIG SET loglevel "notice" OK
Redis支持5种数据类型。
Redis中的字符串是一个字节序列。Redis中的字符串是二进制安全的,这意味着它们的长度不由任何特殊的终止字符决定。因此,可以在一个字符串中存储高达512
兆字节的任何内容。
字符串的内部编码可能为int、embstr或raw。
示例:
set name "yiibai.com"OK
在上面的示例中,set
和get
是Redis命令,name
是Redis中使用的键,yiibai.com
是存储在Redis中的字符串的值。
**注意:**Redis命令不区分大小写,如SET
,Set
和set
都是同一个命令。字符串值的最大长度为 512MB。
下表列出了一些用于在Redis中管理字符串的基本命令。
序号 | 命令 | 描述说明 |
---|---|---|
1 | SET key value | 此命令设置指定键的值 |
2 | GET key | 获取指定键的值 |
3 | GETRANGE key start end | 获取存储在键上的字符串的子字符串 |
4 | GETSET key value | 设置键的字符串值并返回其旧值 |
5 | GETBIT key offset | 返回在键处存储的字符串值中偏移处的位值 |
6 | MGET key1 [key2] | 获取所有给定键的值 |
7 | SETBIT key offset value | 存储在键上的字符串值中设置或清除偏移处的位 |
8 | SETEX key seconds value | 用键和到期时间来设置值 |
9 | SETNX key value | 设置键的值,仅当键不存在时 |
10 | SETRANGE key offset value | 在指定偏移处开始的键处覆盖字符串的一部分 |
11 | STRLEN key | 获取存储在键中的值的长度 |
12 | MSET key value [key value …] | 为多个键分别设置它们的值 |
13 | MSETNX key value [key value …] | 为多个键分别设置它们的值,仅当键不存在时 |
14 | PSETEX key milliseconds value | 设置键的值和到期时间(以毫秒为单位) |
15 | INCR key | 将键的整数值增加1 |
16 | INCRBY key increment | 将键的整数值按给定的数值增加 |
17 | INCRBYFLOAT key increment | 将键的浮点值按给定的数值增加 |
18 | DECR key | 将键的整数值减1 |
19 | DECRBY key decrement | 按给定数值减少键的整数值 |
20 | APPEND key value | 将指定值附加到键 |
Redis散列/哈希(Hashes)是键值对的集合。Redis散列/哈希是字符串字段和字符串值之间的映射。因此,它们用于表示对象。
哈希的内部编码可以是ziplist或hashtable。
示例:
HMSET ukey username "yiibai" password "passswd123" points 200
在上述示例中,散列/哈希数据类型用于存储包含用户的基本信息的用户对象。这里HMSET
,HGETALL
是Redis的命令,而ukey
是键的名称。
每个散列/哈希可以存储多达2^32 - 1
个健-值对(超过40
亿个)。
下表列出了与哈希/散列相关的一些基本命令:
序号 | 命令 | 说明 |
---|---|---|
1 | HDEL key field2 [field2] | 删除一个或多个哈希字段 |
2 | HEXISTS key field | 判断是否存在散列字段 |
3 | HGET key field | 获取存储在指定键的哈希字段的值 |
4 | HGETALL key | 获取存储在指定键的哈希中的所有字段和值 |
5 | HINCRBY key field increment | 将哈希字段的整数值按给定数字增加 |
6 | HINCRBYFLOAT key field increment | 将哈希字段的浮点值按给定数值增加 |
7 | HKEYS key | 获取哈希中的所有字段 |
8 | HLEN key | 获取散列中的字段数量 |
9 | HMGET key field1 [field2] | 获取所有给定哈希字段的值 |
10 | HMSET key field1 value1 [field2 value2 ] | 为多个哈希字段分别设置它们的值 |
11 | 设置散列字段的字符串值 | |
12 | HSETNX key field value | 仅当字段不存在时,才设置散列字段的值 |
13 | HVALS key | 获取哈希中的所有值 |
Redis列表只是字符串列表,按插入顺序排序。您可以向Redis列表的头部或尾部添加元素。
列表的内部编码可以是ziplist或linkedlist。
示例:
lpush alist redis lpush alist mongodb lpush alist sqlitelrange alist 0 101) "sqlite" 2) "mongodb" 3) "redis"
列表的最大长度为2^32 - 1
个元素(4294967295
,每个列表可容纳超过40
亿个元素)。
下表列出了与列表相关的一些基本命令:
序号 | 命令 | 说明 |
---|---|---|
1 | BLPOP key1 [key2 ] timeout | 删除并获取列表中的第一个元素,或阻塞,直到有一个元素可用 |
2 | BRPOP key1 [key2 ] timeout | 删除并获取列表中的最后一个元素,或阻塞,直到有一个元素可用 |
3 | BRPOPLPUSH source destination timeout | 从列表中弹出值,将其推送到另一个列表并返回它; 或阻塞,直到一个可用 |
4 | LINDEX key index | 通过其索引从列表获取元素 |
5 | LINSERT key BEFORE/AFTER pivot value | 在列表中的另一个元素之前或之后插入元素 |
6 | LLEN key | 获取列表的长度 |
7 | LPOP key | 删除并获取列表中的第一个元素 |
8 | LPUSH key value1 [value2] | 将一个或多个值添加到列表 |
9 | LPUSHX key value | 仅当列表存在时,才向列表添加值 |
10 | LRANGE key start stop | 从列表中获取一系列元素 |
11 | LREM key count value | 从列表中删除元素 |
12 | LSET key index value | 通过索引在列表中设置元素的值 |
13 | LTRIM key start stop | 修剪列表的指定范围 |
14 | RPOP key | 删除并获取列表中的最后一个元素 |
15 | RPOPLPUSH source destination | 删除列表中的最后一个元素,将其附加到另一个列表并返回 |
16 | RPUSH key value1 [value2] | 将一个或多个值附加到列表 |
17 | RPUSHX key value | 仅当列表存在时才将值附加到列表 |
Redis集合是字符串的无序集合。在Redis中,可以添加,删除和测试成员存在的时间O(1)复杂性。
集合的内部编码可以是intset或hashtable。
示例:
sadd yiibailist redis sadd yiibailist mongodbsadd yiibailist sqlitesadd yiibailist sqlitesmembers yiibailist1) "sqlite" 2) "mongodb" 3) "redis"
一个集合中的最大成员数量为2^32 - 1
(即4294967295
,每个集合中元素数量可达40
亿个)个。
序号 | 命令 | 说明 |
---|---|---|
1 | SADD key member1 [member2] | 将一个或多个成员添加到集合 |
2 | SCARD key | 获取集合中的成员数 |
3 | SDIFF key1 [key2] | 减去多个集合 |
4 | SDIFFSTORE destination key1 [key2] | 减去多个集并将结果集存储在键中 |
5 | SINTER key1 [key2] | 相交多个集合 |
6 | SINTERSTORE destination key1 [key2] | 交叉多个集合并将结果集存储在键中 |
7 | SISMEMBER key member | 判断确定给定值是否是集合的成员 |
8 | SMOVE source destination member | 将成员从一个集合移动到另一个集合 |
9 | SPOP key | 从集合中删除并返回随机成员 |
10 | SRANDMEMBER key [count] | 从集合中获取一个或多个随机成员 |
11 | SREM key member1 [member2] | 从集合中删除一个或多个成员 |
12 | SUNION key1 [key2] | 添加多个集合 |
13 | SUNIONSTORE destination key1 [key2] | 添加多个集并将结果集存储在键中 |
14 | SSCAN key cursor [MATCH pattern] [COUNT count] | 递增地迭代集合中的元素 |
Redis可排序集合类似于Redis集合,是不重复的字符集合。 不同之处在于,排序集合的每个成员都与分数相关联,这个分数用于按最小分数到最大分数来排序的排序集合。虽然成员是唯一的,但分数值可以重复。
有序集合的内部编码可以是ziplist或skiplist。
示例:
zadd yiibaiset 0 rediszadd yiibaiset 0 mongodbzadd yiibaiset 1 sqlitezadd yiibaiset 1 sqliteZRANGEBYSCORE yiibaiset 0 1000 1) "mongodb" 2) "redis" 3) "sqlite"
因为 ‘sqlite
‘ 的排序值是 1 ,其它两个元素的排序值是 0 ,所以 ‘sqlite
‘ 排在最后一个位置上。
Redis命令是用于在Redis服务器上执行一些操作。
要在Redis远程服务器上运行命令,需要通过客户端redis-cli
连接到服务器:
语法:
redis-cli -h host -p port -a password
示例:
以下示例显示如何连接到Redis远程服务器,在主机(host)127.0.0.1
,端口(port)6379
上运行,并使用密码为 mypass
。
redis-cli -h 127.0.0.1 -p 6379 -a "mypass"
Redis键命令用于管理Redis中的键,以下是使用redis键命令的语法。
语法:
COMMAND KEY_NAME
示例:
SET akey redisOKDEL akey(integer) 1GET akey(nil)
在上面的例子中,DEL
是Redis的命令,而akey
是键的名称。如果键被删除,则命令的输出将为(integer) 1
,否则为(integer) 0
。
下表列出了与键相关的一些基本命令。
序号 | 命令 | 描述 |
---|---|---|
1 | DEL key | 此命令删除一个指定键(如果存在)。 |
2 | DUMP key | 此命令返回存储在指定键的值的序列化版本。 |
3 | EXISTS key | 此命令检查键是否存在。 |
4 | EXPIREAT key timestamp | 设置在指定时间戳之后键到期/过期。这里的时间是Unix时间戳格式。 |
5 | EXPIRE key seconds | 设置键在指定时间秒数之后到期/过期。 |
6 | PEXPIRE key milliseconds | 设置键的到期时间(以毫秒为单位)。 |
7 | PEXPIREAT key milliseconds-timestamp | 以Unix时间戳形式来设置键的到期时间(以毫秒为单位)。 |
8 | KEYS pattern | 查找与指定模式匹配的所有键。 |
9 | MOVE key db | 将键移动到另一个数据库。 |
10 | PERSIST key | 删除指定键的过期时间,得永生。 |
11 | PTTL key | 获取键的剩余到期时间。 |
12 | RANDOMKEY | 从Redis返回一个随机的键。 |
13 | RENAME key newkey | 更改键的名称。 |
14 | RENAMENX key newkey | 如果新键不存在,重命名键。 |
15 | TYPE key | 返回存储在键中的值的数据类型。 |
Redis发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。
Redis 发布订阅(pub/sub)实现了消息系统,发送者(在redis术语中称为发布者)在接收者(订阅者)接收消息时发送消息。传送消息的链路称为信道。
在Redis中,客户端可以订阅任意数量的信道。
示例:
以下示例说明了发布用户概念的工作原理。 在以下示例中,一个客户端订阅名为“redisChat
”的信道。
SUBSCRIBE redisChat
现在,两个客户端在名称为“redisChat
”的相同信道上发布消息,并且上述订阅的客户端接收消息。
PUBLISH redisChat "Redis is a great caching technique"PUBLISH redisChat "Learn redis by yiibai"
下表列出了与Redis发布订阅相关的一些基本命令:
序号 | 命令 | 说明 |
---|---|---|
1 | PSUBSCRIBE pattern [pattern …] | 订阅一个或多个符合给定模式的频道 |
2 | PUBSUB subcommand [argument [argument …]] | 查看订阅与发布系统状态 |
3 | PUBLISH channel message | 将信息发送到指定的频道 |
4 | PUNSUBSCRIBE [pattern [pattern …]] | 退订所有给定模式的频道 |
5 | SUBSCRIBE channel [channel …] | 订阅给定的一个或多个频道的信息 |
6 | UNSUBSCRIBE [channel [channel …]] | 退订给定的频道 |
Redis的发布/订阅功能类似于传统的消息路由机制,发布者发布消息,订阅者接收消息,而连接发布者和订阅者之间的桥梁是订阅的Channel或Pattern。订阅者和发布者之间的关系是松耦合的,发布者不指定哪个订阅者才能接收消息,订阅者也不只接受特定发布者的消息。
示例:
> publish channel one0> subscribe channel one2
Redis事务允许在单个步骤中执行一组命令,以下是事务的两个属性:
语法:
Redis事务由命令MULTI
启动,然后需要传递一个应该在事务中执行的命令列表,然后整个事务由EXEC
命令执行。
redis 127.0.0.1:6379> MULTI OK List of commands here redis 127.0.0.1:6379> EXEC
下表列出了与Redis事务相关的一些基本命令:
序号 | 命令 | 说明 |
---|---|---|
1 | DISCARD | 丢弃在MULTI之后发出的所有命令 |
2 | EXEC | 执行MULTI后发出的所有命令 |
3 | MULTI | 标记事务块的开始 |
4 | UNWATCH | 取消 WATCH 命令对所有 key 的监视 |
5 | WATCH key [key …] | 监视给定的键以确定MULTI / EXEC块的执行 |
Redis脚本使用Lua解释器来执行。从Redis 2.6.0
版开始内置到Redis中,使用脚本的命令是EVAL
。
EVAL script numkeys key [key ...] arg [arg ...]
参数说明:
使用示例:
EVAL "return 'hello world'" 0
SCRIPT LOAD script
将一个脚本编译并且缓存起来,生成一个SHA1值并且返回,为了方便使用,参数script就是脚本内容货地址。
使用示例:
script load "return 'hello word'"
EVALSHA sha numkeys key [key ...] arg [arg ..]
与EVAL类似,执行一段脚本,区别是通过脚本的sha1值执行,去脚本缓存中查询,然后执行,参数说明:
sha1:就是脚本对应的sha1值;
redis.call(“命令名称”,参数1,参数2):执行redis命令,执行遇到错误会直接返回;
redis.pcall(“命令名称”,参数1,参数2):执行redis命令,遇到错误时,错误会以Lua脚本的方式返回;
Redis的内存占用主要可划分为以下几个部分:
info命令可以显示Redis服务器的基本信息,如CPU、内存、持久化、客户端链接等信息。
命令:
info memory# Memoryused_memory:689376 # 分配的内存总量,包括虚拟内存used_memory_human:673.22Kused_memory_rss:652480 # redis进程占用操作系统的内存,不包括虚拟内存used_memory_rss_human:637.19Kused_memory_peak:690160used_memory_peak_human:673.98Ktotal_system_memory:0total_system_memory_human:0Bused_memory_lua:37888used_memory_lua_human:37.00Kmaxmemory:0maxmemory_human:0Bmaxmemory_policy:noevictionmem_fragmentation_ratio:0.95 # 内存碎片比率mem_allocator:jemalloc-3.6.0 # 内存分配器,默认使用jemalloc
优化内存占用:
关注内存碎片率:
内存碎片率是一个非常重要的参数,对Redis内存的优化有很重要的意义。
如果内存碎片率过高(jemalloc在1.03左右比较正常),说明内存碎片过多,内存浪费较为严重,这时可以考虑重启Redis服务,在内存中对数据进行重排,以减少内存碎片。
如果内存碎片率小于1,说明Redis内存不足,部分数据使用了虚拟内存(swap),但由于虚拟内存的存取速度比物理内存要差很多(2-3个数量级),此时Redis的访问速度会变慢很多,因此必须考虑增大物理内存(增加服务器节点数量或提高单机内存),或减少Redis中的数据(选用合适的数据类型、利用共享对象或设置合理的数据回收策略等)。
过期策略:删除过期的key值。
淘汰策略:当内存使用达到maxmemory上限时,触发LRU(Least Recently Used,最近最少使用)淘汰清理内存数据。
数据持久化指的是为了防止因为断电等因素而导致数据丢失,将数据保存到磁盘上,当服务器重启时,可以通过读取磁盘来恢复数据。
Redis支持两种持久化方式,Snapshotting(快照,默认方式)和Append only file(aof)。
RDB(Redis Database Backup file,Redis数据备份文件):
当 Redis 持久化时,程序会将当前内存中的数据库状态保存到磁盘中。创建 RDB 文件主要有两个 Redis 命令:SAVE 和 BGSAVE。
SAVE是同步操作,执行命令时,会阻塞 Redis 服务器进程,拒绝客户端发送的命令请求。
BGSAVE是异步操作,执行命令时,子进程执行保存工作,服务器还可以继续让主线程处理客户端发送的命令请求。
载入工作在服务器启动时自动执行。
服务器在载入 RDB 文件期间,会一直处于阻塞状态,直到载入工作完成为止。
Redis 允许用户通过设置服务器配置的 save 选项,让服务器每隔一段时间自动执行一次 BGSAVE 命令。
配置命令如下:
save 900 1save 300 10
在这种情况下,只要满足以下条件中的一个,BGSAVE 命令就会被执行:
服务器程序会根据 save 选项所设置的保存条件,设置服务器状态 redisServer 结构的 saveparams
属性。
saveparams
属性是一个数组;
数组中的每一个元素都是一个 saveparam
结构;
每个 saveparam
结构都保存了一个 save
选项设置的保存条件。
struct saveparam { // 秒数 time_t seconds; // 修改数 int changes;}
dirty 计数器记录距离上一次成功执行 SAVE 命令或 BGSAVE 命令之后,服务器对数据库状态进行了多少次修改(包括写入、删除、更新等操作)。
UNINX
时间戳,记录了服务器上一次成功执行 SAVE 命令或者 BGSAVE 命令的时间。
服务器周期性操作函数 serverCron
(该函数对正在运行的服务器进行维护)默认每隔 100 毫秒就会执行一次,其中一项工作就是检查 save 选项所设置的保存条件是否已经满足,满足的话就执行BGSAVE
命令。
RDB文件的默认配置如下:
################################ SNAPSHOTTING ################################## Save the DB on disk:#在给定的秒数和给定的对数据库的写操作数下,自动持久化操作。# save # save 900 1save 300 10save 60 10000#bgsave发生错误时是否停止写入,一般为yesstop-writes-on-bgsave-error yes#持久化时是否使用LZF压缩字符串对象?rdbcompression yes#是否对rdb文件进行校验和检验,通常为yesrdbchecksum yes# RDB持久化文件名dbfilename dump.rdb#持久化文件存储目录dir ./
AOF全称为 Append Only File(追加日志文件)。日志是写后日志,Redis 是先执行命令,把数据写入内存,然后才记录日志。
AOF 持久化流程实现主要是通过以下流程来实现的:
若 AOF 持久化功能处于打开状态,服务器在执行完一个命令后,会以协议格式将被执行的写命令追加到服务器状态的 aof_buf 缓冲区的末尾。
服务器每次结束一个事件循环之前,都会调用 flushAppendOnlyFile
函数,这个函数会考虑是否需要将 aof_buf 缓冲区中的内容写入和保存到 AOF 文件里。
flushAppendOnlyFile
函数执行以下流程:
每条命令都会 fsync 到硬盘中,这样 redis 的写入数据就不会丢失。
每秒都会刷新缓冲区到硬盘中(默认值)
根据当前操作系统的规则决定什么时候刷新到硬盘中,不需要我们来考虑。
大致过程:
为何需要文件重写:
实现原理:
为不阻塞父进程,Redis 将 AOF 重写程序放到子进程里执行。 在子进程执行 AOF 重写期间,服务器进程需要执行三个流程:
AOF 文件的默认配置如下:
############################## APPEND ONLY MODE ################################开启AOF持久化方式appendonly no#AOF持久化文件名appendfilename "appendonly.aof"#每秒把缓冲区的数据fsync到磁盘appendfsync everysec# appendfsync no#是否在执行重写时不同步数据到AOF文件no-appendfsync-on-rewrite no# 触发AOF文件执行重写的增长率auto-aof-rewrite-percentage 100#触发AOF文件执行重写的最小sizeauto-aof-rewrite-min-size 64mb#redis在恢复时,会忽略最后一条可能存在问题的指令aof-load-truncated yes#是否打开混合开关aof-use-rdb-preamble yes
简而言之:
Redis的主从复制机制没有动态选举Master节点的能力,主挂了服务就不可以再写数据。仅仅增加了应用读数据的并发量,同时做数据备份的能力。
**注意:**一般生产环境会采用 哨兵 或者 Redis Cluster 这种具备Master自动选举的方案。
优缺点
优点:
缺点:
用户可以通过 SLAVEOF
命令或者配置的方式,让一个服务器去复制另一个服务器即成为它的从服务器。
主redis无需任何配置,从机需要修改redis.conf文件中如下配置项:
port 6378 # 如果是使用的一台机器注意端口要与主机不同# slaveof # 表示当前【从服务器】对应的【主服务器】的IP是192.168.10.135,端口是6379。slaveof 192.168.137.6 6379
Redis的从服务器在向主服务器发起同步时,一般会使用 SYNC
或 PSYNC
命令。
过程:
PSYNC命令具有完整同步(full resynchronization) 和 部分同步(partial resynchronization)两种模式:
当主服务器中断服务后,可以将一个从服务器升级为主服务器,以便继续提供服务,但是这个过程需要人工手动来操作。 为此,Redis 2.8中提供了哨兵工具来实现自动化的系统监控和故障恢复功能。
哨兵的作用就是监控Redis系统的运行状况。它的功能包括以下两个:
优点:
缺点:
Sentinel服务器启动之后便会创建于主服务器的命令连接,并订阅主服务器的sentinel:hello频道以创建订阅连接。
Sentinel默认会每10秒向主服务器发送 INFO 命令,主服务器则会返回主服务器本身的信息,以及其所有从服务器的信息。
根据返回的信息,Sentinel服务器如果发现有新的从服务器上线后也会像连接主服务器时一样,向从服务器同时创建命令连接与订阅连接。
每一个Sentinel服务器每秒会向其连接的所有实例包括主服务器,从服务器,其他Sentinel服务器)发送 PING
命令,根据是否回复 PONG
命令来判断实例是否下线。
如果实例在收到 PING
命令的down-after-milliseconds毫秒内(根据配置),未有有效回复。则该实例将会被发起 PING
命令的Sentinel认定为主观下线。
当一台主服务器没某个Sentinel服务器判定为客观下线时,为了确保该主服务器是真的下线,Sentinel会向Sentinel集群中的其他的服务器确认,如果判定主服务器下线的Sentinel服务器达到一定数量时(一般是N/2+1),那么该主服务器将会被判定为客观下线,需要进行故障转移。
当有主服务器被判定客观下线后,Sentinel集群会选举出一个领头Sentinel服务器来对下线的主服务器进行故障转移操作。整个选举其实是基于RAFT一致性算法而实现的,大致的思路如下:
领头服务器会从从服务中挑选出一个最合适的作为新的主服务器。挑选的规则是:
挑选出新的主服务器后,领头服务器将会向新主服务器发送 SLAVEOF no one
命令将其真正升级为主服务器,并且修改其他从服务器的复制目标,将旧的主服务器设为从服务器,以此达到故障转移的目标。
Redis的哨兵模式基本已经可以实现高可用,读写分离 ,但是在这种模式下每台Redis服务器都存储相同的数据,很浪费内存,所以在Redis3.0上加入了cluster模式,实现的Redis的分布式存储,也就是说每台Redis节点上存储不同的内容。
Redis-Cluster采用无中心结构,特点如下:
在Redis的每一个节点上,都有这么两个东西,一个是插槽(slot),它的的取值范围是:0-16383。还有一个就是cluster,可以理解为是一个集群管理的插件。当我们的存取的key到达的时候,Redis会根据crc16的算法得出一个结果,然后把结果对 16384 求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,通过这个值,去找到对应的插槽所对应的节点,然后直接自动跳转到这个对应的节点上进行存取操作。
为了保证高可用,redis-cluster集群引入了主从模式,一个主节点对应一个或者多个从节点,当主节点宕机的时候,就会启用从节点。当其它主节点ping一个主节点A时,如果半数以上的主节点与A通信超时,那么认为主节点A宕机了。如果主节点A和它的从节点A1都宕机了,那么该集群就无法再提供服务了。
缓存的主要用途有两个方面:高性能和高并发。
缓存的应用宽泛,可将用于数据高速交换的存储介质称之为缓存。
缓存在使用过程中不可避免会遇到一些问题,对于高频的问题可将其大概归为了7类:
当业务系统查询数据时,首先会查询缓存,如果缓存中数据不存在,然后查询DB再将数据预热到Cache中,并返回。缓存的性能比 DB 高 50~100 倍以上。
很多业务场景,如:秒杀商品、微博热搜排行、或者一些活动数据,都是通过跑任务方式,将DB数据批量、集中预热到缓存中,缓存数据有着近乎相同的过期时间。当过这批数据过期时,会一起过期,此时,对这批数据的所有请求,都会出现缓存失效,从而将压力转嫁到DB,DB的请求量激增,压力变大,响应开始变慢。
解决方案:
从缓存的过期时间入口,将原来的固定过期时间,调整为过期时间=基础时间+随机时间,让缓存慢慢过期,避免瞬间全部过期,对DB产生过大压力。
不是所有的请求都能查到数据,不论是从缓存中还是DB中。假如黑客攻击了一个论坛,用了一堆肉鸡访问一个不存的帖子id
。按照常规思路,每次都会先查缓存,缓存中没有,接着又查DB,同样也没有,此时不会预热到Cache中,导致每次查询,都会cache miss
。
由于DB的吞吐性能较差,会严重影响系统的性能,甚至影响正常用户的访问。
解决方案:
方案一:查存DB 时,如果数据不存在,预热一个特殊空值
到缓存中。这样,后续查询都会命中缓存,但是要对特殊值,解析处理。
方案二:构造一个BloomFilter
过滤器,初始化全量数据,当接到请求时,在BloomFilter
中判断这个key是否存在,如果不存在,直接返回即可,无需再查询缓存和DB
缓存雪崩是指部分缓存节点不可用,进而导致整个缓存体系甚至服务系统不可用的情况。
分布式缓存设计一般选择一致性Hash
,当有部分节点异常时,采用 rehash
策略,即把异常节点请求平均分散到其他缓存节点。但是,当较大的流量洪峰到来时,如果大流量 key 比较集中,正好在某 1~2 个缓存节点,很容易将这些缓存节点的内存、网卡过载,缓存节点异常 Crash,然后这些异常节点下线,这些大流量 key 请求又被 rehash 到其他缓存节点,进而导致其他缓存节点也被过载 Crash,缓存异常持续扩散,最终导致整个缓存体系异常,无法对外提供服务。
解决方案:
方案一:增加实时监控,及时预警。通过机器替换、各种故障自动转移策略,快速恢复缓存对外的服务能力;
方案二:缓存增加多个副本,当缓存异常时,再读取其他缓存副本。为了保证副本的可用性,尽量将多个缓存副本部署在不同机架上,降低风险。
解决方法:
对于突发事件,大量用户同时去访问热点信息,这个突发热点信息所在的缓存节点就很容易出现过载和卡顿现象,甚至 Crash
,我们称之为缓存热点。
这个在新浪微博经常遇到,某大V明星出轨、结婚、离婚,瞬间引发数百千万的吃瓜群众围观,访问同一个key,流量集中打在一个缓存节点机器,很容易打爆网卡、带宽、CPU的上限,最终导致缓存不可用。
解决方案:
热key
来,比如通过Spark
实时流分析,及时发现新的热点key
;有序编号
,比如key#01
、key#02
…key#10
多个副本,这些加工后的key位于多个缓存节点上;可以设计一个缓存服务治理管理后台,实时监控缓存的SLA,并打通分布式配置中心,对于一些hot key
可以快速、动态扩容。
当访问缓存时,如果key对应的value过大,读写、加载很容易超时,容易引发网络拥堵。另外缓存的字段较多时,每个字段的变更都会引发缓存数据的变更,频繁的读写,导致慢查询。如果大key过期被缓存淘汰失效,预热数据要花费较多的时间,也会导致慢查询。
所以在设计缓存时,要注意缓存的粒度
,既不能过大,如果过大,容易导致网络拥堵;也不能过小,如果太小,查询频率会很高,每次请求都要查询多次。
解决方案:
方案一:设置一个阈值,当value的长度超过阈值时,对内容启动压缩,降低kv的大小;
方案二:评估大key
所占的比例,由于很多框架采用池化技术
,如:Memcache,可以预先分配大对象空间。真正业务请求时,直接拿来即用;
方案三:颗粒划分,将大key拆分为多个小key,独立维护,成本会降低不少;
方案四:大key要设置合理的过期时间,尽量不淘汰那些大key;
缓存是用来加速的,一般不会持久化储存。所以,一份数据通常会存在DB
和缓存
中,由此会带来一个问题,如何保证这两者的数据一致性。另外,缓存热点问题会引入多个副本备份,也可能会发生不一致现象。
解决方案:
方案一:当缓存更新失败后,进行重试,如果重试失败,将失败的key写入MQ消息队列,通过异步任务补偿缓存,保证数据的一致性;
方案二:设置一个较短的过期时间,通过自修复的方式,在缓存过期后,缓存重新加载最新的数据;
互联网系统典型的特点就是流量大,一旦缓存中的数据过期、或因某些原因被删除等,导致缓存中的数据为空,大量的并发线程请求(查询同一个key)就会一起并发查询数据库
,数据库的压力陡然增加。
如果请求量非常大,全部压在数据库,可能把数据库压垮,进而导致整个系统的服务不可用。
解决方案:
方案一:
引入一把全局锁
,当缓存未命中时,先尝试获取全局锁,如果拿到锁,才有资格去查询DB
,并将数据预热到缓存中。虽然,client端发起的请求非常多,但是由于拿不到锁,只能处于等待状态,当缓存中的数据预热成功后,再从缓存中获取;
方案二:缓存数据创建多个备份,当一个过期失效后,可以访问其他备份;
<dependency> <groupId>com.suning.frameworkgroupId> <artifactId>snf-redis-clientartifactId> <version>2.5.1version> <exclusions> <exclusion> <groupId>com.suning.frameworkgroupId> <artifactId>snf-scm-clientartifactId> exclusion> <exclusion> <groupId>com.suning.frameworkgroupId> <artifactId>snf-sedisartifactId> exclusion> exclusions>dependency><dependency> <groupId>com.suning.frameworkgroupId> <artifactId>snf-sedisartifactId> <version>1.4.1version> <exclusions> <exclusion> <groupId>com.suning.frameworkgroupId> <artifactId>snf-statisticsartifactId> exclusion> exclusions>dependency>