目录
[1.1 引出单线程模型](#1.1 引出单线程模型)
[1.2 为什么单线程还能这样快](#1.2 为什么单线程还能这样快)
[2.1 常见命令](#2.1 常见命令)
[2.1.1 SET](#2.1.1 SET)
[2.1.2 GET](#2.1.2 GET)
[2.1.3 MGET](#2.1.3 MGET)
[2.1.4 MSET](#2.1.4 MSET)
[2.2 计数命令](#2.2 计数命令)
[2.2.1 INCR](#2.2.1 INCR)
[2.2.2 INCRBY](#2.2.2 INCRBY)
[2.2.3 DECR](#2.2.3 DECR)
[2.2.4 DECRBY](#2.2.4 DECRBY)
[2.2.5 INCRBYFLOAT](#2.2.5 INCRBYFLOAT)
[2.3 其他命令](#2.3 其他命令)
[2.3.1 APPEND](#2.3.1 APPEND)
[2.3.2 GETRANGE](#2.3.2 GETRANGE)
[2.3.3 SETRANGE](#2.3.3 SETRANGE)
[2.3.4 STRLEN](#2.3.4 STRLEN)
[2.4 内部编码](#2.4 内部编码)
[2.5 典型使用场景](#2.5 典型使用场景)
[2.5.1 缓存功能](#2.5.1 缓存功能)
[2.5.2 计数功能](#2.5.2 计数功能)
[2.5.3 共享会话](#2.5.3 共享会话)
[2.5.4 手机验证码](#2.5.4 手机验证码)
1.单线程架构
Redis使用了单线程架构来实现高性能内存数据库服务,Redis只使用一个线程来处理所有命令请求
1.1 引出单线程模型
例如开启了两个redis-cli客户端同时执行命令
客户端1针对counter进行incr操作,客户端2也针对counter进行incr操作
![](https://img-blog.csdnimg.cn/direct/d7e5828f7b6b4f5f9a81e7392bac8985.png)
我们都知道在多线程中,两个线程同时针对一个变量进行自增操作时,表面上看是自增两次,实际上可能自增了一次,就会有线程安全的问题。当前这两个客户端并发的发起上述请求,会不会有线程安全的问题呢?
实际上是不会的,由于redis服务器是单线程模型,它保证当前收到多个请求时串行执行的,当多个请求同时到达redis服务器,需要在队列中进行排队,再等redis服务器一个一个的取出里面的命令再执行,微观上讲,redis服务器都是顺序执行多个命令的(就好比去食堂一个窗口打饭,所有人都需要排队一个一个打),其中redis能够使用单线程模型很好的工作,在于redis的核心业务逻辑都是比较短比较快的,不太消耗cpu资源
redis的弊端:如果某个操作占用时间很长,就会阻塞其他命令的执行
1.2 为什么单线程还能这样快
Redis的效率高和速度快是参照mysql这种关系型数据库,和关系型数据库作比较
1)Redis访问的是内存,数据库则是访问的硬盘。Redis将所有数据存储再内存中,内存的响应速度是远远大于硬盘的,这是Redis达到每秒万级别访问的重要基础
2)Redis核心功能比数据库的核心功能更简单,数据库对数据的增删改查都有复杂的功能支持,因此需要花费更多的开销,而redis提供的功能相比mysql来说少了很多
3)单线程模型,避免了一些不必要的线程竞争开销
4)处理网络IO的时候,使用了epoll这样的IO多路复用机制(一个线程可以管理多个socket)
2.String字符串
字符串是Redis最基本的数据类型,在Redis中所有键的类型都是字符串类型,其他几种数据结构也是在字符串类似的基础上构建的,字符串类型的值实际上可以是字符串,数字,甚至是图片、音频、视频等,但是一个字符串的最大值不能超过512MB
2.1 常见命令
2.1.1 SET
将String类型的value设置到key中,如果key之前存在,则覆盖,无论原来的数据类型是什么,之前关于此key的TTL也全部失效
语法:
SET key value [expiration EX seconds|PX milliseconds] [NX|XX]
时间复杂度:O(1)
EX:使用秒作为单位设置key的过期时间
PX:使用毫秒作为单位设置key的过期时间
NX:当key不存在时才进行设置,如果key存在,设置不执行
XX:只有在key存在时才进行设置,如果key不存在,设置不执行
上述如果设置成功,返回OK,如果SET指定了NX或者XX但条件不满足,SET不会执行,并返回(nil)
![](https://img-blog.csdnimg.cn/direct/82f415d707984841ae106440a91e5818.png)
2.1.2 GET
获取key对应的value,如果key不存在,返回nil,如果value的数据类型不是String,会报错
时间复杂度:O(1)
![](https://img-blog.csdnimg.cn/direct/d07a926c486141778d5409d049e711ca.png)
2.1.3 MGET
一次获取多个key的值,如果对应的key不存在或者对应的数据类型不是String,返回nil
时间复杂度:O(1)
![](https://img-blog.csdnimg.cn/direct/a1e088afddbc45218dfbab07fab10f9f.png)
2.1.4 MSET
一次性设置多个key的值
时间复杂度:O(N),N是key的数量
![](https://img-blog.csdnimg.cn/direct/5c135694d50d4eb19f88f30dcd0ba26c.png)
单次mget比多次get效率更高,使用mget/mset可以有效减少网络时间,提高性能
2.2 计数命令
2.2.1 INCR
将key对应的String表示的数字加1,如果key不存在,则视为key对应的value是0,如果key对应的string不是一个整型或者范围超过了64位有符号整型,则报错
时间复杂度:O(1)
返回值:integer类型加完后的数值
![](https://img-blog.csdnimg.cn/direct/521f06e57e5544f38ac349f486f4dbd8.png)
![](https://img-blog.csdnimg.cn/direct/4446f49b40ad40f3aa231022ab5ffc57.png)
2.2.2 INCRBY
将key对应的String表示的数字加对应的值,如果key不存在,则视为key对应的value是0,如果key对应的string不是一个整型或者范围超过了64位有符号整型,则报错
时间复杂度:O(1)
返回值:integer类型加完后的数值
![](https://img-blog.csdnimg.cn/direct/0095becefa734c0ba0460d9d4fa1b44a.png)
2.2.3 DECR
将key对应的String表示的数字减1,如果key不存在,则视为key对应的value是0,如果key对应的string不是一个整型或者范围超过了64位有符号整型,则报错
时间复杂度:O(1)
返回值:integer类型减完后的数值
![](https://img-blog.csdnimg.cn/direct/62149209e56742288ec667436fbe55f5.png)
2.2.4 DECRBY
将key对应的String表示的数字减对应的值,如果key不存在,则视为key对应的value是0,如果key对应的string不是一个整型或者范围超过了64位有符号整型,则报错
时间复杂度:O(1)
返回值:integer类型减完后的数值
![](https://img-blog.csdnimg.cn/direct/7048ac18c94b42adbb6e79d04172602b.png)
2.2.5 INCRBYFLOAT
将key对应的string表示的浮点数加上对应的值。如果对应的值是负数,则视为减去对应的值。如果
key不存在,则视为key对应的value是0。如果key对应的不是string,或者不是⼀个浮点数,则报
错。允许采用科学计数法表示浮点数
时间复杂度:O(1)
返回值:加/减完后的数值
![](https://img-blog.csdnimg.cn/direct/7146edc678e44d2eb3d84d2a24926a31.png)
上述操作的时间复杂度都是O(1),由于Redis处理命令的时候都是单线程模型,多个客户端针对同一个key进行incr操作,不会有线程安全的问题
2.3 其他命令
2.3.1 APPEND
如果key存在并且是一个string,命令会将value拼接到string后边,如果key不存在,则效果等同SET命令
时间复杂度:O(1),拼接的字符串一般长度较短,因此可以视为O(1)
返回值:拼接完之后string的长度
![](https://img-blog.csdnimg.cn/direct/23c4eb47bd1e4b7c9a53ac1e4fe2f288.png)
redis的字符串不会对字符编码做任何处理,当前Xshell默认的字符编码是utf8,其中一个汉字在utf8字符集中占3个字节,上述的解决问题是在启动redis客户端的时候加上--raw就能让redis客户端自动把二进制数据尝试翻译
![](https://img-blog.csdnimg.cn/direct/528827f30dc64ab9a76fc8d07e0b66cf.png)
2.3.2 GETRANGE
返回key对应的string的字串,由start和end确定(左闭右闭),可以使用负数表示倒数,-1表示倒数第一个字符,-2表示倒数第二个,超过范围的偏移量会根据string的长度调整成正确的值
时间复杂度:O(N),N为[start,end]区间的长度,由于string通常比较短,可以视为是O(1)
返回值:string类型的字串
![](https://img-blog.csdnimg.cn/direct/0d81654056f84cd5ab19fa582230494b.png)
2.3.3 SETRANGE
覆盖字符串的一部分,从指定的偏移开始
时间复杂度:O(N),N为value的长度,由于value通常比较短,可以视为是O(1)
返回值:替换后的string的长度
![](https://img-blog.csdnimg.cn/direct/c2e7caeed5de4c3b927cccb5a71b45f5.png)
2.3.4 STRLEN
获取key对应的string长度,当key存放的类型不是string时,会报错
时间复杂度:O(1)
返回值:string的长度,或者当key不存在时,返回0
![](https://img-blog.csdnimg.cn/direct/226b7b078ad84082a2e9e239f3333af8.png)
2.4 内部编码
字符串类型的内部编码右3种:
int:8个字节的长整型
embstr:小于等于39个字节的字符串
raw:大于39个字节的字符串
Redis会根据当前值的类型和长度动态决定使用哪种内部编码实现
![](https://img-blog.csdnimg.cn/direct/ad7744540b994950a48a16c361bac6af.png)
2.5 典型使用场景
2.5.1 缓存功能
Redis被当作缓存来存储一些热点数据,其中Redis作为缓存层,MySQL作为存储层,绝大部分请求的数据都是从Redis中获取,由于Redis具有支持高并发的特性,所以缓存通常能起到加速读写和降低后端压力的作用
![](https://img-blog.csdnimg.cn/direct/6d4cfc32ac3e4fab8a2385162eccabdf.png)
在上述场景中,应用服务器访问数据的时候,先查询Redis,如果Redis上数据存在,就直接从Redis取数据交给应用服务器,不在继续访问数据库了,如果Redis上数据不存在,在读取MySQL,把读到的结果返回给服务器,同时,把这个歌数据写入到Redis中
注意:上述策略存在一个问题,随着时间的推移,肯定有越来越多的key在Redis上访问不到 ,需要从MySQL中读取并写入Redis,此时Redis中的数据就会越来越多
针对这种情况,Redis提供了两种解决方案
1)把数据写入Redis的同时,给这个key设置一个过期时间
2)Redis在内存不足的时候,提供了淘汰策略
2.5.2 计数功能
在许多应用中都会使用Redis作为计数的基础功能,它可以实现快速计数、查询缓存的功能,同时数据可以异步处理到其他的数据源,例如视频网站的视频播放量次数可以使用Redis来完成,用户每播放一次视频,相应的视频播放数就增加1
2.5.3 共享会话
如下图所示,在一个分布式WEB服务将用户的Session信息保存在各自的服务器中,但是这样会有一个问题,由于负载均衡,分布式服务会将用户的访问请求均衡到不同的服务器上,并且通常无法保证用户每次请求都会被均衡到同一台服务器上,当用户刷新一次访问有可能会出现重新登录这种情况
![](https://img-blog.csdnimg.cn/direct/404eb00475e64cf28baeef1d065b08ea.png)
解决上述问题,可以使用Redis将用户的Session信息进行集中管理,如下图所示,在这种模式下,所有的会话都会被各个服务器共享了
![](https://img-blog.csdnimg.cn/direct/01c7331227e842c1a6db27ef91b3cce4.png)
2.5.4 手机验证码
现在在许多APP中进行登录都会有手机验证码这项功能,让用户输入手机号获取验证码,然后再根据验证码进行验证,从而确定是否为用户本人,为了避免频繁的获取验证码,会限制用户每分钟获取验证码的频率
![](https://img-blog.csdnimg.cn/direct/5a5d386e24be49eeba9ad589e132b5a7.png)