0. 前言
此笔记用来记录关于Redis的一些基本知识,主要包括:
- 更新记录
- 2018.2.14
初次记录 - 2018 4.21
重新对目录进行了分级,补充了4.Redis执行效率,以及其他章节的细节 - 2019 3.2
完善了数据过期策略(内存回收),补充各种数据结构的应用
- 2018.2.14
1. Redis简单介绍
Redis 是完全开源免费的,遵守BSD协议,是一个高性能的key-value数据库。
Redis 与其他 key - value 缓存产品有以下三个特点:
- Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。
- Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储。
- Redis支持数据的备份,即master-slave模式的数据备份。
Redis官方网站:redis.io
名词解析————BSD协议
BSD开源协议是一个给于使用者很大自由的协议。可以自由的使用,修改源代码,也可以将修改后的代码作为开源或者专有软件再发布。当你发布使用了BSD协议的代码,或者以BSD协议代码为基础做二次开发自己的产品时,需要满足三个条件:
A.如果再发布的产品中包含源代码,则在源代码中必须带有原来代码中的BSD协议。
B.如果再发布的只是二进制类库/软件,则需要在类库/软件的文档和版权声明中包含原来代码中的BSD协议。
C.不可以用开源代码的作者/机构名字和原来产品的名字做市场推广。
2. Redis常用数据结构和操作
在介绍数据类型之前,先让我们来看看Redis k-v数据结构中非常重要的key形式。
2.1 Key的命名规则
- key不要太长。在redis中可以的最大容量是512M,但是实际中key超过1024byte就非良好的设计方式了。key值太大不仅浪费内存空间,更是在请求中对于key’的查找需要更多的时间进行比较;
- key值不宜太短。这个原则貌似与第一条相冲突,实则不然。有时key的变长使得增加空间相对于使得key更具可读性更显次要;
- key的命名中可以使用冒号和破折号加以细分,使key更具可读性,灵活性。例如:aticle:1000:tags。其中第一位表示类型,第二位表示该类型对象的Id,第三位表示Id为1000的文章中的标记;
2.2 Key的常用命令 *
– | 命令 | 说明 |
---|---|---|
1 | EXISTS | exists主要是查找指定的键是否存在,如果存在则返回(Integer) 1,否则返回(Integer) 0。 |
2 | DEL | del主要用来删除Key,并将Key对应的值随之删除。 |
3 | EXPIRE | expire设置Key的有效期,如果超过设定的存活时间,Key将自动失效并随之删除。 |
4 | TTL | 查看键的过期时间 |
5 | PERSIST | 取消键的过期时间,即持久化 |
6 | KEYS * | 匹配键所有的键. 模糊匹配 KEYS my* 取出所有已my开头的键 |
7 | PING | 检验是否成功连接上客户端,正常则返回PONG |
这些都是redis的常用命令,值得先记住
2.3 Redis常用数据结构和操作
命令和参数之间、参数和参数之间用空格分隔
2.3.1 String
最普通的key-value类型,说是String,其实是任意的byte[],比如图片,最大512M。 所有常用命令的复杂度都是O(1),普通的Get/Set方法,可以用来做Cache。
value参数可以含有双引号也可以不含有
应用:最常规的缓存应用结构,范围宽广,如:微博数,粉丝数等。
常用命令:
– | 命令 | 说明 |
---|---|---|
1 | SET | 设置键值对,若键值已存在则会覆盖值 |
2 | GET | 通过键获取值 |
3 | MSET | 设置多个键值对 一块存错 全成功,全失败。如MSET name kanarien age 20 |
4 | SETNX | 设置键值对 |
5 | STRLEN | 返回键对应的值得字符长度 |
6 | APPEND | 给键对应的值后追加值 |
7 | INCR | 使键对应的值自增1,前提是值可看作Integer |
8 | INCRBY | 使键对应的值增加给定的值,前提是值可看作Integer。如:INCRBY age 3给age对应的值加上3 |
2.3.2 Lists
基于Linked List链式节点实现的List数据结构。
相对于缓存或者数据库系统而言,在插入或者更新的操作上性能更重要,相对于查询操作,查询所带来的性能损耗更低。平衡性能的确采用Linked List实现更具有优越性。
redis中复合类型的基元素都是String类型。Lists也不例外。Lists类型实质就是一串String元素的序列,redis提供了很多丰富命令,用于操作Lists类型。
应用:微博的关注列表,粉丝列表,最新消息排行
常用命令:
– | 命令 | 说明 |
---|---|---|
1 | LPUSH | 从链表的头部(左)压入 |
2 | RPUSH | 从链表的尾部(右)压入 |
3 | LPOP | 从链表的头部(左)弹出一个元素 |
4 | RPOP | 从链表的尾部(右)弹出一个元素 |
5 | LLEN | 返回这个链表的元素的长度 |
6 | LRANGE | 获取List中的范围元素,第二个参数表示要查找元素的起始位,第三个元素表示结束位。如:LRANGE mylist1 0 -1 获取所有元素 |
2.3.3 Hashes
redis中的Hashes类型类似Java中map,key-value键值对映射型数据结构。redis提供了很多命令用于操作Hashes。常常利用Hashes来映射一个简单的Java对象。
常用命令:
– | 命令 | 说明 |
---|---|---|
1 | HMSET | 向Hashes中插入元素。如:HMSET usr:001 username kanarien birth 1997 sex male |
2 | HGET | 获取Hashes中的单个元素。如:HGET usr:001 username |
3 | HMGET | 获取Hashes中的多个元素。如:HMGET usr:001 username birth sex |
4 | HLEN | 返回hash表的所有的字段的数目 |
5 | HKEYS | 返回hash表的所有字段 |
6 | HVALS | 返回hash表中所有的值 |
7 | HGETALL | 返回所有的字段和值 |
8 | HDEL | 对hash的name的值和键删除 |
2.3.4 Sets
Set类型是无序且无重复的String元素的集合。redis也提供了大量对于Sets类型的操作,比如:交集、并集、补集在多个Set之间;测试给定元素是否存在等。
应用:在微博应用中,可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。Redis可以非常方便的实现如共同关注、共同喜好、二度好友等功能。
常用命令:
– | 命令 | 说明 |
---|---|---|
1 | SADD | 向Set中插入新的元素,如果有则覆盖,其中可以接多个参数作为插入的元素 |
2 | SREM | 删除集合中的一个元素 |
3 | SISMEMBER | 测试指定元素在Set中是否存在 |
4 | SMEBERS | 获取Set中的所有元素,并且是无序的 |
5 | SINTER | 取多个Set的交集 |
6 | SUNION | 返回所有给定集合的并集 |
7 | SDIFF | 返回给定所有集合的差集 |
2.3.5 Sorted Set
Sorted Set类型是有序的Set类型,有点像Hashes和Set的混合型数据结构。具有Set的唯一无重复特性,且有序的实现是通过额外的浮点值被称作为score决定,与Hashes的Key有点相似。
Sorted Set的排序规则,假设有两个元素a、b
- 如果a.score > b.score,则a > b;
- 如果a.score = b.score,a > b,则a > b;
应用:在直播系统中,实时排行信息包含直播间在线用户列表,各种礼物排行榜,弹幕消息(可以理解为按消息维度的消息排行榜)等信息,
常用命令:
– | 命令 | 说明 |
---|---|---|
1 | ZADD | 向Set中插入新的元素,如果有则覆盖,其中可以接多个参数作为插入的元素 |
2 | ZREM | 删除集合中的一个元素 |
3 | ZRANGE | 获取Sorted Set中的元素 |
4 | ZREVRANGE | 获取Sorted Set中元素并反向输出 |
5 | ZSCORE | 返回有序集中,成员的分数值 |
3. Redis事务
用Multi(Start Transaction)、Exec(Commit)、Discard(Rollback)实现。
在事务提交前,不会执行任何指令,只会把它们存到一个队列里,不影响其他客户端的操作。在事务提交时,批量执行所有指令。
注意,Redis里的事务,与我们平时的事务概念很不一样:
它仅仅是保证事务里的操作会被连续独占的执行。因为是单线程架构,在执行完事务内所有指令前是不可能再去同时执行其他客户端的请求的。
它没有隔离级别的概念,因为事务提交前任何指令都不会被实际执行,也就不存在”事务内的查询要看到事务里的更新,在事务外查询不能看到”这个让人万分头痛的问题。
它不保证原子性——所有指令同时成功或同时失败,只有决定是否开始执行全部指令的能力,没有执行到一半进行回滚的能力。
在redis里失败分两种,一种是明显的指令错误,比如指令名拼错,指令参数个数不对,在2.6版中全部指令都不会执行。
另一种是隐含的,比如在事务里,第一句是SET foo bar, 第二句是LLEN foo,对第一句产生的String类型的key执行LLEN会失败,但这种错误只有在指令运行后才能发现,这时候第一句成功,第二句失败。
还有,如果事务执行到一半redis被KILL,已经执行的指令同样也不会被回滚。
Watch指令,类似乐观锁,事务提交时,如果Key的值已被别的客户端改变,比如某个list已被别的客户端push/pop过了,整个事务队列都不会被执行。
4. Redis执行效率
Redis的运行效率十分之高,官方提供的数据是可以达到100000+的qps,实际应用中上也能轻松达到每秒上万次的读操作,而Redis之所以能迅速的处理,理由有以下三点:
- 数据存放在内存中,绝大部分请求是纯粹的内存操作
- 单进程单线程架构,避免了不必要的上下文切换和竞争条件
- 网络IO中使用了I/O 多路复用(IO multiplexing)
下面来说明第二点和第三点
4.1 Redis架构
设计是一场与复杂性的战斗
出于Redis作者Salvatore Sanfilippo自身的考量,Redis采用了单线程的架构,使得代码看起来十分的洁简与优美。
详细的来说,Redis为单进程单线程模式,是线程安全的,采用队列模式将并发访问变为串行访问。当然,可以通过在服务端开启多个Redis的实例来实现达到多进程并行访问,但若真有这种情况,请考虑是否有此必要。另外,正因为Redis本身是单进程单线程的,利用此特性可以将一些并发的问题转化成串行处理,达到消除并发带来的各种问题的效果。
单进程单线程好处
- 代码更清晰,处理逻辑更简单
- 不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗
- 不存在多进程或者多线程导致的切换而消耗CPU
单进程单线程弊端
- 无法发挥多核CPU性能
4.2 I/O多路复用
这里对比传统的阻塞I/O来简单介绍下I/O多路复用
4.2.1 阻塞IO模型
进程发起IO系统调用后,进程被阻塞,转到内核空间处理,整个IO处理完毕后返回进程。操作成功则进程获取到数据。
1、典型应用:阻塞socket、Java BIO;
2、特点:
进程阻塞挂起不消耗CPU资源,及时响应每个操作;
实现难度低、开发应用较容易;
适用并发量小的网络应用开发;
不适用并发量大的应用:因为一个请求IO会阻塞进程,所以,得为每请求分配一个处理进程(线程)以及时响应,系统开销大。
4.2.2 IO复用模型
多个的进程的IO可以注册到一个复用器(select)上,然后用一个进程调用该select, select会监听所有注册进来的IO;
如果select没有监听的IO在内核缓冲区都没有可读数据,select调用进程会被阻塞;而当任一IO在内核缓冲区中有可数据时,select调用就会返回;
而后select调用进程可以自己或通知另外的进程(注册进程)来再次发起读取IO,读取内核中准备好的数据。
可以看到,多个进程注册IO后,只有另一个select调用进程被阻塞。
1、典型应用:select、poll、epoll三种方案,nginx都可以选择使用这三个方案;Java NIO;
2、特点:
专一进程解决多个进程IO的阻塞问题,性能好;Reactor模式;
实现、开发应用难度较大;
适用高并发服务应用开发:一个进程(线程)响应多个请求;
5. Redis过期数据清除
官方文档 与 《Redis设计与实现》中的详述,过期数据的清除从来不容易,为每一条key设置一个timer,到点立刻删除的消耗太大,每秒遍历所有数据消耗也大。
Redis key过期的方式有三种:
- 被动删除:当读/写一个已经过期的key时,会触发惰性删除策略,直接删除掉这个过期key
- 定时删除:由于惰性删除策略无法保证冷数据被及时删掉,所以Redis会定期主动淘汰一批已过期的key
- 主动删除:当前已用内存超过maxmemory限定时,触发主动清理策略
5.1 被动删除
当client主动访问key会先对key进行超时判断,过时的key会立刻删除。
这种删除策略对CPU是友好的,删除操作只有在不得不的情况下才会进行,不会其他的expire key上浪费无谓的CPU时间。
但仅是这样是不够的,因为可能存在一些key永远不会被再次访问到,这些设置了过期时间的key也是需要在过期后被删除的,我们甚至可以将这种情况看作是一种内存泄露—-无用的垃圾数据占用了大量的内存,而服务器却不会自己去释放它们,这对于运行状态非常依赖于内存的Redis服务器来说,肯定不是一个好消息。
5.2 定时删除
在 Redis 2.6 版本中, 程序规定serverCron每秒10次的执行如下操作:随机选取100个key校验是否过期,如果有25个以上的key过期了,立刻额外随机选取下100个key(不计算在10次之内)。
这是一个基于概率的简单算法,基本的假设是抽出的样本能够代表整个key空间,redis持续清理过期的数据直至将要过期的key的百分比降到了25%以下。这也意味着在任何给定的时刻已经过期但仍占据着内存空间的key的量最多为每秒的写操作量除以4。
但只要key不被主动get,它占用的内存什么时候最终被清理掉只有天知道。
5.3 主动删除
当前已用内存超过maxmemory
限定时,触发主动清理策略:
- 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(驱逐):禁止驱逐数据
默认的内存策略是noeviction,在Redis中LRU算法是一个近似算法,默认情况下,Redis随机挑选5个键,并且从中选取一个最近最久未使用的key进行淘汰,在配置文件中可以通过maxmemory-samples
的值来设置redis需要检查key的个数,但是查的越多,耗费的时间也就越久,但是结构越精确。
6. Redis的持久化机制
对于persistence持久化存储,Redis提供了两种持久化方法:
- Redis DataBase(简称RDB)
- Append-only file (简称AOF)
除了这两种方法,Redis在早起的版本还存在虚拟内存的方法,现在已经被废弃。
6.1 RDB概述
RDB是在某个时间点将数据写入一个临时文件,持久化结束后,用这个临时文件替换上次持久化的文件,达到数据恢复。
优点:使用单独子进程来进行持久化,主进程不会进行任何IO操作,保证了redis的高性能
缺点:RDB是间隔一段时间进行持久化,如果持久化之间redis发生故障,会发生数据丢失。所以这种方式更适合数据要求不严谨的时候
这里说的这个执行数据写入到临时文件的时间点是可以通过配置来自己确定的,通过配置redis在n秒内如果超过m个key被修改这执行一次RDB操作。这个操作就类似于在这个时间点来保存一次Redis的所有数据,一次快照数据。所有这个持久化方法也通常叫做snapshots。
6.2 AOF概述
Append-only file,将“操作 + 数据”以格式化指令的方式追加到操作日志文件的尾部,在append操作返回后(已经写入到文件或者即将写入),才进行实际的数据变更,“日志文件”保存了历史所有的操作过程;当server需要数据恢复时,可以直接replay此日志文件,即可还原所有的操作过程。AOF相对可靠,它和mysql中bin.log、apache.log、zookeeper中txn-log简直异曲同工。AOF文件内容是字符串,非常容易阅读和解析。
优点:可以保持更高的数据完整性,如果设置追加file的时间是1s,如果redis发生故障,最多会丢失1s的数据;且如果日志写入不完整支持redis-check-aof来进行日志修复;AOF文件没被rewrite之前(文件过大时会对命令进行合并重写),可以删除其中的某些命令(比如误操作的flushall)。
缺点:AOF文件比RDB文件大,且恢复速度慢。
我们可以简单的认为AOF就是日志文件,此文件只会记录“变更操作”(例如:set/del等),如果server中持续的大量变更操作,将会导致AOF文件非常的庞大,意味着server失效后,数据恢复的过程将会很长;事实上,一条数据经过多次变更,将会产生多条AOF记录,其实只要保存当前的状态,历史的操作记录是可以抛弃的;因为AOF持久化模式还伴生了“AOF rewrite”。
AOF的特性决定了它相对比较安全,如果你期望数据更少的丢失,那么可以采用AOF模式。如果AOF文件正在被写入时突然server失效,有可能导致文件的最后一次记录是不完整,你可以通过手工或者程序的方式去检测并修正不完整的记录,以便通过aof文件恢复能够正常;同时需要提醒,如果你的redis持久化手段中有aof,那么在server故障失效后再次启动前,需要检测aof文件的完整性。
AOF默认关闭。
该笔记参考了以下文章:
- 超强、超详细Redis入门教程 - CSDN博客
- Redis 5种数据结构使用及注意事项 - 孟凡柱的专栏 - 博客园
- Redis系列—-(二)redis中的数据结构类型 - CSDN博客
- Redis常用数据结构和操作 - WhoAmMe - 博客园
- 5种IO模型、阻塞IO和非阻塞IO、同步IO和异步IO - CSDN博客
- redis是个单线程的程序,为什么会这么快呢? - 知乎
- Redis为什么使用单进程单线程方式也这么快 - CSDN博客
- Redis持久化存储(AOF与RDB两种模式)_数据库技术_Linux公社-Linux系统门户网站
- 面试中关于Redis的问题看这篇就够了 - 不忘初心 - CSDN博客
参考网站:
Copyright © 2018, GDUT CSCW back-end Kanarien, All Rights Reserved