3.11 BitMap
3.11.1 什么是BitMap
BitMap 是 Redis 2.2.中引入的一种新的数据类型, 属于value型.
该数据类型本质上就是一个仅包含 0 和 1 的二进制字符串。而其所有相关命令都是对这个字符串二进制位的操作。
用于描述该字符串的属性有三个:key、offset、bitValue。
- key:BitMap 是 Redis 的 key-value 中的一种 Value 的数据类型,所以该 Value 一定有其对应的 key。
- offset(索引):每个 BitMap 数据都是一个字符串,字符串中的每个字符都有其对应的offset(索引) ,该offset(索引) 从 0 开始计数。offset(索引)的值的范围是[0,2^32-1],即offset(索引) 的最大值为 4G-1,即 4294967295,42 亿多。
- bitValue:每个 offset(索引) 位上的字符就称为该位的值 bitValue。由于BitMap存储的数据是一串二进制字符串 , 因此bitValue的值只能是0/1
3.11.2 应用场景
由于 offset 的取值范围很大,所以其一般应用于大数据量的二值性统计。例如平台活跃用户统计(二值:访问或未访问)、支持率统计(二值:支持或不支持)、员工考勤统计(二值:上班或未上班)、图像二值化(二值:黑或白)等。
不过,对于数据量较小的二值性统计并不适合 BitMap,可能使用 Set 更为合适。当然,具体多少数据量适合使用 Set,超过多少数据量适合使用 BitMap,这需要根据具体场景进行具体分析。
例如,一个平台要统计日活跃用户数量。
如果使用 Set 来统计,只需上线一个用户,就将其用户 ID 写入 Set 集合即可,最后只需统计出 Set 集合中的元素个数即可完成统计。即 Set 集合占用内存的大小与上线用户数量成正比。假设用户 ID 为 m 位 bit 位,当前活跃用户数量为 n,则该 Set 集合的大小最少应该是m*n 字节。
如果使用 BitMap 来统计,则需要先定义出一个 BitMap,其占有的 bit 位至少为注册用户数量。只需上线一个用户,就立即使其中一个 bit 位置 1,最后只需统计出 BitMap 中 1 的个数即可完成统计。即 BitMap 占用内存的大小与注册用户数量成正比,与上线用户数量无关。假设平台具有注册用户数量为 N,则 BitMap 的长度至少为 N 个 bit 位,即 N/8 字节。
何时使用 BitMap 更合适?令 mn 字节 = N/8 字节,即 n = N/8/m = N/(8m) 时,这个n即使用Set和BitMap的分界线
假设用户 ID 长度为 11 位(m),注册用户数量为 8 亿(N),当活跃用户数量为 8 亿/(811) = 0.09 亿 = 9106 = 900 万,使用 Set
与 BitMap 占用的内存是相等的。即900万是使用Set和BitMap的分界线.
3.11.3 BitMap命令
3.11.3.1 SETBIT
-
格式:SETBIT key offset value
-
功能:为给定key 的 BitMap数据的offset 位置设置值为 value。
-
返回值:
若offset位原本已经设置value, 则返回修改前该 offset位置的 bitValue
若offset位原本没有设置value, 则固定返回0 -
说明:
对于原 BitMap 字符串中不存在的 offset 进行赋值,字符串会自动伸展以确保它可以将 value 保存在指定的 offset 上。当字符串值进行伸展时,空白位置以 0 填充。
不过需要注意的是,对使用较大 offset 的 SETBIT 操作来说,内存分配过程可能造成 Redis 服务器被阻塞。
3.11.3.2 GETBIT
- 格式:GETBIT key offset
- 功能:对 key 所储存的 BitMap 字符串值,获取指定 offset 偏移量上的位值 bitValue。
- 返回值:
当 offset 比字符串值的长度大,或者 key 不存在时,返回 0 。
当offset位不存在value时, 返回错误提示
3.11.3.3 BITCOUNT
- 格式:BITCOUNT key [start] [end]
- 功能:
统计给定字符串中被设置为 1 的 bit 位的数量。在默认情况下, 命令将检测整个 BitMap,但也可以通过指定额外的 start 或 end 参数,实现仅对指定字节范围内字符串进行统计,包括 start 和 end 在内。
注意,这里的 start 与 end 的单位是字节,不是 bit,并且从 0 开始计数。 - 说明:
start 和 end 参数都可以使用负数值: -1 表示最后一个字节, -2 表示倒数第二个字节,以此类推。
另外,对于不存在的 key 被当成是空字符串来处理,因此对一个不存在的 key 进行 BITCOUNT 操作,结果为 0 。
3.11.3.4 BITPOS
- 格式:BITPOS key bit [start] [end]
- 功能:返回 key对应的 BitMap 中第一个值为指定值 bit(非 0 即 1) 的二进制位的位置。 在默认情况下, 命令将检测整个 BitMap,但用户也可以通过可选的 start 参数和 end 参数指定要检测的范围。
- 说明:start 与 end 的意义与 bitcount 命令中的相同。
3.11.3.5 BITOP
- 格式:BITOP operation destkey key [key …]
- 功能:
对一个或多个 BitMap 字符串 key 进行二进制位操作,并将结果保存到 destkey 上。
operation 可以是 AND 、 OR 、 NOT 、 XOR 这四种操作中的任意一种:
BITOP AND destkey key [key …] :对一个或多个 BitMap 执行按位与操作,并将结果保存到 destkey 。
BITOP OR destkey key [key …] :对一个或多个 BitMap 执行按位或操作,并将结果保存到 destkey 。
BITOP XOR destkey key [key …] :对一个或多个 BitMap 执行按位异或操作,并将结果保存到 destkey 。
BITOP NOT destkey key :对给定 BitMap 执行按位非操作,并将结果保存到 destkey 。
- 说明:
除了 NOT 操作之外,其他操作都可以接受一个或多个 BitMap 作为输入。
除了 NOT 操作外,其他对一个 BitMap 的操作其实就是一个复制。
如果参与运算的多个 BitMap 长度不同,较短的 BitMap 会以 0 作为补充位与较长
3.12 HyperLogLog
HyperLogLog 是 Redis 2.8.9 版本中引入的一种新的数据类型,其意义是 hyperlog log,超级日志记录。
该数据类型可以简单理解为一个 set 集合(因此自然是),集合元素为字符串。
但实际上HyperLogLog 是一种基数计数概率算法,通过该算法可以利用极小的内存完成独立总数的统计。其所有相关命令都是对这个“set 集合”的操作。
3.12.1 应用场景
HyperLogLog 可对数据量超级庞大的日志数据做不精确的去重计数统计。 Redis 官方给出的精度误差是 0.81%。这个误差对于大多数超大数据量场景是被允许的。
对于平台上每个页面每天的 UV 数据,非常适合使用 HyperLogLog 进行记录。
3.12.2 PFADD
- 格式:PFADD key element [element …]
- 功能:将一个或多个的元素添加到指定的 HyperLogLog 集合里面。
- 返回值: 如果内部存储被修改了返回 1,否则返回 0(例如添加的元素全是HyperLogLog中原本已存在的)。
3.12.3 PFCOUNT
- 格式:PFCOUNT key [key …]
- 功能:
该命令作用于单个 key 时,返回给定 key 的 HyperLogLog 集合的近似基数;
该命令作用于多个 key 时,返回所有给定 key 的 HyperLogLog 集合的并集的近似基数;
如果key 不存在,则返回 0。
3.12.4 PFMERGE
- 格式:PFMERGE destkey sourcekey [sourcekey …]
- 功能:将多个 HyperLogLog 集合合并为一个 HyperLogLog 集合,并存储到 destkey 中,合并后的 HyperLogLog 的基数接近于所有 sourcekey 的 HyperLogLog 集合的并集。
- 返回值: 合并成功则为ok
3.13 Geospatial
Redis 在 3.2 版本中引入了 Geospatial 这种新的数据类型。
该类型本质上仍是一种集合,只不过集合元素比较特殊,每个元素都由三部分组成, 称为空间元素:
- 经度:longitude。有效经度为[-180,180]。正的表示东经,负的表示西经。
- 纬度:latitude。有效纬度为[-85.05112878, 85.05112878]。正的表示北纬,负的表示南纬。
- 位置名称:该经纬度所标注的位置的名称,也称为该 Geospatial 集合的空 间元素名称。
通过该类型可以设置、查询某地理位置的经纬度,查询某范围内的空间元素,计算两空间元素间的距离等。
3.13.1 命令
3.13.1.1 GEOADD
- 格式:GEOADD key longitude latitude member [longitude latitude member …]
- 功能:将一到多个空间元素添加到指定的空间集合中。
- 说明:当用户尝试输入一个超出范围的经度或者纬度时,该命令会返回一个错误。
3.13.1.2 GEOPOS
- 格式:GEOPOS key member [member …]
- 功能:从指定的地理空间中返回,指定名称的空间元素的位置,即经纬度。
- 说明:因为 该命令接受可变数量元素作为输入,所以即使用户只给定了一个元素,命令也会返回数组。
3.13.1.3 GEODIST
-
格式:GEODIST key member1 member2 [unit]
-
功能:返回两个给定位置之间的距离。其中 unit 必须是以下单位中的一种:
m :米,默认
km :千米
mi :英里
ft:英尺 -
说明:如果两个位置之间的其中一个不存在, 那么命令返回空值。另外,在计算距离时会假设地球为完美的球形, 在极限情况下, 这一假设最大会造成 0.5% 的误差。
3.14 发布/订阅命令
3.14.1 消息系统(channel)
发布/订阅,即 pub/sub,是一种消息通信模式:发布者也称为消息生产者,生产和发送消息到存储系统;订阅者也称为消息消费者,从存储系统接收和消费消息。
这个存储系统可以是文件系统 FS、消息中间件 MQ、数据管理系统 DBMS,也可以是 Redis。
整个消息发布者、订阅者与存储系统称为消息系统。
消息系统中的订阅者订阅了某类消息后,只要存储系统中存在该类消息,其就可不断的接收并消费这些消息。当存储系统中没有该消息后,订阅者的接收、消费阻塞。而当发布者将消息写入到存储系统后,会立即唤醒订阅者。
当存储系统放满时,不同的发布者具有不同的处理方式:有的会阻塞发布者的发布,等待可用的存储空间;有的则会将多余的消息丢失。
当然,不同的消息系统消息的发布/订阅方式也是不同的。例如 RocketMQ、Kafka 等消息中间件构成的消息系统中,发布/订阅的消息都是以主题 Topic 分类的。而 Redis 构成的消息系统中,发布/订阅的消息都是以频道 Channel 分类的。
3.14.2 发布/订阅的命令
3.14.2.1 SUBSCRIBE
- 格式:SUBSCRIBE channel [channel …]
- 功能:
Redis 客户端通过一个 subscribe 命令可以同时订阅任意数量的频道。
在输出了订阅了主题后,命令处于阻塞状态,等待相关频道的消息。
同时开启两个命令行窗口,
在第一个窗口使用订阅命令 , 订阅news和sports两个频道 此时该窗口进入阻塞, 等待消息到达
3.14.2.2 PSUBSCRIBE
- 格式:PSUBSCRIBE pattern [pattern …]
- 功能:订阅一个或多个符合给定模式的频道。
- 说明:这里的模式只能使用通配符 。例如, it 可以匹配所有以 it 开头的频道,像 it.news、 it.blog、it.tweets 等;news.*可以匹配所有以 news.开头的频道,像 news.global.today、 news.it 等。
3.14.2.3 PUBLISH
- 格式:PUBLISH channel message
- 功能:Redis 客户端通过一条 publish 命令可以发布一个频道的消息。返回值为接收到该消息的订阅者数量。
在第二个窗口使用发布命令, 向news和sports两个频道分别发布一条消息
第一个窗口可以监听到, 并且依然保持阻塞保持监听
3.14.2.4 UNSUBSCRIBE
- 格式:UNSUBSCRIBE [channel [channel …]]
- 功能:Redis 客户端退订指定的频道。
- 说明:
如果没有频道被指定,即不携带channel参数 , 那么客户端使用 SUBSCRIBE 命令订阅的所有频道都会被退订。
在这种情况下,命令会返回一个信息,告知客户端所有被退订的频道。
3.14.2.5 PUNSUBSCRIBE
- 格式:PUNSUBSCRIBE [pattern [pattern …]]
- 功能:退订一个或多个符合给定模式的频道。
- 说明:这里的模式只能使用通配符 *。如果没有频道被指定,其效果与 SUBSCRIBE 命令相同,客户端将退订所有订阅的频道。
3.14.2.6 PUBSUB
- 格式:PUBSUB [argument [argument …]]
- 功能:PUBSUB 是一个查看订阅与发布系统状态的内省命令集,它由数个不同格式的子命令组成,下面分别介绍这些子命令的用法。
3.14.2.6.1 PUBSUB CHANNELS
- 格式:PUBSUB CHANNELS [pattern]
- 功能:列出当前所有的活跃频道。活跃频道指的是那些至少有一个订阅者的频道。
- 说明:pattern 参数是可选的。如果不给出 pattern 参数,将会列出订阅/发布系统中的所有活跃频道。如果给出 pattern 参数,那么只列出和给定模式 pattern 相匹配的那些活跃频道。pattern 中只能使用通配符*。
3.14.2.6.2 PUBSUB NUMSUB
- 格式:PUBSUB NUMSUB [channel-1 … channel-N]
- 功能:返回给定频道的订阅者数量。不给定任何频道则返回一个空列表
3.14.2.6.3 PUBSUB NUMPAT
- 格式:PUBSUB NUMPAT
- 功能:查询当前 Redis 所有客户端订阅的所有频道模式的数量总和
说人话: 只有通过 PSUBSCRIBE 命令进行订阅的频道 , 才会被计数
3.15 事务
Redis 的事务的本质是一组命令的批处理。这组命令在执行过程中会被顺序地、一次性全部执行完毕.
某些命令执行失败不会引发回滚 , 只要没有出现语法错误,这组命令在执行期间是不会被中断。
3.15.1 Redis事务的特性
Redis 的事务仅保证了数据的一致性©,不具有像 DBMS 的ACID 特性中的A,I,D。
- 这组命令中的某些命令的执行失败不会影响其它命令的执行,不会引发回滚。即不具备原子性。
- 这组命令通过乐观锁机制实现了简单的隔离性。没有复杂的隔离级别。
- 这组命令的执行结果是被写入到内存的,是否持久取决于 Redis 的持久化策略,与事务无关。
3.15.2 Redis事务的实现
3.15.2.1 三个命令
Redis事务通过三个命令进行控制
- muti:开启事务
- exec:执行事务
- discard:取消事务
3.15.2.2 开启并执行事务
3.15.2.3 开启并取消事务
3.15.3 Redis事务异常处理
3.15.3.1 语法错误
INCRBY命令后必须跟上键值对 , 这里只有键 没有值 属于语法 错误
因此即使exec执行 也不会被提交
3.15.3.2 执行异常
尽管score是A , 不能执行+20的操作
但关键是 , 系统并不会理会score到底是什么类型
系统只知道SET命令和 INCRYBY命令都符合语法, 至于执行过程中出现什么问题 , 那我不管
3.15.4 Redis事务隔离机制
3.15.4.1 为什么要隔离机制
在并发场景下可能会出现多个客户端对同一个数据进行修改的情况。
例如:有两个客户端 C 左与 C 右,C 左需要申请 40 个资源,C 右需要申请 30 个资源。它们首先查看了当前拥有的资源数量,即 resources 的值。它们查看到的都是 50,都感觉资源数量可以满足自己的需求,于是修改资源数量,以占有资源。但结果却是资源出现了“超卖”情况。
为了解决这种情况,Redis 事务通过乐观锁机制实现了多线程下的执行隔离。
3.15.4.2 隔离机制的实现
实现方式
Redis 通过 watch 命令再配合事务实现了多线程下的执行隔离。
本例中, 右边没有加锁, 可以正常减掉
左边加锁, 并且检测到右边已经修改, 所以事务不执行
实现原理
watch命令会自动为watch的资源增加乐观锁version字段, 此后的原理与前述一致