【Redis破障之路】二:Redis安装和基本数据结构

1、安装Redis

Redis6.0在2020年已经发布,所以我们安装Redis3.0。😂

1.1、在Linux上安装Redis

我们在CentOS上安装Redis。常见的的有三种安装方式:

  • yum/apt软件管理软件安装
  • 源码的方式进行安装
  • 容器化安装

我们这里选择第二种方式:

  • 1)下载Redis指定版本的源码压缩包到当前目录
wget //download.redis.io/releases/redis-3.0.7.tar.gz
  • 2)解压缩Redis源码压缩包
tar xzf redis-3.0.7.tar.gz
  • 3)建立一个redis目录的软连接,指向redis-3.0.7
ln -s redis-3.0.7 redis
  • 4)进入redis目录
 cd redis
  • 5)编译(编译之前确保操作系统已经安装gcc)
 make
  • 6)安装
make install

最后可以执行redis-cli–v查看Redis的版本

redis版本

1.2、启动Redis

有三种方法启动Redis:默认配置、运行配置、配置文件启动。

我们这里用默认配置的方式启动Redis:

redis-server

redis启动

一般在生产环境会使用配置文件启动的方式。

1.3、Redis命令行客户端

Redis服务已经启动,现在使用redis-cli来连接。

redis-cli -h 127.0.0.1 -p 6379

redis cli

2、Redis基本数据结构

Redis 有 5 种基础数据结构,分别为:string (字符串)、list (列表)、set (集合)、hash (哈希) 和 zset (有序集合)。

Redis基本数据结构

2.1、字符串

字符串类型是Redis最基础的数据结构。所有的键都是字符串类型,

Redis 所有的数据结构都是以唯一的 key 字符串作为名称,然后通过这个唯一 key 值来获取相应的 value 数据。不同类型的数据结构的差异就在于 value 的结构不一样。

key-value

而且其他几种数据结构都是在字符串类型基础上构建的,所以字符串类型能为其他四种数据结构的学习奠定基础。

除了上图描述简单的字符串,字符串类型的值也可以是复杂的字符串(例如JSON、XML)、数字 (整数、浮点数),甚至是二进制(图片、音频、视频),但是值最大不能 超过512MB。

字符串类型

2.1.1、命令

  • 设置值
set key value [ex seconds] [px milliseconds] [nx|xx]

下面操作设置键为Hello,值为World的键值对,返回结果为OK代表设置成功:

127.0.0.1:6379> set Hello World 
OK

set命令有几个选项:

  • ex seconds:为键设置秒级过期时间。

  • px milliseconds:为键设置毫秒级过期时间。

  • nx:键必须不存在,才可以设置成功,用于添加。

  • xx:与nx相反,键必须存在,才可以设置成功,用于更新。

  • 获取值

get key

下面操作获取键Hello的值:

127.0.0.1:6379> get Hello
"World"

如果要获取的键不存在,则返回nil(空):

127.0.0.1:6379> get some
(nil)
  • 批量设置值
mset key value [key value ...]

可以批量对多个字符串进行读写,节省网络耗时开销。

下面操作通过mset命令一次性设置4个键值对:

127.0.0.1:6379> mset a 1 b 2 c 3 d 4 
OK
  • 批量获取值
mget key [key ...] 

下面操作批量获取了键a、b、c、d的值:

127.0.0.1:6379> mget a b c d
1) "1"
2) "2"
3) "3"
4) "4"
  • 计数
incr key 

incr命令用于对值做自增操作,返回结果分为三种情况:

  • 值不是整数,返回错误。

  • 值是整数,返回自增后的结果。

  • 键不存在,按照值为0自增,返回结果为1。

127.0.0.1:6379>  set age 18
OK
127.0.0.1:6379> incr age
(integer) 19

除了incr命令,Redis提供了decr(自减)、incrby(自增指定数字)、 decrby(自减指定数字)、incrbyfloat(自增浮点数)。

2.1.2、应用场景

字符串可以说是Redis应用最广泛的数据结构,我们来看一下在实际的开发中一些典型的应用场景。

2.1.2.1、缓存功能

下图示比较典型的缓存使用场景,其中Redis作为缓存层,MySQL作为存储层,绝大部分请求的数据都是从Redis中获取。由于Redis具有支撑高并发的特性,所以缓存通常能起到加速读写和降低后端压力的作用。

缓存示意图

2.1.2.2、计数

许多应用都会使用Redis作为计数的基础工具,它可以实现快速计数、 查询缓存的功能,同时数据可以异步落地到其他数据源。例如记录文章的阅读次数。

2.1.2.3、共享Session

用于分布式应用的Session共享,保证用户登录一次即可访问所有服务。

Session共享

2.1.2.4、分布式锁

利用setnx命令可以实现分布式锁,由于Redis的单线程命令处理机制,如果有多个客户端同时执行setnx key value, 根据setnx的特性只有一个客户端能设置成功,所以setnx可以作为分布式锁的一种实现方案。

以下是最简单的Redis分布式锁实现示意图:

最简版Redis分布式锁

2.2、哈希

