本文主要和大家分享redis的高级特性:位操作。力求让大家彻底学会使用redis的位操作,掌握其底层实现原理!主要包括以下内容:redis位操作命令示例底层数据结构分析为什么他的算法时间复杂度是O(1)?10亿数据量需要多少存储空间?redis位操作适合哪些应用场景?本文中的redis测试代码基于以下环境:操作系统:MacOS64位版本:Redis5.0.764位运行方式:standalonemoderedis位操作reids位操作也叫位数组操作,位图,它提供了SETBIT、GETBIT、BITCOUNT、BITTOP四种命令,用于操作二进制位数组。下面看一下基本操作示例SETBIT语法:SETBITkeyoffsetvalueis:commandkeyoffset0/1setbit命令用于在位数组中写入指定偏移量的二进制位设置值,偏移量从开始计数0.且只允许写入1或0。如果写入了0和1以外的值,则写入失败:GETBIT语法:GETBITkeyoffset即:commandkeyoffsetgitbit命令用于获取位数组指定偏移量处的二进制值:BITCOUNT语法:BITCOUNTkey即:commandkeybitcount命令用于获取指定key的位数组中值为1的二进制位数。之前我们把offset0的值写成1,offset10的值为1,offset8的值为0:key2...bitop命令可以进行and(按位与)、or(按位或)、xor(按位异或)运算,并将运算结果设置为destkey:底层数据结构分析SDS是一种数据结构在redis中,称为简单动态字符串(SimpleDynamicString),它是二进制安全的,大多数情况下redis中的字符串都是用SDS存储的。SDS的数据结构:structsdshdr{#记录buff数组中使用的字节数#也是SDS中存储的字符串长度intlen;#记录buff数组中未使用的字节数intfree;#字节数组,字符串存储在这个数组charbuff[];}数据存储示例:图片来源《redis设计与实现》SDS优点:时间复杂度为O(1),防止缓冲区溢出,减少修改字符串长度时需要重新分配内存的次数二进制安全API操作与某些C字符串函数兼容。关于SDS的详细介绍可以参考文章《redis设计与实现》。redis中的位数组是以String字符串数据格式存储的,string对象使用上面提到的SDS简单动态字符串数据结构。图片来源《redis设计与实现》大家都知道一个字节是用8个二进制位存储的,也就是8个0或者1,也就是一个字节可以存储0到127的十进制数字,也就是包含了所有的数字,英文大写以及小写字母和标点符号。1Byte=8bit1KB=1024Byte1MB=1024KB1GB=1024MB位数组在redis存储世界中,每个字节也是8位,初始值为:00000000位操作就是在对应的offset偏移量上设置0或者1,比如设置前3位设置为1,即:00001000#对应redis操作,即:setbitkey31在此基础上,如果要在偏移量13处设置1,即:setbitkey131#在redis中对应存储为:0010|0000|0000|1000时间复杂度GETBIT命令时间复杂度O(1)STEBIT命令时间复杂度O(1)BITCOUNT命令时间复杂度O(n)BITOP命令时间复杂度O(n),O(n2)我们来看GETBIT以及为什么SETBIT命令的时间复杂度是O(1)。当我们执行一个SETBITkey100861的值时,reids的计算方法如下:得到要写入bit数组的哪个字节:10086÷8=1260,需要写入bit的下标1260的字节数组得到要写入这个字节的位数:10086mod8=6,这个字节需要写入的下标为6,即第7位。通过这两种计算方式,可以清楚的看出位操作的GETBIT和SETBIT是常量计算,所以它的时间复杂度是O(1)。BITCOUNT命令需要遍历整个bit数组的所有元素,计算出有多少个值为1。当然,对于大数据位,redis会有一整套复杂的执行bitcount命令的优化算法,但是核心想法还是一样的。就是减少部分遍历查询的次数。比如用128位作为遍历,那么遍历的次数就是所有的数字除以128。BITTOP命令根据不同的操作有不同的执行方式。比如在AND运算中,需要校验1的位值。存储空间的计算根据上面的介绍,相信大家已经知道如何根据位数组数据结构来计算存储数据的内存大小了雷迪斯。比如有100亿条数据,那么它需要的字节数组为:1000000000÷8÷1024÷1024≈119.21MB,也就是说,存储10亿条数据只需要大约119MB的内存空间,这对于目前16G和32G集群版本的redis,一点问题都没有。需要注意的是,如果你的数据量不大,那么起始偏移量就不要设置的太大,同样会占用空间。比如我们只需要存储几百条数据,但是offset是非常大的,这样会造成内存空间的大量浪费。应用场景在实际项目开发中,有很多业务适合使用redisbits来实现。用户签到场景,以每日日期字符串为key,以用户id为偏移量统计每日用户签到情况、用户签到总数、活跃人数用户,以及用户日活、月活、留存率等统计数据可以存储在redis位数组中,仍然以每天的日期为key,当用户活跃时,写入offset作为用户标识的位值1。月活也是如此。用户是否在线和在线用户总数也是用一个位数组,用户id映射偏移量,在线标志为1,离线标志为0。可以实现用户在线和离线的查询和在线用户总数的统计。用户在app中的全局消息提示为小红点。现在大部分APP都有站内信的功能。当有消息时,会提示一个小红点,表示用户有新消息。
