Redis
2020-04-27 18:07:39 0 举报
AI智能生成
Redis
作者其他创作
大纲/内容
Redis
理论基石CAP
C - Consistent ,一致性
A - Availability ,可用性
P - Partition tolerance 分区容错性
网络分区发生时(网络断链),一致性和可用性两难全
生产解决方案
分布式之数据库和缓存双写一致性方案解析
集群下如何批量操作
简介
中文官网
http://www.redis.cn
Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。
总结
Nosql 非关系型数据库,key-value键值对存贮
分布式中间件
数据结构丰富
扩展功能(应用场景)丰富
高性能原理
Redis IO多路复用技术以及epoll实现原理
单个节点在跑满一个 CPU 核心的情况下可以达到了 10w/s 的超高 QPS
原因
1 单线程
避免了多线程的切换性能损耗问题
3 非阻塞I/O:IO多路复用
LINUX epoll ,select poll
默认采用epoll epoll采用的是回调机制
select 采用轮询机制
window iocp
异步IO
4 数据结构做了很多优化
数据类型及结构
https://www.cnblogs.com/MouseDong/p/11134039.html
底层数据结构
包含简单的动态字符串(SDS)、链表、字典、压缩列表、整数集合等等; 五大数据类型(数据对象)都是由一种或几种数结构构成。在命令行中可以使用 OBJECT ENCODING key 来查看key的数据结构。
数据结构
encoding 可通过OBJECT ENCODING 输出key的编码
String
Redis字符串类型内部编码剖析
数据结构及原理
简单动态字符串(SDS)和直接存储 ()类似java的字符串动态数组)
int
保存的是整数值
保存long 型的64位有符号整数
raw
非连续内存空间
保存长度大于44字节的字符串
embstr
连续内存空间
保存长度小于44字节的字符串
采用预留空间机制来支持字符串的扩容 实际分配的空间大于字符串本身的长度
最大512M 字符串长度小于 1M 时,扩容因子 2,是之前2倍 超过 1M,扩容时一次只会多扩 1M 的空间
常用操作
SET key value
//存入字符串键值对 返回值 OK
MSET key value [key value ...]
//批量存储字符串键值对
SETNX key value
//存入一个不存在的字符串键值对
GET key、
//获取一个字符串键值
MGET key [key ...]
//批量获取字符串键值
DEL key [key ...]
//删除一个键
EXPIRE key seconds
//设置一个键的过期时间(秒)
INCR key
//将key中储存的数字值加1
DECR key
//将key中储存的数字值减1
INCRBY key increment
//将key所储存的值加上increment
DECRBY key decrement
//将key所储存的值减去decrement
应用场景
对象缓存
SET user:1 value(json格式数据)
需要序列化和反序列化
分布式锁
SETNX product:10001 true
返回1代表获取锁成功 0 失败
DEL product:10001
//执行完业务释放锁
SET product:10001 true ex 10 nx
//防止程序意外终止导致死锁
session共享
spring session + redis实现session共享
分布式系统全局序列号
INCRBY orderId 1000
redis批量生成序列号提升性能
计数器
点赞,文章阅读数
INCR article:readcount:{文章id}
List
LinkList 链表存储
ziplist
元素较小时采用(连续的内存空间)
java arraylist
quicklist
java linklist
拥有链表的基本特征,增删快,查询慢O(n)
列表对象使用ziplist编码需要满足两个条件:一是所有字符串长度都小于64字节,二是元素数量小于512,不满足任意一个都会使用linkedlist编码。
两个条件的数字可以在Redis的配置文件中修改,list-max-ziplist-value选项和list-max-ziplist-entries选项。
LPUSH key value [value ...] \t
//将一个或多个值value插入到key列表的表头(最左边)
RPUSH key value [value ...]\t
LPOP key
//移除并返回key列表的头元素
RPOP key
LRANGE key start stop
//返回列表key中指定区间内的元素,区间以偏移量start和stop指定
BLPOP key [key ...] timeout
BRPOP key [key ...] timeout
实现数据结构
Stack(栈) = LPUSH + LPOP FILO
Queue(队列)= LPUSH + RPOP
Blocking MQ(阻塞队列)= LPUSH + BRPOP
互联网文章推送
文章推送
LPUSH msg:{张三-ID} 1001{文章ID}
LPUSH msg:{张三-ID} 1002{文章ID}
查看最新微博消息
LRANGE msg:{张三-ID} 0 5
Hash
编码方式
哈希对象保存的所有键值对的键和值的字符串长度都小于 64 字节;
哈希对象保存的键值对数量小于 512 个;
hashtable
不能满足ziplist这两个条件的哈希对象需要使用 hashtable 编码
以上两个条件可以在Reids配置文件中修改hash-max-ziplist-value选项和hash-max-ziplist-entries选项。
渐进式扩容rehash
渐进式 rehash 会在 rehash 的同时,保留新旧两个 hash 结构,查询时会同时查询两个hash 结构,然后在后续的定时任务中以及 hash 的子指令中,循序渐进地将旧 hash 的内容一点点迁移到新的 hash 结构中。
大多数情况下对象的存储用hash比string 的json保存格式好,string json需要序列化和反序列化
HSET key field value
//存储一个哈希表key的键值
HSETNX key field value
//存储一个不存在的哈希表key的键值
HMSET key field value [field value ...]
//在一个哈希表key中存储多个键值对
HGET key field
//获取哈希表key对应的field键值
HMGET key field [field ...]
//批量获取哈希表key中多个field键值
HDEL key field [field ...]
//删除哈希表key中的field键值
HLEN key
返回哈希表key中field的数量
HGETALL key
//返回哈希表key中所有的键值
value过大会严重影响性能
HSCAN
//分页返回哈希表key中所有的键值
HINCRBY key field increment \t
//为哈希表key中field键的值加上增量increment
HMSET user {userId}:name zhuge {userId}:balance 1888
HMSET user 1:name zhuge 1:balance 1888
HMGET user 1:name 1:balance
电商购物车
以用户id为key
商品id为field
商品数量为value
添加商品hset cart:1001 10088 1
增加数量 hincrby cart:1001 10088 1
商品总数 hlen cart:1001
删除商品 hdel cart:1001 10088
获取购物车所有商品 hgetall cart:1001
跟String对比
同类数据归类整合储存,方便数据管理
相比string操作消耗内存与cpu更小
相比string储存更节省空间
过期功能不能使用在field上,只能用在key上
Redis集群架构下不适合大规模使用
Set
无序唯一
intset
整形集合
集合对象使用intset编码需要满足两个条件:一是所有元素都是整数值;二是元素个数小于等于512个;不满足任意一条都将使用hashtable编码。
以上第二个条件可以在Redis配置文件中修改et-max-intset-entries选项。
普通操作
SADD key member [member ...]
//往集合key中存入元素,存在则忽略,否则新建
SREM key member [member ...]\t
//从集合key中删除元素
SMEMBERS key\t
//获取集合key中所有元素
SCARD key
//获取集合key的元素个数
SISMEMBER key member
//判断member元素是否存在于集合key中
SRANDMEMBER key [count]
命令用于返回集合中的COUNT个随机元素
SPOP key [count]
//移除并返回集合中的count个随机元素。
运算操作
SINTER key [key ...] \t
//交集运算
SINTERSTORE destination key [key ..]
//将交集结果存入新集合destination中
SUNION key [key ..]
//并集运算
SUNIONSTORE destination key [key ...]
//将并集结果存入新集合destination中
SDIFF key [key ...]
//差集运算
SDIFFSTORE destination key [key ...]
//将差集结果存入新集合destination中
微信小程序抽奖
点击参与抽奖加入集合
SADD key {userlD}
查看参与抽奖所有用户
SMEMBERS key\t
抽取count名中奖者
SRANDMEMBER key [count] / SPOP key [count]
微信微博点赞,收藏,标签
点赞
SADD like:{消息ID} {用户ID}
取消点赞
SREM like:{消息ID} {用户ID}
检查用户是否点过赞
SISMEMBER like:{消息ID} {用户ID}
获取点赞的用户列表
SMEMBERS like:{消息ID}
获取点赞用户数
SCARD like:{消息ID}
集合操作实现微博微信关注模型
共同关注
关注的人也关注他
SISMEMBER simaSet yangguo
SISMEMBER lubanSet yangguo
我可能认识的人:
集合操作实现电商商品筛选
SADD brand:huawei P30
SADD brand:xiaomi mi-6X
SADD brand:iPhone iphone8
SADD os:android P30 mi-6X
SADD cpu:brand:intel P30 mi-6X
SADD ram:8G P30 mi-6X iphone8
SINTER os:android cpu:brand:intel ram:8G {P30,mi-6X}
ZSET
skiplist编码的有序集合对象底层实现是跳跃表和字典两种
有序集合对象使用ziplist编码需要满足两个条件:一是所有元素长度小于64字节;二是元素个数小于128个;不满足任意一条件将使用skiplist编码。
以上两个条件可以在Redis配置文件中修改zset-max-ziplist-entries选项和zset-max-ziplist-value选项。
ZADD key score member [[score member]…]
往有序集合key中加入带分值元素
ZREM key member [member …]
从有序集合key中删除元素
ZSCORE key member
返回有序集合key中元素member的分值
ZINCRBY key increment member
为有序集合key中元素member的分值加上increment
ZCARD key
返回有序集合key中元素个数
ZRANGE key start stop [WITHSCORES]
正序获取有序集合key从start下标到stop下标的元素
ZREVRANGE key start stop [WITHSCORES]
倒序获取有序集合key从start下标到stop下标的元素
ZUNIONSTORE destkey numkeys key [key ...] \t//并集计算
ZINTERSTORE destkey numkeys key [key …]\t//交集计算
zrangebyscore zk1 10 40 #获取分数内的值
zrangebyscore zk1 (10 (40 #获取分数内的值不包含边界
点击新闻
ZINCRBY hotNews:20190819 1 守护香港
展示当日排行前十
ZREVRANGE hotNews:20190819 0 10 WITHSCORES
七日搜索榜单计算
ZUNIONSTORE hotNews:20190813-20190819 7
hotNews:20190813 hotNews:20190814... hotNews:20190819
展示七日排行前十
ZREVRANGE hotNews:20190813-20190819 0 10 WITHSCORES
Stream
Redis5.0以后新增的数据结构,支持多播的可持久化的消息队列,貌似还没有完全被推广,而且也不能完全取代现在流行的几个MQ中间件
列表(list)、哈希(hash)、集合(set)、有序集合(zset)底层实现都用到了压缩列表结构,并且使用压缩列表结构的条件都是在元素个数比较少、字节长度较短的情况下;
list/set/hash/zset 这四种数据结构是容器型数据结构
如果容器不存在,那就创建一个,再进行操作
如果容器里元素没有了,那么立即删除元素,释放内存
过期时间
过期是以对象为单位,比如一个 hash ,并非hash的key设置过期
调用了set 方法修改,过期时间会消失
通用命令
keys *
慎用,遍历算法,复杂度是 O(n),如果实例中有千万级以上的 key,这个指令就会导致 Redis 服务卡顿,因为redis是单线程
type key
del key
scan
exists key
expire keys seconds
dbsize
ttl
Shutdown
通信协议
RESP(Redis Serialization Protocol)
一种直观的文本协议
持久化方式
redis系列--redis4.0深入持久化
RDB快照(snapshot)
原理
全量备份,二进制存储,默认dump.rdb
使用操作系统的多进程 COW(Copy On Write) 机制来实现快照持久化
什么是多进程cow,简单的理解就是子进程在做持久化的时候父进程某个内容修改了,父进程在修改之前,对修改的内容涉及的页面进行copy了一份给子进程,保证子进程持久化不会因为父进程的内容修改而有影响
会导致内存随时膨胀两倍
编辑/etc/sysctl.conf ,改vm.overcommit_memory=1,然后sysctl -p使配置文件生效
命令
save
同步
阻塞
bgsave
异步
非阻塞
默认设置
主进程fork子进程,子进程进行
使用RDB恢复数据
dump.rdb 放到配置文件里的 dir 所在路径
redis-cli> config get dir
特点
体积小,恢复快,但容易丢数据
save 900 1
#900秒内变更1次才触发bgsave
AOF(append-only file)
内存数据修改的指令记录文本 增量备份
只记录对内存进行修改的指令记录
重写
随着时间增长,日志越来越大
bgrewriteaof 指令瘦身 fork子进程将多条语句进行合并
体积大,恢复慢,相对不容易丢数据
appendfsync no|everysecond|always
f同步备份的频率设置
auto-aof-rewrite-percentage 100
现有的文件比上次多出100%:上次压缩完2G,现在已经4G了如果是50%就是3G
auto-aof-rewrite-min-size 100mb
现有的文件已经超过100mb了
no-appendfsync-on-rewrite no
重写的时候是否要同步,yes则不同步,no同步阻塞可以保证数据安全
yes。Linux的默认fsync策略是30秒。可能丢失30秒数据。默认值为no。
混合持久化(4.x版本以后)
RDB+AOF
混合持久化同样也是通过bgrewriteaof完成的
新的AOF文件前半段是RDB格式的全量数据后半段是AOF格式的增量数据
配置文件开启 # aof-use-rdb-preamble yes
唯一的缺点就是不兼容redis旧版本
部署方式
单机
主从同步
全量同步
从服务器连接主服务器,发送SYNC命令;
主服务器接收到SYNC命名后,开始执行BGSAVE命令生成RDB文件并使用缓冲区记录此后执行的所有写命令;
主服务器BGSAVE执行完后,向所有从服务器发送快照文件,并在发送期间继续记录被执行的写命令;
从服务器收到快照文件后丢弃所有旧数据,载入收到的快照;
主服务器快照发送完毕后开始向从服务器发送缓冲区中的写命令;
从服务器完成对快照的载入,开始接收命令请求,并执行来自主服务器缓冲区的写命令;
全量备份分为落盘和不落盘两种形式,默认是落盘
repl-diskless-sync no # 不落盘复制配置 yes不落盘,no落盘
repl-diskless-sync-delay 5 # 等待其他slave连接的一个时间周期,单位是秒
部分复制(断点续传)
从2.8版本开始,slave与master能够在网络连接断开重连后只进行部分数据复制。支持部分数据复制的命令PSYNC去master同步数据
master会在其内存中创建一个复制数据用的缓存队列,缓存最近一段时间的数据,master和它所有的
slave都维护了复制的数据下标offset和master的进程id,因此,当网络连接断开后,slave会请求master
继续进行未完成的复制,从所记录的数据下标开始。如果master进程id变化了,或者从节点数据下标
offset太旧,已经不在master的缓存队列里了,那么将会进行一次全量数据的复制。
增量同步
Redis增量复制是指Slave初始化后开始正常工作时主服务器发生的写操作同步到从服务器的过程
增量复制的过程主要是主服务器每执行一个写命令就会向从服务器发送相同的写命令,从服务器接收并执行收到的写命令。
读写分离
主可以读写
从只能读
生产方案
一主一从
一主多从
链状架构
树形架构
用得比较少
缺点
如果主服务器宕机,没法自动切换,只能人工切换
哨兵模式(sentinel)
sentinel哨兵是特殊的redis服务,不提供读写服务,主要用来监控redis实例节点。哨兵架构下client端第一次从哨兵找出redis的主节点,后续就直接访问redis的主节点,不会每次都通过sentinel代理访问redis的主节点,当redis的主节点发生变化,哨兵会第一时间感知到,并且将新的redis主节点通知给client端(这里面redis的client端一般都实现了订阅功能,订阅sentinel发布的节点变动消息)
sentinel monitor <master-name> <ip> <redis-port> <quorum>
集群模式
主流的分布式寻址算法
hash算法
hash一致性
redis cluster 的 hash slot
Redis采用的主从 + hash slot
集群至少3个master
任何一台机器宕机,另外两个节点,不影响的。因为 key 找的是 hash slot,不是机器。
槽位定位算法
HASH_SLOT = CRC16(key) mod 16384
跳转重定位
当客户端向一个错误的节点发出了指令,该节点会发现指令的 key 所在的槽位并不归自己管理,这时它会向客户端发送一个特殊的跳转指令携带目标操作的节点地址,告诉客户端去连这个节点去获取数据。客户端收到指令后除了跳转到正确的节点上去操作,还会同步更新纠正本地的槽位映射表缓存,后续所有 key 将使用新的槽位映射表
节点间通信机制
维护集群的元数据有两种方式:集中式和gossip
redis cluster 采用的gossip协议
gossip协议包含多种消息,包括ping,pong,meet,fail等等
选举原理
在 cluster-node-timeout 内,某个节点一直没有返回 pong
对宕机的 master node,从其所有的 slave node 中,选择一个切换成 master node
集群必须至少配置3个主节点
方案1
SETNX product:10001 true
DEL product:10001
客户端挂了,锁没法释放,导致其他客户端没法获取锁
方案2
SETNX product:10001 true加上expire
两个指令,非原始操作,跟方案1一样,两个指令之间客户端挂了,也会导致死锁
分布式锁原理
方案3
SET product:10001 true ex 10 nx
两个指令合成一个指令,实现原子操作
通过设置过期时间保证客户端挂了没法释放锁
带来业务的未知性,例如设置了10秒超时,但是业务进行了10秒以上
导致业务全乱,锁会被上个超时线程释放,虽然释放锁时可通过线程判断来判定该锁是否本线程的锁但还是没法解决因为因为业务超时导致被其他线程乱入的解决方案
方案4
Redisson 第三方包
实现了分布式锁的同时实现了如果业务在规定时间内还没有完成,后台有线程自动发命令set 自动续时
基本解决了大多数因为客户端导致锁的问题,但是没法完全解决分布式锁其他问题,例如服务端挂了,锁申请了然后服务器挂了,哪怕服务器redis集群,但也有可能存在服务器在把锁的数据同步到从服务器上之前挂了,导致信息没法做到同步
方案5
使用zookeeper
非redis内容
假如线程获取锁失败
抛异常
自旋+sleep
放到延迟队列(通过zset结构)规划 ,所有业务放到延迟队列,多线程轮询获取,保证获取到锁的线程执行,获取不到的不执行,执行完把业务从zset结构上删除,多个线程公平竞争,哪怕某个线程挂了,也可以有其他线程替换上
队列
消息队列
blpop/brpop
会导致空闲连接 客户端超时
位图(bitmap)
($offset/8/1024/1024)MB
实现签到功能
bitcount
活跃数
bitop
HyperLogLog
基数统计
有误差
0.81
统计系统UV
pfadd
pfcount
pfmerge
hyperloglog的key占用空间很小只有12K,而用set就需要把这些value都要保存,set存一年数据有多大
布隆过滤器
用来解决缓存穿透
解决数据的重复问题
Redis 4.0
限流
Redis 实现限流的三种方式
简单限流
基于Redis的setnx的操作
setnx的时候可以设置过期时间10,当请求的setnx数量达到20时候即达到了限流效果
zet滑动窗口
漏斗限流
Redis-Cell限流算法
redis-cell
GeoHash
实现位置服务
查找附件的人,附件的餐馆
异步遍历,但没法保证数据的完全正确性
过期策略
Redis采用的是惰性删除和定期删除两种策略。
惰性删除:不管键是否过期,只有每次取值的时候,才检查是否过期,过期就删除。
定期删除:每隔一段时间,程序对数据库进行一次检查,过期的就删除。
持久化和复制对过期键的处理
RDB持久化:
主服务器:RDB文件无论是生成或载入,都会对过期键进行检查;生成时,过期键不写入;载入时,过期键会忽略。
从服务器:载入时,不会检查是否过期,数据都会载入。
AOF持久化
AOF文件写入时,键过期未删除,不影响;键过期已删除,则在AOF文件后追加DEL命令。
AOF重写
AOF重写过程中会进行检查,过且的键忽略。
复制
主从模式下,由主服务器进行删除过期键,并显示的向从服务器发送DEL命令;从服务器自身不具备删除过期键值行为。
内存淘汰机制
(1)noeviction:不淘汰,内存不足时, 新写入会报错。
(2)allkeys-lru:LRU,内存不足时,淘汰最近最少使用的key。
默认
3)allkeys-random:随机,内存不足时,在所有key中随机选择一个key淘汰。
4)volatile-lru:过期时间内LRU,内存不足时,在设置了过期时间的key中,淘汰最近最少使用的key。
(5)volatile-random:过期时间内随机,内存不足时,在设置了过期时间的key中,随机选择一个key淘汰。
(6)volatile-ttl:更早过期时间,内存不足时,在设置了过期时间的key中,选择有更早过期时间的key淘汰。
配置文件中进行配置maxmeory-samples
其他功能
管道Pipeline
客户端可以一次性发送多个请求而不用等待服务器的响应,待所有命令都发送完后再一次性读取服务的响应
管道中前面命令失败,后面命令不会有影响,继续执行
支持Lua脚本
融合redis,可以扩展很多功能,实现原子操作,但非必要
事务
Redis之Redis事务
一个打包的批量执行脚本 不支持回滚,非原子操作
慢查询日志
消息订阅与发布
消费者和生产者,类似MQ中间件,但功能比较简单,消息不支持持久化,实际应该用得不多,因为有其他消息中间件替代
配置信息
禁用危险命令
rename-command FLUSHALL \"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\"
rename-command FLUSHDB \"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\"
rename-command CONFIG \"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\"
rename-command KEYS \"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\"
key值过期后,触发通知事件
notify-keyspace-events Ex 这一行的注释打开
性能优化
key名设计
可读简洁
如业务名:表名:id
trade:order:1
user:{uid}:friends:messages:{mid} 简化为 u:{uid}:fr:m:{mid}
不要包含特殊符号
value设计
bigkey的危害:
redis 阻塞
网络阻塞
拒绝bigkey(防止网卡流量、慢查询)
字符串类型:它的big体现在单个value值很大,一般认为超过10KB就是bigkey
非字符串类型:哈希、列表、集合、有序集合,它们的big体现在元素个数太多。
一般来说,string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000
生产推荐
Master最好不要做任何持久化工作,如RDB内存快照和AOF日志文件
如果数据比较重要,某个Slave开启AOF备份数据,策略设置为每秒同步一次
为了主从复制的速度和连接的稳定性,Master和Slave最好在同一个局域网内
尽量避免在压力很大的主库上增加从库
主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <- Slave3...这样的结构方便解决单点故障问题,实现Slave对Master的替换。如果Master挂了,可以立刻启用Slave1做Master,其他不变。
缓存穿透
缓存空对象
缓存失效
分散过期时间
缓存雪崩
多级缓存架构
第三方ehcache
java Map等
dubbo等等第三方框架都会提供
缓存重建单点加jvm锁
数据预热
0 条评论
回复 删除
下一页