Redis入门知识点

redis有哪些基础数据结构,什么是快速链表,什么是跳跃链表,使用redis特有的数据类型可以实现哪些有趣的功能。

Redis is an open source (BSD licensed), in-memory data structure store, used as a database, cache and message broker. It supports data structures such as strings, hashes, lists, sets, sorted sets with range queries, bitmaps, hyperloglogs, geospatial indexes with radius queries and streams. Redis has built-in replication, Lua scripting, LRU eviction, transactions and different levels of on-disk persistence, and provides high availability via Redis Sentinel and automatic partitioning with Redis Cluster.

上面一段话取自Redis官方的介绍,为什么要把它放在这里,我觉得这是介绍Redis最为标准的一段话,从用途、数据结构已经功能等多方面介绍,如果以后面试官叫你介绍下redis的话,我觉得可以直接把这段话背下来😌。至于为什么把Redis称为瑞士军刀,相信用过redis的人都会这么认为。

Redis key

Redis密钥是二进制安全的,任何二进制序列的字符串都可以作为密钥,空字符串也是有效的键。虽然key的有效长度为512M,但是在日常开发中不建议把key的长度设置太长,或者过短,最友好的一种做法是将key和业务结合起来命名,一般推荐使用user:{id}这样的方式。

数据类型

从redis的官方介绍中就可以得出,redis的基础数据类型有五种,分别为:string,hash,list,set,以及zset。至于后面提到的hyperloglogs、geospatial、bitmaps的类型,有的是后面的版本增加的,有的是基础数据类型扩展来的。相对于其他的nosql数据库来说,redis的数据类型已经很充分了。redis内置的这些类型,对于学过java的同学可以使用java中的HashMap来辅助记忆,比如说string类型就是Map<String,String>,list类型就类比为Map<String,List>,hash则为Map<String,Map<String,String>>。通过这样的辅助记忆,就可以很快的记住redis的数据类型,对于后期各种类型的命令使用也起到一定的帮助。从上面的类比中我们可以看到redis是一个key,value的数据存储系统,key都是字符串类型,value则对应redis着以上几种数据类型。

string类型

string类型,可以说是redis中使用到最多的一种类型了,开发中有很多开发人员把一些热点数据的序列化成json,存储到redis中,最后访问的时候后再取出来反序列化返回到客户端。

重点记忆:

  • 最大能存储512M的内容
  • value的值是可以修改的,内部采取的是一种预扩容机制来减少频繁的扩容,如图中所示,内部为当前字符串实际分配的空间 capacity 一般要高于实际字符串长度 len。当字符串长度小于 1M 时,扩容都是加倍现有的空间,如果超过 1M,扩容时一次只会多扩 1M 的空间。需要注意的是字符串最大长度为 512M。

命令:

// 添加一个键值对
> set key1 value1
OK
// 添加多个键值对,改名相当于多次使用set名,mset可以减少网络请求
> mset key1 value1 key2 value2 
OK
// 当value是int类型的时候可以进行加减操作,超过signed long(Long.Max)则会报错
> set counter 100
OK
> incr counter
(integer) 101
> incr counter
(integer) 102
> incrby counter 50
(integer) 152

list类型

Redis中的list相当于java中的LinkedList,不是ArrayList,也就是说他是一个链表。结合*pop和*push命令可以实现队列或者栈这种数据结构。

快速链表


准确的说list类型的底层实现是快速链表,普通链表在数据量量大的情况下性能是非常低的,所以需要对list进行优化。在列表开始数据量少的情况下,会直接申请一块连续的内存存储,这块内存的结构就是ziplist(压缩列表),当数据量比较多的时候会切换成quicklist,就是多个ziplist结合成的链表。

list扩展应用

  1. 结合rpush以及lpop(或者lpush和rpop)可以实现redis版的队列,右进左出(或左进右出)。
  2. 使用rpush以及rpop(或者lpush和lpop)可以实现redis版的栈,右进右出(左进左出)。

命令:

  • ltrim [start_index, end_index]命令,会保留start_index和end_index之间的部分
  • lindex相当于链表的get方法,时间复杂度为O(n),index越大性能越低。