Redis 的字典和 Java 语言里面的 HashMap类似。在Redis中,哈希类型是指键值本身又是一个键值对结构,形如value={{field1,value1},…{fieldN,valueN}}

hash示意图

2.2.1、命令

  • 设置值
hset key field value

下面为user:1添加一对field-value:

127.0.0.1:6379> hset user:1 name tom 
(integer) 1

如果设置成功会返回1,反之会返回0。此外Redis提供了hsetnx命令,它们的关系就像set和setnx命令一样,只不过作用域由键变为field。

  • 获取值
hget key field 

获取user:1的name域(属性)对应的值:

127.0.0.1:6379> hget user:1 name 
"tom"
  • 删除field
hdel key field [field ...]

hdel会删除一个或多个field,返回结果为成功删除field的个数,例如:

127.0.0.1:6379> hdel user:1 name 
(integer) 1 
127.0.0.1:6379> hdel user:1 age 
(integer) 0
  • 批量设置或获取field-value
hmget key field [field ...] 
hmset key field value [field value ...]

hmset和hmget分别是批量设置和获取field-value,hmset需要的参数是key和多对field-value,hmget需要的参数是key和多个field。例如:

127.0.0.1:6379> hmset user:1 name mike age 12 city tianjin 
OK
127.0.0.1:6379> hmget user:1 name city 
1) "mike" 
2) "tianjin"

2.2.2、应用场景

2.2.2.1、存储对象

hash的filed-value的结构非常适合用来存储对象,field用来存储属性名称,value用来存储属性值。在一些客户端里也提供了json序列化器。

Redis存储对象

2.3、列表

Redis 的列表类似于 Java 语言里面的 LinkedList,同样地list 的插入和删除操作非常快,时间复杂度为 O(1),但是索引定位很慢,时间复杂度为O(n)。

那么,同样地,list也可以充当栈和队列的角色。

例如,在list两端的插入和弹出,可以模拟栈的操作:

list模拟栈的插入弹出

列表类型有两个特点:第一、列表中的元素是有序的,这就意味着可以通过索引下标获取某个元素或者某个范围内的元素列表。第二、列表中的元素可以是重复的。

2.3.1、命令

列表主要有5种操作类型:

操作类型 操作
添加 rpush lpush linsert
查找 lrange lindex llen
删除 lpop rpop lren ltrim
修改 lset
阻塞操作 blpop brpop
  • 添加操作

从右边插入元素

rpush key value [value ...]

下面代码从右向左插入元素c、b、a:

127.0.0. 1:6379> rpush listkey c b a 
(integer) 3

lrange0-1命令可以从左到右获取列表的所有元素:

127.0.0.1:6379> lrange listkey 0 -1 
1) "c" 
2) "b" 
3) "a"

从左边插入元素类似,不再赘述

  • 向某个元素前或者后插入元素
linsert key before|after pivot value

linsert命令会从列表中找到等于pivot的元素,在其前(before)或者后 (after)插入一个新的元素value,例如下面操作会在列表的元素b前插入 java:

127.0.0.1:6379> linsert listkey before b java 
(integer) 4

返回结果为4,代表当前命令的长度,当前列表变为:

127.0.0.1:6379> lrange listkey 0 -1 
1) "c" 
2) "java" 
3) "b" 
4) "a"
  • 查找

获取指定范围内的元素列表

lrange key start end

lrange操作会获取列表指定索引范围所有的元素。

例如想获取列表的第2到第4个元素:

127.0.0.1:6379> lrange listkey 1 3 
1) "java" 
2) "b" 
3) "a"

获取列表指定索引下标的元素

lindex key index

例如当前列表最后一个元素为a:

127.0.0.1:6379> lindex listkey -1 
"a"

获取列表长度

llen key

例如,下面示例当前列表长度为4:

127.0.0.1:6379> llen listkey 
(integer) 4
  • 删除

从列表左侧弹出元素

lpop key

从列表右侧弹出

rpop key

删除指定元素

lrem key count value
  • 修改
lset key index newValue
  • 阻塞操作

阻塞式弹出如下:

blpop key [key ...] timeout 
brpop key [key ...] timeout

blpop和brpop是lpop和rpop的阻塞版本,它们除了弹出方向不同,使用方法基本相同。

2.3.2、应用场景

2.3.2.1、消息队列

Redis的lpush+brpop命令组合即可实现阻塞队列,生产客户端使用lrpush从列表左侧插入元素,多个消费者客户端使用brpop命令阻塞式的“抢”列表尾部的元素,多个客户端保证了消费的负载均衡和高可用性。

Redis消息队列模型

list可以灵活组合,在不同的场景使用,总结如下:

  • lpush+lpop=Stack(栈)

  • lpush+rpop=Queue(队列)

  • lpsh+ltrim=Capped Collection(有限集合)

  • lpush+brpop=Message Queue(消息队列)

2.4、集合

集合类似Java语言中的HashSet,集合中不允许有重复元素,并且集合中的元素是无序的,不能通过索引下标获取元素。

集合类型

2.4.1、命令

2.4.1.1、集合内操作

  • 添加元素
sadd key element [element ...]

无法添加重复元素,添加重复元素会返回0。

  • 删除元素
