背景与前言

 

在线客服系统缓存模块使用Redis,例如消息通讯缓存、会话状态、客服信息缓存等等。
Redis是一个开源的Key-Value数据库,并提供多种语言的API。
Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。
Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)。

Window 下安装
下载地址:https://github.com/MSOpenTech/redis/releases Linux 下安装
下载地址:http://redis.io/download

1、各个数据类型应用场景

各个数据类型应用场景:

String(字符串)

二进制安全

可以包含任何数据,比如jpg图片或者序列化的对象,一个键最大能存储512M

---(例:需要缓存的JSON对象)

类型

简介

特性

场景

Hash(字典)

键值对集合,即编程语言中的Map类型

适合存储对象,并且可以像数据库中update一个属性一样只修改某一项属性值(Memcached中需要取出整个字符串反序列化成对象修改完再序列化存回去)

存储、读取、修改用户属性(例:客服的各种属性值)

List(列表)

链表(双向链表)

增删快,提供了操作某一段元素的API

1,最新消息排行等功能(比如朋友圈的时间线) 2,消息队列(例:访客和客服的聊天消息)

Set(集合)

哈希表实现,元素不重复

1、添加、删除,查找的复杂度都是O(1) 2、为集合提供了求交集、并集、差集等操作

1、共同好友 2、利用唯一性,统计访问网站的所有独立ip 3、好友推荐时,根据tag求交集,大于某个阈值就可以推荐(例:访客接入的业务类别客服分组)

Sorted Set(有序集合)

将Set中的元素增加一个权重参数score,元素按score有序排列

数据插入集合时,已经进行天然排序

1、排行榜 2、带权重的消息队列(例:访客排队等待队列)

 

2、Redis 命令

下面是相关命令,每项命令都超链接了W3C网站上的例子。

哈希(Hash)

列表(List)

集合(Set)

有序集合(sorted set)

HDEL key field1 [field2] 

删除一个或多个哈希表字段

BLPOP key1 [key2 ] timeout 
移出并获取列表的第一个元素, 如果列表没有元素会

阻塞列表直到等待超时或发现可弹出元素为止。

SADD key member1 [member2] 

向集合添加一个或多个成员

ZADD key score1 member1 [score2 member2] 
向有序集合添加一个或多个成员,或者更新已存在

成员的分数

HEXISTS key field 

查看哈希表 key 中,指定的字段是否存在。

BRPOP key1 [key2 ] timeout 
移出并获取列表的最后一个元素, 如果列表没有元素

会阻塞列表直到等待超时或发现可弹出元素为止。

SCARD key 

获取集合的成员数

ZCARD key 

获取有序集合的成员数

HGET key field 

获取存储在哈希表中指定字段的值。

BRPOPLPUSH source destination timeout 
从列表中弹出一个值,将弹出的元素插入到另外一个

列表中并返回它; 如果列表没有元素会阻塞列表直到

等待超时或发现可弹出元素为止。

SDIFF key1 [key2] 

返回给定所有集合的差集

ZCOUNT key min max 

计算在有序集合中指定区间分数的成员数

HGETALL key 

获取在哈希表中指定 key 的所有字段和值

LINDEX key index 

通过索引获取列表中的元素

SDIFFSTORE destination key1 [key2] 

返回给定所有集合的差集并存储在 destination 中

ZINCRBY key increment member 

有序集合中对指定成员的分数加上增量 increment

HINCRBY key field increment 
为哈希表 key 中的指定字段的整数值

加上增量 increment 。

LINSERT key BEFORE|AFTER pivot value 

在列表的元素前或者后插入元素

SINTER key1 [key2] 

返回给定所有集合的交集

ZINTERSTORE destination numkeys key [key ...] 
计算给定的一个或多个有序集的交集并将结果集存储在

新的有序集合 key 中

HINCRBYFLOAT key field increment 
为哈希表 key 中的指定字段的浮点数值

加上增量 increment 。

LLEN key 

获取列表长度

SINTERSTORE destination key1 [key2] 

返回给定所有集合的交集并存储在 destination 中

ZLEXCOUNT key min max 

