[Redis] Redis基本命令与数据类型+单线程模型

🌸个人主页: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搞挂.

拓展知识: 企业中的几种环境

  1. 办公环境: 办公环境就是公司给你发的那台电脑的本机操作系统的环境.
  2. 开发环境: 在项目未上线之前,我们需要对一款软件进行开发,我们在开发的时候所在的操作系统的环境叫做开发环境.有些情况下,我们会使用本机的环境进行开发,也就是办公环境和开发环境是一样的,但是很多时候,公司也有专门的开发环境的服务器.有些情况下,一些软件的业务逻辑比较复杂,这时候本机环境是带动不起来的,就需要性能更高的服务器来带动.
  3. 测试环境: 测试工程师用来测试项目的操作系统环境.
  4. 生产环境(线上环境): 开发完成之后项目上线,部署到生产环境上之后,项目就可以用来给用户提供服务,为外界用户直接提供服务的环境叫做生产环境,这个环境外界用户可以直接访问到.生产环境会直接影响到公司的营收.
  • 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采用了一下的策略:

  1. 惰性删除
    假设一个key已经到了过期的时间,但是我们先不要删除这个key,当下次访到这个key之后,查看这个key是否过期,如果过期了,就删除掉.
  2. 定期删除
    redis会对过期的key进行定期删除,但是这里的删除并不是吧key全部遍历之后在找到过期的key进行删除,而是抽取一些key来判断是否过期,之后进行对应的删除.
  3. 内存淘汰策略
    我们后面再详细介绍.

举例说明: 去商店买东西

当我们拿了一件商品之后,在结账的时候,我们发现这件商品过期了,这时候我们会告诉老板这件商品过期了,老板这时候就会把这件商品扔掉,这就是惰性删除.其次老板会定期抽取一部分的商品来查看商品是否过期,过期就会人掉,这就是定期删除.

[注意事项] 网上有很多人说redis的过期机制是定时删除,在redis中有一个定时器,定时器到达时间之后,redis就会将之删除,但是我们注意,redis并没有采取这种删除的方式.

拓展: 比较高效的定时器实现方式

  1. 优先级队列方式
    这个方式我们在之前多线程的部分曾经向大家介绍过.
    首先我们按照key的过期时间把这些数据组成一个优先级队列,过期时间越短,优先级越高,之后就会有一个线程去不停地检查队首的元素,只要队首的元素到达过期时间,我们就把队首结点和最后一个结点进行交换,之后删除最后一个结点,之后再对这个优先级队列进行向下调整.
    当然该线程也不是一直在对一个队首的元素进行扫描,这样就会出现忙等的状态,我们会根据过期时间设置一个线程休眠的时间,在时间差不多的时候,就会检查一次队首的元素是否过期,之后如果等待的中间来了新的请求,就唤醒一次线程,顺便检查一下队首的元素是否过期.
  2. 基于时间轮的方式实现定时器
    我们可以把时间轮上的时间划分为多个小段(具体的划分粒度,看具体的业务场景),其中每一个小段上都挂着一个链表,这个链表中是需要执行的任务,时间轮上有一个指针,指针每隔一段时间就需要向下走一格.没走一个,就要检查链表中有没有需要执行的任务.
  • 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.
相关推荐
酷帅且洋仔1 分钟前
Redis——常用数据类型List
数据库·redis·list
惟长堤一痕8 分钟前
MySQL基础篇(黑马程序员2022-01-18)
数据库·mysql
兔十卅10 分钟前
1、常用的数据库、表操作
数据库
2401_8581202614 分钟前
如何在Oracle中实现数据的加密
数据库·oracle
楠神说软件测试41 分钟前
MySQL调优
数据库·mysql
Cedric_Anik1 小时前
MYSQL数据库基础篇——DDL
数据库·mysql
文牧之1 小时前
PostgreSQL的walsender和walreceiver进程介绍
运维·数据库·postgresql
爬山算法1 小时前
Oracle(121)如何进行数据文件的恢复?
数据库·oracle
咔咔学姐kk1 小时前
2024最新版,人大赵鑫老师《大语言模型》新书pdf分享
数据库·人工智能·语言模型·自然语言处理·pdf·知识图谱·产品经理
littleschemer1 小时前
Go缓存系统
缓存·go·cache·bigcache