srem key element [element ...]
  • 计算元素个数
scard key
  • 从集合随机弹出元素
spop key

2.4.1.2、集合间操作

  • 求多个集合的交集
sinter key [key ...]
  • 求多个集合的并集
suinon key [key ...]
  • 求多个集合的差集
sdiff key [key ...]

集合交并差运算

2.4.2、应用场景

2.4.2.1、标签

集合类型比较典型的使用场景是标签(tag)。例如一个用户可能对娱乐、体育比较感兴趣,另一个用户可能对历史、新闻比较感兴趣,这些兴趣点就是标签。有了这些数据就可以得到喜欢同一个标签的人,以及用户的共 同喜好的标签,这些数据对于用户体验以及增强用户黏度比较重要。

2.4.2.2、共同关注

可以利用交集的运算,实现社交社区用户的“共同关注”功能。

2.5、有序集合

zset 可能是 Redis 提供的最为特色的数据结构,它类似于 Java 的 SortedSet 和 HashMap 的结合体,一方面它是一个 set,保证了内部value 的唯一性,另一方面它可以给每个 value 赋予一个 score,代表这个 value 的排序权重。

有序集合

有序集合提供了获取指定score和元素范围查询、计算成员排名等功能,合理的利用有序集合,能帮助

我们在实际开发中解决很多问题。

有序集合中的元素不能重复,但是score可以重复,就和一个班里的同学学号不能重复,但是考试成绩可以相同。

2.5.1、命令

2.5.1.1、集合内

  • 添加成员
zadd key score member [score member ...]

下面操作向有序集合user:rank添加用户李四和他的score250:

127.0.0.1:6379> zadd user:ranking 250 李四
(integer) 1
  • 计算成员个数
zcard key
  • 计算某个成员的score
zscore key member
  • 计算成员的排名
zrank key member zrevrank key member
  • 删除成员
zrem key member [member ...]

2.5.1.2、集合间的操作

  • 交集
zinterstore destination numkeys key [key ...] [weights weight [weight ...]] [aggregate sum|min|max]
  • 并集
zunionstore destination numkeys key [key ...] [weights weight [weight ...]] [aggregate sum|min|max]

2.5.2、应用场景

有序集合比较适合用于需要排行的地方。

2.5.2.1、用户点赞统计

可以用于统计博客、视频网站等作品的点赞数,可以根据点赞数对作品进行排行。

3、基本数据类型内部编码

通过上面的介绍,我们已经了解了五种基本数据结构,既然叫数据结构,那么一定是有内部的组成,就比如Java中的String由char或者byte数组组成。我们这里简单了解一下Redis五种基本数据结构的内部编码。

Redis数据结构和内部编码

3.1、字符串

字符串类型的内部编码有3种:

  • int:8个字节的长整型。

  • embstr:小于等于39个字节的字符串。

  • raw:大于39个字节的字符串。

embstr&raw

3.2、哈希

哈希类型的内部编码有两种:

  • ziplist(压缩列表):当哈希类型元素个数小于hash-max-ziplist-entries 配置(默认512个)、同时所有值都小于hash-max-ziplist-value配置(默认64 字节)时,Redis会使用ziplist作为哈希的内部实现,ziplist使用更加紧凑的结构实现多个元素的连续存储,所以在节省内存方面比hashtable更加优秀。

  • hashtable(哈希表):当哈希类型无法满足ziplist的条件时,Redis会使用hashtable作为哈希的内部实现,因为此时ziplist的读写效率会下降,而hashtable的读写时间复杂度为O(1)。

3.3、列表

列表类型的内部编码有两种。

  • ziplist(压缩列表):当列表的元素个数小于list-max-ziplist-entries配置 (默认512个),同时列表中每个元素的值都小于list-max-ziplist-value配置时 (默认64字节),Redis会选用ziplist来作为列表的内部实现来减少内存的使 用。

  • linkedlist(链表):当列表类型无法满足ziplist的条件时,Redis会使用linkedlist作为列表的内部实现。

3.4、集合

集合类型的内部编码有两种:

  • intset(整数集合):当集合中的元素都是整数且元素个数小于set-max- intset-entries配置(默认512个)时,Redis会选用intset来作为集合的内部实现,从而减少内存的使用。

  • hashtable(哈希表):当集合类型无法满足intset的条件时,Redis会使用hashtable作为集合的内部实现。

3.5、有序集合

有序集合类型的内部编码有两种:

  • ziplist(压缩列表):当有序集合的元素个数小于zset-max-ziplist- entries配置(默认128个),同时每个元素的值都小于zset-max-ziplist-value配置(默认64字节)时,Redis会用ziplist来作为有序集合的内部实现,ziplist 可以有效减少内存的使用。

  • skiplist(跳跃表):当ziplist条件不满足时,有序集合会使用skiplist作为内部实现,因为此时ziplist的读写效率会下降。

跳跃表

这里只是简单地了解一下,未来如果有机会,会再进一步学习Redis的实现来更深入地了解Redsi基本数据结构的内部原理。


参考:

【1】:《Redis开发与运维》

【2】:掘金小册 《Redis 深度历险:核心原理与应用实践》