在有序集合中计算指定字典区间内成员数量

HKEYS key 

获取所有哈希表中的字段

LPOP key 

移出并获取列表的第一个元素

SISMEMBER key member 

判断 member 元素是否是集合 key 的成员

ZRANGE key start stop [WITHSCORES] 

通过索引区间返回有序集合成指定区间内的成员

HLEN key 

获取哈希表中字段的数量

LPUSH key value1 [value2] 

将一个或多个值插入到列表头部

SMEMBERS key 

返回集合中的所有成员

ZRANGEBYLEX key min max [LIMIT offset count] 

通过字典区间返回有序集合的成员

HMGET key field1 [field2] 

获取所有给定字段的值

LPUSHX key value 

将一个值插入到已存在的列表头部

SMOVE source destination member 

将 member 元素从 source 集合移动到 destination 集合

ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT] 

通过分数返回有序集合指定区间内的成员

HMSET key field1 value1 [field2 value2 ] 
同时将多个 field-value (域-值)对

设置到哈希表 key 中。

LRANGE key start stop 

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

SPOP key 

移除并返回集合中的一个随机元素

ZRANK key member 

返回有序集合中指定成员的索引

HSET key field value 

将哈希表 key 中的字段 field 的值设为 value 。

LREM key count value 

移除列表元素

SRANDMEMBER key [count] 

返回集合中一个或多个随机数

ZREM key member [member ...] 

移除有序集合中的一个或多个成员

HSETNX key field value 
只有在字段 field 不存在时,设置

哈希表字段的值。

LSET key index value 

通过索引设置列表元素的值

SREM key member1 [member2] 

移除集合中一个或多个成员

ZREMRANGEBYLEX key min max 

移除有序集合中给定的字典区间的所有成员

HVALS key 

获取哈希表中所有值

LTRIM key start stop 
对一个列表进行修剪(trim),就是说,让列表只保留

指定区间内的元素,不在指定区间之内的元素都将

被删除。

SUNION key1 [key2] 

返回所有给定集合的并集

ZREMRANGEBYRANK key start stop 

移除有序集合中给定的排名区间的所有成员

HSCAN key cursor [MATCH pattern] [COUNT count] 

迭代哈希表中的键值对。

RPOP key 

移除列表的最后一个元素,返回值为移除的元素。

SUNIONSTORE destination key1 [key2] 

所有给定集合的并集存储在 destination 集合中

ZREMRANGEBYSCORE key min max 

移除有序集合中给定的分数区间的所有成员

HSCAN key cursor [MATCH pattern] [COUNT count] 

迭代哈希表中的键值对。

RPOPLPUSH source destination 
移除列表的最后一个元素,并将该元素添加到

另一个列表并返回

SSCAN key cursor [MATCH pattern] [COUNT count] 

迭代集合中的元素

ZREVRANGE key start stop [WITHSCORES] 

返回有序集中指定区间内的成员,通过索引,分数从高到底

 

RPUSH key value1 [value2] 

在列表中添加一个或多个值

 

ZREVRANGEBYSCORE key max min [WITHSCORES] 

返回有序集中指定分数区间内的成员,分数从高到低排序

 

RPUSHX key value 

为已存在的列表添加值

 

ZREVRANK key member 
返回有序集合中指定成员的排名,有序集成员按分数值

递减(从大到小)排序

 

 

 

ZSCORE key member 

返回有序集中,成员的分数值

 

 

 

ZUNIONSTORE destination numkeys key [key ...] 

计算给定的一个或多个有序集的并集,并存储在新的 key 中

 

 

 

ZSCAN key cursor [MATCH pattern] [COUNT count] 

迭代有序集合中的元素(包括元素成员和元素分值)

 

3、数据结构HyperLogLog

如果我们要实现记录在线客服访客每天访问的独立IP数量这样的一个功能

集合实现:

使用集合来储存每个访客的 IP ,通过集合性质(集合中的每个元素都各不相同)来得到多个独立 IP ,
然后通过调用 SCARD 命令来得出独立 IP 的数量。
举个例子,程序可以使用以下代码来记录 2019 年 1 月 1 日,每个网站访客的 IP :
ip = get_vistor_ip()
SADD '2019.1.1::unique::ip' ip
然后使用以下代码来获得当天的唯一 IP 数量:
SCARD '2019.1.1::unique::ip'

