Redis—第2周—Data Type
一.数据结构和编码序
Redis有5种数据结构:分别是字符串string,哈希hash,列表list,集合set,有序集合zset
每种数据结构有不同的编码形式,数据结构可以理解为对外提供的类型,数据结构的编码可以理解为内部时间的方式。
因为在内存中使用,使用小空间的方式来存放可以由更好的效果。适用于不同的类型。
字符串—string raw int embstr 哈希—hash hashtable ziplist 列表—list linkedlist ziplist 集合—set hashtable intset 有序集合—zset skiplist ziplist
由于redis是一种单线程的结构,每一个操作都是串行执行的。一个接一个的执行。
单线程非常快,完全依靠内存,使用非阻塞io。避免线程的切换和资源的竞争消耗。
一次应该只运行一条命令,拒绝长命令。以下命令不推荐在线上使用。尤其是数据量非常大的情况下。
keys, flushall, flushdb, show lua script, mutil/exec, operate, big value。
二.数据结构详解
2.1 字符串 string
字符串最高512MB,但应该不超过100kb
适用场景:
1.缓存
2.计数器
3.分布式锁
4.等等
基本操作都以S开头
keys * 打印所有key dbsize 计算key数量 set 设置key-value,也可以修改值(不存在设置,存在修改) 127.0.0.1:6379> set java issobad OK 127.0.0.1:6379> set python iloveit OK 127.0.0.1:6379> set php fakefrist OK 127.0.0.1:6379> set c issohard OK 127.0.0.1:6379> get python "i love it" 127.0.0.1:6379> set python 100 OK 127.0.0.1:6379> get python “100” get 获取key对应的value 127.0.0.1:6379> get java "issobad" 127.0.0.1:6379> get python "iloveit" del 删除key-value 127.0.0.1:6379> del c (integer) 1 127.0.0.1:6379> del php (integer) 1 incr key自增1,如果key不存在,自增后get(key)=1 o(1)复杂度 127.0.0.1:6379> incr python2 #python2不存在 “1” 127.0.0.1:6379> incr python2 "2" decr key自减1,如果key不存在,自减后get(key)=-1 127.0.0.1:6379> decr python2 (integer) 27 127.0.0.1:6379> decr python3 (integer) -1 127.0.0.1:6379> get python3 "-1" incrby key自增k,如果key不存在,自增后get(key)=k 127.0.0.1:6379> incrby python2 100 (integer) 127 127.0.0.1:6379> incrby python3 99 (integer) 98 decrby key自减k,如果key不存在,自增后get(key)=k 127.0.0.1:6379> decrby python2 100 (integer) 27 127.0.0.1:6379> decrby python3 99 (integer) -1 set setnx setxx的区别 set key value 无论key是否存在,都设置 setnx key value key不存在才设置,类似add添加操作 127.0.0.1:6379> setnx python2 27 (integer) 0 127.0.0.1:6379> setnx python4 127 (integer) 1 set key value XX key存在才设置,类似update。XX为存在在执行 127.0.0.1:6379> set python5 100 OK 127.0.0.1:6379> set python5 110 XX OK 127.0.0.1:6379> get python5 "110" set key value NX key不存在才设置,类似于SETNX key value 127.0.0.1:6379> set python5 130 NX (nil) 127.0.0.1:6379> set python6 130 NX OK 127.0.0.1:6379> get python6 "130" exists 检查key是否存在 127.0.0.1:6379> exists python6 (integer) 1 127.0.0.1:6379> exists python7 (integer) 0 mget 批量获取key 原子操作o(n),执行n次,效率高。不是随便操作,对网络开销和返回时间不太友好。应该批量获取加入100万个数据。 127.0.0.1:6379> mget python python2 python3 python4 python5 python6 1) "100" 2) "28" 3) "-1" 4) "100" 5) "110" 6) "130" mset 设置多个key value o(n) 127.0.0.1:6379> mset java bad pythn lovely php gohell OK getset 设置新的值,返回旧的值 127.0.0.1:6379> getset java donttouch "bad" 127.0.0.1:6379> get java "donttouch" append 将新值追加到旧的value 127.0.0.1:6379> get php “gohell” 127.0.0.1:6379> append php isjoke (integer) 12 127.0.0.1:6379> get php "gohellisjoke" strlen 返回字节长度,内部有计数器实时更新大小 127.0.0.1:6379> strlen php (integer) 12 127.0.0.1:6379> strlen java (integer) 9 incrbyfloat 增加key对应的值,浮点值 127.0.0.1:6379> set float1 1.3 OK 127.0.0.1:6379> INCRBYFLOAT float1 4.4 "5.7" 127.0.0.1:6379> get float1 "5.7" 127.0.0.1:6379> get python "100" 127.0.0.1:6379> INCRBYFLOAT python 1.1 "101.1" 127.0.0.1:6379> get python "101.1" getrange 获取字符串指定下标的所有的值 127.0.0.1:6379> get php "gohellisjoke" 127.0.0.1:6379> GETRANGE php 2 7 "hellis" setrange 设置下标对应的值 127.0.0.1:6379> get php "gohellisjoke" 127.0.0.1:6379> SETRANGE php 4 t #将下标4的字母e换成t (integer) 12 127.0.0.1:6379> get php "gohetlisjoke" 除了mget ,mset 复杂度是o(n) 其他都是o(1)
实战
实战1:访问每个用户个人主页的访问量,类似于一个计数器。
incr userid:pageview (单线程,无竞争)
实战2:缓存视频的基本信息,数据源在mysql中
通过vid(视频id),访问redis,有就返回,没有mysql读取,然后写入redis,再将结果返回给用户

