Redis的原理及应用
Posted ran_D
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Redis的原理及应用相关的知识,希望对你有一定的参考价值。
redis原理及应用
一、redis来源
二、数据类型
三、主流的应用场景
四、特性
五、补充
一、 redis来源
作者:Salvatore Sanfilippo (antirez),男,意大利人.
需求: 一个访客信息追踪网站,网站可以通 过 javascript 脚本,将访客的 IP 地 址、所属国家、阅览器信息、被访问页 面的地址等数据传送给 LLOOGG. com 。 然后 LLOOGG.com 会将这些浏览数 据通过 web 页面实时地展示给用户, 并储存起最新的 5 至 10,000 条浏览 记录以便进行查阅。
redis解决方案
每当某个被追踪的网站新增一条 浏览记录时, LLOOGG.com 就会将这条新的浏览记录推入 (push)到与该网站相对应的列表里面,当列表的 长度超过用户指定的最大长度时,程序每向 列表推入一条新的记录,就需要从列表中弹出(pop)一条最旧的记录。
现在已经被广泛使用:
Twitter 使用 Redis 来储存用户时间线(user timeline)。
StackOverflow 使用 Redis 来进行缓存和消息分发。
Pinterest 使用 Redis 来构建关注模型(follow model)和兴趣图谱(interest graph)。
Flickr 使用 Redis 来构建队列。
Github 使用 Redis 作为持久化的键值对数据库,并使用 Resque 来实现消息队列。
新浪微博使用 Redis 来实现计数器、反向索引、排行榜、消息 队列,并储存用户关系。
知乎使用 Redis 来进行计数、缓存、消息分发和任务调度。
掌上医讯使用redis来进行缓存,分布式锁等。
阿里云、百度云、Amazon、 RedisLab 等公司都提供了基于 Redis 的应用服务。
二、支持的数据类型
Redis 所有的数据结构都是以唯一的 key 字符串作为名称,然后通过这个唯一 key 值来获取相应的 value 数据。不同类型的数据结构的差异就在于 value 的结构不一样。
a.字符串
常用命令 SET key value [NX|XX] 保存值
SETNX key value 命令仅在键 key 不存在的情况下,才进行设置操作
SETXX key value 命令仅在键 key 存在的情况下,才进行设置操作
APPEND key value
STRLEN key
get key
SETRANGE key index value 根据索引设置值
GETRANGE key start end
字符串结构使用非常广泛,一个常见的用途就是缓存用户信息。我们将用户信息结构体使用 JSON 序列化成字符串,然后将序列化后的 字符串塞进 Redis 来缓存。同样,取用户信息会经过一次反序列化的过程。
struct SDS<T> {
T capacity; // 数组容量
T len; // 数组长度
byte flags; // 特殊标识位,
byte[] content; // 数组内内容
}
Redis 的字符串是动态字符串,是可以修改的字符串,内部结构实现上类似于 Java 的 ArrayList,采用预分配冗余空间的方式来减少内存的频繁分配,如图中所示,内部为当前字符串实际分配的空间 capacity 一般要高于实际字符串长度 len。当字符串长度小于 1M 时,扩容都是加倍现有的空间,如果超过 1M,扩容时一次只会多扩 1M 的空间。需要注意的是字符串最大长度为 512M。
存储二进制数据相关的命令
SETBIT key index value
GETBIT key index
BITCOUNT key [start] [end]
b 列表
Redis 的列表相当于 Java 语言里面的 LinkedList,注意它是链表而不是数组。这意味着 list 的插入和删除操作非常快
常用的命令有 LPUSH key value [value ...]
LPOP key
RPUSH key value [value ...]
RPOP key
LLEN key
LINDEX key index
LRANGE key start stop
LSET key index value
LREM key count value
LTRIM key start stop
阻塞弹出
BLPOP key [key ...] timeout
Redis 的列表结构常用来做异步队列使用。将需要延后处理的任务结构体序列化成字符串塞进 Redis 的列表,另一个线程从这个列表中轮询数据进行处理
hash 字典
Redis 的字典相当于 Java 语言里面的 HashMap,它是无序字典。内部实现结构上同 Java 的 HashMap 也是一致的
常用指令:HSET key field value
HGET key field
HEXISTS key field
HDEL key field [field ...]
HLEN key
hash 结构也可以用来存储用户信息,不同于字符串一次性需要全部序列化整个对象,hash 可以对用户结构中的每个字段单独存储。这样当我们需要获取用户信息时可以进行部分获取。而以整个字符串的形式去保存用户信息的话就只能一次性全部读取,这样就会比较浪费网络流量...
集合(set)
Redis 的集合相当于 Java 语言里面的 HashSet,它内部的键值对是无序的唯一的。它的内部实现相当于一个特殊的字典,字典中所有的 value 都是一个值NULL
。
用户可以速地向集合添加元素,或者从集合里面 删除元素,也可以对多个集合进行集合运算操作,比 如计算并集、交集和差集。
SDIFF key [key ...] 计算所有给定集合的差集,并返回结果。
SDIFF key [key ...] 计算所有给定集合的差集,,并将结果储存到 destkey 。
SINTER key [key ...] 计算所有给定集合的交集,并返回结果
SINTERSTORE destkey key [key ...] 计算所有给定集合的交集,并将结果储存到 destkey
SUNION key [key ...] 计算所有给定集合的并集,并返回结果
SUNIONSTORE destkey key [key ...] 计算所有给定集合的并集,并将结果储存到 destkey
使用集合实现共同关注功能
使用集合可以实现商品筛选功能
有序集合
有序集合和集合一样,都可以包含任意数量的、各不相同的元素( element),不同于集合的是,有序集 合的每个元素都关联着一个浮点数格式的分 值(score),并且有序集合会按照分 值,以从小到大的顺序 来排列有序集合中的各个元素。 虽然有序集合中的每个元素都必 须是各不相同的,但元素的分 值并没有这一限制,换句话来说,两个不 同元素的分值可以是相同的。
ZADD key score element [[score element] [score element] ...]
ZREM key element [element ...]
ZSCORE key element 返回分值
ZINCRBY key increment element 添加分值 ZINCRBY fruits-price 1.5 西瓜 3.5
ZCARD key 返回有序集合包含的元素数量
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count] 获取指定分值范围内的升序元素
ZCOUNT key min max 计算给定分值范围内的元素数量
zset 可以用来存粉丝列表,value 值是粉丝的用户 ID,score 是关注时间。我们可以对粉丝列表按关注时间进行排序
ZUNIONSTORE destkey numkeys key [key ...] 计算多个集合的并集
eg:水果的销售情况:
HyperLogLog
需求:记录网站每天获得的独立 IP 数量
集合方式:
SADD ‘2018.9.10::unique::ip‘ ip 加入IP
SCARD ‘20148:9:10:unique::ip‘ 计算独立IP数量
集合实现的问题
使用字符串来储存每个 IPv4 地址最多需要耗费 15 字节(格式为 ‘XXX.XXX.XXX.XXX‘ ,比如 ‘202.189.128.186‘)。下表给给出了不同用户量使用内存的数量
随着集合记录的 IP 越来越多,消耗的内存也会越来越多。 另外如果要储存 IPv6 地址的话,需要的内存还会更多一些。为了解决此类问题,Redis 提供了 HyperLogLog 数据结构就是用来解决这种统计问题的。
HyperLogLog 提供了两个指令 pfadd 和 pfcount
pfadd key value 增加对象
pfcount key 获取计数
HyperLogLog 是不精确的去重计数方案,虽然不精确但是也不是非常不精确,标准误差是 0.81%,重点是省空间,总共占用的内存是12k.关于该算法的介绍https://blog.csdn.net/firenet1/article/details/77247649
发明这个算法的牛人,名字叫Philippe Flajolet 。
pfmerge 用于将多个 pf 计数值累加在一起形成一个新的 pf 值。
比如获取两个网站的合并起来的独立ip。
主流的应用场景
1、缓存
2、分布式锁
3、 消息列队
4、位图
在我们平时开发过程中,会有一些 bool 型数据需要存取,比如用户一年的签到记录,签了是 1,没签是 0,要记录 365 天。如果使用普通的 key/value,每个用户要记录 365 个,当用户上亿的时候,需要的存储空间是惊人的。 为了解决这个问题,Redis 提供了位图数据结构,这样每天的签到记录只占据一个位,365 天就是 365 个位,46 个字节 (一个稍长一点的字符串) 就可以完全容纳下,这就大大节约了存储空间。
5、HyperLogLog
6、geoHash 附近的人
- geoadd 为成员增加某个地理位置的坐标
- geodist 获取两个成员之间的地理位置的距离,可以设置距离单位 m:米 ,km:千米,ft:英尺 ,mi:英里
- geohash 获取某个地理位置的hash值
- geopos 获取某个成员的地理位置的坐标
- georadius 根据给定地理位置坐标获取指定范围内的地理位置集合
- WITHCOORD:传入WITHCOORD参数,则返回结果会带上匹配位置的经纬度。
- WITHDIST:传入WITHDIST参数,则返回结果会带上匹配位置与给定地理位置的距离。
- ASC|DESC:默认结果是未排序的,传入ASC为从近到远排序,传入DESC为从远到近排序。
- WITHHASH:传入WITHHASH参数,则返回结果会带上匹配位置的hash值。
- COUNT count:传入COUNT参数,可以返回指定数量的结果。
- georadiusbymember 根据给定成员获取指定范围内的地理位置集合,相对于georadius命令,使用起来比较方便.参数基本一样
7、排行榜
8、关注点赞
四、特性
1、快
2、支持多种数据类型
3、过期键功能
4、支持持久化
5、管道
6、支持主从模式
7、sentinel高可用
8、Cluster集群
快!!!内存存储,不受IO到硬盘IO速度限制 速度极快!
采用了非阻塞 I/O 多路复用机制 极大增加访问速度。
过期键功能
设置生存时间 EXPIRE 命令和 PEXPIRE 命令。 SETEX 命令 PSETEX 命令
设置过期时间 EXPIREAT 命令和 PEXPIREAT 命令。
查看剩余生存时间 TTL 命令和 PTTL 命令。
删除生存时间或过期时间 PERSIST 命令。
应用:1、自动更新的缓存
2、自动刷新的排行榜:在有序集合中,通过给日排行榜设置生存时间,我们可以 让 Redis 在每个新的一天开始时,自动删除旧的排行榜。
过期删除机制:
redis 会将每个设置了过期时间的 key 放入到一个独立的字典中,以后会定时遍历这个字典来删除到期的 key。除了定时遍历之外,它还会使用惰性策略来删除过期的 key,所谓惰性策略就是在客户端访问这个 key 的时候,redis 对 key 的过期时间进行检查,如果过期了就立即删除。定时删除是集中处理,惰性删除是零散处理。
定时扫描:
Redis 默认会每秒进行十次过期扫描,过期扫描不会遍历过期字典中所有的 key,而是采用了一种简单的贪心策略。
1、从过期字典中随机 20 个 key;
2、 删除这 20 个 key 中已经过期的 key;
3、 如果过期的 key 比率超过 1/4,那就重复步骤 1;
注意:如果有大批量的 key 过期,要给过期时间设置一个随机范围,而不能全部在同一时间过期。否则可能会系统资源不足,造成卡顿。
支持持久化
Redis 的数据全部在内存里,如果突然宕机,数据就会全部丢失,因此必须有一种机制来保证 Redis 的数据不会因为故障而丢失,这种机制就是 Redis 的持久化机制。
如果我们仅仅是将 Redis 用作缓存的话,那么这种数据丢失带来的问题并不是非常大,我们只需要重 启机器,然后再次将数据放到 缓存里面就可以了;但如果我 们将 Redis 用作数据库的话,那么这种数据 丢失就不能接受了。
Redis 的持久化机制有两种:
第一种是快照 RDB,全量备份 记录数据
那么 Redis 服务器在什么时候才会创建 RDB 文件呢?
在 Redis 服务器创建 RDB 文件的情况中,以下三种是最常 见的:
1. 服务器执行客户端发送的 SAVE 命令; 手动 阻塞 快
2. 服务器执行客户端发送的 BGSAVE 命令;手动 不阻塞 慢
3. 使用 save 配置选项设置的自动保存条件被满足,服务器自动执行 BGSAVE 。自动 不阻塞
BGSAVE 命令不会?成服务器阻塞的原因在于:
1. 当 Redis 服务器接收到 BGSAVE 命令的时候,它不会自己来创建 RDB 文件,而是通过 fork() 来生 成一个子进程,然后由子进程负责创建 RDB 文件,而自己则继续处理客户端的命令请求;
2. 当子进程创建好 RDB 文件并退出时,它会向父进程(也即是负责处理命令请求的 Redis 服务器)发 送一个信号,告知它 RDB 文件已经创建完毕;
3. 最后 Redis 服务器(父进程)接收子进程创建的 RDB 文件,BGSAVE 执行完毕。
自动BGSAVE的条件
save 900 1
save 300 10
save 60 10000
表示“如果距离上一次创建 RDB 文件已经过去了 900 秒,并且服务器的所有数据库总共已经发生了 不少于 1 次修改,那么执行 BGSAVE 命令”。
RDB持久化策略的不足:
因 为创建 RDB 文件需要将服务器所有数据库的数据都保存起来, 这是一个非常耗费资源和时间的操作,所以服务器需要隔一段时间才创建一个新的 RDB 文件,也即 是说,创建 RDB 文件的操作不能执行得过于频繁,否则就会严重地影响服务器的性能。 比如说,在 save 配置选项的默认设置下,即使有超过 10000 次修改操作发生,服务器也至少会间隔 一分钟才创建下一个 RDB 文件: save 900 1 save 300 10 save 60 10000 如果在等待创建下一个 RDB 文件的过程中,服务器遭遇了意外停机,那么用 户将丢失最后一次创建 RDB 文件之后,数据库发生的所有修改。
第二种是 AOF 日志 增量备份 记录指令
AOF 持久化有一个巨大的优势,那就是,用户可以根据自己的需要对 AOF 持 久化进行调整,让 Redis 在遭遇意外停机时不丢失任何数据,或者只丢失一秒钟数据,这比 RDB 持 久化遭遇意外停机时,丢失的数据要少得多。
配置 相关配置
appendonly yes no#开启AOF模式
appendfilename "appendonly.aof" #保存数据的AOF文件名称
appendfsync always everysec no
原理
AOF 持久化保存数据库数据的方法是:每当有修改数据 库的命令被执行时,服务器就会将被执行的命 令写入到 AOF 文件的末尾。下次服务启动时还原。
AOF重写-----给文件瘦身
AOF 重写的触发
有两种方法可以触发 AOF 重写:
1. 客户端向服务器发送 BGREWRITEAOF 命令。
2. 通过设置配置选项来让服务器自动执行 BGREWRITEAOF 命令,它们分别是:
• auto-aof-rewrite-min-size ,触发 AOF 重写所需的最小体积:只有在 AOF 文件的体积 大于等于 size 时,服务器才会考虑是否需要进行 AOF 重写。这个选项用于避免对体积过小的 AOF 文件进行重写。
• auto-aof-rewrite-percentage ,指定触发重写所需的 AOF 文件体积百分比:当 AOF 文件的体积大于 auto-aof-rewrite-min-size 指定的体积,并且超过上一次重写之后的 AOF 文件 体积的 percent% 时,就会触发 AOF 重写。(如果服务器刚刚启动不久,还没有进行过 AOF 重 写,那么使用服务器启动时载入的 AOF 文件的体积来作为基准值。)将这个值设置为 0 表示关 闭自动 AOF 重写。
管道
在一般情况下, 用户每执行一个 Redis 命令,客户端与服务器都需要进行一次通信:客户端会将命令 请求发送给服务器,而服务器则会将执行命令所得的结果返回给客户端。 当程序执行一些复杂的操作时, 客户端可能需要执行多个命令, 并与服务器进行多次通信。
Redis 的流水线功能允许客户端一次将多个命令请求发送给服务器, 并将被执行的多个命令请求的结 果在一个命令回复中全部返回 给客户端, 使用这个功能可以有效地减少客 户端在执行多个命令时需要 与服务器进行通信的次数。
支持主从模式
为了分担读压力,Redis支持主从复制,Redis的主从结构可以采用一主多从或者级联结构,Redis主从复制可以根据是否是全量分为全量同步和增量同步。
Redis 的复制(replication)功能允许用户根据一个 Redis 服务器来创建任意多个该服务器的复制品,其 中被复制的服务器为主服务器(master),而通过复制创建出来的服务器复制品则为从服务器(slave)。 主从服务器两者拥有相同的数据库数据:只要主从服务器之间的网络连接正常,主服务器就会一直将 发生在自己身上的数据更新同步 给从服务器,从而一直保证主从服务器的数据相同。
Redis 提供了两种方法来为某个主服务器创建从服务器:
1. 使用 SLAVEOF 命令,比如向一个服务器发送 SLAVEOF 127.0.0.1 6379 ,可以让接收到该命令的服务器变为 127.0.0.1:6379 的从服务器。 在将一个服务器设置成从服务器之后,可以通过向它发送 SLAVEOF no one 来让它变回一个主 服务器(数据库已有的数据会被保留)。
2. 在启动服务器时,通过设置 slaveof 配置选项来让服务器成为指定 服务器的从服务器。
我配置的服务器分布:
6376 | 6377 | 6378 | 6379 |
master | slave | slave | slave |
配置并且启动主节点:
port 6376
daemonize yes
logfile "6376.log"
dbfilename "dump_6376.rdb"
dir "/var/redis/data/"
启动并重启从节点
port 6377
daemonize yes
logfile "6377.log"
dbfilename "dump-6377.rdb"
dir "/var/redis/data/"
slaveof 127.0.0.1 6376 // 从属主节点
查看服务状态,登陆客户端,查看主从关系 INFO replication
sentinel高可用
监视主从服务器,并在主服务器下线时自动进行故障转移.
它负责持续监控主从节点的健康,当主节点挂掉时,自动选择一个最优的从节点切换为主节点。客户端来连接集群时,会首先连接 sentinel,通过 sentinel 来查询主节点的地址,然后再去连接主节点进行数据交互。当主节点发生故障时,客户端会重新向 sentinel 要地址,sentinel 会将最新的主节点地址告诉客户端。如此应用程序将无需重启即可自动完成节点切换。比如上图的主节点挂掉后,集群将可能自动调整为下图所示结构。
部署sentinel集群
sentinel26379 | sentinel26380 | sentinel26381 |
修改sentinel.conf文件
// Sentinel节点的端口
port 26379
dir /var/redis/data/
logfile "26379.log"
// 当前Sentinel节点监控 127.0.0.1:6379 这个主节点
// 2代表判断主节点失败至少需要2个Sentinel节点节点同意
// mymaster是主节点的别名
sentinel monitor mymaster 127.0.0.1 6376 2
//每个Sentinel节点都要定期PING命令来判断Redis数据节点和其余Sentinel节点是否可达,如果超过30000毫秒且没有回复,则判定不可达
sentinel down-after-milliseconds mymaster 30000
//故障转移超时时间为180000毫秒
sentinel failover-timeout mymaster 180000
启动sentinel节点
redis-sentinel sentinel.conf
确认登陆客户端确认:
redis-cli -h 127.0.0.1 -p 26379 INFO Sentinel
测试
相关指令 sentinel masters
sentinel slaves mymaster
Redis Sentinel
的以下几个功能。
- 监控:
Sentinel
节点会定期检测Redis
数据节点和其余Sentinel
节点是否可达。 - 通知:
Sentinel
节点会将故障转移通知给应用方。 - 主节点故障转移:实现从节点晋升为主节点并维护后续正确的主从关系。
- 配置提供者:在
Redis Sentinel
结构中,客户端在初始化的时候连接的是Sentinel
节点集合,从中获取主节点信息。
消息丢失
Redis 主从采用异步复制,意味着当主节点挂掉时,从节点可能没有收到全部的同步消息,这部分未同步的消息就丢失了。如果主从延迟特别大,那么丢失的数据就可能会特别多。Sentinel 无法保证消息完全不丢失,但是也尽可能保证消息少丢失。它有两个选项可以限制主从延迟过大。
min-slaves-to-write 1
min-slaves-max-lag 10
第一个参数表示主节点必须至少有一个从节点在进行正常复制,否 则就停止对外写服务,丧失可用性。 何为正常复制,何为异常复制?这个就是由第二个参数控制的,它的单位是秒,表示如果 10s 没有收到从节点的反馈,就意味着从节点同步不正常,要么网络断开了,要么一直没有给反馈。
Cluster集群
------复制特性可以创建指定服务器的复制品,这些复制品可以用于扩展系统处理读请求的能力。
------Redis Sentinel 可以在复制特性的基础上,通过监视主从服务器并在主服务器故障时执行自动故
障转移来保证系统的可用性。
写能力不足怎么办??????
分片技术
集群使用分片来扩展数据库的容量,并将命令请求的负载交给不同的节点来分担。
集群将整个数据库分为 16384 个槽(slot),所有键都属于这 16384 个槽的其中一个,计算键 key 属于哪个槽的公式为 slot_number = crc16(key) % 16384 ,其中 crc16 为 16 位的循环冗余校验和函数。
集群中的每个主节点都可以处理 0 个至 16384 个槽,当 16384 个槽都有某个节点在负责处理时,集群进入上线状态,并开始处理客户端发送的数据命令请求。
比如说,如果我们有三个主节点 7000 、 7001 和 7002 ,那么我们可以:
- 将槽 0 至 5460 指派给节点 7000 负责处理;
- 将槽 5461 至 10922 指派给节点 7001 负责处理;
- 将槽 10923 至 16383 指派给节点 7002 负责处理;
这样就可以将 16384 个槽平均地指派给三个节点负责处理。
请求转向:
对于一个被指派了槽的主 节点来说,这个主节点只会处理属于指派给自己的槽的命令请求。如果一个节点接收到了和自己处理的槽无关的命令请求,那么节点会向客户端返回一个转向错误(redirection error),告诉客户端,哪个节点才是负责处理这条命令的,之后客户端需要根据错误中包含的地址和端口号重新向正确的 节点发送命令请求。
集群搭建:
1、创建多个节点
2、为每个节点指派槽,并将多个节点连接起来,组成一个集群
3、当集群数据库的 16384 个槽都有节点在处理时,集群进入上线状态。
五、补充
1、应用场景不同,配置不同。
缓存:关闭aof化,rdb持久化视情况而定 ,开启最大内存设置(否则为物理内存)
数据库:开启 aof ,开启rdb 关闭最大内存设置
以上是关于Redis的原理及应用的主要内容,如果未能解决你的问题,请参考以下文章