> rpush books python java golang
(integer) 3
> lindex books 1  # O(n) 慎用
"java"
> lrange books 0 -1  # 获取所有元素,O(n) 慎用
1) "python"
2) "java"
3) "golang"
> ltrim books 1 -1 # O(n) 慎用
OK
> lrange books 0 -1
1) "java"
2) "golang"
> ltrim books 1 0 # 这其实是清空了整个列表,因为区间范围长度为负
OK
> llen books
(integer) 0

hash类型

hash类型可以类比成java中HashMap,一种键值对类型,只不过这里的key只能是string类型。

rehash

相信大多数java开发人员在面试的时候都会被问到rehash,这个过程在数据量大的时候是一个性能相当低的操作,而redis为了提高性能,就采取了另一种方式进行rehash——渐近式,就是在rehash的时候不立马进行rehash,保存一个新的hash结构的同时,旧的也保存下来,在后续的定时任务中,或者有hash操作的时候进行迁移,当数据迁移完成之后,旧的hash就会被新的取代。

与string应用比较

hash也可以用来保存用户信息的类似操作,但是相对于string来说,hash可以保存一个用户的单个属性,在数据量以及请求非常大的情况下,获取单个属性相比较或者所有属性来说,更加节约网络流量,但是hash的存储结构消耗要高于string。

命令

> hset books java "think in java"  # 命令行的字符串如果包含空格,要用引号括起来
(integer) 1
> hset books golang "concurrency in go"
(integer) 1
> hset books python "python cookbook"
(integer) 1
> hgetall books  # entries(),key 和 value 间隔出现
1) "java"
2) "think in java"
3) "golang"
4) "concurrency in go"
5) "python"
6) "python cookbook"
> hlen books
(integer) 3
> hget books java
"think in java"
> hset books golang "learning go programming"  # 因为是更新操作,所以返回 0
(integer) 0
> hget books golang
"learning go programming"
> hmset books java "effective java" python "learning python" golang "modern golang programming"  # 批量 set
OK
# 整形变量的自增操作
> hincrby user-laoqian age 1
(integer) 30

set类型

同样的set类型可以类比为java中的HashSet,是一种键值对唯一的数据结构,大家都知道java中的HashSet是由HashMap“变异”的,相对于HashMap,HashSet的所有value都是Null。set具有自动去重的功能,可以保证set中的可以是唯一的。

命令:

> sadd books python
(integer) 1
> sadd books python  #  重复
(integer) 0
> sadd books java golang
(integer) 2
> smembers books  # 注意顺序,和插入的并不一致,因为 set 是无序的
1) "java"
2) "python"
3) "golang"
> sismember books java  # 查询某个 value 是否存在,相当于 contains(o)
(integer) 1
> sismember books rust
(integer) 0
> scard books  # 获取长度相当于 count()
(integer) 3
> spop books  # 弹出一个
"java"

zset(有序集合)

set是一个无序不重复集合,zset是一个有序的不重复集合,redis内部是通过跳跃链表来实现的。要想对一个链表排序,普通的链表肯定是不行的。需要对普通链表进行改进,改造后就是跳跃链表了。

上图就是跳跃链表的一个简化示意图,相信不熟悉的人看到这里肯定会懵,这个“跳跃”二字如何解释,在这里跳跃是指层与层之间的一个切换,不是元素和元素之间。层是代表高度词,所以用跳跃来形容也比较合适。

最后

这里只是对Redis的基本数据类型进行一个简单记录,将所有重要的地方都记录下来,但是没有详细记录,在面试的时候如果聊到Redis,可以简单的做个大概介绍,后面再慢慢深入,后续的文章中会对文中的一些点进行详细的学习记录。本篇文章主要根据网上的资料进行总结,说不上复制,如果单纯的把别人的东西复制过来放在自己的博客上,行为上本身就是可耻的(未标明原著),其次在效果上也是微乎其微 。建议大家平时看过一些东西之后转换成自己的理解后记录下来,这样的话效果也会好很多🤓。

相关文档

Redis官方文档
Redis 深度历险:核心原理与应用实践