SQL实践篇(三):什么是Redis
简介
Redis是一种基于内存的键值数据库,键值数据库会使用哈希表存储key和value。其中key和value可以是任何内容,无论是简单对象还是复杂对象。
键值数据库是NoSQL思想,其规则是"只提供你想要的",因此模型很灵活,查询效率也高,成本也低。
而RDBMS是建立在关系模型的基础上,强调的是数据的一致性和各种约束条件,成本很高。
本节我们将从以下几个方面,简单了解下Redis:
- Redis是什么,为什么它这么快?
- Redis支持的数据类型都有哪些?
Redis是什么,为什么这么快?
Redis,全称是REmote Dictionary Server。
Redis的查询效率非常高,根据官方的数据,Redis每秒最多处理的请求可以达到10w次。
为什么这么快呢?
Redis是采用ANSI C语言编写的,跟SQLite一样。这样的好处是底层代码执行效率更高,相比面向对象的语言,依赖性也更低。系统兼容性好,稳定性高。
另外,Redis是基于内存的数据库,没有磁盘IO的限制,自然快的飞起。
其次,数据结构简单,采用key-value的方式进行存储,也就是使用Hash结构进行操作,数据的操作复杂度为O(1)。
但是Redis快的原因远不止这些,还有很多各种各样的原因,比如说它采用单进程单线程的模型,避免了上下文切换和不必要的线程资源争抢问题。
在技术上,Redis还采用了多路IO复用技术。这里的多路指的是多个socket网络连接,复用指的是复用同一个线程。这样的好处是可以在同一个线程中处理多个IO请求,尽量减少网络IO的损耗。
可以看到,Redis可以说是把效率做到了极致,各方各面可能存在的时间消耗都被优化过。
2023-11-10 00:47:47 不过,在2020年新推出的Redis6.0版本里,还是引进了多线程模型,来提高Redis的性能和并发能力。不过Redis默认情况下不会开启多线程模式,官方建议是除非达到了性能瓶颈,否则没必要开启多线程。
Redis的数据类型
Memcached也是一个键值型数据库,但与它相比,Redis还有一个非常大的优势,就是支持多种数据类型。
Redis支持的数据类型包括字符串、哈希、列表、集合、有序集合等。
字符串
字符串类型是Redis提供的最基本的数据类型。对应的结构是key-value
如果我们想要设置某个键的值,可以使用set key value
,如果想要获取某个值,那就用get key
。如图:
Hash
哈希(hash)进一步提供了字段和字段值的映射,对应的结构是key-field-value。
设置某个键的哈希值:hset key field value
。
比如说要给user1设置username为zhangfei,设置age为28,可以写成:
hset user1 username zhangfei
hset user1 age 28
或者可以连写成:
Hmset user1 username zhangfei age 28
取出某个键的某个field字段:hget key field
比如说hget user1 username
。
一次性取出某个键的多个field字段: hmget key field_1 field_2...
。
比如说hmget user1 usename age
。
字符串列表
字符串列表(list)的底层是一个双向链表结构,所以我们可以向链表的两头添加元素,时间复杂度都是O(1),同时我们也可以比较方便的获取列表中的某个片段。
向列表左侧添加元素:LPUSH key value [...]
。
比如说我要在heroslist列表左侧添加zhagnfei、guanyu、liubei这三条数据,可以写成:
LPUSH heroList zhangfei guanyu liubei
向列表右侧添加元素:RPUSH key value [...]
获取某一片段的内容:
LRANGE key start stop
如获取herolist从0到4位置的数据,写成:LRANGE herolist 0 4
即可。
字符串集合
字符串集合(set)是字符串类型的无序集合,与列表(list)的区别是集合内的元素是无序且不重复的。
在集合中添加元素:SADD key member [....]
比如我想在heroSet集合添加zhangfei、guanyu、liubei、dianwei和lvbu这五个元素,可以写成:
SADD heroSet zhangfei guanyu liubei dianwei lvbu
在集合中删除元素:SREM key member [....]
比如我们想在heroSet集合中删除liubei和lvbu两个元素,可以写成:
SREM heroSet liubei lvbu
获取集合内全部元素:SISMEMBER key
比如我想获取heroSet集合中的所有元素,可以写成(可以理解成s is member):
SISMEMBER heroSet
判断集合内是否存在某个元素:SISMEMBER key member
。
比如要判断集合中是否存在zhangfei和liubei,可以写成:
SISMEMBER heroSet zhangfei
SISMEMBER heroSet liubei
有序字符串集合
有序字符串集合(SortedSet,简称ZSET),可以理解成是集合的升级版,是内部有序的集合。
实际上,ZSET是在集合的基础上增加了一个分数属性,这个属性在添加和修改元素的时候可以被指定。每次指定后,ZSET都会按照分数来自动排序。
有序集合跟列表有一定程度上的相似性。比如这俩都是有序的,都可以获取某一范围内的数据,但是它们在数据结构上有很大的不同。
列表是通过双向链表的结构来实现的,因此在操作左右两侧的数据时比较快,但是越到中间的数据,操作的越慢,整体查询的时间复杂度是O(n)。
有序集合的实现结构比较复杂,它在内部是通过Hash表的形式来存储所有元素和分数,因此查找指定元素的分数时,时间复杂度是O(1),因为通过哈希值可以直接找到位置。
同时它还通过跳表(skiplist)来维护元素的顺序,类似二叉树或者二分查找,不管读哪部分的数据都会比较快,因此涉及到顺序的操作时,查询的时间复杂度是O(log(N))。同时有序集合可以通过score来灵活调整元素位置,但是列表就不行了,它调整比较麻烦。
什么是跳表呢?在参考文献4里扒了一张图,描述的很形象:
其实非常类似B+树结构。
假设链表有N个节点,每2个节点生成一个上层索引,则第一层索引的节点数为N/2,第二层则是第一层的一半,即N/4,依次类推,第h层的节点个数就是 N / ( 2 h ) N/(2^h) N/(2h),因此整个跳表的高度就是 l o g 2 ( N ) log_2(N) log2(N),跟二叉树一样了,所以查询的时间复杂度就是 l o g ( N ) log(N) log(N)。
有兴趣了解跳表结构的话,可以看一下参考文献4,写的非常形象。
在有序列表中添加元素和分数:ZADD key score member [...]
比如我们给heroScore集合添加下面5个英雄的hp_max数值作为score,如下表:
所以可以写成:
ZADD heroScore 8341 zhangfei 7107 guanyu 6900 liubei 7516 dianwei 7344 lvbu
获取某个元素的分数:ZSCORE key member
比如获取关羽的分数:
ZSCORE heroScore guanyu
删除一个或多个元素:ZREM key member [member...]
比如删除guanyu这个元素:
ZREM heroScore guanyu
获取某个范围内的元素列表:
ZRANGE key start stop [WITHSCORES]
:按分数从小到大排序,即ASC;ZREVRANGE key start stop [WITHSCORES]
:按分数从大到小排序,即DESC;
其中WITHSCORES
是个可选项,加上的表示需要将分数一起显示出来。
比如说要查询heroScore这个有序集合中分数排名前3的英雄及数值,可以写成:
ZREVRANGE heroScore 0 2 WITHSCORES
其他数据类型
除了以上5种数据类型之外,Redis还支持位图(Bitmaps)数据结构。
在2.8版本之后,增加了基数统计(HyperLogLog)。
3.2版本之后,加入了地理空间(Geospatial)以及索引半径查询的功能。
5.0版本,引入了数据流(Streams)数据类型。
总结
了解Redis还是非常重要的。在实际的工作中,我们经常会将RDBMS和Redis配合使用,优势互补。
作为常见的 NoSQL 数据库,Redis 比Memcached 的优势要高很多:
- Redis支持的数据类型比 Memcached 丰富得多,
- 在 I/O 性能上,Redis 采用的是单线程 I/O 复用模型,而 Memcached 是多线程,可以利用多核优势。
- 在持久化上,Redis 提供了两种持久化的模式(RDB和AOF),可以让数据永久保存,这是 Memcached 不具备的。
MongoDB里可以通过mmp调用来将数据映射到内存中。可以将mmp理解成是一种加速的手段。其将文件映射到调用进程的地址空间里,实现了文件所在的磁盘物理地址与进程空间的虚拟地址一一映射的关系,这样就可以直接在内存中进行操作,然后写完成之后同步一下就可以存放到文件中,效率非常高。有兴趣可以去了解一下。
另外,Redis中的有序集合比较特殊,它通过Hash表的形式来存储所有元素和分数,同时使用跳表来维护元素的顺序。因此不同场景下的查询时间复杂度会有不同。