Redis汇总

Redis

Posted by Ekko on July 14, 2024

参考资料 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的数据类型就比较丰富了,主要包括五种

  1. String
  2. Hash
  3. List
  4. Set
  5. Sorted Set

Redis数据结构.png


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采用无中心结构,每个节点都保存数据,节点之间互相连接从而知道整个集群状态

集群模式.png

Cluster模式其实就是多个主从复制的结构组合起来的,每一个主从复制结构可以看成一个节点,那么上面的Cluster集群中就有三个节点


遍历

以某个固定的已知的前缀开头遍历

1
keys pre*

redis 的单线程的,keys 指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复

这个时候可以使用 scan 指令,scan 指令可以无阻塞的提取出指定模式的 key 列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间 会比直接用 keys 指令长