参考资料 github
[TOC]
什么是Redis,Redis有哪些特点
Redis全称为:Remote Dictionary Server(远程数据服务),Redis是一种支持key-value等多种数据结构的存储系统
可用于缓存,事件发布或订阅,高速队列等场景
提供字符串,哈希,列表,队列,集合结构直接存取,基于内存,可持久化
特点1:丰富的数据类型
很多数据库只能处理一种数据结构:
- 传统SQL数据库处理二维关系数据
- MemCached数据库,键和值都是字符串
- 文档数据库(MongoDB)是由Json/Bson组成的文档
不是他们这些数据库不好,而是一旦数据库提供数据结构不适合去做某件事情的话,程序写起来就非常麻烦和不自然
Redis虽然也是键值对数据库,但是和Memcached不同的是:Redis的值不仅可以是字符串,它还可以是其他五中数据机构中的任意一种
通过选用不同的数据结构,用户可以使用Redis解决各种各样的问题,使用Redis,你碰到一个问题,首先会想到是选用那种数据结构把哪些功能问题解决掉,有了多样的数据结构,方便你解决问题
特点2:内存存储
数据库有两种:一种是硬盘数据库,一种是内存数据库
硬盘数据库是把值存储在硬盘上,在内存中就存储一下索引,当硬盘数据库想访问硬盘的值时,它先在内存里找到索引,然后再找值
问题在于,在读取和写入硬盘的时候,如果读写比较多的时候,它会把硬盘的IO功能堵死
内存存储是讲所有的数据都存储在内存里面,数据读取和写入速度非常快
特点3:持久化功能
将数据存储在内存里面的数据保存到硬盘中,保证数据安全,方便进行数据备份和恢复
Redis有哪些数据结构?
Redis是key-value数据库,key的类型只能是String,但是value的数据类型就比较丰富了,主要包括五种
- String
- Hash
- List
- Set
- Sorted Set
String 字符串
1
SET KEY_NAME VALUE
string类型是二进制安全的。意思是redis的string可以包含任何数据。比如jpg图片或者序列化的对象
string类型是Redis最基本的数据类型,一个键最大能存储512MB
String 使用场景
信息缓存、计数器、分布式锁等等
常用命令:get/set/del/incr/decr/incrby/decrby
实战场景1: 记录每一个用户的访问次数,或者记录每一个商品的浏览次数
常用键名: userid:pageview 或者 pageview:userid
如果一个用户的id为123,那对应的redis key就为pageview:123
value就为用户的访问次数
增加次数可以使用命令:incr
使用理由:每一个用户访问次数或者商品浏览次数的修改是很频繁的,如果使用mysql这种文件系统频繁修改会造成mysql压力,效率也低
redis的好处有二:使用内存,很快;单线程,所以无竞争,数据不会被改乱
实战场景2: 缓存频繁读取,但是不常修改的信息,如用户信息,视频信息
业务逻辑上:先从redis读取,有值就从redis读取,没有则从mysql读取,并写一份到redis中作为缓存,注意要设置过期时间
直接将用户一条mysql记录做序列化(通常序列化为json)作为值
userInfo:userid 作为key,键名如:userInfo:123,value存储对应用户信息的json串。如 key为:”user:id :name:1”, value为”{“name”:”leijia”,”age”:18}”
实战场景3: 限定某个ip特定时间内的访问次数
用key记录IP,value记录访问次数,同时key的过期时间设置为60秒
如果key过期了则重新设置,否则进行判断,当一分钟内访问超过100次,则禁止访问
实战场景4: 分布式session
session是以文件的形式保存在服务器中的
如果应用做了负载均衡,将网站的项目放在多个服务器上,当用户在服务器A上进行登陆,session文件会写在A服务器
当用户跳转页面时,请求被分配到B服务器上的时候,就找不到这个session文件,用户就要重新登陆
想要多个服务器共享一个session,可以将session存放在redis中,redis可以独立于所有负载均衡服务器
也可以放在其中一台负载均衡服务器上;但是所有应用所在的服务器连接的都是同一个redis服务器
Hash 哈希
1
HSET KEY_NAME FIELD VALUE
Redis hash 是一个键值(key=>value)对集合
Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象
Hash 使用场景
实战场景1: 购物车
用户id设置为key,那么购物车里所有的商品就是用户key对应的值了
每个商品有id和购买数量,对应hash的结构就是商品id为field,商品数量为value
将商品id和商品数量序列化成json字符串,那么也可以用上面讲的string类型存储
当对象的某个属性需要频繁修改时,不适合用string+json
因为它不够灵活,每次修改都需要重新将整个对象序列化并赋值
如果使用hash类型,则可以针对某个属性单独修改,没有序列化,也不需要修改整个对象
比如,商品的价格、销量、关注数、评价数等可能经常发生变化的属性,就适合存储在hash类型里
List 列表
1
2
3
4
5
6
7
8
9
10
11
//在 key 对应 list 的头部添加字符串元素
LPUSH KEY_NAME VALUE1.. VALUEN
//在 key 对应 list 的尾部添加字符串元素
RPUSH KEY_NAME VALUE1..VALUEN
//对应 list 中删除 count 个和 value 相同的元素
LREM KEY_NAME COUNT VALUE
//返回 key 对应 list 的长度
LLEN KEY_NAME
Redis 列表是简单的字符串列表,按照插入顺序排序
可以添加一个元素到列表的头部(左边)或者尾部(右边)
List 使用场景
列表本质是一个有序的,元素可重复的队列
实战场景1: 定时排行榜
list类型的lrange命令可以分页查看队列中的数据
可将每隔一段时间计算一次的排行榜存储在list类型中
如QQ音乐内地排行榜,每周计算一次存储在list类型中
访问接口时通过page和size分页转化成lrange命令获取排行榜数据
并不是所有的排行榜都能用list类型实现,只有定时计算的排行榜才适合使用list类型存储
实时计算的排行榜 有序集合sorted set
Set 集合
1
SADD KEY_NAME VALUE1...VALUEn
Redis的Set是string类型的无序集合
集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)
Set 使用场景
集合的特点是无序性和确定性(不重复)
实战场景1: 收藏夹
例如QQ音乐中如果你喜欢一首歌,点个『喜欢』就会将歌曲放到个人收藏夹中
每一个用户做一个收藏的集合,每个收藏的集合存放用户收藏过的歌曲id
key为用户id,value为歌曲id的集合
Sorted Set 有序集合
1
ZADD KEY_NAME SCORE1 VALUE1.. SCOREN VALUEN
Redis zset 和 set 一样也是string类型元素的集合,且不允许重复的成员
不同的是每个元素都会关联一个double类型的分数
redis正是通过分数来为集合中的成员进行从小到大的排序
zset的成员是唯一的,但分数(score)却可以重复
Sorted Set 使用场景
有序集合的特点是有序,无重复值
与set不同的是sorted set每个元素都会关联一个score属性
redis正是通过score来为集合中的成员进行从小到大的排序
实战场景1: 实时排行榜
QQ音乐中有多种实时榜单,比如飙升榜、热歌榜、新歌榜
可以用redis key存储榜单类型,score为点击量,value为歌曲id
用户每点击一首歌曲会更新redis数据,sorted set会依据score即点击量将歌曲id排序
Redis 的线程模型
redis 内部使用文件事件处理器 file event handler,这个文件事件处理器是单线程的,所以 redis 才叫做单线程的模型
采用 IO 多路复用机制同时监听多个 socket,根据 socket 上的事件来选择对应的事件处理器进行处理
- 纯内存操作
- 核心是基于非阻塞的 IO 多路复用机制
- 单线程反而避免了多线程的频繁上下文切换问题
Redis有事务机制吗
有事务机制。Redis事务生命周期: 开启事务:使用MULTI开启一个事务
命令入队列:每次操作的命令都会加入到一个队列中,但命令此时不会真正被执行
提交事务:使用EXEC命令提交事务,开始顺序执行队列中的命令
Redis事务到底是不是原子性的
关系型数据库ACID 中关于原子性的定义:一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节
事务在执行过程中发生错误,会被恢复(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样
Redis官方文档对事务的定义:
- 事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断
- 事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。EXEC 命令负责触发并执行事务中的所有命令:如果客户端在使用 MULTI 开启了一个事务之后,却因为断线而没有成功执行 EXEC ,那么事务中的所有命令都不会被执行。另一方面,如果客户端成功在开启事务之后执行 EXEC ,那么事务中的所有命令都会被执行
官方认为Redis事务是一个原子操作,这是站在执行与否的角度考虑的
但是从ACID原子性定义来看,严格意义上讲Redis事务是非原子型的,因为在命令顺序执行过程中,一旦发生命令执行错误Redis是不会停止执行然后回滚数据
Redis为什么不支持回滚
在事务运行期间虽然Redis命令可能会执行失败,但是Redis依然会执行事务内剩余的命令而不会执行回滚操作
Redis官方的理由如下: 只有当被调用的Redis命令有语法错误时,这条命令才会执行失败(在将这个命令放入事务队列期间,Redis能够发现此类问题)
或者对某个键执行不符合其数据类型的操作
实际上,这就意味着只有程序错误才会导致Redis命令执行失败,这种错误很有可能在程序开发期间发现,一般很少在生产环境发现
支持事务回滚能力会导致设计复杂,这与Redis的初衷相违背,Redis的设计目标是功能简化及确保更快的运行速度
对于官方的这种理由有一个普遍的反对观点:程序有bug怎么办?
但其实回归不能解决程序的bug,比如某位粗心的程序员计划更新键A,实际上最后更新了键B,回滚机制是没法解决这种人为错误的
正因为这种人为的错误不太可能进入生产系统,所以官方在设计Redis时选用更加简单和快速的方法,没有实现回滚的机制
Redis事务相关的命令有哪几个
WATCH
Redis事务提供 check-and-set (CAS)行为
被WATCH的键会被监视,并会发觉这些键是否被改动过了
如果有至少一个被监视的键在 EXEC 执行之前被修改了, 那么整个事务都会被取消, EXEC 返回nil-reply来表示事务已经失败
MULTI
用于开启一个事务,它总是返回OK
MULTI执行之后,客户端可以继续向服务器发送任意多条命令, 这些命令不会立即被执行,而是被放到一个队列中,当 EXEC命令被调用时, 所有队列中的命令才会被执行
UNWATCH
取消 WATCH 命令对所有 key 的监视,一般用于DISCARD和EXEC命令之前
如果在执行 WATCH 命令之后, EXEC 命令或 DISCARD 命令先被执行了的话,那么就不需要再执行 UNWATCH 了
因为 EXEC 命令会执行事务,因此 WATCH 命令的效果已经产生了
而 DISCARD 命令在取消事务的同时也会取消所有对 key 的监视,因此这两个命令执行之后,就没有必要执行 UNWATCH 了
DISCARD
当执行 DISCARD 命令时, 事务会被放弃, 事务队列会被清空,并且客户端会从事务状态中退出
EXEC
负责触发并执行事务中的所有命令
如果客户端成功开启事务后执行EXEC,那么事务中的所有命令都会被执行
如果客户端在使用MULTI开启了事务后,却因为断线而没有成功执行EXEC,那么事务中的所有命令都不会被执行
需要特别注意的是:即使事务中有某条/某些命令执行失败了,事务队列中的其他命令仍然会继续执行,Redis不会停止执行事务中的命令,更不会像通常使用的关系型数据库一样进行回滚
集群模式
引入Cluster模式的原因
不管是主从模式还是哨兵模式都只能由一个master在写数据,在海量数据高并发场景,一个节点写数据容易出现瓶颈,引入Cluster模式可以实现多个节点同时写数据
Redis-Cluster采用无中心结构,每个节点都保存数据,节点之间互相连接从而知道整个集群状态
Cluster模式其实就是多个主从复制的结构组合起来的,每一个主从复制结构可以看成一个节点,那么上面的Cluster集群中就有三个节点
遍历
以某个固定的已知的前缀开头遍历
1
keys pre*
redis 的单线程的,keys 指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复
这个时候可以使用 scan 指令,scan 指令可以无阻塞的提取出指定模式的 key 列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间 会比直接用 keys 指令长