Redis的数据结构概述

image.png
image.png
redis是单线程,一次只执行一条命令,那为什么可以这么快:

  1. 纯内存
  2. 非阻塞IO
  3. 避免线程切换和竞态消耗

在使用过程中要注意:

  1. 一次只运行一条命令
  2. 避免长(慢)命令,例如keys、flushall、flushdb、slow sua script、multi/exec、operate big value(collection)
  3. redis在 fsync file descriptior、close file descriptor时会有独立的线程来执行

Redis的5种数据结构

1、string 字符串

字符串的键值结构
string类型的value值可以是字符串、int、二进制、或者一些json、xml、object序列化的字符串。
字符串的类型不能大于512MB
image.png
字符串的常用命令

  • 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"

image.png
如果是n次get,那么网络时间是很大的开销,使用mget可以批量地将命令传输给redis,减少网络时间。
image.png
当网络时间越长,或者命令越多,效果月明显,当然也不能无限制的使用,命令如果太多,可以拆分几次执行。

  • 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中)
image.png

# 伪代码如下
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不会重复完全自增。可能在实际中会更为复杂,但这是基础思路
image.png

incr id(原子操作)

2、hash 哈希

hash的键值结构
image.png
可以单独添加、更新、删除field
image.png
注意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的结构特点
image.png
列表是有序的,value值是可重复,值可从左右两边插入弹出。
可以对列表进行以下的相关操作
image.png
image.png

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
image.png

4、set 集合

集合的结构
image.png
集合的特点:
(1)集合是无序的。
(2)集合是不允许插入重复元素的。
(3)集合支持集合间的API交互。
image.png

集合常用的命令

  • 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 有序集合

有序集合的结构
image.png
集合和有序集合的对比:
(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)


South
182 声望5 粉丝

« 上一篇
Redis通用命令