Redis的数据结构概述
redis是单线程,一次只执行一条命令,那为什么可以这么快:
- 纯内存
- 非阻塞IO
- 避免线程切换和竞态消耗
在使用过程中要注意:
- 一次只运行一条命令
- 避免长(慢)命令,例如keys、flushall、flushdb、slow sua script、multi/exec、operate big value(collection)
- redis在 fsync file descriptior、close file descriptor时会有独立的线程来执行
Redis的5种数据结构
1、string 字符串
字符串的键值结构
string类型的value值可以是字符串、int、二进制、或者一些json、xml、object序列化的字符串。
字符串的类型不能大于512MB。
字符串的常用命令
- get key
获取key的value值,时间复杂度为O(1)
- set key value
设置key的值为value, 时间复杂度为O(1)
- del key
删除key,时间复杂度为O(1)
127.0.0.1:6379> set hello world
OK
127.0.0.1:6379> get hell
(nil)
127.0.0.1:6379> get hello
"world"
127.0.0.1:6379> del hello
(integer) 1
127.0.0.1:6379> get hello
(nil)
set命令还有一些参数,例如
setnx key value,是key不存在时才能设置
set key value xx,是key存在时才能设置
setex key seconds value, 设置key的value值同时设置过期时间
127.0.0.1:6379> exists php
(integer) 0
127.0.0.1:6379> set php good
OK
127.0.0.1:6379> setnx php bad
(integer) 0
# php已经存在,不能修改
127.0.0.1:6379> get php
"good"
127.0.0.1:6379> set php bad xx
OK
127.0.0.1:6379> get php
"bad"
127.0.0.1:6379> setex php 20 good
OK
127.0.0.1:6379> ttl php
(integer) 15
127.0.0.1:6379> get php
(nil)
get、set命令还有一些批量操作mget、mset,
mget key1 key2 key3..., 批量获取key,原子操作,时间复杂度为O(n)
mset key1 value1 key2 value2 key3 value3...,批量设置key,原子操作,时间复杂度为O(n)
127.0.0.1:6379> mset test1 value1 test2 value2 test3 value3
OK
127.0.0.1:6379> mget test1 test2 test3
1) "value1"
2) "value2"
3) "value3"
如果是n次get,那么网络时间是很大的开销,使用mget可以批量地将命令传输给redis,减少网络时间。
当网络时间越长,或者命令越多,效果月明显,当然也不能无限制的使用,命令如果太多,可以拆分几次执行。
- getset key newvalue
set key newvalue并返回旧的value。时间复杂度为O(1)。
- append key value
将value追加到旧的value中。时间复杂度为O(1)。
- strlen key
返回value的长度,注意中文,一个中文占用3个字节。时间复杂度为O(1)。
127.0.0.1:6379> set hello world
OK
127.0.0.1:6379> getset hello php
"world"
127.0.0.1:6379> get hello
"php"
127.0.0.1:6379> append hello ",java"
(integer) 8
127.0.0.1:6379> get hello
"php,java"
127.0.0.1:6379> strlen hello
(integer) 8
127.0.0.1:6379> set hello "你好"
OK
127.0.0.1:6379> strlen hello
(integer) 6
- incr key
key自增1,如果key不存在,则自增后get(key)=1。时间复杂度为O(1)。
- decr key
key自减1,如果key不存在,则自减后get(key)=-1。时间复杂度为O(1)。
- incrby key k
key自增k,如果key不存在,则自增后get(key)=k。时间复杂度为O(1)。
- decrby key k
key自减k,如果key不存在,则自减后get(key)=-k。时间复杂度为O(1)。
127.0.0.1:6379> get counter
(nil)
127.0.0.1:6379> incr counter
(integer) 1
127.0.0.1:6379> get counter
"1"
127.0.0.1:6379> incrby counter 99
(integer) 100
127.0.0.1:6379> get counter
"100"
127.0.0.1:6379> decr counter
(integer) 99
127.0.0.1:6379> get counter
"99"
127.0.0.1:6379> decrby counter 10
(integer) 89
如果数据不是int类型,会提示“(error) ERR value is not an integer or out of range”
- incrbyfloat key k
key增加float类型k值,如果key不存在,则自增后get(key)=1。时间复杂度为O(1)。
- getrange key start end
获取key指定start到end下标的所有值
- setrange key index value
设置key的下标index所对应的值
127.0.0.1:6379> incr counter
(integer) 1
127.0.0.1:6379> incrbyfloat counter 3.8
"4.8"
127.0.0.1:6379> get counter
"4.8"
127.0.0.1:6379> set test1 testvalue
OK
127.0.0.1:6379> getrange test1 0 4
"testv"
127.0.0.1:6379> setrange test1 2 ave
(integer) 9
127.0.0.1:6379> get test1
"teavealue"
字符串的使用场景
字符串数据结构常用来缓存、计数器和分布式锁等应用场景中。
在一些实战中的应用场景:
1.记录网站每个用户个人主页的访问量
incr userid:pageview
# 例如用户的userid为123456
incr 1234:pageview
2.缓存视频基础信息(数据源在mysql中)
# 伪代码如下
public VideoInfo get(long id){
String redisKey = redisPrefix + id;
VideoInfo videoInfo = redis.get(redisKey);
if(videoInfo == null){
videoInfo = mysql.get(id);
if(videoInfo != null){
//序列化
redis.set(redisKey, serialize(videoInfo))
}
}
return videoInfo;
}
3.分布式id生成器
多个应用并发获取ID,ID不会重复完全自增。可能在实际中会更为复杂,但这是基础思路
incr id(原子操作)
2、hash 哈希
hash的键值结构
可以单独添加、更新、删除field
注意field是不能相同的,但value可以相同
hash的常用命令
- hget key field
获取hash key对应的field的value。时间复杂度为O(1)。
- hset key field value
设置hash key对应的field的value值。时间复杂度为O(1)。
- hdel key field
删除hash key对应的field。时间复杂度为O(1)。
127.0.0.1:6379> hset user:1:info name nancy
(integer) 1
127.0.0.1:6379> hset user:1:info age 18
(integer) 1
127.0.0.1:6379> hgetall user:1:info
1) "name"
2) "nancy"
3) "age"
4) "18"
127.0.0.1:6379> hdel user:1:info age
(integer) 1
127.0.0.1:6379> hgetall user:1:info
1) "name"
2) "nancy"
- hexists key field
判断hash key是否有field
- hlen key
获取hash key的数量
127.0.0.1:6379> hgetall user:1:info
1) "name"
2) "nancy"
127.0.0.1:6379> hexists user:1:info name
(integer) 1
127.0.0.1:6379> hlen user:1:info
(integer) 1
- hmget key field1 field2 ... fieldN
批量获取hash key的一批field值。时间复杂度为O(n)。
- hmset key field1 value1 field2 value2 ... fieldN valueN
批量设置hash key的一批field值。时间复杂度为O(n)。
127.0.0.1:6379> hmset user:1:info name nancy age 18 birth 2002
OK
127.0.0.1:6379> hlen user:1:info
(integer) 3
127.0.0.1:6379> hmget user:1:info name age
1) "nancy"
2) "18"
- hgetall key
返回hash key的所有field和value。时间复杂度为O(n)。要小心使用hgetall,如果hash key存储很多的属性
- hvals key
返回hash key对应所有field的value值。时间复杂度为O(n)。
- hkeys key
返回hash key对应的所有field。时间复杂度为O(n)。
127.0.0.1:6379> hgetall user:1:info
1) "name"
2) "nancy"
3) "age"
4) "18"
5) "birth"
6) "2002"
127.0.0.1:6379> hvals user:1:info
1) "nancy"
2) "18"
3) "2002"
127.0.0.1:6379> hkeys user:1:info
1) "name"
2) "age"
3) "birth"
- hsetnx key field value
设置hash key对应的field的value值,如果field已经存在,则失败。时间复杂度为O(1)。
- hincrby key field intCount
hash key对应的field的value值自增intCount。时间复杂度为O(1)。
- hincrbyfloat key field floatCount
hash key对应的field的value值自增floatCount。时间复杂度为O(1)。
hash的使用场景
1.记录网站用户在个人主页中的访问量
hincrby user:1:info pageview count
string的数据结构也可以做,不过hash就将用户的相关信息作为一个整体,具体看实际的应用场景。
2.缓存视频基础信息(数据源在mysql中)
//伪代码如下
public VideoInfo get(long id){
String redisKey = redisPrefix + id;
Map<String, String> hashMap = redis.hgetAll(redisKey);
VideoInfo videoInfo = transferMapToVideoInfo(hashMap);
if(videoInfo == null){
videoInfo = mysql.get(id);
if(videoInfo != null){
redis.hmset(redisKey, transferVideoInfoToMap(videoInfo))
}
return videoInfo;
}
}
3、list 列表
list的结构特点
列表是有序的,value值是可重复,值可从左右两边插入弹出。
可以对列表进行以下的相关操作
list的常用命令
- rpush key value1 value2 ... valueN
从列表key右边插入值1~N个。时间复杂度为O(1~n)
- lpush key value1 value2 ... valueN
从列表key左边插入值1~N个。时间复杂度为O(1~n)
- linsert key before|after value newValue
在list指定的value值前|后插入newValue值。时间复杂度为O(n)
- lpop key
从列表左侧弹出一个item。时间复杂度为O(1)
- rpop key
从列表右侧弹出一个item。时间复杂度为O(1)
- lrem key count value
根据count的值,从列表中删除等于value的值。
当count>0,则从左到右,删除count个与value相同的项。
当count<0,则从右到左,删除Math.abs(count)个value相等的项。
当count=0,删除所有value相等的项。
时间复杂度为O(n)。
- ltrim key start end
按照索引范围修剪列表,只保留start、end要求的索引范围。时间复杂度为O(n)。
- lrange key start end
获取列表指定索引范围start到end的所有item。时间复杂度为O(n)。
- lindex key index
获取列表指定索引index的item。时间复杂度为O(n)。
- llen key
获取列表长度。时间复杂度为O(n)。
- lset key index newValue
设置列表指定索引值index为newValue。时间复杂度为O(n)。
127.0.0.1:6379> rpush mylist a b c
(integer) 3
127.0.0.1:6379> lrange mylist 0 -1
1) "a"
2) "b"
3) "c"
127.0.0.1:6379> lpush mylist 0
(integer) 4
127.0.0.1:6379> lrange mylist 0 -1
1) "0"
2) "a"
3) "b"
4) "c"
127.0.0.1:6379> rpop mylist
"c"
127.0.0.1:6379> lrange mylist 0 -1
1) "0"
2) "a"
3) "b"
- blpop key timeout
lpop的阻塞版本,一直等待这列表有元素再弹出。timeout是阻塞的超时时间,timeout=0为永远不阻塞。
- brpop key timeout
rpop的阻塞版本,一直等待这列表有元素再弹出。timeout是阻塞的超时时间,timeout=0为永远不阻塞。
list的使用场景
可以做一些时间线,例如要显示你关注的人更新微博,可以使用LPUSH
4、set 集合
集合的结构
集合的特点:
(1)集合是无序的。
(2)集合是不允许插入重复元素的。
(3)集合支持集合间的API交互。
集合常用的命令
- sadd key element
向集合key添加element,如果element存在,则添加失败。时间复杂度为O(1)。
- srem key element
将集合key的element元素移除掉。时间复杂度为O(1)。
- scard key
计算集合key的大小
- sismember key element
判断元素element是否在集合key中存在。
- srandmember key count
随机从集合key取出count个元素。不会破坏集合的数据
- spop key
从集合key中随机弹出一个元素。
- smembers key
取出集合key中的所有元素。这个命令要小心使用,如果集合中有非常多的元素,执行时间较长可能会阻塞redis。
127.0.0.1:6379> sadd user:1:follow hello world test1 test2
(integer) 4
127.0.0.1:6379> smembers user:1:follow
1) "test1"
2) "test2"
3) "world"
4) "hello"
127.0.0.1:6379> spop user:1:follow
"hello"
127.0.0.1:6379> smembers user:1:follow
1) "test1"
2) "test2"
3) "world"
127.0.0.1:6379> scard user:1:follow
(integer) 3
127.0.0.1:6379> sismember user:1:follow hello
(integer) 0
集合间的API
- sdiff key1 key2
集合key1和key2的差集。
- sinter key1 key2
集合key1和key2的交集。
- sunion key1 key2
集合key1和key2的并集。
- sdiffstore destkey key1 key2
将集合key1和key2差集的计算结果保存到集合destkey中。
- sinterstore destkey key1 key2
将集合key1和key2交集的计算结果保存到集合destkey中。
- sunionstore destkey key1 key2
将集合key1和key2并集的计算结果保存到集合destkey中。
127.0.0.1:6379> sadd user:1:follow it music his sport
(integer) 4
127.0.0.1:6379> sadd user:2:follow it news ent sport
(integer) 4
127.0.0.1:6379> sdiff user:1:follow user:2:follow
1) "music"
2) "his"
127.0.0.1:6379> sinter user:1:follow user:2:follow
1) "sport"
2) "it"
127.0.0.1:6379> sunion user:1:follow user:2:follow
1) "news"
2) "it"
3) "his"
4) "music"
5) "ent"
6) "sport"
127.0.0.1:6379> sunionstore follows user:1:follow user:2:follow
(integer) 6
127.0.0.1:6379> smembers follows
1) "news"
2) "it"
3) "his"
4) "music"
5) "ent"
6) "sport"
set的使用场景
1.抽奖系统。可以将抽奖人存在集合中,然后使用spop来随机弹出一个元素。
2.一些社交网站上的like、点赞、踩功能。
3.一些标签tag功能,譬如可以给用户添加标签,给标签添加用户
> sadd user:1:tag tag1 tag2 tag3...
> sadd user:2:tag tag2 tag3...
> sadd tag:1:users user1 user2...
> sadd tag:2:users user2 user3...
4.集合间的API应用场景,例如微博中的共同关注的好友,或者共同关注的兴趣。
应用场景总结起来有几个:
SADD 做一些标签场景
SPOP/SRANDMEMBER 做随机数相关场景
SADD + SINTER 做社交相关的场景
5、zset 有序集合
有序集合的结构
集合和有序集合的对比:
(1)两者都是没有重复元素的。
(2)集合是无序的,有序集合是有序的。
(3)集合只有元素element,有序集合有元素element和分值score。
有序集合的常用命令
- zadd key score1 element1 score2 element2 ...
添加scord和element。score和element可以是多对。时间复杂度为O(logN)。
- zrem key element(可以是多个)
删除1个或多个元素。时间复杂度为O(1)。
- zscore key element
返回元素element的分值score。时间复杂度为O(1)。
- zincrby key increScore element
增加或减少元素element的score分值。如果increScore为负数则为减少。时间复杂度为O(1)。
- zcard key
返回元素的总个数。
- zrank key element
获取元素element的从小到大升序的排名,排名从0开始算。
- zrevrank key element
获取元素element的从大到小降序的排名,排名从0开始算。
- zrange key start end [withscores]
返回有序集合key从索引为start到end的所有升序元素,如果带着withescore参数,则会将元素的分值也一同返回。时间复杂度为O(log(n)+m),n代表有序集合的元素个数,m代表要返回的元素个数。
- zrevrange key start end [withscores]
返回有序集合key从索引为start到end的所有降序元素
- zrangbyscore key minScore maxScore [withscores]
返回有序集合key指定分值范围minScore到maxScore范围内的升序元素。时间复杂度为O(log(n)+m)。
- zrevrangbyscore key maxScore minScore [withscores]
返回有序集合key指定分值范围maxScore到minScore范围内的降序元素。
- zcount key minScore maxScore
返回有序集合key指定分值范围minScore、maxScore内的个数。时间复杂度为O(log(n)+m)。
- zremrangebyrank key start end
删除有序集合key指定排名内的升序元素。时间复杂度为O(log(n)+m)。
- zremrangebyscore key minScore maxScore
删除有序集合key指定分值内的升序元素。时间复杂度为O(log(n)+m)。
- zinterstore destkey key1 key2
将有序集合key1和key2交集的计算结果保存到集合destkey中。
- zunionstore destkey key1 key2
将有序集合key1和key2并集的计算结果保存到集合destkey中。
127.0.0.1:6379> zadd user:1:score 100 nancy1 98 nancy2 70 nancy3 50 nancy4
(integer) 4
127.0.0.1:6379> zscore user:1:score nancy3
"70"
127.0.0.1:6379> zcard user:1:score
(integer) 4
127.0.0.1:6379> zrem user:1:score nancy2
(integer) 1
127.0.0.1:6379> zrank user:1:score nancy4
(integer) 0
127.0.0.1:6379> zrank user:1:score nancy3
(integer) 1
127.0.0.1:6379> zrange user:1:score 0 -1 withscores
1) "nancy4"
2) "50"
3) "nancy3"
4) "70"
5) "nancy1"
6) "100"
127.0.0.1:6379> zadd user:1:score 100 nancy1 98 nancy2 70 nancy3 50 nancy4
(integer) 1
127.0.0.1:6379> zrange user:1:score 0 -1
1) "nancy4"
2) "nancy3"
3) "nancy2"
4) "nancy1"
127.0.0.1:6379> zcount user:1:score 60 100
(integer) 3
127.0.0.1:6379> zremrangebyscore user:1:score 0 59
(integer) 1
127.0.0.1:6379> zrange user:1:score 0 -1
1) "nancy3"
2) "nancy2"
3) "nancy1"
127.0.0.1:6379> zremrangebyrank user:1:score 0 1
(integer) 2
127.0.0.1:6379> zrange user:1:score 0 -1
1) "nancy1"
zset的应用场景
1.排行榜。如音乐排行榜、销售排行榜,新书排行榜(用timestap作为score)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。