🌸个人主页:https://blog.csdn.net/2301_80050796?spm=1000.2115.3001.5343
🏵️热门专栏:
🧊 Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm=1001.2014.3001.5482
🍕 Collection与数据结构 (92平均质量分)https://blog.csdn.net/2301_80050796/category_12621348.html?spm=1001.2014.3001.5482
🧀线程与网络(96平均质量分) https://blog.csdn.net/2301_80050796/category_12643370.html?spm=1001.2014.3001.5482
🍭MySql数据库(93平均质量分)https://blog.csdn.net/2301_80050796/category_12629890.html?spm=1001.2014.3001.5482
🍬算法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12676091.html?spm=1001.2014.3001.5482
🍃 Spring(97平均质量分)https://blog.csdn.net/2301_80050796/category_12724152.html?spm=1001.2014.3001.5482
🎃Redis(96平均质量分)https://blog.csdn.net/2301_80050796/category_12777129.html?spm=1001.2014.3001.5482
感谢点赞与关注~~~
目录
- [1. Redis常见指令](#1. Redis常见指令)
- [2. redis中的常见数据类型](#2. redis中的常见数据类型)
-
- [2.1 常见的数据类型](#2.1 常见的数据类型)
- [2.2 对数据类型的优化](#2.2 对数据类型的优化)
- [3. Redis中的单线程模型](#3. Redis中的单线程模型)
1. Redis常见指令
Redis中有上百个指令,但是我们只要记住常见的指令即可,剩下的指令如果不记得,可以去官方网站的文档上查阅即可.
- Set
set key value
我们知道,Redis是以键值对的方式来存储数据的,set用来在Redis中设置键值对,把key和value存入.
其中redis中的value可以支持多种类型的数据结构. - get
get key
可以根据key获取到对应的value.
- keys
查询当前服务器上符合条件的所有keys.在keys操作中,我们引入了通配符,通过通配符来描述通配符的模样,就可以把key查询出来.类似与MySQL中的模糊查询.
keys patten
其中patten中的通配符可以有多重类型.?
匹配任意一个字符*
匹配0个或任意多个字符[abc]
只能匹配abc三个字符中的任意一个字符,其他字符不可以匹配[^e]
除了e不能匹配,其他的任意一个字符都可以匹配.[a-c]
只能匹配a~c之间的任意一个字符,其他的字符都不可以匹配.
我们给Redis中设置一些key和value
shell
127.0.0.1:6379> set hello 1
OK
127.0.0.1:6379> set hallo 2
OK
127.0.0.1:6379> set hbllo 3
OK
127.0.0.1:6379> set heeeeeeeeello 4
OK
127.0.0.1:6379> set hpllo 5
OK
我们接下来使用keys指令进行查询
shell
127.0.0.1:6379> keys *
1) "heeeeeeeeello"
2) "hello"
3) "hbllo"
4) "hpllo"
5) "hallo"
6) "key1"
127.0.0.1:6379> keys h?llo
1) "hello"
2) "hbllo"
3) "hpllo"
4) "hallo"
127.0.0.1:6379> keys h*llo
1) "heeeeeeeeello"
2) "hello"
3) "hbllo"
4) "hpllo"
5) "hallo"
127.0.0.1:6379> keys h[a-c]llo
1) "hbllo"
2) "hallo"
127.0.0.1:6379> keys h[^a]llo
1) "hello"
2) "hbllo"
3) "hpllo"
127.0.0.1:6379> keys h[o-p]llo
1) "hpllo"
127.0.0.1:6379> keys h[abc]llo
1) "hbllo"
2) "hallo"
[注意事项] keys命令的时间复杂度是O(N),也就是把redis服务器中的所有key全部都遍历一遍,所以在公司生产环境的服务器上,我们严禁使用keys *
这样的指令.这时候就会使得其他客户端的redis指令超时(发生阻塞),超时之后,就会去查询MySQL数据库,这时候MySQL就会收到一大波请求,使得MySQL搓手不及,就容易把MySQL搞挂.
拓展知识: 企业中的几种环境
- 办公环境: 办公环境就是公司给你发的那台电脑的本机操作系统的环境.
- 开发环境: 在项目未上线之前,我们需要对一款软件进行开发,我们在开发的时候所在的操作系统的环境叫做开发环境.有些情况下,我们会使用本机的环境进行开发,也就是办公环境和开发环境是一样的,但是很多时候,公司也有专门的开发环境的服务器.有些情况下,一些软件的业务逻辑比较复杂,这时候本机环境是带动不起来的,就需要性能更高的服务器来带动.
- 测试环境: 测试工程师用来测试项目的操作系统环境.
- 生产环境(线上环境): 开发完成之后项目上线,部署到生产环境上之后,项目就可以用来给用户提供服务,为外界用户直接提供服务的环境叫做生产环境,这个环境外界用户可以直接访问到.生产环境会直接影响到公司的营收.
- exists
exists key [key...]
判断一个key是否存在于服务器上.**一次可以查询一个key,也可以查询多个key.最终返回的是存在key的个数,查询的时间复杂度是O(1)
shell
127.0.0.1:6379> exists hello
(integer) 1
127.0.0.1:6379> exists hello hallo
(integer) 2
一般在想要查询多个key是否存在的时候,我们一般不使用分开多次查询的做法,我们一般使用写在一起的写法.这是因为,这其中每一次查询就是一次网络请求,网络会对数据报进行封装和分用,这其中封装和分用会占用网络资源,降低性能,所以我们建议把查询的数据放在一次网络请求中全部查询完成.**其中除了这个操作之外,redis中还有好多指令都支持一次操作多个key的操作.
- del
del key [key...]
删除一个key或者是多个key.
shell
127.0.0.1:6379> del key1 hallo
(integer) 2
127.0.0.1:6379> del hpllo
(integer) 1
127.0.0.1:6379> keys *
1) "heeeeeeeeello"
2) "hello"
3) "hbllo"
这里注意,在redis中删除键值对并不想没有MySQL中那样危险,即使不小心误删了之后依然可以从MySQL中同步过来,但是redis一次不可以删除大量的数据,这样在从MySQL恢复数据的时候,就会向MySQL发送大量请求,很容易把MySQL搞挂.
- expire
expire key seconds
给一个key设置过期时间,其中时间是秒级别.这个key必须在redis中存在.在给一个key设置过期时间之后,这个key就会在指定的时间之后过期.
如果想设置毫秒级别,可以用
pexpire key 时间
来设置.
shell
127.0.0.1:6379> set key1 value1
OK
127.0.0.1:6379> EXPIRE key1 10
(integer) 1
127.0.0.1:6379> get key1
"value1"
127.0.0.1:6379> get key1
(nil)
这个功能使用的场景非常广泛,比如我们常用的手机验证码,还有美团优惠劵的到期时间,都是使用了redis的过期时间.
- ttl
ttl key
获取指定key的过期时间,时间为秒级,如果想获取毫秒级别的,可以使用pttl
.
其中,如果key不存在过期时间的时候,返回-1,如果没有找到这个key,返回-2
shell
127.0.0.1:6379> keys *
1) "heeeeeeeeello"
2) "hello"
3) "hbllo"
127.0.0.1:6379> ttl hello
(integer) -1
127.0.0.1:6379> set key1 val1
OK
127.0.0.1:6379> EXPIRE key1 10
(integer) 1
127.0.0.1:6379> ttl key1
(integer) 5
127.0.0.1:6379> ttl key1
(integer) -2
[常考面试题] redis中的key的过期时间是基于什么来实现的呢?
redis中有好多的键值对,redis怎么知道哪个过期了,哪个没有过期呢?绝对不是一个一个的遍历来实现的,这样的效率非常低下.redis采用了一下的策略:
- 惰性删除
假设一个key已经到了过期的时间,但是我们先不要删除这个key,当下次访到这个key之后,查看这个key是否过期,如果过期了,就删除掉. - 定期删除
redis会对过期的key进行定期删除,但是这里的删除并不是吧key全部遍历之后在找到过期的key进行删除,而是抽取一些key来判断是否过期,之后进行对应的删除. - 内存淘汰策略
我们后面再详细介绍.
举例说明: 去商店买东西
当我们拿了一件商品之后,在结账的时候,我们发现这件商品过期了,这时候我们会告诉老板这件商品过期了,老板这时候就会把这件商品扔掉,这就是惰性删除.其次老板会定期抽取一部分的商品来查看商品是否过期,过期就会人掉,这就是定期删除.
[注意事项] 网上有很多人说redis的过期机制是定时删除,在redis中有一个定时器,定时器到达时间之后,redis就会将之删除,但是我们注意,redis并没有采取这种删除的方式.
拓展: 比较高效的定时器实现方式
- 优先级队列方式
这个方式我们在之前多线程的部分曾经向大家介绍过.
首先我们按照key的过期时间把这些数据组成一个优先级队列,过期时间越短,优先级越高,之后就会有一个线程去不停地检查队首的元素,只要队首的元素到达过期时间,我们就把队首结点和最后一个结点进行交换,之后删除最后一个结点,之后再对这个优先级队列进行向下调整.
当然该线程也不是一直在对一个队首的元素进行扫描,这样就会出现忙等的状态,我们会根据过期时间设置一个线程休眠的时间,在时间差不多的时候,就会检查一次队首的元素是否过期,之后如果等待的中间来了新的请求,就唤醒一次线程,顺便检查一下队首的元素是否过期.- 基于时间轮的方式实现定时器
我们可以把时间轮上的时间划分为多个小段(具体的划分粒度,看具体的业务场景),其中每一个小段上都挂着一个链表,这个链表中是需要执行的任务,时间轮上有一个指针,指针每隔一段时间就需要向下走一格.没走一个,就要检查链表中有没有需要执行的任务.
- type
type key
获取该key对应value的数据类型.虽然redis中的key只有String类型,但是value可以有多种类型.常见的数据类型有:string ,list ,set ,zset ,hash, stream,如果没有这个key的时候,返回none.具体如何创建不同的数据类型,我们后面再详细介绍.
shell
127.0.0.1:6379> type hello
string
127.0.0.1:6379> type hallo
none
2. redis中的常见数据类型
2.1 常见的数据类型
redis目前支持的数据类型有10种,常用的数据结构有5种,剩下的5种事针对特殊场景的特殊的数据结构.
上面的几种数据类型,首先是字符串,对应的是我们java中的String,其次Hash表对应的是java中的HashMap,列表对应的是Java中的List,集合对应的是Java中的Set,有序集合比较特殊,其中除了存储成员之外,还会存储该成员(member)的权重(score),有点像Java中的PriorityQueue.
2.2 对数据类型的优化
有时候redis在实现这些数据结构的时候,底层并不一定使用的是相应的数据结构,会在对应的数据结构上视情况进行一些优化.但是redis承诺,你在用起来的时候,和原来的数据结构是一样的效果.
比如我们现在创建的一个key其中的数据结构是Hash表,但是在数据比较少的时候,也在底层也不一定使用的是Hash表来实现的,但是我们用起来和Hash表没有什么区别,时间复杂度都是O(1).
这和前段时间的"指鼠为鸭"事件有些相似,虽然说外表长得不太像鸭脖,但是商家承诺,这个"鸭脖"吃起来和正常的"鸭脖"没有什么区别
具体优化情况如下:
- raw
这个是最基本的字符串,类似与Java中的byte[ ]数组. - int
redis有时会实现一些计数的功能,当value是整数的时候,redis就会直接用int类型来保存. - embstr
当字符串比较短的时候,redis就会把字符串优化为embstr. - hashtable
最基本的hash表,这里不要误会,这和Java中那个线程安全的HashTable不一样. - ziplist
当hash表中的元素比较少的时候,会被压缩为ziplist. - linkedlist
链表 - ziplist
压缩列表,但是在redis3.2之后就引入了quicklist,它兼顾了linkedlist和ziplist两者的优点. - intset
当set中存储的都是整数的时候,会被优化为intset. - skiplist
跳表,这个跳表有点类似与链表中的复杂链表,结点中存储着下一个结点的引用,然后还存储着随机一个结点的引用.
我们可以使用object encoding
来查看value中存储数据的实际类型.
shell
127.0.0.1:6379> set key1 1
OK
127.0.0.1:6379> object encoding key1
"int"
3. Redis中的单线程模型
redis中的单线程模型指的就是,redis在处理命令的时候,都只使用一个线程来处理,说redis只有单线程,其实也有使用多线程的地方,在网络io中使用的就是多路复用的情况.
如果redis不使用单线程的情况下,就会出现线程安全问题.假如有两个客户端在连接redis服务器,他们同时向redis服务器发送了一个incr请求.
这时候就会出现两个客户端同时对一个变量进行++的操作.客户端先获取到服务器的value值,两个线程都获取到的是0,在线程中++之后都是1,之后再把value++之后的值赋值给value,此时两个线程++之后的值都是1,赋值之后也自然就是1.
幸好我们的redis服务器是单线程模式的,它保证了这两个客户端发送过来的请求是串行化执行的.宏观上redis同时处理了客户端的请求,微观上是串行化执行的.
就像我们在食堂打饭一样,假如说一个食堂只有一个窗口开放,这时候学生和老师就不得不排队一个一个的打饭.
因为redis是使用单线程模型的,所以我们在使用redis指令的时候要额外小心,避免一个命令长期占用redis服务器的线程.
[面试题] 既然redis是单线程的,为什么redis还是如此之快呢?
- 首先,redis是在内存中存储数据的,内存中存储的数据要相对于硬盘中要快好几个数量级.
- 其次,redis的业务功能主要突出的就是一个"短平快".业务逻辑比较简单.所以比较快.
- 既然说redis是单线程的,它也避免了多线程中的线程竞争现象,线程竞争的现象会拖慢执行的效率.
- 处理网络请求io的时候,使用的是epoll多路复用的机制.就是一个线程管理了多个Socket.