集合实现的问题

使用字符串来储存每个 IPv4 地址最多需要耗费 15 字节(格式为 'XXX.XXX.XXX.XXX' ,比如
'202.189.128.186')。
下表给出了使用集合记录不同数量的独立 IP 时,需要耗费的内存数量:
独立 IP 数量一天一个月一年
一百万15 MB 450 MB 5.4 GB
一千万150 MB 4.5 GB 54 GB
一亿1.5 GB 45 GB 540 GB
随着集合记录的 IP 越来越多,消耗的内存也会越来越多。
另外如果要储存 IPv6 地址的话,需要的内存还会更多一些

为了更好地解决像独立 IP 地址计算这种问题,
Redis 在 2.8.9 版本添加了 HyperLogLog 结构。

HyperLogLog介绍

HyperLogLog 可以接受多个元素作为输入,并给出输入元素的基数估算值:
基数:集合中不同元素的数量。比如 {'apple', 'banana', 'cherry', 'banana', 'apple'} 的基数就是 3 。
估算值:算法给出的基数并不是精确的,可能会比实际稍微多一些或者稍微少一些,但会控制在合
理的范围之内。
HyperLogLog 的优点是,即使输入元素的数量或者体积非常非常大,计算基数所需的空间总是固定
的、并且是很小的。
在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基
数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。
但是,因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以
HyperLogLog 不能像集合那样,返回输入的各个元素。

将元素添加至 HyperLogLog
PFADD key element [element ...]
将任意数量的元素添加到指定的 HyperLogLog 里面。
这个命令可能会对 HyperLogLog 进行修改,以便反映新的基数估算值,如果 HyperLogLog 的基数估算
值在命令执行之后出现了变化, 那么命令返回 1 , 否则返回 0 。
命令的复杂度为 O(N) ,N 为被添加元素的数量。

返回给定 HyperLogLog 的基数估算值
PFCOUNT key [key ...]
当只给定一个 HyperLogLog 时,命令返回给定 HyperLogLog 的基数估算值。
当给定多个 HyperLogLog 时,命令会先对给定的 HyperLogLog 进行并集计算,得出一个合并后的
HyperLogLog ,然后返回这个合并 HyperLogLog 的基数估算值作为命令的结果(合并得出的
HyperLogLog 不会被储存,使用之后就会被删掉)。
当命令作用于单个 HyperLogLog 时, 复杂度为 O(1) , 并且具有非常低的平均常数时间。
当命令作用于多个 HyperLogLog 时, 复杂度为 O(N) ,并且常数时间也比处理单个 HyperLogLog 时要
大得多。

论文当中对于算法的具体实现过程如下:

REDIS 保存日志 redis保存数据的几种类型_HyperLogLog

 

4、排序

Redis 有序集合和集合一样也是string类型元素的集合,且不允许重复的成员。
不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。
有序集合的成员是唯一的,但分数(score)却可以重复。
集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。 集合中最大的成员数为 232 - 1 (4294967295, 每个集合可存储40多亿个成员)。

SkipList跳表基本原理

其实在redis sorted sets里面当items内容大于64的时候同时使用了hash和skiplist两种设计实现。这也会为了排序和查找性能做的优化。所以如上可知: 
添加和删除都需要修改skiplist,所以复杂度为O(log(n))。 
但是如果仅仅是查找元素的话可以直接使用hash,其复杂度为O(1) 
其他的range操作复杂度一般为O(log(n))
当然如果是小于64的时候,因为是采用了ziplist的设计,其时间复杂度为O(n)

下面的结构是就是跳表:

其中 -1 表示 INT_MIN, 链表的最小值,1 表示 INT_MAX,链表的最大值。

REDIS 保存日志 redis保存数据的几种类型_Redis_02

跳表具有如下性质:

(1) 由很多层结构组成

(2) 每一层都是一个有序的链表