伪代码
public VideoInfo get(lond id){ String rediskey = redisPrefix + id; VideoInfo videoinfo = redis.get(rediskey); if(videoinfo == null){ videoinfo = mysql.get(id); if(videoinfo!=null){ //序列化 redis.set(rediskey,serrialize(videoinfo)); } } return videoinfo }
实战3:3个程序 service。每次获取的id都是递增的,而且多个应用并发获取,还不会冲突,分布式id生成器
incr id
2.2 哈希 hash
基本操作:
hash 可以理解为小的redis
格式:
key field value
名称 属性 值
可以定义一个key名称,比如用户信息。可以由age属性,可以有name等不同的属性。不同的属性有不通的值。
field属性不可相同。但是value值可以相同。
可以直接添加新值不需要序列化。比如添加了age属性。还可以添加name属性,可以使用del删除一个hash
基本操作都以H开头
命令都以h开头 hset key field 设置key对应的单个field的值 127.0.0.1:6379> hset user1:info age 23 (integer) 1 127.0.0.1:6379> hset user1:info name roanlado (integer) 1 hget key field 获取key对应的field的值 127.0.0.1:6379> hget user1:info age "23" hgetall key 获取user1所有的值 127.0.0.1:6379> hgetall user1:info 1) "age" 2) "23" 3) "name" 4) "roanlado" hdel key field 删除key对应的field的值 127.0.0.1:6379> hdel user1:info age (integer) 1 127.0.0.1:6379> hgetall user1:info 1) "name" 2) "roanlado" hexists key field 检查field是否存在 127.0.0.1:6379> hexists user1:info name (integer) 1 127.0.0.1:6379> hexists user1:info age (integer) 0 hlen key 获取hash属性数量 127.0.0.1:6379> hlen user1:info (integer) 1 hmset key field1 value1 field2 value2 批量谁知key对应的属性值。一个key对应一个属性值。无法设置了 name 1在设置name2 127.0.0.1:6379> hmset stu:info name lilei age 20 tel 1444444444444 sex m 以下设置不会增加韩梅梅属性到stu:info中。会将stu:info直接修改为以下内容 127.0.0.1:6379> hmset stu:info name hanmeimei age 18 tel 1333333333333 sex n OK hmget key field1 field2 批量获取hash key的一批field对应的值 o(n) 127.0.0.1:6379> hmget stu:info name age tel sex 1) "lilei" 2) "20" 3) "1444444444444" 4) "m" hdel key 可以删除单个或多个field属性 127.0.0.1:6379> hdel user2:i nfo name age (integer) 2 hsetnx 设置hash key 对应的field的value,如果已经存在就失败 127.0.0.1:6379> hgetall stu:info 1) "name" 2) "lilei" 3) "age" 4) "20" 5) "tel" 6) "1444444444444" 7) "sex" 8) "m" 127.0.0.1:6379> hsetnx stu:info sex n (integer) 0 127.0.0.1:6379> hsetnx stu:info class 2 (integer) 1 127.0.0.1:6379> hgetall stu:info 1) "name" 2) "lilei" 3) "age" 4) "20" 5) "tel" 6) "1444444444444" 7) "sex" 8) "m" 9) "class" 10) "2" hincrby hash key对应的field的value自增int 127.0.0.1:6379> hincrby stu:info class 3 (integer) 5 127.0.0.1:6379> hget stu:info class "5" hincrbyfloat 浮点数自增 127.0.0.1:6379> HINCRBYFLOAT stu:info class 5.2 "10.2" 127.0.0.1:6379> hget stu:info class "10.2" 全都是o(1) hgetall key 返回hash key所对应的field和value 127.0.0.1:6379> hgetall stu:info 1) "name" 2) "lilei" 3) "age" 4) "20" 5) "tel" 6) "1444444444444" 7) "sex" 8) "m" 9) "class" 10) "10.2" hvals key 返回hash key 对应的field的value 127.0.0.1:6379> hvals stu:info 1) "lilei" 2) "20" 3) "1444444444444" 4) "m" 5) "10.2" hkeys key 返回hash key 对应所有的field 127.0.0.1:6379> hkeys stu:info 1) "name" 2) "age" 3) "tel" 4) "sex" 5) "class" 这三条命令都是o(n) 尤其注意hgetall 因为是单线程结构。会造成阻塞。不推荐在生产系统上使用
实战:
实战1:访问每个用户个人主页的访问量
hincrby user:1:info pageview count
实战2:缓存视频的基本信息,数据源在mysql中,伪代码,不需要序列化
伪代码 public VideoInfo get(lond id){ String rediskey = redisPrefix + id; Map<string,string> hashMap = redis.hgetAll(redisKey); VideoInfo videoinfo = transferMapToVideo(hashMap); if(videoinfo == null){ videoinfo = mysql.get(id); if(videoinfo!=null){ redis.hmset(rediskey,transferVideoToMap(videoInfo)); } } return videoinfo }
字符串与哈希对比
命令对比
字符串和哈希对比
get hget
set setnx hset hsetnx
del hdel
incr incrby decr decrby hincrby
mset hmset
mget hmget
优缺点对比:
字符串
如何更新用户属性
set user:1 serialize(userinfo)
包装成对象,或者xml,或者json,写入的时候要序列化,然后再写入redis。
优点:编程简单,节约内存
缺点:序列化开销(cpu开销),操作会更改整个数据
因为key都是单独的。所以更改一个key不影响其他,但是用户信息不是一个整体。不便于整理,key较为分散
set user:1 age 41
新增的key也不影响原来的key
set user:1:link tv.souhu.com
hash—哈希
优点:比较直观,可以部分更新,添加新属性不影响,节省空间
缺点:变成较为复杂,ttl过期时间不好控制,数据库上不方便实现,只能通过程序维护这个关系
2.3 列表 list
类似于数据结构中的栈或者队列。也可以看作是一个单链表。
有序,左右两边都可以添加和删除,可以使用del删除一个列表。
类似结构如下:
key elements
user:1:message a-b-c-d-e-f-g
基本操作都以L开头:
key elements user:1:message a-b-c-d-e-f lpush 左边添加添加一个元素 o(1-n) 127.0.0.1:6379> lpush user python1 (integer) 1 127.0.0.1:6379> lpush user java2 (integer) 2 127.0.0.1:6379> lpush user ruby3 (integer) 3 lrange 获取元素的值 0 -1 可以遍历所有值,-1代表从右边向左边数 127.0.0.1:6379> lrange user 0 2 1) "ruby3" 2) "java2" 3) "python1" lpush后顺序为 ruby3-java2-python1 rpush 右边添加一个元素 o(1-n) 127.0.0.1:6379> rpush user go4 (integer) 4 127.0.0.1:6379> rpush user swift5 (integer) 5 127.0.0.1:6379> rpush user sql6 (integer) 6 rpush 过后顺序为:ruby3-java2-python1-go4-swift5-sql6 lpop 左边弹出一个元素,并返回弹出元素 127.0.0.1:6379> lpop user "ruby3" rpop 右边弹出一个元素,并返回弹出元素 127.0.0.1:6379> rpop user "sql6" 弹出后结果: 127.0.0.1:6379> lrange user 0 3 1) "java2" 2) "python1" 3) "go4" 4) "swift5" llen key 获取长度 o(1) 127.0.0.1:6379> llen user (integer) 4 lrem key count value 删除指定元素 o(n) count > 0 从左到右,删除最多count个value一样的项 count < 0 从右到左,删除做多math.abs(count)个vlaue相等的项目 count = 0 删除所有value相等的值 127.0.0.1:6379> lrange user 0 -1 1) "java2" 2) "java2" 3) "python1" 4) "java2" 5) "go4" 6) "ruby3" 7) "swift5" 8) "go4" 9) "go4" 10) "go4" 127.0.0.1:6379> lrem user 2 java2 (integer) 2 127.0.0.1:6379> lrange user 0 -1 1) "python1" 2) "java2" 3) "go4" 4) "ruby3" 5) "swift5" 6) "go4" 7) "go4" 8) "go4" 127.0.0.1:6379> lrem user -4 go4 (integer) 4 127.0.0.1:6379> lrange user 0 -1 1) "python1" 2) "java2" 3) "ruby3" 4) "swift5" lindex key index 根据索引获取元素 127.0.0.1:6379> lindex user 2 "swift5" 127.0.0.1:6379> lindex user 1 "go4" linsert key before/after value newvalue 在list指定值得前/后插入新元素,复杂度o(n),因为要遍历列表 127.0.0.1:6379> lrange user 0 2 1) "python1" 2) "go4" 3) "swift5" 127.0.0.1:6379> linsert user before go4 java2 (integer) 4 127.0.0.1:6379> linsert user after go4 ruby3 (integer) 5 127.0.0.1:6379> lrange user 0 4 1) "python1" 2) "java2" 3) "go4" 4) "ruby3" 5) "swift5" ltrim 按照一定的索引范围修剪列表 o(n) 适合删除大列表,因为del会阻塞。用ltrim的化每次删除一部分 运行更优 127.0.0.1:6379> lrange user 0 -1 1) "python1" 2) "java2" 3) "ruby3" 4) "swift5" 5) "a" 6) "b" 7) "bc" 8) "bcd" 9) "bcde" 127.0.0.1:6379> ltrim user 0 7 OK 127.0.0.1:6379> lrange user 0 -1 1) "python1" 2) "java2" 3) "ruby3" 4) "swift5" 5) "a" 6) "b" 7) "bc" 8) "bcd" lset key index newValue 指定索引值为新值 127.0.0.1:6379> lrange user 0 -1 1) "python1" 2) "java2" 3) "ruby3" 4) "swift5" 127.0.0.1:6379> lset user 2 java3 OK 127.0.0.1:6379> lrange user 0 -1 1) "python1" 2) "java2" 3) "java3" 4) "swift5" blpop key timeout 阻塞左边 timeout=0 永远不阻塞,阻塞弹出,一直等或者等一段时间,等到我需要的一个元素我就快速返回。适用于生产者——消费者模型,或者消息队列 o(1) brpop key timeout 阻塞右边 timeout=0 永远不阻塞
实战:微博事件轴 TimeLine,微博按照从新到旧的排列,
就是类似于你关注一些微博的人。以微博的id作为区分
你的首页类似如下
weibo1 博文
weibo2 博文
weibo3 博文
weibo4 博文
weibo5 博文
然后突然weibo6用户更新了。你刷新之后。weibo6就在微博1的上边 lpush,可以看成从左到右的顺序
weibo6 博文
weibo1 博文
weibo2 博文
weibo3 博文
weibo4 博文
weibo5 博文
也可以看成一个对象。包含转发,时间,评论,可以用字符串或者哈希来实现
微博id可以作为一个外联的key,微博获取就可以做一个关联,用lrange来获取假如10条的更新
mset获取所有id的内容
tips 口诀
栈功能 lpush+lpop
队列功能 lpush+rpop
有限列表 lpush+ltrim
消息队列 lpush+brpop
2.4 集合 set
集合是无序的,没有重复元素,支持集合间的操作。
基本操作都以S开头
sdd key elements 向集合里面添加元素。如果存在就失败,返回0. 127.0.0.1:6379> sadd user1:follow IT NewS Sport (integer) 3 127.0.0.1:6379> sadd user1:follow IT (integer) 0 smembers 获取所有的元素,返回结构是无序的,小心使用,会造成阻塞 127.0.0.1:6379> smembers user1:follow 1) "NewS" 2) "IT" 3) "Sport" scard 计算集合中元素 127.0.0.1:6379> scard user1:follow (integer) 3 sismember 判断元素是否在集合中 127.0.0.1:6379> sismember user1:follow IT (integer) 1 127.0.0.1:6379> sismember user1:follow ENTER (integer) 0 srem 删除一个指定元素 127.0.0.1:6379> srem user1:follow Sport (integer) 1 127.0.0.1:6379> smembers user1:follow 1) "NewS" 2) "IT" srandmember 随机获取一个元素,不弹出元素,也可以返回多个元素 127.0.0.1:6379> srandmember user1:follow "NewS" 127.0.0.1:6379> smembers user1:follow 1) "SPORT" 2) "EAT" 3) "IT" 4) "NewS" 5) "DACNEING" 127.0.0.1:6379> srandmember user1:follow 3 1) "EAT" 2) "DACNEING" 3) "NewS" spop 在集合中随机弹出一个元素,并返回弹出元素,只能弹出一个 127.0.0.1:6379> spop user1:follow "IT" 127.0.0.1:6379> smembers user1:follow 1) "NewS" 集合间 127.0.0.1:6379> smembers user1:follow 1) "SPORT" 2) "EAT" 3) "IT" 4) "NewS" 5) "DACNEING" 127.0.0.1:6379> smembers user2:floow 1) "FLY" 2) "EAT" 3) "DACNEING" 4) "IT" 5) "RUNING" sdiff user1:follow user:2:follow 两个集合的差集 127.0.0.1:6379> sdiff user1:follow user2:floow 1) "SPORT" 2) "NewS" sinter user1:follow user:2:follow 两个集合的交集 127.0.0.1:6379> sinter user1:follow user2:floow 1) "EAT" 2) "IT" 3) "DACNEING" sunion user1:follow user:2:follow 两个集合的并集 127.0.0.1:6379> sunion user1:follow user2:floow 1) "SPORT" 2) "EAT" 3) "DACNEING" 4) "FLY" 5) "RUNING" 6) "IT" 7) "NewS" sdiff|sinter|suion + store destkey 将差,交,并保存在destkey中 127.0.0.1:6379> sdiffstore d11 user1:follow user2:floow (integer) 2 127.0.0.1:6379> smembers d11 1) "SPORT" 2) "NewS" 127.0.0.1:6379> sinterstore d12 user1:follow user2:floow (integer) 3 127.0.0.1:6379> smembers d12 1) "EAT" 2) "DACNEING" 3) "IT" 127.0.0.1:6379> sunionstore d13 user1:follow user2:floow (integer) 7 127.0.0.1:6379> smembers d13 1) "SPORT" 2) "EAT" 3) "DACNEING" 4) "FLY" 5) "RUNING" 6) "IT" 7) "NewS"
实战:抽奖系统
微博的转发抽奖活动
can be pop 随机弹出一个用户
实战:like,赞,踩
美团在redis上踩的坑
实战标签
给用户添加标签
sadd user:1:tags tag1 tag2 tag5
sadd user:2:tags tag3 tag4
给标签添加用户,看那些用户属于tag1
sadd tag1:users user:1 user:3
实战
共同关注用集合实现
tips
sadd = tagging 做标签
spop/srandmember 做随机取。比如抽奖
sadd+sinter 社交网络
2.5 有序集合 zset
格式如下:
zset key score value
有序集合里的元素是有序的,没有重复元素。
集合与有序集合对比
集合与有序集合都没有重复元素
集合是无需的,有序集合是有序
集合只有element,有序结合是element+score
列表与有序集合对比
列表可以重复,有序集合不可重复
列表是有序的,有序集合是有序的
列表只有element,有序结合是element+score
基本操作:全部以Z打头
增加 zadd key score element 添加操作,score不能重复,值可以重复 o(logN),如果存在插入增加失败 127.0.0.1:6379> zadd player:zset 1000 roanldo 900 messi 800 c-luo 600 kaka (integer) 4 删除,可以删除多个 zrem key element 删除,可以是多个element o(1) 127.0.0.1:6379> zrem player:zset kaka c-luo (integer) 2 返回 zscore 返回分数 127.0.0.1:6379> zscore player:zset kaka "600" zcard key 返回元素个数,redis自动维护 所以是o(1) 127.0.0.1:6379> zcard player:zset (integer) 4 zrank 返回排名 127.0.0.1:6379> zrank player:zset kaka (integer) 0 127.0.0.1:6379> zrank player:zset messi (integer) 2 zrange 范围的一个遍历,升序[是否打印分数] o(log(n)+m) 127.0.0.1:6379> zrange player:zset 0 -1 1) "kaka" 2) "c-luo" 3) "messi" 4) "roanldo" 127.0.0.1:6379> zrange player:zset 0 -1 withscores 1) "kaka" 2) "600" 3) "c-luo" 4) "800" 5) "messi" 6) "900" 7) "roanldo" 8) "1000" zcount 返回有序结合内指定分数范围内的个数 o(log(n)+m) 127.0.0.1:6379> zcount player:zset -300 1000 (integer) 5 127.0.0.1:6379> zrange player:zset 0 -1 1) "kmessi" 2) "messi" 3) "kaka" 4) "c-luo" 5) "roanldo" zincrby 增减或者减少元素分数,因为可以传递负值,若元素没有直接赋予值 127.0.0.1:6379> zincrby player:zset 50 kaka "650" 127.0.0.1:6379> zincrby player:zset -300 kmessi "-300" 127.0.0.1:6379> zincrby player:zset -300 messi "600" zrangebyscore 返回指定分数范围内的升序元素 o(log(n)+m)[是否打印分数] 127.0.0.1:6379> zrangebyscore player:zset 500 900 1) "kaka" 2) "c-luo" 3) "messi" 127.0.0.1:6379> 127.0.0.1:6379> zrangebyscore player:zset 500 900 withscores 1) "kaka" 2) "600" 3) "c-luo" 4) "800" 5) "messi" 6) "900" zremrangebyrank 删除指定排名内的升序元素 o(log(n)+m) 127.0.0.1:6379> zremrangebyrank player:zset 0 2 (integer) 3 127.0.0.1:6379> zrange player:zset 0 -1 1) "roanldo" zremrangebyscore 删除指定分数内的升序元素 o(log(n)+m) 127.0.0.1:6379> zremrangebyscore player:zset -300 800 (integer) 4 127.0.0.1:6379> zrange player:zset 0 -1 1) "roanldo" 实战-排行榜 score timeStamp saleCount followCount
127.0.0.1:6379> zrange player:zset 0 -1 1) "kaka" 2) "c-luo" 3) "messi" 4) "roanldo" zrevrank 从高到低排名 127.0.0.1:6379> zrevrank player:zset kaka (integer) 3 zrevrange 从高到低排名取范围 127.0.0.1:6379> zrevrange player:zset 0 2 1) "roanldo" 2) "messi" 3) "c-luo" zrevrangebyscore 给定分数从高到低获取范围 127.0.0.1:6379> zrevrangebyscore player:zset 1000 500 1) "roanldo" 2) "messi" 3) "c-luo" 4) "kaka" 难了我两个小时的两个命令。不懂。。 zinterstore 交集存储 zunionstore 并集存储

浙公网安备 33010602011771号