一、Redis简介
Redis是基于键值对形式的NoSQL数据库,在Redis数据库中其键的类型必须是String,但是其值可以是string(字符串)、hash(哈希)、list(列表)、set(集合)、zset(有序集合)、 Bitmaps(位图)、HyperLogLog、GEO(地理信息定位)等多种数据结构和算法组成,所以说Redis能满足多种应用场景,Redis会将所有的数据存放在内存中,还可以将内存中的数据利用快照和日志的形式保存到硬盘中,因此读写性能也较好,并且在发生断电或者机器故障时,内存数据不会丢失。除此之外,Redis还提供键过期、发布订阅、事务、流水线、Lua脚本等附加功能。
Redis的特性
- 在内存中存储数据:Redis是通过表来组织存储数据的,属于“关系型数据库”,Redis是通过键值对来存储数据的,key必须是String,Value可以是String、List、Set等数据结构,Redis自身的Hash表是通过哈希表来组织的,属于“非关系型数据库”
- 可编程性:可以通过简单的交互式命令或者脚本的方式,批量执行一些操作
- 可拓展性:可以在Redis原有提供的API的基础上在进行拓展
- 持久性:Redis会把数据主要存储在内存上,硬盘作为辅助(内存的数据备份),如果Redis重启了,就会在重启时加载硬盘中的备份数据,使Redis的内存恢复到重启前的状态。
- 支持集群:Redis作为分布式系统的中间件,能存储的数据是有限的,支持集群就能引入多个主机,部署多个Redis节点,每个Redis能够存储一部分数据
- 高可用性:Redis自身支持“主从”结构的,从节点相当于对主节点的备份。
Redis的应用场景
- 当作数据库使用:Redis是存储在内存中的,访问速度较快,性能较好,Redis中存储的是全量数据,数据不会随便丢失,但是需要更多的硬件资源
- 作为缓存使用:Mysql存储数据多,但访问速度较慢,热点数据存储在Redis中(二八原则),Redis存储部分数据,全量数据是存储在Mysql中的,Redis中即使数据丢失也能从Mysql中再进行加载恢复出来的
- 会话数据存储在Redis中:之前的session存储在应用服务器中的,现在将会话存储在Redis中时,不论负载均衡器将用户请求放到那个应用服务器,都能通过Redis拿到会话信息,并且在应用重启后session信息不会丢失。
- 消息队列(服务器):对于分布式系统中服务器之间可以使用生产者-消费者模型(可以解耦合和削峰填谷),Redis也是客户端-服务器程序(Mysql也是)
Redis过期删除策略
Redis的过期删除策略是定期删除和惰性删除相结合
惰性删除:假设key已经到过期时间了,但是暂时还没有删除,如果后面访问到了这个key,redis服务器就会触发删除key的操作,同时再返回一个nil;
定期删除:每次会抽取一部分,进行验证过期时间,保证抽取检查的过程的速度足够快(Redis是单线程的程序,主要的任务是处理每个命令的任务,如果扫描过期key耗用的时间太多了,正式处理的请求命令就有可能阻塞)
Redis并没有采取定时器的方式来实现过期key的删除
Redis的单线程模型
Redis只用一个线程来处理所有的命令请求(避免了线程安全问题),但并不代表一个Redis服务器进程内部只有一个线程,只是多个线程在处理网络IO,Redis的核心业务逻辑都是短平快的,不太消耗CPU资源,故可以使用单线程模型。
单线程模型的弊端:操作命令占用时间长就会阻塞其他命令的执行
Redis虽然是单线程但是效率高的原因(参照物是数据库(mysql、Oracle、sql server等)):
- Redis的操作是访问内存,数据库是访问硬盘
- Redis的核心业务比数据库的更简单(数据库对数据的增删改查都有复杂的业务支持,这样的功能会耗用更多的额外开销)
- Redis的单线程模型避免了一些不必要的线程进程开销,Redis的核心业务逻辑都是短平快的,不太消耗CPU资源,多个线程对于Redis的提升不大
处理网络IO的时候,使用了epoll这样的IO多路复用机制
二、Redis的全局命令
- 必须要先进入redis客户端才能操作Redis命令
- Redis命令不区分大小写
- Redis内部是按照二进制来存储数据的,要想把二进制字节换回汉字,就需要客户端来支持 redis-cli --raw
keys pattern:查询当前服务器上匹配的key,通过一些特殊符号(通配符)来匹配key,时间复杂度是O(n),执行keys *的时间会非常长,不建议使用
Pattern:包含特殊符号的字符串,为了描述符合要求的字符串
?匹配任意一个字符
* 匹配0个或任意字符
[] 匹配[]其中的任意一个
[^] 排除其中的字符,其余都能匹配
[a-b] 匹配从a到b范围内的字符,包含边界字符
exists key [key...]:判断key是否存在,返回key存在的个数(针对多个key),时间复杂度为O(1),Redis是按照哈希表的方式来存储的
del key [key...]: 删除key,返回删除key的个数,若不存在返回0,时间复杂度为O(1)
expire key seconds:给指定的key设置过期时间,超出指定值就会被自动删除,该命令适用于存在的key,设置成功返回1,失败返回0,时间复杂度为O(1),时间单位是秒(例如短信验证码、优惠券的有效期)
ttl key:查询key的过期时间,key没有设置过期时间返回-1,key不存在返回-2,时间复杂度为O(1)
Flushall:清空redis上的所有数据,相当于清库
三、Redis常见数据类型
String数据类型
常见命令:
SET:将string 类型的value设置到key中。如果key之前存在,则覆盖,⽆论原来的数据类型是什么。之前关于此key的TTL也全部失效。
语法格式: SET key value [expiration EX seconds|PX milliseconds] [NX|XX]
时间复杂度:O(1)
选项:
SET命令⽀持多种选项来影响它的⾏为:
• EX seconds⸺使⽤秒作为单位设置key的过期时间。
• PX milliseconds⸺使⽤毫秒作为单位设置key的过期时间。
• NX⸺只在key不存在时才进⾏设置,即如果key之前已经存在,设置不执⾏(不存在才执行)。
• XX⸺只在key存在时才进⾏设置,即如果key之前不存在,设置不执⾏(存在才执行)。
GET:获取key对应的value。如果key不存在,返回nil。如果value的数据类型不是string,会报错。
语法格式: GET key
时间复杂度:O(1)
返回值:key对应的value,或者nil当key不存在。
MSET:⼀次性设置多个key的值。
语法格式: MSET key value [key value ...]
时间复杂度:O(N)N是key数量
返回值:永远是OK
MGET:⼀次性获取多个key的值。如果对应的key不存在或者对应的数据类型不是string,返回nil。
语法格式:MGET key [key ...]
时间复杂度:O(N)N是key数量,命令中key的数量,可以近似认为是O(1)
返回值:对应value的列表
SETNX:设置key-value但只允许在key之前不存在的情况下。
SETXX:设置key-value但只允许在key之前存在的情况下。
SETEX:设置过期时间
PSETEX:设置过期时间,单位是毫秒
语法格式:SETNX key value
时间复杂度:O(1)
返回值:1表⽰设置成功。0表⽰没有设置。
APPEND:如果key已经存在并且是⼀个string,命令会将value追加到原有string的后边。如果key不存在,则效果等同于SET命令
语法格式:APPEND KEY value
时间复杂度:O(1)
返回值:追加完成后string的长度(字节)(utf8编码中一个汉字是3个字节)
GETRANGE:返回key对应的string的⼦串,由start和end确定(左闭右闭)。可以使⽤负数表⽰倒数。-1代表倒数第⼀个字符,-2代表倒数第⼆个,其他的与此类似。超过范围的偏移量会根据string的⻓度调整成正确的值。
语法格式:GETRANGE key start end
时间复杂度:O(n)
返回值:string的指定范围的子串
SETRANGE:覆盖字符串的⼀部分,从指定的偏移开始,如果key不存在,会将offset之前的内容填充为0X00
语法格式:SETRANGE key offset value
时间复杂度:O(n)
返回值:替换后string的长度
STRLEN:获取key对应的string的⻓度(单位是字节)。当key存放的类似不是string时,报错。
语法格式:STRLEN key
时间复杂度:O(1)
返回值:string的长度
字符串类型的编码方式:
Int:8个字节类型的长整型
Embstr:小于等于39个字节的字符串,存储小数也是按照压缩字符串类型
Raw:大于39个字节的字符串
String类型的应用场景:
- 缓存
- 计数
- 共享会话
- 手机验证码(过期删除)
计数命令:
时间复杂度均是O(1)
INCR:将key对应的string表⽰的数字加⼀。如果key不存在,则视为key对应的value是0。如果key对应的string不是⼀个整型或者范围超过了64位有符号整型,则报错。
语法格式:INCR key
INCRBY:将key对应的string表⽰的数字加上对应的值。如果key不存在,则视为key对应的value是0。如果key对应的string不是⼀个整型或者范围超过了64位有符号整型,则报错。
语法格式: INCRBY key decrement
DECR:将key对应的string表⽰的数字减⼀。如果key不存在,则视为key对应的value是0。如果key对应的string不是⼀个整型或者范围超过了64位有符号整型,则报错。
语法格式:DECR key
DECRBY:将key对应的string表⽰的数字减去对应的值。如果key不存在,则视为key对应的value是0。如果key对应的string不是⼀个整型或者范围超过了64位有符号整型,则报错。
语法格式:DECRBY key decrement
INCRBYFLOAT:将key对应的string表⽰的浮点数加上对应的值。如果对应的值是负数,则视为减去对应的值。如果key 不存在,则视为key对应的value是0。如果key对应的不是string,或者不是⼀个浮点数,则报错。允许采⽤科学计数法表示浮点数
语法格式:INCRVYFLAOT key increment
List数据类型
List列表类型:用于存储多个有序的字符串,约定起始下标从左侧0开始,两侧都可以进行插入和删除(可以当作栈和队列来使用)
List列表类型的特点:
列表中的元素是有序的(元素顺序调换和之前的List是不等价的)
区分获取和删除的区别(获取并不会修改列表,但是删除会修改列表)
允许元素重复
常见命令(使用如下命令时要求key对应的value是list,否则就会报错):
LPUSH:将一个或多个元素从左侧(头插)到list中
语法格式:LPUSH key element [element...]
时间复杂度:O(1)
返回值:插入后List的长度
RPUSH:将一个或多个元素从右侧(尾插)到list中
语法格式:RPUSH key element [element...]
时间复杂度:O(1)
返回值:插入后List的长度
LRANGE:获取从start到stop的所有元素,左闭右闭
语法格式:LRANGE key start stop
时间复杂度:O(1)
返回值:指定区间的元素
当区间下标非法时,Redis会尽可能取到给定区间的元素
LPOP:从List左侧取出元素(头删)
语法格式:LPOP key
时间复杂度:O(1)
返回值:取出的元素或者nil
RPOP:从List右侧取出元素(尾删)
语法格式:RPOP key count count参数的使用在redis 6.2以及之后的版本
时间复杂度:O(1)
返回值:取出的元素或者nil
LINDEX:获取从左数第index位置的元素
语法格式:LINDEX key index
时间复杂度:O(N) N指列表的长度 该命令需要遍历链表
返回值:取出的元素或者nil
LINSERT:在特定位置插入元素
语法格式:LINSERT key pivot element 是在基准元素第一次出现的位置进行插入
时间复杂度:O(N)
返回值:插入成功后List的长度
LLEN:获取List的长度
语法格式:LLEN key
时间复杂度:O(1)
返回值:List的长度
LREM:删除指定的元素
语法格式:LREM key count element
count是要删除的个数 count>0 从左向右删除, count<0 从右往左删除, count=0删除所有的元素
element是指要删除的值
时间复杂度:O(N)
返回值:成功删除的元素的个数
LTRIM:删除指定范围的元素
语法格式:LRIM key start stop 保留start和stop区间内的元素,将两边的元素进行删除
时间复杂度:O(N)
返回值:是否删除成功
LSET:根据下标修改元素
语法格式:LSET key index element
时间复杂度:O(N)
返回值:下标不合法时会报错,否则返回ok
阻塞版本的命令:
blpop和brpop是lpop和rpop的阻塞版本(阻塞队列,支持“队列为空”的情况,不考虑“队列为满”的情况)和对应的非阻塞版本的作用一致(当list中存在元素时),但仍存在如下区别:
如果list为空,blpop和brpop就会一直阻塞到队列不空为止,阻塞期间Redis依然可以执行别的命令,并且可以显示设置阻塞时间
blpop和brpop可以同时指定多个key,命令中如果设置了多个键,那么就会从左到右来进行遍历,一旦有键对应的链表可以弹出元素,命令就会立即返回
如果多个客户端同时执行一个键pop,则最先执行命令的客户端会得到弹出的元素
BLPOP:从List左侧取出元素(头删)
语法格式:BLPOP key [key …] timeout(单位是s)Redis6允许设置超时时间为小数形式
这些List中右任意一个非空,blpop就都能把这些元素获取到并立即返回
如果这些List都为空,此时就需要进行阻塞等待,等待其他客户端往这些list中插入元素
时间复杂度:O(1)
返回值:
列表非空情况:二元组形式,可以说明数据来源与那个key,以及取到的数据
空列
表情况:进行阻塞等待,得到数据后进行返回,并返回等待时间
list的内部编码:通过quickList进行内部实现,quick List是链表和压缩链表的结合,整体是通过链表实现,链表的每个节点是一个压缩链表,每个压缩列表都不会太大然后用压缩链表进行相连,整体的操作效率高,并且能够节省空间
list类型的应用场景:
- 作为类似数组的结构来存储数据(班级中存储学生列表)
- 作为消息队列使用(生产者-消费者模型)
- 对于一对多场景中进行列表分页
- Pipline(流水线)机制,减少网络交互的次数
- 选择列表类型时:同侧存取(lpush和lpop或者rpush和rpop)为栈,异侧存取(lpush和rpop或者rpush和lpop)为队列
Hash数据类型
Hash哈希类型:Redis自身属于hash键值对建构,value的类型也可以是Hash表类型(“fileld -> value”),此处的value也可以当作数字进行处理
常见命令:
HSET:设置Hash中指定的字段(field)的值(value),此处的value只能是字符串类型
语法格式:HSET key field value [ field value ...]
时间复杂度:O(1)
返回值:设置成功的键值对的个数
HGET:获取hash中指定字段的值。
语法格式:HGET key field
时间复杂度:O(1)
返回值:字段对应的值或者nil
HEXISTS:判定hash中是否有指定的字段
语法格式: HEXISTS key field
时间复杂度:O(1)
返回值:1表示存在,0表示不存在
HDEL:删除hash中的指定字段
语法格式:HDEL key field
时间复杂度:O(1)
返回值:本次操作成功删除的字段个数
HKEYS:获取hash中的所有字段
语法格式:HKEYS key
时间复杂度:O(N)(N指的是hash表的长度),该操作会根据key找到hash,然后会进行遍历
返回值:字段列表
HVALS:获取hash中所有的值
语法格式:HVALS key
时间复杂度:O(N)
返回值:值列表
HGETALL:获取hash中所有的字段以及对应的值
语法格式:HGETALL key
时间复杂度:O(N)
返回值:所有的字段以及对应的值
HMGET:获取hash中多个field
语法格式:HMGET key field [field...]
时间复杂度:O(N)
返回值: 指定多个字段对应的值(field和value的顺序匹配)
HLEN:获取hash中所有字段的个数
语法格式:HLEN key
时间复杂度:O(1)
返回值:字段个数
HSETNX:在字段不存在的情况下,设置hash中的字段和值
语法格式:HSETNX key field value
时间复杂度:O(1)
返回值:1表示设置成功,2表示设置失败
HINCRBY:将hash字段对应的值添加指定的值
语法格式:HINCRBY key field increment
时间复杂度:O(1)
返回值:该字段变化后的值
HINCRBYFLOAT:将hash字段对应的值添加指定的值(浮点数)
语法格式:HINCRBYFLOAT key field increment
时间复杂度:O(1)
返回值:该字段变化后的值
HASH类型的应用场景:
缓存
作为关系型数据库使用
Redis基础2:https://developer.aliyun.com/article/1521822