get和set
get和set两个命令是最基本也是最常用的命令,主要用于操作字符串类型的数据。
1.SET 命令:
SET 命令用于设置指定 key 的值。如果 key 已经持有其他值,SET 就覆写旧值,无视类型。具体的命令格式如下:
SET key value
例如,SET mykey "Hello"
将key为"mykey"的值设置为"Hello"。
2.GET 命令:
GET 命令用于获取指定 key 的值。如果 key 不存在,返回 nil 。否则,返回 key 的值。如果 key 不是字符串类型,那么返回一个错误。具体的命令格式如下:
GET key
例如,GET mykey
将获取key为"mykey"的值。
对于上述这里的key value,不需要加上引号,就是表示字符串的类型。当然,如果要是给key和value 加上引号,也是可以的(单引号或者双引号都行)
示例:
[nullptr@bogon Code]$ redis-cli
127.0.0.1:6379> set mykey "Hello,World!"
OK
127.0.0.1:6379> get mykey
"Hello,World!"
127.0.0.1:6379>
Redis全局命令
keys
keys命令用于在Redis数据库中查找匹配给定模式的所有键。这是一个非常有用的命令,因为它允许你查找和检查Redis数据库中的键
以下是keys命令的基础语法:
KEYS pattern
在这里,“pattern”参数是一个与键匹配的模式。可以在模式中使用星号(*)作为通配符。
例如,要查找以"test"开头的所有键,你可以使用以下命令:
KEYS test*
这将会返回所有以"test"开头的键。
keys的通配样式:
- h?llo 匹配 hello , hallo 和 hxllo
- h*llo 匹配 hllo 和 heeeello
- h{ae}llo 匹配 hello 和 hallo 但不匹配 hillo
- h{^e}lo 匹配 hallo , hbllo , … 但不匹配 hello
- h{a-b}llo 匹配 hallo 和 hbllo
时间复杂度:O(N)
返回值:匹配 pattern 的所有 key。
示例:
127.0.0.1:6379> set key1 value1
OK
127.0.0.1:6379> set key2 value2
OK
127.0.0.1:6379> set mykey myvalue
OK
127.0.0.1:6379> keys key*
1) "key2"
2) "key1"
127.0.0.1:6379> keys *
1) "mykey"
2) "key2"
3) "key1"
需要注意的是,尽管KEYS命令可以在开发环境中进行调试和测试很有用,但是在生产环境中使用需要谨慎,因为如果数据库中包含大量的键,执行此命令可能会消耗大量的计算资源并阻塞Redis服务器。这是因为Redis是单线程的,而KEYS命令在执行时会阻塞其他的Redis操作。
对于生产环境,Redis提供了一个SCAN命令,可以用来增量地查找数据库中的键。这是一个更为安全和高效的方式来查找和操作大量的键。
exists
判断某个key是否存在。
语法:
EXISTS key [key ...]
时间复杂度:O(1),redis组织这些key就是按照哈希表的方式来组织的
返回值:key存在的个数。
示例:
127.0.0.1:6379> exists key1
(integer) 1
127.0.0.1:6379> exists key1 key2
(integer) 2
127.0.0.1:6379>
从Redis 3.0.3版本开始,EXISTS命令才支持检查多个键。
del
删除指定的key
语法:
DEL key [key ...]
时间复杂度:O(1)
返回值:删除掉的key的个数。
示例:
127.0.0.1:6379> keys key*
1) "key2"
2) "key1"
127.0.0.1:6379> del key1 key2
(integer) 2
127.0.0.1:6379> keys key*
(empty list or set)
注意:在mysql数据库中删除操作是非常危险的,而在redis中需要根据场景来分析是否危险
Redis的三种主要应用场景:作为缓存、数据库和消息队列,以及在这些场景下误删数据带来的影响。
- 当Redis被用作缓存时,它主要存储的是热点数据,而全量数据仍在MySQL等数据库中。这种情况下,如果删除了Redis中的部分键,问题通常不大,因为这些数据可以从MySQL重新加载到缓存。但如果误删除了大量或全部数据,会对系统性能产生重大影响,因为大部分请求将直接打到MySQL,可能导致MySQL负载过重。
- 当Redis被用作数据库时,误删数据的影响就更大了,因为Redis可能存储了无法从其他地方恢复的重要数据。
- 当Redis被用作消息队列(MQ)时,误删数据的影响则需要具体问题具体分析。一些重要的消息可能无法恢复,导致丢失,而一些可以重新发布的消息则可能影响不大。
expire和ttl
expire命令用于设置一个键的过期时间。一旦键达到过期时间,它就会被自动删除。这对于缓存失效的场景特别有用。
以下是expire
命令的基本语法:
EXPIRE key seconds
在这里,“key”参数是你想要设置过期时间的键的名称,“seconds”参数是你希望键在多少秒后过期。
例如,你可以使用以下命令来设置一个名为"mykey"的键,在60秒后过期:
expire mykey 60
如果键成功设置了过期时间,EXPIRE命令会返回1。如果键不存在或者不能设置过期时间,它将返回0。
你可以使用TTL
命令来检查一个键还有多少秒会过期:
TTL mykey
这将返回剩余的过期时间(以秒为单位)。如果键不存在或者没有设置过期时间,它将返回-1。如果键已经过期,它将返回-2。
示例:
127.0.0.1:6379> set mykey 1
OK
127.0.0.1:6379> expire mykey 60
(integer) 1
127.0.0.1:6379> TTL mykey
(integer) 54
对于计算机来说,秒是一个非常长的时间,那么如何设置更细的时间呢?
使用pexpire
命令:
pexpire key 毫秒
同时使用pttl
命令查看
key的过期策略
一个redis 中可能同时存在很多很多key。这些key中可能有很大一部分都有过期时间.此时, redis服务器咋知道哪些key已经过期要被删除,哪些key还没过期?
在 Redis 中,有三种主要的键过期策略:
- 定时删除:当设置键的过期时间时,同时创建一个定时器,当过期时间到达时,立即删除该键。这种方式可以立即释放过期键所占用的内存,但是在大量键同时过期时,可能会阻塞服务器,导致延迟。
- 惰性删除:只有当有客户端试图访问一个键时,才会检查该键是否过期,如果过期则删除。这种方式可以避免大量键同时过期导致的服务器阻塞,但是如果过期键不被访问,它们会一直占用内存。
- 定期删除:每隔一段时间,程序就会随机检查一些键是否过期,如果过期则删除。这种方式是定时删除和惰性删除的折中方案,通过控制检查的频率和数量,可以在内存占用和服务器阻塞之间找到一个平衡。
在实际运行中,Redis 同时使用了惰性删除和定期删除两种策略。当有客户端试图访问一个键时,会使用惰性删除。而定期删除则会在定时任务中进行,根据配置的策略,定时检查一定数量的随机键,删除其中已经过期的键
type
返回 key 对应的数据类型。
语法:
TYPE key
时间复杂度:O(1)
返回值: none , string , list , set , zset , hash and stream.
示例:
127.0.0.1:6379> set key1 value
OK
127.0.0.1:6379> lpush key2 value
(integer) 1
127.0.0.1:6379> sadd key3 value
(integer) 1
127.0.0.1:6379> type key1
string
127.0.0.1:6379> type key2
list
127.0.0.1:6379> type key3
set
Redis数据类型和内部编码
type命令实际返回的就是当前键的数据结构类型,它们分别是: string (字符串)、list(列表)、hash (哈希)、set(集合)、zset(有序集合),但这些只是Redis对外的数据结构
Redis的5种常见数据类型
具体如下:
- String(字符串):最基本的类型,一个 key 对应一个 value,value 可以是字符串、整数或者浮点数,并且对整数和浮点数的值可以进行自增或自减操作。
- List(列表):String 类型元素的集合,是链表实现,可以支持从两端压入或者弹出元素,时间复杂度为O(1)。还可以获取、删除列表中的某一元素,或者对元素进行修剪(trim),但是这些操作时间复杂度都是 O(n)。
- Set(集合):String 类型元素的无序集合,通过哈希表实现,可以进行添加、删除、查找等操作,时间复杂度为 O(1)。还可以对两个集合进行并集,交集,差集等操作。
- Sorted Set(有序集合):String 类型元素的有序集合,每个元素都会关联一个 double 类型的分数,表示该元素的排序权重,Redis 通过分数来为集合中的元素进行从小到大的排序。操作时间复杂度为 O(log N)。
- Hash(哈希):键值对的集合,适用于存储对象。相当于是 String 类型的 field 和 value 的映射表,特别适合用于存储对象。同时,Redis 还提供了多个操作 Hash 的命令,如:增加、删除、修改和搜索等。
实际上Redis针对每种数据结构都有自己的底层内部编码实现,而且是多种实现,这样Redis会在合适的场景选择合适的内部编码
Redi五种数据类型的内部编码如下:
- String(字符串):可以编码为 int、raw 或者 embstr。如果一个字符串可以被表示为一个整数且这个整数小于1亿,则编码为 int。如果字符串长度大于 39 字节,则编码为 raw,否则为 embstr。
- List(列表):可以编码为 ziplist 或者 linkedlist。当列表中所有元素都是小整数或者长度小于64字节的字符串,且列表元素数量少于512个时,使用 ziplist 编码,否则使用 linkedlist 编码。
- Set(集合):可以编码为 intset 或者 hashtable。当集合中所有元素都是整数,且元素数量少于512个时,使用 intset 编码,否则使用 hashtable 编码。
- Sorted Set(有序集合):可以编码为 ziplist 或者 skiplist。当有序集合元素数量少于128个,且所有元素都是小整数或者长度小于64字节的字符串时,使用 ziplist 编码,否则使用 skiplist 编码。
- Hash(哈希):可以编码为 ziplist 或者 hashtable。当哈希表中所有字段值都是小整数或者长度小于64字节的字符串,且字段数量少于512个时,使用 ziplist 编码,否则使用 hashtable 编码。
可以看到每种数据结构都有至少两种以上的内部编码实现,例如list数据结构包含了linkedlist和ziplist两种内部编码。同时有些内部编码,例如ziplist,可以作为多种数据结构的内部实现,可以通过object encoding命令查询内部编码:
127.0.0.1:6379> set hello world
OK
127.0.0.1:6379> lpush mylist a b c
(integer) 3
127.0.0.1:6379> object encoding hello
"embstr"
127.0.0.1:6379> object encoding mylist
"quicklist"
可以看到hello对应值的内部编码是embstr,键 mylist对应值的内部编码是ziplist
Redis这样设计有两个好处:
- 可以改进内部编码,而对外的数据结构和命令没有任何影响,这样一旦开发出更优秀的内部编码,无需改动外部数据结构和命令,例如Redis 3.2提供了quicklist,结合了ziplist和linkedlist两者的优势,为列表类型提供了一种更为优秀的内部编码实现,而对用户来说基本无感知。
- 多种内部编码实现可以在不同场景下发挥各自的优势,例如ziplist比较节省内存,但是在列表元素比较多的情况下,性能会下降,这时候Redis会根据配置选项将列表类型的内部实现转换为linkedlist,整个过程用户同样无感知。
Redis单线程架构
Redis使用了单线程架构来实现高性能的内存数据库服务,我们首先通过多个客户端命令调用的例子说明Redis单线程命令处理机制,接着分析Redis单线程模型为什么性能如此之高,最终给出为什么理解单线程模型是使用和运维Redis的关键。
引出单线程模型
现在开启了三个redis-cli客户端同时执行命令。
客户端1设置⼀个字符串键值对:
127.0.0.1:6379> set hello world
客户端2对counter做自增操作:
127.0.0.1:6379> incr counter
客户端3对 counter做自增操作:
127.0.0.1:6379> incr counter
我们已经知道从客户端发送的命令经历了︰发送命令、执行命令、返回结果三个阶段,其中我们重点关注第2步。我们所谓的Redis是采用单线程模型执行命令的是指:虽然三个客户端看起来是同时要求Redis去执行命令的,但微观角度,这些命令还是采用线性方式去执行的,只是原则上命令的执行顺序是不确定的,但一定不会有两条命令被同步执行。
宏观上同时要求服务的客户端
微观上客户端发送命令的时间有先后次序的
Redis的单线程模型
可以想象Redis内部只有一个服务窗口,多个客户端按照它们达到的先后顺序被排队在窗口前,依次接受Redis的服务,所以两条incr命令无论执行顺序,结果一定是2,不会发生并发问题,这个就是Redis的单线程执行模型。
为什么单线程还能这么快
通常来讲,单线程处理能力要比多线程差,例如有10000公斤货物,每辆车的运载能力是每次200公斤,那么要50次才能完成;但是如果有50辆车,只要安排合理,只需要依次就可以完成任务。那么为什么Redis使用单线程模型会达到每秒万级别的处理能力呢?可以将其归结为三点:
- 纯内存访问。Redis将所有数据放在内存中,内存的响应时长大约为100纳秒,这是Redis达到每秒万级别访问的重要基础。
- 非阻塞IO。Redis使用epoll作为I/O多路复用技术的实现,再加上Redis自身的事件处理模型将epoll中的连接、读写、关闭都转换为事件,不在网络I/O上浪费过多的时间
- 单线程避免了线程切换和竞态产生的消耗。单线程可以简化数据结构和算法的实现,让程序模型更简单;其次多线程避免了在线程竞争同一份共享数据时带来的切换和等待消耗。
Redis使用I/O多路复用模型
虽然单线程给Redis带来很多好处,但还是有一个致命的问题:对于单个命令的执行时间都是有要求的。如果某个命令执行过长,会导致其他命令全部处于等待队列中,迟迟等不到响应,造成客户端的阻塞,对于Redis这种高性能的服务来说是非常严重的,所以Redis是面向快速执行场景的数据库。