(3) 最底层(Level 1)的链表包含所有元素

(4) 如果一个元素出现在 Level i 的链表中,则它在 Level i 之下的链表也都会出现。

(5) 每个节点包含两个指针,一个指向同一链表中的下一个元素,一个指向下面一层的元素。

跳表的搜索

REDIS 保存日志 redis保存数据的几种类型_Redis命令_03

例子:查找元素 117

(1) 比较 21, 比 21 大,往后面找

(2) 比较 37,   比 37大,比链表最大值小,从 37 的下面一层开始找

(3) 比较 71,  比 71 大,比链表最大值小,从 71 的下面一层开始找

(4) 比较 85, 比 85 大,从后面找

(5) 比较 117, 等于 117, 找到了节点。

 

指定区间内,带有 score 值(可选)的有序集成员的列表。

ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]
O(log(N)+M),N 为有序集的基数,M 为被结果集的基数。
返回有序集key中,所有score值介min和max之间(包括等于 mi或max )的成员。有序集成员按score值递增次序排列,具有相同 score 值的成员按字典序来排列.可选的 LIMIT 参数指定返回结果的数量及区间.注意当offset很大时,定位offset的操作可能需要遍历整个有序集,此过程最坏复杂度O(N)时间。min和max可以是-inf和+inf .默认情况下,区间的取值使用闭区间 (小于等于或大于等于),你也可以通过给参数前增加 ( 符号来使用可选的开区间 (小于或大于)。

 

应用范围

可以用于一个大型在线游戏的积分排行榜。每当玩家的分数发生变化时,可以执行ZADD命令更新玩家的分数,此后再通过ZRANGE命令获取积分TOP TEN的用户信 
息。当然我们也可以利用ZRANK命令通过username来获取玩家的排行信息。最后我们将组合使用ZRANGE和ZRANK命令快速的获取和某个玩家积分相近的其他用户 
的信息。
Sorted-Sets类型还可用于构建索引数据。

 

 

5、事务

Redis 事务可以一次执行多个命令, 并且带有以下3个重要的保证:

1 批量操作在发送 EXEC 命令前被放入队列缓存。
2 收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行。
3 在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中。

一个事务从开始到执行会经历以下三个阶段:

1 开始事务。
2 命令入队。
3 执行事务。

单个 Redis 命令的执行是原子性的,但 Redis 没有在事务上增加任何维持原子性的机制,所以 Redis 事务的执行并不是原子性的。
事务可以理解为一个打包的批量执行脚本,但批量指令并非原子化的操作,中间某条指令的失败不会导致前面已做指令的回滚,也不会造成后续的指令不做。

这是官网上的说明 From redis docs on transactions:

It's important to note that even when a command fails, all the other commands in the queue are processed – Redis will not stop the processing of commands.

 

 

6、管道

Redis是一种基于客户端-服务端模型以及请求/响应协议的TCP服务。这意味着通常情况下一个请求会遵循以下步骤:
1 客户端向服务端发送一个查询请求,并监听Socket返回,通常是以阻塞模式,等待服务端响应。
2 服务端处理命令,并将结果返回给客户端。

Redis 管道技术可以在服务端未响应时,客户端可以继续向服务端发送请求,并最终一次性读取所有服务端的响应。

 

7、其他问题

配置 redis 外网可访问
由于 redis 采用的安全策略,默认会只准许本地访问。需要通过简单配置,完成允许外网访问。
修改 redis 的配置文件,将所有 bind 信息全部屏蔽。
# bind 192.168.1.100 10.0.0.1 
# bind 192.168.1.8 
# bind 127.0.0.1
修改完成后,需要重新启动 redis 服务。

有时候会有中文乱码。
要在 redis-cli 后面加上 --raw
redis-cli --raw
就可以避免中文乱码了。

 

8、总结

Redis处理缓存几乎是业界标配,例如在线客服消息通讯缓存、会话状态、客服信息缓存等等。

熟练掌握Hash、List、set排序,是程序员的基本功。

熟练掌握排序、事务、管道,是程序员的基本功。有机会最好了解Redis的底层原理,这样有利于开发的时候提高性能。