Redis技术手册(基础)
Rides技术手册
什么是redis?
redis是一个高性能的key-value数据库,它是完全开源免费的,而且redis是一个NOSQL类型数据库,是为了解决高并发、高扩展,大数据存储等一系列的问题而产生的数据库解决方案,是一个非关系型的数据库
Reids的特点
Redis本质上是一个Key-Value类型的内存数据库,很像memcached,整个数据库统统加载在内存当中进行操作,定期通过异步操作把数据库数据flush到硬盘上进行保存。
因为是纯内存操作,Redis的性能非常出色,每秒可以处理超过 10万次读写操作,是已知性能最快的Key-Value DB。
Redis的出色之处不仅仅是性能,Redis最大的魅力是支持保存多种数据结构,此外单个value的最大限制是1GB,不像 memcached只能保存1MB的数据,因此Redis可以用来实现很多有用的功能,比方说用他的List来做FIFO双向链表,实现一个轻量级的高性 能消息队列服务,用他的Set可以做高性能的tag系统等等。
另外Redis也可以对存入的Key-Value设置expire时间,因此也可以被当作一 个功能加强版的memcached来用。
Redis的主要缺点是数据库容量受到物理内存的限制,不能用作海量数据的高性能读写,因此Redis适合的场景主要局限在较小数据量的高性能操作和运算上。
String类型:一个String类型的value最大可以存储512M
List类型:list的元素个数最多为2^32-1个,也就是4294967295个。
Set类型:元素个数最多为2^32-1个,也就是4294967295个。
Hash类型:键值对个数最多为2^32-1个,也就是4294967295个。
Sorted set类型:跟Set类型相似。
面试回答
- 性能极高 - Redis 读速度 110000次/s,写的速度是 81000次/s。
- 丰富的数据类型 - Redis 支持的类型 String, Hash 、List 、Set 及 Ordered Set 数据类型操作。
- 原子性 - Redis 的所有操作都是原子性的,意思就是要么成功,要么失败。
- 单个操作时原子性的,多个操作也支持事务,即原子性,通过 MULTI 和 EXEC 指令包起来。
- 丰富的特性 - Redis 还支持 publis/subscribe,通知,key 过期等等特性。
- 高速读写 ,redis 使用自己实现的分离器,代码量很短,没有使用 lock(MySQL),因此效率非常高
Rides优缺点
优点:
- 读写性能好,读的速度可达110000次/s,写的速度可达81000次/s。
- 支持数据持久化,有AOF和RDB两中持久化方式
- 数据结构丰富,支持String、List、Set、Hash等结构
- 支持事务,Redis所有的操作都是原子性的,并且还支持几个操作合并后的原子性执行,原子性指操作要么成功执行,要么失败不执行,不会执行一部分。
- 支持主从复制,主机可以自动将数据同步到从机,进行读写分离。
缺点:
- 因为Redis是将数据存到内存中的,所以会受到内存大小的限制,不能用作海量数据的读写
- Redis不具备自动容错和恢复功能,主机或从机宕机会导致前端部分读写请求失败,需要重启机器或者手动切换前端的IP才能切换
- 持久化:Redis 直接将数据存储到内存中,要将数据保存到磁盘上,Redis 可以使用两种方式实现持久化过程。
- 定时快照(snapshot):每隔一段时间将整个数据库写到磁盘上,每次均是写全部数据,代价非常高。
- 第二种方式基于语句追加(aof):只追踪变化的数据,但是追加的 log 可能过大,同时所有的操作均重新执行一遍,回复速度慢。
- 耗内存,占用内存过高。
Redis 的应用场景
可以作为数据库,缓存热点数据(经常被查询,但是不经常被修改或者删除的数据)和消息中间件等大部分功能。
Redis 常用的场景示例如下:
1、缓存
缓存现在几乎是所有大中型网站都在用的必杀技,合理利用缓存提升网站的访问速度,还能大大降低数据库的访问压力。Redis 提供了键过期功能,也提供了灵活的键淘汰策略,所以,现在 Redis 用在缓存的场合非常多。
2、排行榜
Redis 提供的有序集合
数据类结构能够实现复杂的排行榜应用。
3、计数器
视频网站的播放量,每次浏览 +1,并发量高时如果每次都请求数据库操作无疑有很大挑战和压力。Redis 提供的 incr 命令来实现计数器功能,内存操作,性能非常好,非常适用于这些技术场景。
4、分布式会话
相对复杂的系统中,一般都会搭建 Redis 等内存数据库为中心的 session 服务,session 不再由容器管理,而是由 session 服务及内存数据管理。
5、分布式锁
在并发高的场合中,可以利用 Redis 的 setnx 功能来编写分布式的锁,如果设置返回 1,说明获取锁成功,否则获取锁失败。
6、社交网络
点赞、踩、关注/被关注,共同好友等是社交网站的基本功能,社交网站的访问量通常来说比较大,而且传统的关系数据库不适合这种类型的数据,Redis 提供的哈希,集合等数据结构能很方便的实现这些功能。
7、最新列表
Redis 列表结构,LPUSH 可以在列表头部插入一个内容 ID 作为关键字,LTRIM 可以用来限制列表的数量,这样列表永远为 N ,无需查询最新的列表,直接根据 ID 去到对应的内容也即可。
8、消息系统
消息队列是网站经常用的中间件,如 ActiveMQ,RabbitMQ,Kafaka 等流行的消息队列中间件,主要用于业务解耦,流量削峰及异步处理试试性低的业务。Redis 提供了发布/订阅及阻塞队列功能,能实现一个简单的消息队列系统。另外,这个不能和专业的消息中间件相比。
使用redis有哪些好处?
速度快,因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1)
支持丰富数据类型,支持string,list,set,sorted set,hash;
Redis数据类型
String 类型
String 类型是 Redis 最基本的数据类型,一个键最大能存储 512 MB。
String 数据结构是最简单的 key-value 类型,value 既可以是 string,也可以是数字,是包含很多种类型的特殊类型。
String 类型是二进制安全的。意思是 redis 的 string 可以包含任何数据,比如序列化的对象进行存储,比如一张图片进行二进制存储,比如一个简单的字符串,数值等等。
String 命令
1、复制语法:
SET KEY_NAME VALUE : (说明:多次设置 name 会覆盖)
(Redis SET 命令用于设置给定 key 的值。如果 key 已经存储值,SET 就要写旧值,且无视类型)。
2、命令:
SETNX key1 value:(not exist) 如果 key1 不存在,则设置 并返回1。
如果 key1 存在,则不设置并返回 0;
(解决分布式锁 方案之一,只有在 key 不存在时设置 key 的值。
setnx (SET if not exits)命令在指定的key不存在时,为key设置指定的值)。
SETEX key1 10 lx :(expired)设置 key1 的值为 lx ,过期时间为 10 秒,10 秒后 key1 清除( key 也清除)
SETEX key1 10 lx :(expired) 设置 key1 的值为 lx,过期时间为 10 秒,10 秒后 key1 清除(key 也清除)
SETRANG STRING range value : 替换字符串
3、取值语法:
GET KEY_NAME : Redis GET 命令用于获取指定 key 的值。
如果 key 不存在,返回 nil。如果key存储的值不是字符串类型,返回一个错误。
GETRANGE key start end : 用于获取存储在指定key中字符串的子字符串。
字符串的截取范围由 start 和 end 两个偏移量来决定(包括 start 和 end 在内)
GETBIT key offset :对 key 所存储的字符串值,获取指定偏移量上的为(bit);
GETTEST语法 :GETSET KEY_NAME VALUE : GETSET 命令用于设置指定 key 的值,并返回key的旧值。当 key 不存在是,返回 null
STRLEN key :返回 key 所存储的字符串值的长度
4、删除语法:
DEL KEY_NAME : 删除指定的key,如果存在,返回数字类型。
5、批量写:MSET K1 V1 K2 V2 ... (一次性写入多个值)
6、批量读:MGET K1 K2 K3
7、GETSET NAME VALUE : 一次性设置和读取(返回旧值,写上新值)
8、自增/自减:
INCR KEY_Name : Incr 命令将key中存储的数组值增1。
如果 key 不存在,那么key的值会先被初始化为0,然后在执行INCR操作
自增:INCRBY KEY_Name :增量值Incrby 命令将key中存储的数字加上指定的增量值
自减:DECR KEY_Name 或 DECYBY KEY_NAME 减值:DECR 命令将key中存储的数字减少1
:(注意这些key对应的必须是数字类型字符串,否则会出错。)
字符串拼接:APPEND KEY_NAME VALUE
:Append 命令用于为指定的key追加至末尾,如果不存在,为其赋值
字符串长度 :STRLEN key
####
setex (set with expire) #设置过期时间
setnx (set if not exist) #不存在设置 在分布式锁中会常常使用!
string 应用场景
- 1、String通常用于保存单个字符串或JSON字符串数据
- 2、因String是二进制安全的,所以你完全可以把一个图片文件的内容作为字符串来存储
- 3、计数器(常规 key-value 缓存应用。常规计数:微博数,粉丝数)
Hash 类型
Hash 类型是 String 类型的 field 和 value 的映射表,或者说是一个 String 集合。hash 特别适合用于存储对象,相比较而言,将一个对象类型存储在 Hash 类型比存储在 String 类型里占用更少的内存空间,并对整个对象的存取。可以看成具有 KEY 和 VALUE 的 MAP 容器,该类型非常适合于存储值对象的信息。
如:uname,upass,age 等。该类型的数据仅占用很少的磁盘空间(相比于 JSON ).
Redis 中每一个 hash 可以存储 2 的 32 次方 -1 键值对(40 多亿)
Hash 命令
常用命令
1、赋值语法:
1、 HSET KEY FIELD VALUE : 为指定的 KEY,设定 FILD/VALUE
2、 HMSET KEY FIELD VALUE [FIELD1,VALUE]... : 同时将多个 field-value(域-值)对设置到哈希表 key 中。
2、取值语法:
HGET KEY FIELD :获取存储在HASH中的值,根据 FIELD 得到 VALUE
HMGET KEY FIELD [FIELD1] :获取 key 所有给定字段的值
HGETALL KEY :返回 HASH 表中所有的字段和值
HKEYS KEY : 获取所有哈希表中的字段
HLEN KEY : 获取哈希表中字段的数量
3、删除语法:
HDEL KEY FIELD[FIELD2] :删除一个或多个 HASH 表字段
4、其它语法:
HSETNX KEY FIELD VALUE : 只有在字段 field 不存在时,设置哈希表字段的值
HINCRBY KEY FIELD INCREMENT :为哈希 key 中的指定字段的整数值加上增量 increment。
HINCRBYFLOAT KEY FIELD INCREMENT :为哈希表 key 中的指定字段的浮点数值加上增量 increment
HEXISTS KEY FIELD : 查看哈希表中 key 中,指定的字段是否存在
Hash 的应用场景 :(存储一个用户信息对象数据)
常用于存储一个对象
为什么不用 string 存储一个对象
hash 值最接近关系数据库结构的数据类型,可以将数据库一条记录或程序中一个对象转换成 hashmap 存放在 redis 中。
用户 ID 为查找的 key,存储的 value 用户对象包含姓名,年龄,生日等信息,如果用普通的 key/value 结构来存储,主要有以下 2 种方式:
第一种方式将用户 ID 作为查找 key,把其他信息封装成为一个对象以序列化的方式存储,这种方式增加了序列化/反序列化的开销,并且在需要修改其中一项信息时,需要把整个对象取回,并且修改操作需要对并发进行保护,引入 CAS 等复杂问题。
第二种方法是这个用户信息对象有多少成员就存成多少个 key-value 对,用用户 ID+ 对应属性的名称作为唯一标识来取的对应属性的值,虽然省去了序列化开销和并发问题,但是用户ID 重复存储,如果存在大量这样的数据,内存浪费还是非常可观的。
Redis 提供的 Hash 很好的解决了这个问题,Redis 的 Hash 实际内部存储的 Value 为一个 HashMap。
List 类型
List 类型是一个链表结构的集合,其主要功能有 push、pop、获取元素等。更详细的说,List 类型是一个双端链表的结构,我们可以通过相关的操作进行集合的头部或者尾部添加和删除元素,List 的设计是非常简单精巧,既可以为栈,又可以作为队列,满足绝大多数的需求。
常用命令
1、赋值语法:
LPUSH KEY VALUE1 [VALUE2] :将一个或多个值插入到列表头部(从左侧添加)
RPUSH KEY VALUE1 [VALUE2] :在列表中添加一个或多个值(从有侧添加)
LPUSHX KEY VAKUE :将一个值插入到已存在的列表头部。如果列表不在,操作无效
RPUSHX KEY VALUE :一个值插入已经在的列表尾部(最右边)。如果列表不在,操作无效
2、取值语法:
LLEN KEY :获取列表长度
LINDEX KEY INDEX :通过索引获取列表中的元素
LRANGE KEY START STOP :获取列表指定范围内的元素
描述:返回列表中指定区间的元素,区间以偏移量 START 和 END 指定。
其中 0 表示列表的第一个元素,1 表示列表的第二个元素,以此类推。。。
也可以使用负数下标,以 -1 表示列表的最后一个元素,-2 表示列表的倒数第二个元素,依次类推
start:页大小(页数-1)
stop:(页大小页数)-1
3、删除语法:
LPOP KEY 移除并获取列表的第一个元素(从左侧删除)
RPOP KEY 移除列表的最后一个元素,返回值为移除的元素(从右侧删除)
BLPOP key1 [key2]timeout 移除并获取列表的第一个元素,如果列表没有元素会阻塞列表知道等待超时或发现可弹出元素为止。
4、修改语法:
LSET KEY INDEX VALUE :通过索引设置列表元素的值
LINSERT KEY BEFORE|AFTER WORIL VALUE :在列表的元素前或者后 插入元素 描述:将值 value 插入到列表 key 当中,位于值 world 之前或之后。
高级命令
高级语法:
RPOPLPUSH source destiation : 移除列表的最后一个元素,并将该元素添加到另外一个列表并返回
示例描述:
RPOPLPUSH a1 a2 : a1的最后元素移到a2的左侧
RPOPLPUSH a1 a1 : 循环列表,将最后元素移到最左侧
BRPOPLPUSH source destination timeout :从列表中弹出一个值,将弹出的元素插入到另外一个列表中并返回它;
如果列表没有元素会阻塞列表知道等待超时或发现可弹出的元素为止。
List 的应用场景
项目应用于:1、对数据量大的集合数据删除;2、任务队列
1、对数据量大的集合数据删减
列表数据显示,关注列表,粉丝列表,留言评论等.....分页,热点新闻等
利用 LRANG 还可以很方便的实现分页的功能,在博客系统中,每篇博文的评论也可以存入一个单独的 list 中。
2、任务队列
(list 通常用来实现一个消息队列,而且可以确认表先后顺序,不必像 MySQL 那样还需要通过 ORDER BY 来进行排序)
任务队列介绍(生产者和消费者模式:)
在处理 web 客户端发送的命令请求时,某些操作的执行时间可能会比我们预期的更长一些,通过将待执行任
务的相关信息放入队列里面,并在之后队列进行处理,用户可以推迟执行那些需要一段时间才能完成的操作,
这种将工作交个任务处理器来执行的做法被称为任务队列(task queue)。
RPOPLPUSH source destination
移除列表的最后一个元素,并将该元素添加到另一个列表并返回
Set 类型
Redis 的 Set 是 String 类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。Redis 中集合是通过哈希表实现的,set 是通过 hashtable 实现的
集合中最大的成员数为 2^32 -1,类似于 JAVA 中的 Hashtable 集合。
命令
1、赋值语法:
SADD KEY member1 [member2]:向集合添加一个或多个成员
2、取值语法:
SCARD KEY :获取集合的成员数
SMEMBERS KEY :返回集合中的所有成员
SISMEMBER KEY MEMBER :判断 member 元素是否是集合 key 的成员(开发中:验证是否存在判断)
SRANDMEMBER KEY [COUNT] :返回集合中一个或对个随机数
3、删除语法:
SREM key member1 [member2] : 移除集合中一个或多个成员
SPOP key [count] : 移除并返回集合中的一个随机元素
SMOVE source destination member :将member 元素从Source集合移动到destination集合中
4、差集语言:
SDIFF key1 [key2] :返回给定所有集合的差集
SDIFFSTORE destination key1 [key2] :返回给定所有集合的茶几并存储在destination中
5、交集语言:
SUNION key1 [key2] : 返回所有给定集合的并集
SUNIONSTORE destination key1 [key2] :所有给定集合的并集存储在 destinatiion集合中
ZSet 类型
有序集合(sorted set)
简介
1、Redis 有序集合和集合一样也是 string 类型元素的集合,且不允许重复的成员。
2、不同的是每个元素都会关联一个 double 类型的分数。redis 正是通过分数来为集合中的成员进行从小到大的排序。
3、有序集合的成员是唯一的,但分数(score)却可以重复。
4、集合是通过哈希表实现的。集合中最大的成员数为 2^32 -1。Redis 的 ZSet 是有序,且不重复。
(很多时候,我们都将 redis 中的有序结合叫做 zsets,这是因为在 redis 中,有序集合相关的操作指令都是以 z 开头的)
命令
1、赋值语法:
ZADD KEY score1 member1 【score2 member2】 :向有序集合添加一个或多个成员,或者更新已经存在成员的分数
2、取值语法:
ZCARD key :获取有序结合的成员数
ZCOUNT key min max :计算在有序结合中指定区间分数的成员数
####
127.0.0.1:6379> ZADD kim 1 tian
(integer) 0
127.0.0.1:6379> zadd kim 2 yuan 3 xing
(integer) 2
127.0.0.1:6379> zcount kim 1 2
(integer) 2
127.0.0.1:6379>
####
ZRANK key member :返回有序集合中指定成员的所有
ZRANGE KEY START STOP [WITHSCORES]:通过索引区间返回有序集合成指定区间内的成员(低到高)
ZRANGEBYSCORE KEY MIN MAX [WITHSCORES] [LIMIT] :通过分数返回有序集合指定区间内的成员
ZREVRANGE KEY START STOP [WITHSCORES] :返回有序集中是定区间内的成员,通过索引,分数从高到底
ZREVERANGEBYSCORE KEY MAX MIN [WITHSCORES] :返回有序集中指定分数区间的成员,分数从高到低排序
删除语法:
DEL KEY : 移除集合
ZREM key member [member...] 移除有序集合中的一个或多个成员
ZREMRANGEBYSCORE KEY MIN MAX :移除有序集合中给定的分数区间的所有成员。
ZREMRANGEBYSCORE KEY MIN MAX :移除有序集合中给定的分数区间的所有成员。
ZINCRBY KEY INCREMENT MEMBER :增加 member 元素的分数 increment,返回值是更改后的分数
HyperLogLog
常用命令
PFADD key element [element ...] : 添加指定元素到 HyperLoglog 中
PFCOUNT KEY [key ...] :返回给定 HyperLogLog的基数估算值
PFMERGE destkey sourcekey [sourcekey ...] :将过个HyperLogLog 合并为一个HyperLoglog
应用场景
基数不大,数据量不大就用不上,会有点大材小用浪费空间
有局限性,就是指能统计基数数量,而没办法去知道具体的内容是什么
统计注册 IP 数
统计每日访问 IP 数
统计页面实时 UV 数
统计在线用户数
统计用户每天搜索不同词条的个数
统计真实文章阅读数
缓存有那些类型
缓存是高并发场景下提高热点数据访问性能的一个有效手段,在开发项目时会经常使用到。
缓存的类型分为:
- 本地缓存:通常使用HashMap
- 分布式缓存:Rides数据库
- 多级缓存
本地缓存
本地缓存就是在进程的内存中进行缓存,比如我们的 JVM 堆中,可以用 LRUMap 来实现,也可以使用 Ehcache 这样的工具来实现。
本地缓存是内存访问,没有远程交互开销,性能最好,但是受限于单机容量,一般缓存较小且无法扩展。
分布式缓存
分布式缓存可以很好得解决这个问题。
分布式缓存一般都具有良好的水平扩展能力,对较大数据量的场景也能应付自如。
缺点就是需要进行远程请求,性能不如本地缓存。
多级缓存
为了平衡这种情况,实际业务中一般采用多级缓存,本地缓存只保存访问频率最高的部分热点数据,其他的热点数据放在分布式缓存中。
在目前的一线大厂中,这也是最常用的缓存方案,单考单一的缓存方案往往难以撑住很多高并发的场景。
你为什么需要使用Rides
这里回答在实时数仓中为什么使用Rides缓存。
因为我们在做实时计算的时候,数据一般是存储在Hbase数据库中,向一些维度表,如果我们实时计算的时候,需要使用维度表,如果这个时候取Hbase数据库中查询数据的时候,他是基于mr计算模型的,延迟很高,而我们实时计算需要低延迟,所以这个时候就不得不考虑使用一个缓存,将一些热点数据存储在缓存中,这样效率更高。
为什么redis需要把所有数据放到内存中?
Redis为了达到最快的读写速度将数据都读到内存中,并通过异步的方式将数据写入磁盘。所以redis具有快速和数据持久化的特征。如果不将数据放在内存中,磁盘I/O速度为严重影响redis的性能。在内存越来越便宜的今天,redis将会越来越受欢迎。
如果设置了最大使用的内存,则数据已有记录数达到内存限值后不能继续插入新值。
缓存数据淘汰算法
不管是本地缓存还是分布式缓存,为了保证较高性能,都是使用内存来保存数据,由于成本和内存限制,当存储的数据超过缓存容量时,需要对缓存的数据进行剔除。
不可能实现的算法 OPT
OPT(OPTimal Replacement,OPT)算法,其所选择的被淘汰的数据将是以后永不使用的,或是在最长(未来)时间内不再被访问的数据。
未来发生的事情是无法预测的,所以该算法从根本上来说是无法实现的,OPT算法对于内存缓存来说,能够提供最高的cache命中(cache hite)率,通过OPT算法也可以衡量其他缓存淘汰的算法的优劣。
无脑算法 FIFO
FIFO(First Input First Output,FIFO)算法算是一种很无脑的淘汰算法,实现起来也很简单,即每次淘汰最先被缓存的数据。
FIFO算法很少会应用在实际项目中,因为该算法并未考虑数据的 “热度”,一般来说,应该是越热的数据越应该晚点淘汰出去,而FIFO算法并未考虑到这一点,所以,该算法的cache命中率一般会比较低。
常见算法 LRU
LRU(Least Recently Used,LRU)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。
LRU最友好的数据模型为具有时间局部性的请求队列,每访问一个已缓存的节点,就将该节点转移到队列头部,每次淘汰时,以此淘汰队列尾部节点
采用队列实现的话,每转移一个节点,都需要遍历该队列,为了提高查找效率,通常会采用Hashmap+双向链表来实现LRU算法。
使用双向链表记录访问的时间,因为链表的插入效率比较高,所以新插入的元素在前面,旧的数据存储在后面,使用哈希表记录缓存(key,value),哈希表的查找效率近似于o(1),发生冲突最坏查询效率也是o(n),同时哈希表中得记录 (key, (value, key_ptr)),key_ptr 是key在链表中的地址,为了能在O(1)时间内找到该节点,并把节点提升到表头。链表中的key,能快速找到hash中的value,并删除。
LFU算法
为了解决LRU算法未考虑频率因素的问题,人们在此基础上又提出了LRU-K算法,其中,K代表最近使用的次数,因此LRU可以认为是LRU-1算法,其核心思想是将 “最近使用过1次”的判断标准扩展为“最近使用过K次”。
相比LRU,LRU-K需要多维护一个访问历史队列,用于记录所有缓存数据被访问的历史。只有当数据的访问次数达到K次的时候,才将数据放入缓存。当需要淘汰数据时,LRU-K会淘汰第K次访问时间距当前时间最大的数据。
LFU算法是LRU算法的改进
LRU对于循环出现的数据,缓存命中不高
比如,这样的数据,1,1,1,2,2,2,3,4,1,1,1,2,2,2.....
当走到3,4的时候,1,2会被淘汰掉,但是后面还有很多1,2LFU对于交替出现的数据,缓存命中不高
比如,1,1,1,2,2,3,4,3,4,3,4,3,4,3,4,3,4......
由于前面被(1(3次),2(2次))
3加入把2淘汰,4加入把3淘汰,3加入把4淘汰,然而3,4才是最需要缓存的,1去到了3次,谁也淘汰不了它了。
一般的剔除策略有 FIFO 淘汰最早数据、LRU 剔除最近最少使用、和 LFU 剔除最近使用频率最低的数据几种策略。
noeviction:返回错误,当内存限制达到并且客户端尝试执行会让更多内存被使用的命令(大部分的写入指令,但DEL和几个例外)
allkeys-lru: 尝试回收最少使用的键(LRU),使得新添加的数据有空间存放。
volatile-lru: 尝试回收最少使用的键(LRU),但仅限于在过期集合的键,使得新添加的数据有空间存放。
allkeys-random: 回收随机的键使得新添加的数据有空间存放。
volatile-random: 回收随机的键使得新添加的数据有空间存放,但仅限于在过期集合的键。
volatile-ttl: 回收在过期集合的键,并且优先回收存活时间(TTL)较短的键,使得新添加的数据有空间存放
如果没有键满足回收的前提条件的话,策略volatile-lru, volatile-random以及volatile-ttl就和noeviction 差不多了。
Redis内存维护策略
redis 作为优秀的中间缓存件,时常会存储大量的数据,即使采取了集群部署来动态扩容,也应该及时的整理内存,维持系统性能。
在 redis 中有两种解决方案
为数据设置超时时间
//设置过期时间
expire key time(以秒为单位)--这是最常用的方式
setex(String key, int seconds, String value) --字符串独有的方式
1、除了字符串自己独有设置过期时间的方法外,其他方法都需要依靠 expire 方法来设置时间
2、如果没有设置时间,那缓存就是永不过期
3、如果设置了过期时间,之后又想让缓存永不过期没使用persist key
采用 LRU 算法动态将不用的数据删除
内存管理的一种页面置换算法,对于在内存中但又不用的数据块(内存块)叫做 LRU, 操作系统会根据哪些数据属于 LRU 而将其移除内存而腾出空间来加载另外的数据。
1.volatile-lru:设定超时时间的数据中,删除最不常使用的数据
2.allkeys-lru:查询所有的 key 对最近最不常使用的数据进行删除,这是应用最广泛的策略。
3.volatile-random:在已经设定了超时的数据中随机删除。
4.allkeys-random:查询所有的 key,之后随机删除。
5.volatile-ttl:查询全部设定超时时间的数据,之后排序,将马上要过期的数据进行删除操作。
6.noeviction:如果设置为该属性,则不会进行删除操作,如果内存溢出则报错返回。
7.volatile-lfu:从所有配置了过期时间的键中驱逐使用频率最少的键
8.allkeys-lfu:从所有键中驱逐使用频率最少的键
Redis是单线程还是多线程?Redis为什么这么快?
Redis6.0之前是单线程的,为什么Redis6.0之前采用单线程而不采用多线程呢?
简单来说,就是Redis官方认为没必要,单线程的Redis的瓶颈通常在CPU的IO,而在使用Redis时几乎不存在CPU成为瓶颈的情况。使用Redis主要的瓶颈在内存和网络,并且使用单线程也存在一些优点,比如系统的复杂度较低,可为维护性较高,避免了并发读写所带来的一系列问题。
Redis为什么这么快主要有以下几个原因:
- 运行在内存中
- 数据结构简单
- 使用多路IO复用技术
- 单线程实现,单线程避免了线程切换、锁等造成的性能开销。
Rides中得高级数据类型
Bitmap:位图,是一个以位为单位的数组,数组中只能存储1或0,数组的下标在Bitmap中叫做偏移量。Bitmap实现统计功能,更省空间。
面试中常问的布隆过滤器就有用到这种数据结构,布隆过滤器可以判断出哪些数据一定不在数据库中,所以常被用来解决Redis缓存穿透问题。
Memcache和Rides对比
Memcache
注意后面会把 Memcache 简称为 MC。
先来看看 MC 的特点:
- MC 处理请求时使用多线程异步 IO 的方式,可以合理利用 CPU 多核的优势,性能非常优秀;
- MC 功能简单,使用内存存储数据;
- MC 的内存结构以及钙化问题我就不细说了,大家可以查看官网了解下;
- MC 对缓存的数据可以设置失效期,过期后的数据会被清除;
- 失效的策略采用延迟失效,就是当再次使用数据时检查是否失效;
- 当容量存满时,会对缓存中的数据进行剔除,剔除时除了会对过期 key 进行清理,还会按 LRU 策略对数据进行剔除。
另外,使用 MC 有一些限制,这些限制在现在的互联网场景下很致命,成为大家选择Redis、MongoDB的重要原因:
- key 不能超过 250 个字节;
- value 不能超过 1M 字节;
- key 的最大失效时间是 30 天;
- 只支持 K-V 结构,不提供持久化和主从同步功能。
Redis
先简单说一下 Redis 的特点,方便和 MC 比较。
- 与 MC 不同的是,Redis 采用单线程模式处理请求。这样做的原因有 2 个:
- 一个是因为采用了非阻塞的异步事件处理机制;
- 另一个是缓存数据都是内存操作 IO 时间不会太长,单线程可以避免线程上下文切换产生的代价。
- Redis 支持持久化,所以 Redis 不仅仅可以用作缓存,也可以用作 NoSQL 数据库。
- 相比 MC,Redis 还有一个非常大的优势,就是除了 K-V 之外,还支持多种数据格式,例如 list、set、sorted set、hash 等。
- Redis 提供主从同步机制,以及 Cluster 集群部署能力,能够提供高可用服务。
两者对比
对于 redis 和 memcached 的区别有下面四点。
- redis支持更丰富的数据类型(支持更复杂的应用场景):Redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,zset,hash等数据结构的存储。memcache支持简单的数据类型,String。
- Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用,而Memecache把数据全部存在内存之中。
- 集群模式:memcached没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据;但是 redis 目前是原生支持 cluster 模式的.
- Memcached是多线程,非阻塞IO复用的网络模型;Redis使用单线程的多路 IO 复用模型。
相同点:
- 两者的读写性能都比较高
- 都是基于内存的数据库,通常被当作缓存使用
- 都有过期策略
- 都是基于C语言实现
为什么要用 redis 而不用 map/guava 做缓存?
缓存分为本地缓存和分布式缓存。以 Java 为例,使用自带的 map 或者 guava 实现的是本地缓存,最主要的特点是轻量以及快速,生命周期随着 jvm 的销毁而结束,并且在多实例的情况下,每个实例都需要各自保存一份缓存,缓存不具有一致性。
使用 redis 或 memcached 之类的称为分布式缓存,在多实例的情况下,各实例共用一份缓存数据,缓存具有一致性。缺点是需要保持 redis 或 memcached服务的高可用,整个程序架构上较为复杂。
这也是在Flink流式处理项目种使用Rides作为缓存的原因,为了保证数据的一致性。
Rides有那些数据结构
Rides中基础的数据结构有:String、Hash、List、Set、SortedSet。
但是还有一些高级的数据类型,比如Bitmaps,HyperLogLog,GEO。
通常还会使用BloomFilter。
如果有大量的key需要设置同一时间过期,一般需要注意什么?
如果大量的key过期时间设置的过于集中,到过期的那个时间点,Redis可能会出现短暂的卡顿现象。严重的话会出现缓存雪崩,我们一般需要在时间上加一个随机值,使得过期时间分散一些。
电商首页经常会使用定时任务刷新缓存,可能大量的数据失效时间都十分集中,如果失效时间一样,又刚好在失效的时间点大量用户涌入,就有可能造成缓存雪崩
使用过Redis分布式锁么,它是什么回事?
分布式锁的实现方案有:
- 基于mysql的乐观锁
- 基于Rides的分布式锁
- 基于zookeeper的分布式锁。
首先,为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件:
- 互斥性,在任意时刻,只有一个客户端能持有锁。
- 不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。
- 具有容错性。只要大部分的Redis节点正常运行,客户端就可以加锁和解锁。
- 解铃还须系铃人:加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。
- 锁最好还是一把公平锁
- 获取锁和释放锁的性能需要好。
setnx将 key 的值设为 value,当且仅当 key 不存在。 若给定的 key 已经存在,则 SETNX 不做任何动作。 SETNX 是SET if Not eXists的简写。
127.0.0.1:6379> expire lock 10
(integer) 1
127.0.0.1:6379> ttl lock
8
127.0.0.1:6379> get lock
(nil)
基于Rides的分布式锁
redis实现分布式锁问题
如果出现了这么一个问题:如果setnx
是成功的,但是expire
设置失败,那么后面如果出现了释放锁失败的问题,那么这个锁永远也不会被得到,业务将被锁死?
之所以产生这样的情况,是因为这两个命令的执行不是原子操作的,如果是原子操作,就不会发生这样的问题。
解决的办法:使用set
的命令,同时设置锁和过期时间
set
参数:
set key value [EX seconds] [PX milliseconds] [NX|XX]
EX seconds:设置失效时长,单位秒
PX milliseconds:设置失效时长,单位毫秒
NX:key不存在时设置value,成功返回OK,失败返回(nil)
XX:key存在时设置value,成功返回OK,失败返回(nil)
这个命令相当于把上面获取锁和释放锁的命令组成一个原子操作。
假如Redis里面有1亿个key,其中有10w个key是以某个固定的已知的前缀开头的,如何将它们全部找出来?
使用keys指令可以扫出指定模式的key列表。
如果这个redis正在给线上的业务提供服务,那使用keys指令会有什么问题?
这个时候你要回答Redis关键的一个特性:Redis的单线程的。keys指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。
这个时候可以使用scan指令,scan指令可以无阻塞的提取出指定模式的key列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用keys指令长。
不过,增量式迭代命令也不是没有缺点的: 举个例子, 使用 SMEMBERS 命令可以返回集合键当前包含的所有元素, 但是对于 SCAN 这类增量式迭代命令来说, 因为在对键进行增量式迭代的过程中, 键可能会被修改, 所以增量式迭代命令只能对被返回的元素提供有限的保证 。