geoadd:存储指定的地理空间位置:
#语法格式:GEOADDkeylongitudelatitudemember[longitudelatitudemember...]#测试:>GEOADDlocations116.41921739.921133beijing>GEOADDlocations120.36955736.094406qingdao
来看一下geo数据在Redis中的存储办法,可以看到因此zset格式进行存储的,因此geo是zset的一个扩展:
geopos:返回指定地理位置的经纬度坐标:
#语法格式:GEOPOSkeymember[member...]#测试:>GEOPOSlocationsbeijingqingdao116.4192196726799011239.92113206197632991120.3695556521415710436.09440522913565275
也可以利用zrange返回所有的位置元素而不带经纬度信息:
>ZRANGElocations0-1qingdaobeijing
geodist:打算指定位置间的间隔,并可以指定返回的间隔单位:
#语法格式:GEODISTkeymember1member2[m|km|ft|mi]#测试:>GEODISTlocationsbeijingqingdaokm548.5196
georadiusbymember:找出以给定位置为中央,返回key包含的元素中,与中央的间隔不超过给定最大间隔的所有位置元素:
#语法格式:GEORADIUSBYMEMBERkeymemberradius[m|km|ft|mi]#测试:>GEORADIUSBYMEMBERlocationsbeijing150kmbeijing#扩大范围>GEORADIUSBYMEMBERlocationsbeijing600kmqingdaobeijing
georadius与georadiusbymember类似,不过因此指定的经纬度为中央:
#语法格式:GEORADIUSkeylongitudelatituderadius[m|km|ft|mi]#测试:>GEORADIUSlocations116.419239.921110kmbeijing
geo并没有供应删除指令,但根据其底层是zset实现,我们可以利用zrem对数据进行删除:
>ZREMlocationsbeijing
基于geo,可以很大略的存储人或物关联的经纬度信息,并对这些地理信息进行处理,例如基于查询相邻的经纬度范围,能大略实现类似“附近的人”等功能。
BitmapBitmap 也被称为位图,因此 String 类型作为底层数据构造实现的一种统计二值状态的数据类型。个中每一个bit都只能是0或1,以是常日用来表示一个对应于数组下标的数据是否存在。Bitmap 供应了一系列api,紧张用于对 bit 位进行读写、打算、统计等操作。
setbit:对key所存储的字符串值,设置或打消指定偏移量上的位(bit):
#语法格式:SETBITkeyoffsetvalue#测试:>SETBITkey1001>SETBITkey1281
getbit:对key所存储的字符串值,获取指定偏移量上的位(bit):
#语法格式:GETBITkeyoffset#测试:>GETBITkey1001
bitcount:可以统计bit 数组中指定例模内所有 1 的个数,如果不指定例模,则获取所有:
#语法格式:BITCOUNTkey[startend]#测试:>BITCOUNTkey2
bitpos:打算 bit 数组中指定例模第一个偏移量对应的的值即是targetBit的位置:
#语法格式:BITPOSkeytartgetBit[startend]#测试:>BITPOSkey1100
bitop:做多个bit 数组的and(交集)、or(并集)、not(非)、xor(异或)。例如对key和key2做交集操作,并将结果保存在key:and:key2中:
#语法格式:BITOPopdestKeykey1[key2...]#测试:>BITOPandkey:and:key2keykey217
Bitmap底层利用String实现,value的值最大能存储512M字节,可以表示 512 1024 10248=4294967296个位,已经能够知足我们绝大部分的利用场景。再看一下底层存储数据的格式,以刚刚存储的key为例:
\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x80
将16进制的数据转化为2进制数据,如下图所示,第100位和第128位为1,其他为0:
此外,由于Redis在存储string类型的时候存储形式为二进制,以是也可以通过操作bit位来对string类型进行操作,不才面的例子中,通过直接操作bit,将string类型的abc变成了bbc。
>setkey2abc>setbitkey261>setbitkey270>getkey2bbc
其余,可以通过bitfield命令实现类似的效果:
>setkey3a>BITFIELDkey3getu8097>BITFIELDkey3setu809897>getkey3b
利用bitfield 命令可以返回指定位域的bit值,并将它转化为整形,有符号整型需在位数前加 i,无符号在位数前加u。上面我们将8位转化为无符号整形,恰好是a的ASCII码,再对ASCII码进行修正,可以直接改变字符串的值。
Bitmap的运用非常广泛,例如在缓存三大问题中我们先容过利用Bitmap作为布隆过滤器应对缓存穿透的问题,此外布隆过滤器也被广泛用于邮件系统中拦截垃圾邮件的地址。其余,常用的用户签到、朋友圈点赞等功能也可以用它来实现。
以实现用户签到功能为例,可以将每个用户按月存储为一条数据,key的格式可以定义为 sign:userId:yyyyMM ,如果签到了就将对应的位置改为1,未签到为0,这样最多只须要31个bit位就可以存储一个月的数据,转换为字节的话也只要4个字节就已经足够。
#1月10日签到,由于offset从0起始,以是将天数减1>SETBITsign:6666:202101910#查看1月10日是否签到>GETBITsign:6666:20210191#统计签到天数>BITCOUNTsign:6666:2021011#查看首次签到的日期>BITPOSsign:6666:20210119#提取整月的签到数据>BITFIELDsign:6666:202101getu3102097152
把稳在利用bitfield指令时,有符号整型最大支持64位,而无符号整型最大支持63位。如果位数超过限定,会报如下缺点:
>bitfieldkey3getu640ERRInvalidbitfieldtype.Usesomethinglikei16u8.Notethatu64isnotsupportedbuti64is.
以是在存储签到数据时,如果按月存储的话在之后提取数据时会比较方便,如果按年存储数据,在提取整年的签到数据时可能须要进行分段。
HyperLogLogRedis 在 2.8.9 版本添加了 HyperLogLog 构造,它是一种用于基数统计的数据凑集类型。它的最大上风就在于,当凑集元素数量非常多时,它打算基数所需的空间总是固定的,而且还很小。
pfadd:向HyperLogLog中添加数据:
#语法格式:PFADDkeyelement[element...]#测试:>PFADDindex.htmluuid1uuid2uuid3uuid4
pfcount:返回HyperLogLog的基数统计结果:
#语法格式:PFCOUNTkey[key...]#测试:>PFCOUNTindex.html4
pfmerge:将多个 HyperLogLog 合并为一个 HyperLogLog ,合并后的 HyperLogLog 的基数估算值是通过对所有 给定 HyperLogLog 进行并集打算得出的。
#语法格式:PFMERGEdestkeysourcekey[sourcekey...]#测试:>PFMERGEindex.htmlhome.htmlOK>PFCOUNTindex.html6
例如在上面的例子中,利用HyperLogLog 可以很方便的统计网页的UV。在官方文档中指明,Redis 中每个 HyperLogLog 只须要花费 12 KB 内存,就可以对 2^64 个数据完成基数统计。只管利用Set或Hash等构造也能实现基数统计,但这些数据构造都会花费大量的内存。而利用HyperLogLog 时,和其他数据构造打算基数时,元素越多耗费内存就越多形成了光鲜比拟。
须要把稳的是,HyperLogLog是一种算法,并非是Redis独占的,并且HyperLogLog 的统计规则是基于概率完成的,以是它给出的统计结果是有一定偏差的,官方给出的标准误算率是 0.81%。 HyperLogLog 只会根据输入元向来打算基数,而不会存储输入的元素本身,以是 HyperLogLog 不能像凑集那样,返回输入的各个元素。
针对以上这些特性,可以总结出,HyperLogLog适用于大数据量的基数统计,但是它也存在局限性,它只能够实现统计基数的数量,但无法知道详细的原数据是什么。如果须要原数据的话,我们可以将 Bitmap 和 HyperLogLog 合营利用,例如在统计网站UV时,利用Bitmap 标识哪些用户属于生动用户,利用 HyperLogLog 实现基数统计。
StreamStream是Redis 5.0版本之后新增加的数据构造,实现了行列步队的功能,并且实现的持久化和主备复制功能,可以让任何客户端访问任何时候的数据,并且能记住每一个客户真个访问位置,担保不丢失,下面我们看一下详细的指令。
xadd:向行列步队添加
#语法格式:XADDkeyIDfieldvalue[fieldvalue...]#测试:>XADDstream1phone88888888nameHydra34;1614316213565-0">XADDstream1key1value1key2value2key3value3"1614317444558-0"
添加是天生的 1614316213565-0,是天生的id,由韶光戳加序号组成,韶光戳是Redis的做事器韶光,如果在同一个韶光戳内,序号会递增来标识不同的。并且为了担保的有序性,天生的id是保持自增的。可以利用可视化工具查看数据,因此json格式被存储:
这里由于是不同韶光戳,以是序号都是从0开始。我们可以通过redis的事务添加进行测试:
>MULTI"OK">XADDstreammsg1"QUEUED">XADDstreammsg2"QUEUED">XADDstreammsg3"QUEUED">XADDstreammsg4"QUEUED">XADDstreammsg5"QUEUED">EXEC1)"OK"2)"1614319042782-0"3)"OK"4)"1614319042782-1"5)"OK"6)"1614319042782-2"7)"OK"8)"1614319042782-3"9)"OK"10)"1614319042782-4"11)"OK"
通过上面的例子,可以瞥见同一韶光戳内,序号会不断递增。
xrange:获取消息列表,会自动过滤删除的
#语法格式:XRANGEkeystartend[COUNTcount]#测试:>XRANGEstream1-+count51)1)"1614316213565-0"2)1)"phone"2)"88888888"3)"name"4)"Hydra"2)1)"1614317444558-0"2)1)"key1"2)"value1"3)"key2"4)"value2"5)"key3"6)"value3"
xread:以壅塞或非壅塞办法获取消息列表
#语法格式:XREAD[COUNTcount][BLOCKmilliseconds]STREAMSkey[key...]id[id...]#测试:>XREADcount1STREAMSstream10-11)1)"stream1"2)1)1)"1614316213565-0"2)1)"phone"2)"88888888"3)"name"4)"Hydra"
xdel:删除
#语法格式:XDELkeyID[ID...]#测试:>XDELstream11614317444558-0"1"
除了上面行列步队的基本操作外,还可以创建消费者组对进行消费。首先利用xgroup create 创建消费者组:
#语法格式:XGROUP[CREATEkeygroupnameid-or-$][SETIDkeygroupnameid-or-$][DESTROYkeygroupname][DELCONSUMERkeygroupnameconsumername]#创建一个行列步队,从头开始消费:>XGROUPCREATEstream1consumer-group-10-0#创建一个行列步队,从尾部开始消费,只吸收新:>XGROUPCREATEstream1consumer-group-2$
下面利用消费者组消费:
#语法格式XREADGROUPGROUPgroupconsumer[COUNTcount][BLOCKmilliseconds][NOACK]STREAMSkey[key...]ID[ID...]
把稳这里消费的工具是 consumer消费者,而不是消费者组。在消费时,不须要预先创建消费者,在消费过程中直接指定就可以。接下来再向stream中发送一条,比较两个消费者组的消费顺序差异:
#重新发送一条>XADDstream1newmsghi"1614318022661-0"#利用消费者组1消费:>XREADGROUPGROUPconsumer-group-1consumer1COUNT1STREAMSstream1>1)1)"stream1"2)1)1)"1614316213565-0"2)1)"phone"2)"88888888"3)"name"4)"Hydra"#利用消费者组2消费:>XREADGROUPGROUPconsumer-group-2consumer2COUNT1STREAMSstream1>1)1)"stream1"2)1)1)"1614318022661-0"2)1)"newmsg"2)"hi"
可以看到,消费者组1从stream的头部开始消费,而消费者组2从创建消费者组后的最新开始消费。在消费者组2内利用新的消费者再次进行消费:
>XREADGROUPGROUPconsumer-group-2consumer4COUNT1STREAMSstream1>>XADDstream1newmsg2hi2"1614318706162-0">XREADGROUPGROUPconsumer-group-2consumer4COUNT1STREAMSstream1>1)1)"stream1"2)1)1)"1614318706162-0"2)1)"newmsg2"2)"hi2"
在上面的例子中,可以看到在一个消费者组中,存在互斥原则,即一条被一个消费者消费过后,其他消费者就不能再消费这条了。
xpending:等待列表用于记录读取但并未处理完毕的,可以利用它来获取未处理完毕的。
>XPENDINGstream1consumer-group-21)"2"#2条已读取但未处理的2)"1614318022661-0"#起始ID3)"1614318706162-0"#结束ID4)1)1)"consumer2"#消费者2有1个2)"1"2)1)"consumer4"#消费者4有1个2)"1"
在 xpending 命令后添加start end count参数可以获取详细信息:
>XPENDINGstream1consumer-group-2-+101)1)"1614318022661-0"#ID2)"consumer2"#消费者3)"1867692"#从读取到现在经历的毫秒数4)"1"#被读取次数2)1)"1614318706162-0"2)"consumer4"3)"1380323"4)"1"
xack:奉告被处理完成,移出pending列表
>XACKstream1consumer-group-21614318022661-0"1"
再次查看pending列表,可以看到1614318022661-0 已被移除:
>XPENDINGstream1consumer-group-21)"1"2)"1614318706162-0"3)"1614318706162-0"4)1)1)"consumer4"2)"1"
基于以上功能,如果我们的系统中已经利用了redis,乃至可以移除掉不须要的其他行列步队中间件,来达到精简运用系统的目的。并且,Redis Stream供应了的持久化和主从复制,能够很好的担保的可靠性。
如果文章对您有所帮助,欢迎关注"大众年夜众号 码农参上