目录
[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操作
我们都知道在多线程中,两个线程同时针对一个变量进行自增操作时,表面上看是自增两次,实际上可能自增了一次,就会有线程安全的问题。当前这两个客户端并发的发起上述请求,会不会有线程安全的问题呢?
实际上是不会的,由于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)
2.1.2 GET
获取key对应的value,如果key不存在,返回nil,如果value的数据类型不是String,会报错
时间复杂度:O(1)
2.1.3 MGET
一次获取多个key的值,如果对应的key不存在或者对应的数据类型不是String,返回nil
时间复杂度:O(1)
2.1.4 MSET
一次性设置多个key的值
时间复杂度:O(N),N是key的数量
单次mget比多次get效率更高,使用mget/mset可以有效减少网络时间,提高性能
2.2 计数命令
2.2.1 INCR
将key对应的String表示的数字加1,如果key不存在,则视为key对应的value是0,如果key对应的string不是一个整型或者范围超过了64位有符号整型,则报错
时间复杂度:O(1)
返回值:integer类型加完后的数值
2.2.2 INCRBY
将key对应的String表示的数字加对应的值,如果key不存在,则视为key对应的value是0,如果key对应的string不是一个整型或者范围超过了64位有符号整型,则报错
时间复杂度:O(1)
返回值:integer类型加完后的数值
2.2.3 DECR
将key对应的String表示的数字减1,如果key不存在,则视为key对应的value是0,如果key对应的string不是一个整型或者范围超过了64位有符号整型,则报错
时间复杂度:O(1)
返回值:integer类型减完后的数值
2.2.4 DECRBY
将key对应的String表示的数字减对应的值,如果key不存在,则视为key对应的value是0,如果key对应的string不是一个整型或者范围超过了64位有符号整型,则报错
时间复杂度:O(1)
返回值:integer类型减完后的数值
2.2.5 INCRBYFLOAT
将key对应的string表示的浮点数加上对应的值。如果对应的值是负数,则视为减去对应的值。如果
key不存在,则视为key对应的value是0。如果key对应的不是string,或者不是⼀个浮点数,则报
错。允许采用科学计数法表示浮点数
时间复杂度:O(1)
返回值:加/减完后的数值
上述操作的时间复杂度都是O(1),由于Redis处理命令的时候都是单线程模型,多个客户端针对同一个key进行incr操作,不会有线程安全的问题
2.3 其他命令
2.3.1 APPEND
如果key存在并且是一个string,命令会将value拼接到string后边,如果key不存在,则效果等同SET命令
时间复杂度:O(1),拼接的字符串一般长度较短,因此可以视为O(1)
返回值:拼接完之后string的长度
redis的字符串不会对字符编码做任何处理,当前Xshell默认的字符编码是utf8,其中一个汉字在utf8字符集中占3个字节,上述的解决问题是在启动redis客户端的时候加上--raw就能让redis客户端自动把二进制数据尝试翻译
2.3.2 GETRANGE
返回key对应的string的字串,由start和end确定(左闭右闭),可以使用负数表示倒数,-1表示倒数第一个字符,-2表示倒数第二个,超过范围的偏移量会根据string的长度调整成正确的值
时间复杂度:O(N),N为[start,end]区间的长度,由于string通常比较短,可以视为是O(1)
返回值:string类型的字串
2.3.3 SETRANGE
覆盖字符串的一部分,从指定的偏移开始
时间复杂度:O(N),N为value的长度,由于value通常比较短,可以视为是O(1)
返回值:替换后的string的长度
2.3.4 STRLEN
获取key对应的string长度,当key存放的类型不是string时,会报错
时间复杂度:O(1)
返回值:string的长度,或者当key不存在时,返回0
2.4 内部编码
字符串类型的内部编码右3种:
int:8个字节的长整型
embstr:小于等于39个字节的字符串
raw:大于39个字节的字符串
Redis会根据当前值的类型和长度动态决定使用哪种内部编码实现
2.5 典型使用场景
2.5.1 缓存功能
Redis被当作缓存来存储一些热点数据,其中Redis作为缓存层,MySQL作为存储层,绝大部分请求的数据都是从Redis中获取,由于Redis具有支持高并发的特性,所以缓存通常能起到加速读写和降低后端压力的作用
在上述场景中,应用服务器访问数据的时候,先查询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信息保存在各自的服务器中,但是这样会有一个问题,由于负载均衡,分布式服务会将用户的访问请求均衡到不同的服务器上,并且通常无法保证用户每次请求都会被均衡到同一台服务器上,当用户刷新一次访问有可能会出现重新登录这种情况
解决上述问题,可以使用Redis将用户的Session信息进行集中管理,如下图所示,在这种模式下,所有的会话都会被各个服务器共享了
2.5.4 手机验证码
现在在许多APP中进行登录都会有手机验证码这项功能,让用户输入手机号获取验证码,然后再根据验证码进行验证,从而确定是否为用户本人,为了避免频繁的获取验证码,会限制用户每分钟获取验证码的频率