目录
目标
列举redis中与string val相关的指令,将指令分为bit、string、数字3组,包括作用、使用时需要注意的地方等。可用于高效计算实时指标、排它锁、计数器、限制器。
将val视为bit数据
BITCOUNT
BITCOUNT key [startCharIndex endCharIndex]
O(N)
将String val视为bit数组形式,返回bit为1的bit数量,即群体统计,可指定字符index范围,index为0表示第一个字符,index为-1表示最后一个字符
BITFIELD
BITFIELD key [GET type offset] [SET type offset value] [INCRBY type offset increment] [OVERFLOW WRAP|SAT|FAIL]
since 3.2
O(1)
将key对应string val视为bit数组,使用offset(based-0)和特定bit宽度确定field且进行操作,支持在一个命令中对多个field执行不同的操作。如从位偏移1000开始,对5位的无符号整数进行读取、设置、增加、减少并可设置上溢下溢行为。
为什么提供该指令
出于高效使用内存的目的,可以将很多小整数存储在一个大bitmap中,这样也避免出现大批key。
bit顺序
- offset是从左向右计数,左侧第一个bit为offset-0.
- offset和宽度确定field后,该field左为高位,右为低位。
子命令
- GET <type> <offset> -- 返回特定宽度的 bit field
- SET <type> <offset> <value> -- 设置特定宽度 bit field,返回旧数据.
- INCRBY <type> <offset> <increment> --针对特定宽度bit field进行增加或减少操作,返回最新数据.
- OVERFLOW [WRAP|SAT|FAIL] 仅对紧跟其后的一个INCRBY子命令其作用,指定溢出的处理方式
类型
- 使用i表示有符号整数,u表示无符号整数,后边使用数字表示占用bit位数,如i6 6bit有符号整数,u2 2bit无符号整数。
- redis仅支持最多64bit有符号整数,最多63bit无符号整数(redis无法复制64bit无符号整数).
offset
- 无前缀整数,表示based-0的bit偏移量,如1000,表示偏移量为1000的bit
- 前缀#,表示offset=bit宽度乘以#后的数字, i8 #0 表示offset=8*0, i8 #i表示offset=8*1。当使用string val的存储整数数组数据时,可以使用该方式#后为元素index。
OVERFLOW溢出
允许用户针对增加或减少操作,选择溢出的处理方式
wrap 环绕式,默认方式
无符号整数,对溢出进行取模处理,如u2最大整数个数为2^2=4,当前数据为3,则加1后为4,溢出采用4%4,则结果为0
有符号整数,对溢出的处理为在正max和负min之间变化,如i8,当前为正max127,加1后变为负min-128,之后再加1,变为正max127
SAT 饱和式
当增加时,上限为正max,如i8当前120,加10,结果为上限127,继续增加结果不变
当减少时,下限为负min
FAIL 操作失败式
溢出时不对数据进行任何操作,仅返回NULL以告知client发生溢出
每个overflow命令仅对紧跟overflow之后一个increby命令起作用,未指定overflow,则默认使用wrap方式。
举例
> BITFIELD mykey incrby u2 100 1 OVERFLOW SAT incrby u2 102 1
1) (integer) 1
2) (integer) 1
> BITFIELD mykey incrby u2 100 1 OVERFLOW SAT incrby u2 102 1
1) (integer) 2
2) (integer) 2
> BITFIELD mykey incrby u2 100 1 OVERFLOW SAT incrby u2 102 1
1) (integer) 3
2) (integer) 3
> BITFIELD mykey incrby u2 100 1 OVERFLOW SAT incrby u2 102 1
1) (integer) 0 // 第一个incryby默认使用wrap溢出模式
2) (integer) 3
性能
bitfield一般情况是很高效的,如果在短string定位很远的bit,将导致内存分配。
BITOP
BITOP operation destkey key [key ...]
O(N)
since 2.6.0
对多个source key进行位操作,如AND、OR、XOR、NOT(仅支持一个source key),并将结果存储到destkey。
举例
BITOP AND destkey srckey1 srckey2 srckey3 ... srckeyN
BITOP OR destkey srckey1 srckey2 srckey3 ... srckeyN
BITOP XOR destkey srckey1 srckey2 srckey3 ... srckeyN
BITOP NOT destkey srckey
长短不一的string如何处理
以最长string长度为准,短string使用0补位。
对于不存在的key,视为所有位均为0
返回dest key对应string val的字符长度
使用bitmap进行实时计算
性能
如果命令中source key数量很多、key对应val很长时,会成为耗时操作。
为了避免阻塞master server,可以将read-only配置关闭,使用salve node处理。
BITPOS
BITPOS key bit(1or0) [byteStartIndex] [byteEndIndex]
O(N)
since 2.8.7
将key对应string val视为bit数组,在字节index范围内定位第一个为1或0的bit,返回从offset-0开始的绝对位置。
关于bit数组,从左向右based-0计数bit offset,第0个字节为第0-7bit,第二字节为第8-15bit,依次类推;字节index支持负数,-1表示最后一个字节。
返回值
- 若定位bit 1,但string val为empty或所有位为0,则返回-1
- 若定位bit 0,但string val所有位均为1且不指定endIndex,该命令认为val右侧均为0,,所以返回val之后的第一bit的offset
set testK1 '\xff'
"OK"
bitpos testK1 0
8 // 0-7bit均为1,则返回8
- 若定位bit 0,但string val所有位均为1且指定start和endIndex,返回-1
set testK1 '\xff'
"OK"
bitpos testK1 0 0 0
-1
SETBIT
SETBIT key offset value(1or0)
O(1)
将string val视为bit数组,设置offset index的bit数据,返回bit的原始数据。
offset必须位于[0,2^32),即bitmap最大为512M
key不存在则创建key和满足offset的val。如offset为2^32-1,则将分配完整的内存空间,即使存储的数据很小。
GETBIT key offset
O(1)
将string val视为bit数组,返回offset index的bit数据。key不存在或offset越界返回0
将val视为数字
DECR key
O(1)
将key对应string val减一,返回最新val。
如果val类型不对或string无法表示为64bit有符号整数,则error
如果key不存在,则创建key,val初始值为0
INCR key
O(1)
将key对应string val加一,返回最新val。
如果val类型不对或string无法表示为64bit有符号整数,则error
如果key不存在,则创建key,val初始值为0
场景
计数器counter
针对某个维度,进行计数
- 统计每个用户每天的访问量
使用用户ID和日期作为key,用户每次请求则加一
- INCR和EXPIRE组合
如最近时间段内pv
- INCR和GETSET
使用GETSET获取当前counter值并重置
- INCR INCRBY DECR DECRBY
数据可以幅度变化,如游戏中用户分数
限制器
仍然是计数器,但是用来限制操作的执行次数。
如限制每个IP每秒钟最多访问10次,以下是redis官网的实现,个人认为在高并发时无法准确限制。
- 每秒每IP维度提供一个计数器并设置超时
FUNCTION LIMIT_API_CALL(ip)
ts = CURRENT_UNIX_TIME()
keyname = ip+":"+ts
current = GET(keyname)
IF current != NULL AND current > 10 THEN
ERROR "too many requests per second"
ELSE
MULTI
INCR(keyname,1)
EXPIRE(keyname,10)
EXEC
PERFORM_API_CALL()
END
事务形式保证一起执行
- IP维护的计数器并设置超时,不可采用
FUNCTION LIMIT_API_CALL(ip):
current = GET(ip)
IF current != NULL AND current > 10 THEN
ERROR "too many requests per second"
ELSE
value = INCR(ip)
IF value == 1 THEN
EXPIRE(ip,1)
END
PERFORM_API_CALL()
END
问题在于EXPIRE可能未执行,导致无法超时
- IP维度的队列并设置超时
FUNCTION LIMIT_API_CALL(ip)
current = LLEN(ip)
IF current > 10 THEN
ERROR "too many requests per second"
ELSE
IF EXISTS(ip) == FALSE
MULTI
RPUSH(ip,ip)
EXPIRE(ip,1)
EXEC
ELSE
RPUSHX(ip,ip)
END
PERFORM_API_CALL()
END
使用list达到计数器的目的,使用事务保证RPUSH和EXPIRE都被执行
- 思考如何准确限流
为了达到准确限制的目标,必然要求 获取当前值、验证、递增 3个流程在并发情况下同步执行。redis单线程已经保证指令同步执行,那如何保证执行一个请求的多条指令时不被其他请求的指令穿插呢?
我选择使用lua脚本。
local limit = redis.call('get', KEYS[1]); if ( limit == false or ARGV[1] - limit > 0 ) then local current = redis.call('incr',KEYS[1]) redis.call('expire',KEYS[1],ARGV[2]) return current else return -1 end
使用redis client执行eval script 1 key limitMax expireSeconds
关于性能差异,该脚本ttl与get指令ttl相差很少。
INCRBY key increment
INCRBYFLOAT
INCRBYFLOAT key increment
DECRBY key decrement
将val视为字符串
SET
SET key value [expiration EX seconds|PX milliseconds] [NX|XX]
设置key和string val,同时提供排他或仅更新、超时的可选项
选项
EX seconds -- Set the specified expire time, in seconds.
PX milliseconds -- Set the specified expire time, in milliseconds.
NX -- Only set the key if it does not already exist.
XX -- Only set the key if it already exist.
如果设置成功返回OK,如果由于NX或XX限制导致没有执行成功则返回null
排它锁
set key val NX EX seconds
- val数据选择随机方式生成
- 验证val相等时才可执行DEL
- 建议使用eval执行如下脚本
eval " if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('DEL', KEYS[1]) else return 0 end" 1 具体key 具体随机val
GET key
O(1)
返回key对应的string val
如果key 不存在则返回null,如果val不是string类型则error
APPEND
APPEND key value
O(1)
在原string val基础上,追加string。若key不存在则新建。
返回追加后val的长度
SETRANGE
SETRANGE key offset value
从offset开始,对参数value长度的数据进行覆盖
如果offset超过val长度或key不存在则新建key,与offset的间距使用zero-byte进行补全。
redis> SETRANGE key2 6 "Redis" // key2不存在时
(integer) 11
redis> GET key2
"\u0000\u0000\u0000\u0000\u0000\u0000Redis"
返回修改后的val长度
场景:高效的随机访问string数组
使用SETRANGE和GETRANGE命令,可以将string作为高效的随机访问string数组,且具有O(1)时间复杂度。
GETRANGE
GETRANGE key start end
O(N) N:返回string的长度
返回key对应string val的子字符串,start和end可以为负,越界则使用边界。
STRLEN key
返回string val的长度
GETSET key value
O(1)
原子性操作,对key设置新string val,并返回旧string val。若key对应类型不是string则error。若key不存在则null
GETSET和INCR组合使用,对事件发生次数进行计数,某种情况时获取计数并重置为0.
MGET
MGET key [key ...]
以数组形式返回多个key的stirng val,当val不是string类型或key不存在,在返回数组对应位置返回null
MSET
MSET key value [key value ...]
原子性操作,保证设置或更新多个key的val都被执行
返回OK
MSETNX
MSETNX key value [key value ...]
原子性操作,保证设置多个key的val都被执行,不会更新已存在key的val。
如果没有重复key,都是新建key和val,则返回1
如果至少存在一个key,则什么也不做,返回0
PSETEX
PSETEX key milliseconds value
原子性指令,同时设置val与超时,单位毫秒,但不具有排他性
SETEX
SETEX key seconds value
原子性指令,同时设置val与超时,单位秒,但不具有排他性
SETNX
SETNX key value
仅当key不存在时才执行操作
如果key不存在,则执行且返回1
如果key存在,则不执行操作,返回0