Redis五种数据类型及应⽤场景
Redis五种数据类型及应⽤场景
MySql+Memcached架构的问题
实际MySQL是适合进⾏海量数据存储的,通过Memcached将热点数据加载到cache,加速访问,很多公司都曾经使⽤过这样的架构,但随着业务数据量的不断增加,和访问量的持续增长,我们遇到了很多问题:
1.MySQL需要不断进⾏拆库拆表,Memcached也需不断跟着扩容,扩容和维护⼯作占据⼤量开发时间。
2.Memcached与MySQL数据库数据⼀致性问题。
3.Memcached数据命中率低或down机,⼤量访问直接穿透到DB,MySQL⽆法⽀撑。
4.跨机房cache同步问题。
众多NoSQL百花齐放,如何选择
最近⼏年,业界不断涌现出很多各种各样的NoSQL产品,那么如何才能正确地使⽤好这些产品,最⼤化地发挥其长处,是我们需要深⼊研究和思考的问题,实际归根结底最重要的是了解这些产品的定位,并且了解到每款产品的tradeoffs,在实际应⽤中做到扬长避短,总体上这些NoSQL主要⽤于解决以下⼏种问题
1.少量数据存储,⾼速读写访问。此类产品通过数据全部in-momery 的⽅式来保证⾼速访问,同时提供数据落地的功能,实际这正是Redis最主要的适⽤场景。
2.海量数据存储,分布式系统⽀持,数据⼀致性保证,⽅便的集节点添加/删除。
3.这⽅⾯最具代表性的是dynamo和bigtable 2篇论⽂所阐述的思路。前者是⼀个完全⽆中⼼的设计,节点之间通过gossip⽅式传递集信息,数据保证最终⼀致性,后者是⼀个中⼼化的⽅案设计,通过类似⼀个分布式锁服务来保证强⼀致性,数据写⼊先写内存和redo log,然后定期compat归并到磁盘上,将随机写优化为顺序写,提⾼写⼊性能。
4.Schema free,auto-sharding等。⽐如⽬前常见的⼀些⽂档数据库都是⽀持schema-free的,直接存储json格式数据,并且⽀持auto-sharding等功能,⽐如mongodb。
redis支持的数据结构Redis最适合所有数据in-momory的场景,虽然Redis也提供持久化功能,但实际更多的是⼀个disk-bac
ked的功能,跟传统意义上的持久化有⽐较⼤的差别,那么可能⼤家就会有疑问,似乎Redis更像⼀个加强版的Memcached,那么何时使⽤Memcached,何时使⽤Redis呢?
如果简单地⽐较Redis与Memcached的区别,⼤多数都会得到以下观点:
1 、Redis不仅仅⽀持简单的k/v类型的数据,同时还提供list,set,zset,hash等数据结构的存储。
2 、Redis⽀持数据的备份,即master-slave模式的数据备份。
3 、Redis⽀持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进⾏使⽤。
通过⼀张图了解下Redis内部内存管理中是如何描述这些不同数据类型的:
⾸先Redis内部使⽤⼀个redisObject对象来表⽰所有的key和value,redisObject最主要的信息如上图所⽰:type代表⼀个value对象具体是何种数据类型,encoding是不同数据类型在redis内部的存储⽅式,⽐如:type=string代表value存储的是⼀个普通字符串,那么对应的encoding可以是raw或者是int,如果是int则代表实际redis内部是按数值型类存储和表⽰这个字符串的,当然前提是这个字符串本⾝可以⽤数值表⽰,⽐如:“123” "456"这样的字符串。
这⾥需要特殊说明⼀下vm字段,只有打开了Redis的虚拟内存功能,此字段才会真正的分配内存,该功能默认是关闭状态的。通过上图我们可以发现Redis使⽤redisObject来表⽰所有的key/value数据是⽐较浪费内存的,当然这些内存管理成本的付出主要也是为了给Redis不同数据类型提供⼀个统⼀的管理接⼝,实际作者也提供了多种⽅法帮助我们尽量节省内存使⽤,我们随后会具体讨论。
Redis⽀持5种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)。
① string 是 redis 最基本的类型,你可以理解成与 Memcached ⼀模⼀样的类型,⼀个 key 对应⼀个 value。value其实不仅是String,也可以是数字。string 类型是⼆进制安全的。意思是 redis 的 string 可以包含任何数据。⽐如jpg图⽚或者序列化的对象。string 类型是Redis 最基本的数据类型,string 类型的值最⼤能存储 512MB。
常⽤命令:get、set、incr、decr、mget等。
应⽤场景:String是最常⽤的⼀种数据类型,普通的key/ value 存储都可以归为此类,即可以完全实现⽬前 Memcached 的功能,并且效率更⾼。还可以享受Redis的定时持久化,操作⽇志及 Replication等功能。除了提供与 Memcached ⼀样的get、set、incr、decr 等操作外,Redis还提供了下⾯⼀些操作:
获取字符串长度
往字符串append内容
设置和获取字符串的某⼀段内容
设置及获取字符串的某⼀位(bit)
批量设置⼀系列字符串的内容
使⽤场景:常规key-value缓存应⽤。常规计数: 微博数, 粉丝数。
实现⽅式:String在redis内部存储默认就是⼀个字符串,被redisObject所引⽤,当遇到incr,decr等操作时会转成数值型进⾏计算,此时redisObject的encoding字段为int。
redis 127.0.0.1:6379> SET name "runoob"
"OK"
redis 127.0.0.1:6379> GET name
"runoob"
在以上实例中我们使⽤了 Redis 的 SET 和 GET 命令。键为 name,对应的值为 runoob。
注意:⼀个键最⼤能存储512MB。
② Redis hash 是⼀个键值(key => value)对集合。Redis hash 是⼀个 string 类型的 field 和 value 的映射表,hash 特别适合⽤于存储对象。
常⽤命令:hget,hset,hgetall 等。
应⽤场景:我们简单举个实例来描述下Hash的应⽤场景,⽐如我们要存储⼀个⽤户信息对象数据,包含以下信息:
⽤户ID为查的key,存储的value⽤户对象包含姓名,年龄,⽣⽇等信息,如果⽤普通的key/value结
构来存储,主要有以下2种存储⽅式:
第⼀种⽅式将⽤户ID作为查key,把其他信息封装成⼀个对象以序列化的⽅式存储,这种⽅式的缺点是,增加了序列化/反序列化的开销,并且在需要修改其中⼀项信息时,需要把整个对象取回,并且修改操作需要对并发进⾏保护,引⼊CAS等复杂问题。
第⼆种⽅法是这个⽤户信息对象有多少成员就存成多少个key-value对⼉,⽤⽤户ID+对应属性的名称
作为唯⼀标识来取得对应属性的值,虽然省去了序列化开销和并发问题,但是⽤户ID为重复存储,如果存在⼤量这样的数据,内存浪费还是⾮常可观的。
那么Redis提供的Hash很好的解决了这个问题,Redis的Hash实际是内部存储的Value为⼀个HashMap,并提供了直接存取这个Map成员的接⼝,如下图:
也就是说,Key仍然是⽤户ID, value是⼀个Map,这个Map的key是成员的属性名,value是属性值,这样对数据的修改和存取都可以直接通过其内部Map的Key(Redis⾥称内部Map的key为field), 也就是通过 key(⽤户ID) + field(属性标签) 就可以操作对应属性数据了,既不需要重复存储数据,也不会带来序列化和并发修改控制的问题,很好的解决了问题。
这⾥同时需要注意,Redis提供了接⼝(hgetall)可以直接取到全部的属性数据,但是如果内部Map的成员很多,那么涉及到遍历整个内部Map的操作,由于Redis单线程模型的缘故,这个遍历操作可能会⽐
较耗时,⽽另其它客户端的请求完全不响应,这点需要格外注意。
使⽤场景:存储部分变更数据,如⽤户信息等。
实现⽅式:上⾯已经说到Redis Hash对应Value内部实际就是⼀个HashMap,实际这⾥会有2种不同实现,这个Hash的成员⽐较少时Redis为了节省内存会采⽤类似⼀维数组的⽅式来紧凑存储,⽽不会采⽤真正的HashMap结构,对应的value redisObject的encoding为zipmap,当成员数量增⼤时会⾃动转成真正的HashMap,此时encoding为ht。
redis> HSET myhash field1 "Hello" field2 "World"
"OK"
redis> HGET myhash field1
"Hello"
redis> HGET myhash field2
"World"
实例中我们使⽤了 Redis HMSET, HGET 命令,HMSET 设置了两个 field=>value 对, HGET 获取对应 field 对应的 value。每个 hash 可以存储 232 -1 键值对(40多亿)。
③ Redis list 列表是简单的字符串列表,按照插⼊顺序排序。你可以添加⼀个元素到列表的头部(左边)或者尾部(右边)。
常⽤命令:lpush(添加左边元素),rpush,lpop(移除左边第⼀个元素),rpop,lrange(获取列表⽚段,LRANGE key start stop)等。
应⽤场景:Redis list的应⽤场景⾮常多,也是Redis最重要的数据结构之⼀,⽐如twitter的关注列表,粉丝列表等都可以⽤Redis的list结构来实现。
List 就是链表,相信略有数据结构知识的⼈都应该能理解其结构。使⽤List结构,我们可以轻松地实现最新消息排⾏等功能。List的另⼀个应⽤就是消息队列,
可以利⽤List的PUSH操作,将任务存在List中,然后⼯作线程再⽤POP操作将任务取出进⾏执⾏。Redis还提供了操作List中某⼀段的api,你可以直接查询,删除List中某⼀段的元素。
实现⽅式:Redis list的实现为⼀个双向链表,即可以⽀持反向查和遍历,更⽅便操作,不过带来了部分额外的内存开销,Redis内部的很多实现,包括发送缓冲队列等也都是⽤的这个数据结构。
Redis的list是每个⼦元素都是String类型的双向链表,可以通过push和pop操作从列表的头部或者尾部添加或者删除元素,这样List即可以作为栈,也可以作为队列。 获取越接近两端的元素速度越快,但通过索引访问时会⽐较慢。
使⽤场景:
消息队列系统:使⽤list可以构建队列系统,使⽤sorted set甚⾄可以构建有优先级的队列系统。⽐如:将Redis⽤作⽇志收集器,实际上还是⼀个队列,多个端点将⽇志信息写⼊Redis,然后⼀个worker统⼀将所有⽇志写到磁盘。
取最新N个数据的操作:记录前N个最新登陆的⽤户Id列表,超出的范围可以从数据库中获得。
//把当前登录⼈添加到链表⾥
ret = r.lpush("login:last_login_times", uid)
//保持链表只有N位
ret = redis.ltrim("login:last_login_times",0, N-1)
//获得前N个最新登陆的⽤户Id列表
last_login_list = r.lrange("login:last_login_times",0, N-1)
⽐如微博:
在Redis中我们的最新微博ID使⽤了常驻缓存,这是⼀直更新的。但是我们做了限制不能超过5000个ID,因此我们的获取ID函数会⼀直询问Redis。只有在start/count参数超出了这个范围的时候,才需要去访问数据库。我们的系统不会像传统⽅式那样“刷新”缓存,Redis实例中的信息永远是⼀致的。SQL数据库(或是硬盘上的其他类型数据库)只是在⽤户需要获取“很远”的数据时才会被触发,⽽主页或第⼀个评论页是不会⿇烦到硬盘上的数据库了。
redis 127.0.0.1:6379> lpush runoob redis
(integer)1
redis 127.0.0.1:6379> lpush runoob mongodb
(integer)2
redis 127.0.0.1:6379> lpush runoob rabitmq
(integer)3
redis 127.0.0.1:6379> lrange runoob 010
1)"rabitmq"
2)"mongodb"
3)"redis"
redis 127.0.0.1:6379>
列表最多可存储 232 - 1 元素 (4294967295, 每个列表可存储40多亿)。
④ Redis set是string类型的⽆序集合。集合是通过hashtable实现的,概念和数学中个的集合基本类似,可以交集,并集,差集等等,set 中的元素是没有顺序的。所以添加,删除,查的复杂度都是O(1)。
sadd 命令添加⼀个 string 元素到 key 对应的 set 集合中,成功返回1,如果元素已经在集合中返回 0,如果 key 对应的 set 不存在则返回错误。
常⽤命令:sadd,spop,smembers,sunion 等。
应⽤场景:Redis set对外提供的功能与list类似是⼀个列表的功能,特殊之处在于set是可以⾃动排重的,当你需要存储⼀个列表数据,⼜不希望出现重复数据时,set是⼀个很好的选择,并且set提供了判断某个成员是否在⼀个set集合内的重要接⼝,这个也是list所不能提供的。
Set 就是⼀个集合,集合的概念就是⼀堆不重复值的组合。利⽤Redis提供的Set数据结构,可以存储⼀些集合性的数据。
案例:在微博中,可以将⼀个⽤户所有的关注⼈存在⼀个集合中,将其所有粉丝存在⼀个集合。Redis还为集合提供了求交集、并集、差集等操作,可以⾮常⽅便的实现如共同关注、共同喜好、⼆度好友等功能,对上⾯的所有集合操作,你还可以使⽤不同的命令选择将结果返回给客户端还是存集到⼀个新的集合中。
实现⽅式: set 的内部实现是⼀个 value永远为null的HashMap,实际就是通过计算hash的⽅式来快速排重的,这也是set能提供判断⼀个成员是否在集合内的原因。
使⽤场景:
①交集,并集,差集:(Set)
//book表存储book名称
set book:1:name    ”The Ruby Programming Language”
set book:2:name    ”Ruby on rail”
set book:3:name    ”Programming Erlang”
//tag表使⽤集合来存储数据,因为集合擅长求交集、并集
sadd tag:ruby 1
sadd tag:ruby 2
sadd tag:web 2
sadd tag:erlang 3
//即属于ruby⼜属于web的书?
inter_list = redis.sinter("tag.web","tag:ruby")
//即属于ruby,但不属于web的书?
inter_list = redis.sdiff("tag.ruby","tag:web")
//属于ruby和属于web的书的合集?
inter_list = redis.sunion("tag.ruby","tag:web")
②获取某段时间所有数据去重值
这个使⽤Redis的set数据结构最合适了,只需要不断地将数据往set中扔就⾏了,set意为集合,所以会⾃动排重。
sadd key member
redis 127.0.0.1:6379> sadd runoob redis
(integer)1
redis 127.0.0.1:6379> sadd runoob mongodb
(integer)1
redis 127.0.0.1:6379> sadd runoob rabitmq
(integer)1
redis 127.0.0.1:6379> sadd runoob rabitmq
(integer)0
redis 127.0.0.1:6379> smembers runoob
1)"redis"
2)"rabitmq"
3)"mongodb"
注意:以上实例中 rabitmq 添加了两次,但根据集合内元素的唯⼀性,第⼆次插⼊的元素将被忽略。集合中最⼤的成员数为 232 -
1(4294967295, 每个集合可存储40多亿个成员)。
⑤ Redis zset 和 set ⼀样也是string类型元素的集合,且不允许重复的成员。
zadd 命令添:加元素到集合,元素在集合中存在则更新对应score。
常⽤命令:zadd,zrange,zrem,zcard等
使⽤场景:Redis sorted set的使⽤场景与set类似,区别是set不是⾃动有序的,⽽sorted set可以通过⽤户额外提供⼀个优先级(score)的参数来为成员排序,并且是插⼊有序的,即⾃动排序。当你需要⼀个有序的并且不重复的集合列表,那么可以选择sorted set数据结构,⽐如twitter 的public timeline可以以发表时间作为score来存储,这样获取时就是⾃动按时间排好序的。和Set相⽐,Sorted Set关联了⼀个double类型权重参数score,使得集合中的元素能够按score进⾏有序排列,redis正是通过分数来为集合中的成员进⾏从⼩到⼤的排序。zset的成员是唯⼀的,但分数(score)却可以重复。⽐如⼀个存储全班同学成绩的Sorted Set,其集合value可以是同学的学号,⽽score就可以是其考试得分,这样在数据插⼊集合的时候,就已经进⾏了天然的排序。另外还可以⽤Sorted Set来做带权重的队列,⽐如普通消息的score为1,重要消息的score为2,然后⼯作线程可以选择按score的倒序来获取⼯作任务。让重要的任务优先执⾏。
实现⽅式:Redis sorted set的内部使⽤HashMap和跳跃表(SkipList)来保证数据的存储和有序,HashMap⾥放的是成员到score的映射,⽽跳跃表⾥存放的是所有的成员,排序依据是HashMap⾥存的score,使⽤跳跃表的结构可以获得⽐较⾼的查效率,并且在实现上⽐较简单。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。