Redis是典型的Key-Value类型数据库,Key为字符类型,Value的类型经常使用的为五种类型:String、Hash 、List 、 Set 、 Ordered Sethtml
Redis 内部使用一个 redisObject 对象来表示全部的 key 和 value。redis
type :表明一个 value 对象具体是何种数据类型。数据库
encoding :是不一样数据类型在 redis 内部的存储方式,好比:type=string 表明 value 存储的是一个普通字符串,那么对应的 encoding 能够是 raw 或者是 int,若是是 int 则表明实际 redis 内部是按数值型类存储和表示这个字符串的,固然前提是这个字符串自己能够用数值表示,好比:"123" "456"这样的字符串。json
vm 字段:只有打开了 Redis 的虚拟内存功能,此字段才会真正的分配内存,该功能默认是关闭状态的。 Redis 使用 redisObject 来表示全部的 key/value 数据是比较浪费内存的,固然这些内存管理成本的付出主要也是为了给 Redis 不一样数据类型提供一个统一的管理接口,实际做者也提供了多种方法帮助咱们尽可能节省内存使用。数组
官网Key连接:https://redis.io/commands#generic缓存
过时数据的清除历来不容易,为每一条key设置一个timer,到点马上删除的消耗太大,每秒遍历全部数据消耗也大,Redis使用了一种相对务实的作法: 当client主动访问key会先对key进行超时判断,过期的key会马上删除。 若是clien永远都再也不get那条key呢? 它会在Master的后台,每秒10次的执行以下操做: 随机选取100个key校验是否过时,若是有25个以上的key过时了,马上额外随机选取下100个key(不计算在10次以内)。可见,若是过时的key很少,它最多每秒回收200条左右,若是有超过25%的key过时了,它就会作得更多,但只要key不被主动get,它占用的内存何时最终被清理掉只有天知道。安全
Key的长度限制:Key的最大长度不能超过1024字节,在实际开发时不要超过这个长度,可是Key的命名不能过短,要能惟一标识缓存的对,做者建议按照在关系型数据库中的库表惟一标识字段的方式来命令Key的值,用分号分割不一样数据域,用点号做为单词链接。数据结构
Key的查询:Keys,返回匹配的key,支持通配符如 “keys a*” 、 “keys a?c”,但不建议在生产环境大数据量下使用。并发
对Key对应的Value进行的排序:Sort命令对集合按数字或字母顺序排序后返回或另存为list,还能够关联到外部key等。由于复杂度是最高的O(N+Mlog(M))*(N是集合大小,M 为返回元素的数量),有时会安排到slave上执行。官网连接https://redis.io/commands/sortdom
Key的超时操做:Expire(指定失效的秒数)/ExpireAt(指定失效的时间戳)/Persist(持久化)/TTL(返回还可存活的秒数),关于Key超时的操做。默认以秒为单位,也有p字头的以毫秒为单位的版本
能够是String,也但是是任意的byte[]类型的数组,如图片等。String 在 redis 内部存储默认就是一个字符串,被 redisObject 所引用,当遇到 incr,decr 等操做时会转成数值型进行计算,此时 redisObject 的 encoding 字段为int。
https://redis.io/commands#string
大小限制:最大为512Mb,基本能够存储任意图片啦。
经常使用命令的时间复杂度为O(1),读写同样的快。
对String表明的数字进行增减操做(没有指定的Key则设置为0值,而后在进行操做):Incr/IncrBy/IncrByFloat/Decr/DecrBy(原子性),** 能够用来作计数器,作自增序列,也能够用于限流,令牌桶计数等**。key不存在时会建立并贴心的设原值为0。IncrByFloat专门针对float。。
设置Value的安全性:SetNx命令仅当key不存在时才Set(原子性操做)。能够用来选举Master或作分布式锁:全部Client不断尝试使用SetNx master myName抢注Master,成功的那位不断使用Expire刷新它的过时时间。若是Master倒掉了key就会失效,剩下的节点又会发生新一轮抢夺。SetEx, Set + Expire 的简便写法,p字头版本以毫秒为单位。
获取:GetSet(原子性), 设置新值,返回旧值。好比一个按小时计算的计数器,能够用GetSet获取计数并重置为0。这种指令在服务端作起来是举手之劳,客户端便方便不少。MGet/MSet/MSetNx, 一次get/set多个key。
其余操做:Append/SetRange/GetRange/StrLen,对文本进行扩展、替换、截取和求长度,只对特定数据格式如字段定长的有用,json就没什么用。
BitMap的用法:GetBit/SetBit/BitOp,与或非/BitCount, BitMap的玩法,好比统计今天的独立访问用户数时,每一个注册用户都有一个offset,他今天进来的话就把他那个位设为1,用BitCount就能够得出今天的总人数。
Redis 的 Hash 实际是内部存储的 Value 为一个 HashMap,并提供了直接存取这个 Map 成员的接口。Hash将对象的各个属性存入Map里,能够只读取/更新对象的某些属性。另外不一样的模块能够只更新本身关心的属性而不会互相并发覆盖冲突。
![]()
不一样程序经过 key(用户 ID) + field(属性标签)就能够并发操做各自关心的属性数据
https://redis.io/commands#hash
Redis Hash 对应 Value 内部实际就是一个 HashMap,实际这里会有2种不一样实现,** 这个 Hash 的成员比较少时 Redis 为了节省内存会采用相似一维数组的方式来紧凑存储,而不会采用真正的 HashMap 结构,对应的 value redisObject 的 encoding 为 zipmap,当成员数量增大时会自动转成真正的 HashMap,此时 encoding 为 ht**。通常操做复杂度是O(1),要同时操做多个field时就是O(N),N是field的数量。
Redis list 的应用场景很是多,也是 Redis 最重要的数据结构之一,好比 twitter 的关注列表,粉丝列表等均可以用 Redis 的 list 结构来实现,还提供了生产者消费者阻塞模式(B开头的命令),经常使用于任务队列,消息队列等。
Redis list 的实现为一个双向链表,便可以支持反向查找和遍历,更方便操做,不过带来了部分额外的内存开销,Redis 内部的不少实现,包括发送缓冲队列等也都是用的这个数据结构。
若是消费者把job给Pop走了又没处理完就死机了怎么办?
加多一个sorted set,分发的时候同时发到list与sorted set,以分发时间为score,用户把job作完了以后要用ZREM消掉sorted set里的job,而且定时从sorted set中取出超时没有完成的任务,从新放回list。 若是发生重复能够在sorted set中在查询确认一遍,或者将消息的消费接口设计成幂等性。
为每一个worker多加一个的list,弹出任务时改用RPopLPush,将job同时放到worker本身的list中,完成时用LREM消掉。若是集群管理(如zookeeper)发现worker已经挂掉,就将worker的list内容从新放回主list
复合操做:RPopLPush/ BRPopLPush,弹出来返回给client的同时,把本身又推入另外一个list,是原子操做。
按值进行的操做:LRem(按值删除元素)、LInsert(插在某个值的元素的先后),复杂度是O(N),N是List长度,由于List的值不惟一,因此要遍历所有元素,而Set只要O(log(N))。
按下表进行操做(下标从0开始,队列从左到右算,下标为负数时则从右到左,-1为右端第一个元素)
时间复杂度为O(N)
LTrim:限制List的大小,保留指定范围的元素。(N是移除元素的个数)
LRange:返回列表内指定范围下标的元素,经常使用于分页。(N = start+range)
Set就是HashSet,能够将重复的元素随便放入而Set会自动去重,底层实现也是HashMap,而且 set 提供了判断某个成员是否在一个 set 集合内的重要接口,这个也是 list 所不能提供的。
set 的内部实现是一个 value 永远为 null 的 HashMap,实际就是经过计算 hash 的方式来快速排重的,这也是 set 能提供判断一个成员是否在集合内的缘由。
增删改查:SAdd/SRem/SIsMember/SCard/SMove/SMembers等等。除了SMembers都是O(1)。
集合操做:SInter/SInterStore/SUnion/SUnionStore/SDiff/SDiffStore,各类集合操做。交集运算能够用来显示在线好友(在线用户 交集 好友列表),共同关注(两个用户的关注列表的交集)。O(N),并集和差集的N是集合大小之和,交集的N是小的那个集合的大小的2倍。
set 不是自动有序的,而** sorted set 能够经过用户额外提供一个优先级(score)的参数来为成员排序,而且是插入有序的,即自动排序**。当你须要一个有序的而且不重复的集合列表,那么能够选择 sorted set 数据结构,好比 twitter 的 public timeline 能够以发表时间做为 score 来存储,这样获取时就是自动按时间排好序的。
内部使用 HashMap 和跳跃表(SkipList)来保证数据的存储和有序
Sorted Set的实现是HashMap(element->score, 用于实现ZScore及判断element是否在集合内),和SkipList(score->element,按score排序)的混合体。SkipList有点像平衡二叉树那样,不一样范围的score被分红一层一层,每层是一个按score排序的链表。
ZAdd/ZRem是O(log(N));ZRangeByScore/ZRemRangeByScore是O(log(N)+M),N是Set大小,M是结果/操做元素的个数。复杂度的log取对数很关键,可使,1000万大小的Set,复杂度也只是几十不到。可是,若是一次命中不少元素M很大则复杂度很高。
ZRange/ZRevRange,按排序结果的范围返回元素列表,能够为正数与倒数。
ZRangeByScore/ZRevRangeByScore,按score的范围返回元素,能够为正数与倒数。
ZRemRangeByRank/ZRemRangeByScore,按排序/按score的范围限删除元素。
ZCount,统计按score的范围的元素个数。
ZRank/ZRevRank ,显示某个元素的正/倒序的排名。
ZScore/ZIncrby,显示元素的Score值/增长元素的Score。
ZAdd(Add)/ZRem(Remove)/ZCard(Count),ZInsertStore(交集)/ZUnionStore(并集),与Set相比,少了IsMember和差集运算。
上面的一些实现上的分析能够看出 redis 实际上的内存管理成本很是高,即占用了过多的内存,属于用空间换时间。做者对这点也很是清楚,因此提供了一系列的参数和手段来控制和节省内存
VM 选项是做为 Redis 存储超出物理内存数据的一种数据在内存与磁盘换入换出的一个持久化策略,将严重地拖垮系统的运行速度,因此要关闭 VM 功能,请检查你的 redis.conf 文件中 vm-enabled 为 no。
最好设置下 redis.conf 中的 maxmemory 选项,该选项是告诉 Redis 当使用了多少物理内存后就开始拒绝后续的写入请求,该参数能很好的保护好你的 Redis 不会由于使用了过多的物理内存而致使 swap,最终严重影响性能甚至崩溃。
通常还须要设置内存饱和回收策略
Redis 为不一样数据类型分别提供了一组参数来控制内存使用
redis.conf 配置文件中下面2项
含义是当 value 这个 Map 内部不超过多少个成员时会采用线性紧凑格式存储,默认是64,即 value 内部有64个如下的成员就是使用线性紧凑存储zipmap,超过该值自动转成真正的 HashMap(ht)。
hash-max-zipmap-value 含义是当 value 这个 Map 内部的每一个成员值长度不超过
多少字节就会采用线性紧凑存储zipmap来节省空间。
以上2个条件任意一个条件超过设置值都会转换成真正的 HashMap,也就不会再节省内存了,可是也不是越大越好(空间和查改效率须要根据实际状况来权衡)
Redis 内部实现没有对内存分配方面作过多的优化,在必定程度上会存在内存碎片,不过大多数状况下这个不会成为 Redis 的性能瓶颈。
Redis 缓存了必定范围的常量数字做为资源共享,在不少数据类型是数值型则能极大减小内存开销,默认为1-10000,能够从新编译配置修改源代码中的一行宏定义 REDIS_SHARED_INTEGERS。
根据业务须要选择合适的数据类型,并为不一样的应用场景设置相应的紧凑存储参数。
当业务场景不须要数据持久化时,关闭全部的持久化方式能够得到最佳的性能以及最大的内存使用量。
若是须要使用持久化,根据是否能够容忍重启丢失部分数据在快照方式与语句追加方式之间选择其一,不要使用虚拟内存以及 diskstore 方式。
不要让你的 Redis 所在机器物理内存使用超过实际内存总量的3/5。
Redis 的持久化使用了 Buffer IO ,所谓 Buffer IO 是指 Redis 对持久化文件的写入和读取操做都会使用物理内存的 Page Cache,而当 Redis 的持久化文件过大操做系统会进行Swap,这时你的系统就会有内存还有余量可是系统不稳定或者崩溃的风险。