Redis常见性能问题、⼏种数据类型及应⽤场景
性能问题
Redis 常见的性能问题都有哪些?如何解决?
Master写内存快照,save命令调度rdbSave函数,会阻塞主线程的⼯作,当快照⽐较⼤时对性能影响是⾮常⼤的,会间断性暂停服务,所以Master最好不要写内存快照。
Master AOF持久化,如果不重写AOF⽂件,这个持久化⽅式对性能的影响是最⼩的,但是AOF⽂件会不断增⼤,AOF⽂件过⼤会影响Master重启的恢复速度。Master最好不要做任何持久化⼯作,包括内存快照和AOF⽇志⽂件,特别是不要启⽤内存快照做持久化,如果数据⽐较关键,某个Slave开启AOF备份数据,策略为每秒同步⼀次。
Master调⽤BGREWRITEAOF重写AOF⽂件,AOF在重写的时候会占⼤量的CPU和内存资源,导致服务load过⾼,出现短暂服务暂停现象。
Redis主从复制的性能问题,为了主从复制的速度和连接的稳定性,Slave和Master最好在同⼀个局域⽹内
Redis ⼏种数据类型及应⽤场景
Redis⽀持5种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)。何时使⽤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 不同数据类型提供⼀个统⼀的管理接⼝,实际作者也提供了多种⽅法帮助我们尽量节省内存使⽤,我们随后会具体讨论。
⼀、string
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。
⼆、Hash
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多亿)。
三、list
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 0 10
1) "rabitmq"
2) "mongodb"
3) "redis" redis 127.0.0.1:6379>
列表最多可存储 232 - 1 元素 (4294967295, 每个列表可存储40多亿)。
四、 set
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
redis支持的五种数据类型(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多亿个成员)。
五 、zset
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,使⽤跳跃表的结构可以获得⽐较⾼的查效率,并且在实现上⽐较简单。
zadd key score member
redis 127.0.0.1:6379> zadd runoob 0 redis
(integer) 1 redis 127.0.0.1:6379> zadd runoob 0 mongodb
(integer) 1 redis 127.0.0.1:6379> zadd runoob 0 rabitmq
(integer) 1 redis 127.0.0.1:6379> zadd runoob 0 rabitmq
(integer) 0 redis 127.0.0.1:6379> > ZRANGEBYSCORE runoob 0 1000
1) "mongodb"
2) "rabitmq"
3) "redis"
各个数据类型应⽤场景:
类型简介特性场景
String(字符串)⼆进制安全
可以包含任何数据,⽐如jpg图⽚或者序列化的对象,⼀个键最⼤能
存储512M
---
Hash(字典)键值对集合,即编程语⾔中
的Map类型
适合存储对象,并且可以像数据库中update⼀个属性⼀样只修改
某⼀项属性值(Memcached中需要取出整个字符串反序列化成对
象修改完再序列化存回去)
存储、读取、修改⽤户属性
List(列表)链表(双向链表)增删快,提供了操作某⼀段元素的API
1、最新消息排⾏等功能(⽐如朋友圈的时间线)
2、消息队列
Set(集合)哈希表实现,元素不重复1、添加、删除、查的复杂度都是O(1) 2、为集合提供了求交
1、共同好友
2、利⽤唯⼀性,统计访问⽹站的所
有独⽴ip 3、好友推荐时,根据tag求交集,⼤于某
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论