基本数据结构
通用命令
-
keys [pattern]
遍历出所有的key,可以根据正则表达式匹配 -
dbsize
统计key的数量 -
exists [key]
检查key是否存在 -
del [key]
删除key -
expire [key] [seconds]
设置key在指定的时间后过期 -
ttl [key]
查看key过期时间 -
persist [key]
设置永不过期?
-
type key
查看key的类型
字符串类型(string)
数据结构
? 字符串类型的value不能大于512M,key和value其实存储的都是二进制数据。String类型是二进制安全的。意思是redis的string可以包含任何数据。比如jpg图片或者序列化的对象 。
主要命令
get [key] # 获取
set [key] [value] # 设置值
del [key] # 删除key
----------------------------------------
incr [key] # 自增1,如果key不存在则创建并自增
decr [key] # 自减1
incrby [key] [k] # 自增k
decrby [key] [k] # 自减k
----------------------------------------
set [key] [value] # 不管key是否存在都会执行成功
setnx [key] [value] # key不存在时才成功
setxx [key] [value] # key存在时则成功
----------------------------------------
mset key1 [value1 key2 value2 ....keyn valuen] #批量设置 (若有多次的处理操作可以放入一次IO中,节省网络IO时间)
mget [key1 key2 key3...keyn] # 批量获取
----------------------------------------
getset [key] [newvalue] # 给key设置一个新值并将旧值返回,操作是原子的
append [key] [value] # 追加值到后面
strlen [key] # 字符串的长度
----------------------------------------
incrbyfloat [key] [value] # 对数据做浮点数处理
getrange [key] [startIndex] [endIndex] # 获取字符串索引从startIndex到endIndex的值
setrange [key] [index] [value]# 设置字符串索引index位置的值
使用场景:
- 适合存储热点数据,如视频信息,列表,热点数据的json串。
- 由于字符串类型可以实现自增且配合单线程的线程安全很适合用于做计数器。如分布式自增id
哈希类型(hash)
数据结构:
? key看作是数据库中的一行的索引,而filed和value就是数据库中的属性。可以看成从redis中获取一个Map。
? 主要命令
hget [key] [field] # 从key对应的map中获取键为field的值
hset [key] [field] [value] # 从key对应的map中设置键为field的值
hdel [key] [field ] # 删除key对应的map中的field的键
----------------------------------------
hexists [key] [field] # 查询key对应的map中是否有field对应的键
hlen [key] # 判断map有多少条键值对
----------------------------------------
hmget [key] [field1...fieldn] # 批量获取map中的filed域
hmset [key] [field1 value1.....fieldn valuen] # 批量设置
----------------------------------------
hgetall [key] # 返回所有的key-value 谨慎使用
hvals [key] # 返回所有的value
hkeys [key] # 返回所有的field
----------------------------------------
hincrby [key] [field] # 自增
hincrbyfloat [key] [field] [value] # 自增浮点数
hsetnx [key] [field] [value] # key不存在时才设置
使用场景:
- 适合作为存储对象的介质,比字符串类型更好去处理每个字段的值
- 由于hash数据类型的key-value的特性,用来存储关系型数据库中表记录,是redis中哈希类型最常用的场景。一条记录作为一个key-value,把每列属性值对应成field-value存储在哈希表当中,然后通过key值来区分表当中的主键
列表(list)
数据结构:
特性:
- 有序
- 可以重复
- 可以从左右弹出或者压入
主要命令
----------------------------------------增
rpush [key] [value1....valuen] # 从队列右边压入数据
lpush [key] [value1....valuen] # 从队列左边压入数据
linsert [key] [before|after] [value] [nevalue] # 在给定list指定值的前或后插入新值
----------------------------------------删
lpop [key] # 左边弹出一个元素
rpop [key] # 右边弹出一个元素
lrem [key] [count] [value] # 删除value为给定值的数据,删除给定数量的值
ltrim [key] [star]t [end] # 按照给定索引范围修剪列表
----------------------------------------改
lset [key] [index] [newValue] # 设置指定索引的值为newValue
----------------------------------------查
lrange [key] [start] [end] # 获取指定范围的所有数据
lindex [key] [index] #获取指定索引的值
llen [key] #获取列表长度
----------------------------------------不常用
blpop|brpop [key] [timeout] # lpop的阻塞版本,timeout=0永远不阻塞。用作消息队列时好用
使用场景:
- 微博关注人发布消息列表的时间轴就可以通过redis的列表来实现,通过lpush将新发布的消息插入到列表中可以实现消息的时间排列
- 可以用list模拟出消息队列的功能,还能模拟栈的功能
- LPUSH + LPOP = Stack
- LPUSH + RPOP = Queue
- LPUSH + BRPOP = Message Queue
集合(set)
数据结构
特点:
- 无序
- 无重复
- 支持集合间操作
集合内API
sadd [key] [element] # 向集合中添加element,可以插入多个
srem [key] [element] # 删除
scard [key] # 计算集合大小
sismember [key] [element] # 判断是否在集合中
srandmember [key] [count] # 随机取出count个元素
spop [key] # 随机弹出一个元素
smembers [key] #获取所有元素
集合间API
sdiff [key1] [key2] # 差集
sinter[key1] [key2] # 交集
sunion [key1] [key2] # 并集
使用场景
- 标签-用户系统中可以使用set来实现,用户对应的set可以sadd插入不同的标签,标签set中可以sadd中不同的用户
- 抽奖系统可以通过set的
spop
或者srandmember
实现 - 可以实现共同关注的功能,通过集合间的操作如
sinter
有序集合(zset)
数据结构
重要API
---------------------------------------- 基本操作
zadd [key] [score] [element] # 添加元素,可以插入多个
zrem [key] [element] # 删除元素,可以多个
zscore [key] [element] # 获取元素的分数
---------------------------------------- 范围操作
zincrby [key] [incrScore] [element] # 自增元素的score
zcard [key] # 元素个数
zrange [key] [start] [end] # 返回指定索引范围内的顺序元素
zrangebyscore [key] [minScore] [maxScore] # 返回指定分数期间的升序元素
zcountbyscore [key] [minScore] [maxScore] # 获取指定分数区间的元素的个数
zremrangebyrank [key] [start] [end] # 删除给定排序索引范围的元素
zrevrank # zrev前缀的代表从大到小排序
zrevrange
zrevrangebyscore
---------------------------------------- 集合操作
zunionstore # 并集
zinterstore # 交集
使用场景:
- 排行榜,如音乐排行榜,新书榜可以使用有序队列来实现。
高级特性
pipline 管道
未使用pipeline:
使用pipeline:
? pipeline时间一系列操作通过一次网络IO传送过去并执行,减少网络IO带来的开销。redis处理一行命令的时间大概是微秒级别的,但是网络延迟可能是十几毫秒这样就大大的浪费了redis高速运行的优势,所以pipeline可以优化网络延迟带来的开销。
不具备原子性
传统的命令都是具有原子性的,而pipeline命令保护了多个命令时,可能在命令处理的间隙会插入其他的命令进来。
传统的命令执行:
pipeline命令:
注意:
- 注意每次pipeline携带的数据量
- pipeline每次只能作用在一个redis节点上。
在Jedis中使用pipeline
/**
* 删除多个字符串key 并释放连接
*
* @param keys*
* @return 成功返回value 失败返回null
*/
public boolean mdel(List<String> keys) {
Jedis jedis = null;
boolean flag = false;
try {
jedis = pool.getResource();//从连接借用Jedis对象
Pipeline pipe = jedis.pipelined();//获取jedis对象的pipeline对象
for(String key:keys){
pipe.del(key); //将多个key放入pipe删除指令中
}
pipe.sync(); //执行命令,完全此时pipeline对象的远程调用
flag = true;
} catch (Exception e) {
pool.returnBrokenResource(jedis);
e.printStackTrace();
} finally {
returnResource(pool, jedis);
}
return flag;
}
事务
? Redis 事务的本质是一组命令的集合。事务支持一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中。
总结说:redis事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令。
特性
- 没有隔离级别:批量操作在没有发送
EXEC
之前只是放入队列中并没有执行。 - 不保证原子性,任意指令失败也不会回滚
- 只是会在执行前检查一下语法错误,运行中的错误不会回滚
相关命令
- watch [key1] ... [key2],监视给定的key,如果在事务启动前被其他命令修改则事务被打断
- multi 标记一个事务块的开始
- exec 执行所有的事务块的命令(watch被取消)
- discard 取消事务
- unwatch 取消watch
正常事务:
放弃事务:
错误时的场景:
- 当有指令编译性错误时会将所有的执行队列中的命令都discard掉,全体失败
- 当有运行时错误时,只失败那一条指令
pub/sub
角色
-
发布者
-
订阅者
-
频道
发布者发布到某个频道上去,订阅者通过订阅功能订阅频道就能收到发布者的发布的信息。
模型:
命令
publish [channel] [message] # 发布消息到指定的频道,返回的是订阅者数量
subscribe [channel] # 订阅频道
unsubscribe [channel] # 取消订阅
和消息队列对比
? 在消息队列中,发送了一条消息只能被一个订阅者给抢到,而发布订阅模式中一条消息发送到频道中能被所有的订阅者给获取到。如果要做通知功能则pub/sub比消息队列合适,如果要做抢红包等功能时用消息队列更合适。
bitmap
? 在平时的开发中,对于一些大量的布尔变量如果用字符串类型那么太浪费内存空间了,可以尝试使用位图的功能,位图实际上也是字符串但是他每一位都可以用来表示一个bool变量,这样一个字节就能当成8个字节来使用。
命令
Redis 的位数组是自动扩展,如果设置了某个偏移位置超出了现有的内容范围,就会自动将位数组进行零扩充。
setbit [key] [offset] [0|1] # 设置位图
getbit [key] [offset] # 获取偏移量对应的值
bitcount [key] # 统计为1的位数的个数
bitop [and|or|not|xor] destkey [key] [key1...keyn] # 多个位图做操作后的结果存入destkey中
bitpos [key] [0|1] [start] [end] # 计算位图范围内第一个等于0|1的数所在的位置
使用场景
? 场景就是用来存储状态值,如存储用户的每天登陆情况。可以将用户的userid作为key,日期作为偏移量,登陆时就置1。可以通过bitcount
geoHash
? 将二维的地址坐标转换成一维的变量,通过ZSET的有序集合可以实现从远到近的排序功能。
原理
? 经纬度范围:东经180度到西经180度,纬度是南纬90度到北纬90度。设定:西经为负,南纬为负。那么用数学表示就是经度:[-180,180],纬度:[-90,90],以子午线和赤道平分地球就是如下图:
? 那么按照象限划分分别是:11,01,00,10。再精确一步在每个象限中再去划分:
这样划分的约多次,每一个子象限就越小,越小的子象限就越能精确的描述位置,所以位置描述的经度是和划分的次数相关。
示例
给定一个点(39.923201, 116.390705):采用二分法得到以下的列表
最后得出维度的二进制为
10111000110001111001
同理得出维度的二进制
11010010110001000100
经纬度合并 (经读在偶数位 纬度在奇数位)
11100 11101 00100 01111 00000 01101 01011 00001
再将二进制数据用Base32转换为最终值:
wx4g0ec1
在redis中,会分别对经度和维度进行26次切分,最后融合经纬度得到一个52位的比特位,通过base32进行处理,得到一个11为长度的字符串,
将52位比特的数值当做score插入到zset中,之后的操作就跟操作zset类似了
命令
geo add key 经度 维度 value 将key对应的value的经纬度存入geo中
geodis key value1 value2 km 获得key中的value1和value2的距离 (距离单位可以是 m、km、ml、ft,分别代表米、千米、英里和尺)
geops key value 获取key-value的经纬度坐标
geohash key value base32的编码字符串
georadiusbymember key value 距离 count N asc 查询距离key-value在某个范围内的元素正排序
georadiusbymember key value 20 km **withcoord withdist withhash** count 3 asc **withcoord withdist withhash附加的参数 withdist 为距离**
EXPIRE key seconds //将key的生存时间设置为ttl秒
PEXPIRE key milliseconds //将key的生成时间设置为ttl毫秒
EXPIREAT key timestamp //将key的过期时间设置为timestamp所代表的的秒数的时间戳
PEXPIREAT key milliseconds-timestamp //将key的过期时间设置为timestamp所代表的的毫秒数的时间戳
特点
- 用一个字符来代替精度和纬度,在数据库上可以实现一列上应用索引
- GeoHash不是一个点,而是一个区域
- 在redis中使用的话,最好只用单机redis来存储geohash数据
- 编码越长表示的范围约小,就越精确
适用场景
? QQ附件的人功能,APP上关于地理位置的列表都可以通过geohash来实现。
hyperloglog
? HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且是很小的。在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基数。和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。
- HyperLoglog 是一种基于概率的算法并不是redis独占的
- 用于做基数统计
- 空间损耗小
- 值具有少量误差(0.81%)
- 无法取出单条数据
原理
? 基数
? 基数就是指一个集合中不同值的数目,比如[a,b,c,d]的基数就是4,[a,b,c,d,a]的基数还是4,因为a重复了一个,不算。基数也可以称之为Distinct Value,简称DV。
? 伯努利实验
? 起源的本质是来自于生活中的抛硬币的发现,称作伯努利实验:硬币拥有正反两面,一次的上抛至落下,最终出现正反面的概率都是50%。假设一直抛硬币,直到它出现正面为止,我们记录为一次完整的试验,间中可能抛了一次就出现了正面,也可能抛了4次才出现正面。无论抛了多少次,只要出现了正面,就记录为一次试验。这个试验就是伯努利试验
。
那么对于多次的伯努利试验
,假设这个多次为n
次。就意味着出现了n
次的正面。假设每次伯努利试验
所经历了的抛掷次数为k
。第一次伯努利试验
,次数设为k1
,以此类推,第n
次对应的是kn
。
其中,对于这n
次伯努利试验
中,必然会有一个最大的抛掷次数k
,例如抛了12次才出现正面,那么称这个为k_max
,代表抛了最多的次数。
伯努利试验
容易得出有以下结论:
- n 次伯努利过程的投掷次数都不大于 k_max。
- n 次伯努利过程,至少有一次投掷次数等于 k_max
最终结合极大似然估算的方法,发现在n
和k_max
中存在估算关联:$n = 2^(k_max) $。这种通过局部信息预估整体数据流特性的方法似乎有些超出我们的基本认知,需要用概率和统计的方法才能推导和验证这种关联关系。
? 简单的想法
? 对于集合{a,b}分别对两个元素进行hash得到二进制数据:00110111,10010000111,从左到右最近的一个1的位置分别为3和0,我们只保留最大值,根据伯努利公式:n=2^3=8,显然这个算法是不准确的需要进一步优化。
? 数据分桶
? 将数据分成m等份后分别估算每个桶的值后取得平均值后再乘以m,具体分桶的步骤:
? 分为两桶,那么就会拿到二进制数字第一位来做判断,0进桶1 ,1进桶2。这样00110111会进入桶1中计算出估值为kmax=2,10010000111估算出kmax=3,通过公式:\(DV_{LL} = constant * m * 2^R\),所以上面演示的这个集合的值就等同于\(DV_{LL} = 2 * 2^{(2+3)/2}\)= 11.313,平均数任意收到最大数的影响,所以一般要采用调和平均数或算数平均数来解决这一问题。,而常数量constant是根据《HyperLogLog: the analysis of a near-optimal cardinality estimation algorithm》论文中得出的参数进行设置的。
命令
pfadd [key] [value....]# 添加元素
pfcount [key] # 获得基数值(去重)
pfmerage [key .. keyn]# 合并key
适用场景
? 一般用在数据量极大的统计上,并且这个统计能够容忍细微的误差,比如在线人数,注册人数,访问人数。
布隆过滤器
? 布隆过滤器实际上就是一个Bit数组,本质上是一个数据,可以根据下标快速找到数据。
通过hash函数对要记录的数据进行hash操作,得到BIT数组并将redis中的数组对应的标志位也置为1,布隆过滤器的长度会影响误报率,长度越长错误率越低。布隆过滤器也可以通过bitmap和编程来实现布隆过滤器的功能。
将一对KEY-VALUE插入到布隆过滤器中:
- 由多种HASH算法将KEY计算为一个整数的索引
- 将索引跟布隆中的容量数组取模,得到该key落在数组的位置
- 多个HASH算法算出多个位置,将数组这些位置都标为1
使用exists时的过程:
- 用多种hash算法将此key的所有可能落点都计算出来
- 判断是否所有落点都为1,如果是则存在 如果不全是1则为不存在
错误率计算
F
为错误率 L
为数组长度(bit位)N
为预计的元素数量 K
为hash函数的数量
\(K = 0.7 * (L/N)\)
\(F = 0.6187^{L/N}\)
由此可见 错误率和容量成反比关系,容量设置的越大则错误率越低需要的hash函数数目越少
如果设置的过大则会浪费内存空间,如果设置的容量越小则错误率越大,而且需要hash函数的过程变多,使得计算效率下降
大致的看
- 错误率为10% 时,一个元素需要4.79个bit 近似看成5比特
- 错误率为1%时,一个元素需要9.58个bit 近似看成10比特
- 错误率为0.1%时,一个元素需要14.38个bit 近似看成15比特
相当于SET来说,布隆只存储key的“指纹信息”,而SET会将字符串或者对象序列化存储进redis,元素的内容通常几十到上百个字节,而布隆只有15位左右也就是2个字节,优势相当大
实例
向redis的布隆过滤器中插入“baidu”,8为bit,三次hash后得到为1的位数为1,4,7。
再向redis中插入“tencent”,可以看到4这个位置发生了hash碰撞,所以长度过短造成碰撞的次数越多误差率越高。
特性
- 可以设置精确度,当bit数组位数越长时精度越高。但是达不到百分百精度。
- 只能存入,无法删除。
- 布隆过滤器说不存在一定不存在,存在可能是误判。
命令
1. bf.add [key] [value] # 加入
2. bf.exists [key] [value] # 判断是否存在
3. bf..madd [key] [value1] [value2] [value3] # 批量添加
4. bf.mexists [key] [value1] [value2] [value3] # 批量判断是否存在
适用场景
- 社交软件上推送新闻或者视频的时候都可以通过布隆过滤器去判断视频时候已经被推送过,这样就能推送没有看过的新闻
- 在高并发的情况下,可以让redis和数据库做同步,查询先通过redis的布隆过滤器,如果通过布隆过滤器则再去查询对应的值。