Redis是目前最火爆的内存数据库之一,通过在内存中读写数据,大大提高了读写速度,可以说Redis是实现高并发服务不可或缺的一部分。通常我们使用Redis时,会接触到5种数据类型(字符串、哈希、列表、集合、有序集合),丰富的类型可以协助我们实现各种业务场景需求。知其然知其所以然,进一步了解Redis的内存模型及相关持久化,事物,锁的实现方式和原理可以进一步让我们深入理解Redis,在使用时选择最佳的实现方式,在遇到问题时也能有分析解决的思路;
先介绍一下Redis对外提供支持的数据类型, String, List, Hash, Set, Sorted-Set;
字符串是最常用且简单的类型,Redis中key是字符串类型的,如果value也用字符串,这样我们可以做到一个字符串到另一个字符串的映射,这个在缓存html页面,或者将一个大对象序列化为字符串,都是非常有用的;
List即一系列有序的对象,通常有Arrays和Linked-List两种实现方式,且两者相关属性有所不同;Redis中的list是基于linked-list实现的,这样就保证了即使在有百万条数据的list中,在头部或者尾部插入一条新记录也会在很短的时间内完成,相对应的缺点就是当按照数组索引去访问元素时,就会随着lists的增大而效率变低;但是为什么这样实现的原因还是在于为数据库提供较高的插入效率是至关重要的;如果需要快速访问一个大list里面中间的元素时,可以考虑使用下文会提及的有序集合;
应用场景
Hashes是一个键值(key=>value)对集合,很多场景下都可能会用到它。
应用场景
无序的字符串集合(不重复),底层是通过hash表实现,对value计算hash可以实现快速判重;
有序的字符串集合(不重复),内部使用HashMap和跳跃表(SkipList)来保证数据的存储和有序,HashMap里放的是成员到score的映射,而跳跃表里存放的是所有的成员,排序依据是HashMap里存的score,使用跳跃表的结构可以获得比较高的查找效率,并且在实现上比较简单。
上面我们罗列了Redis对外提供可直接使用的几种数据类型,接下来我们了解一下Redis的内存模型,即在Redis内部是如何实现丰富的数据类型及相关操作;
在客户端通过redis-cli连接服务器后,通过info命令查看内存使用情况: info memory
Redis作为内存数据库,在内存中存储的内容主要是数据(键值对);通过前面的叙述可以知道,除了数据以外,Redis的其他部分也会占用内存。
作为数据库,数据是最主要的部分;这部分占用的内存会统计在used_memory中。
Redis使用键值对存储数据,其中的值(对象)包括5种类型,即字符串、哈希、列表、集合、有序集合。这5种类型是Redis对外提供的,实际上,在Redis内部,每种类型可能有2种或更多的内部编码实现;此外,Redis在存储对象时,并不是直接将数据扔进内存,而是会对对象进行各种包装:如redisObject、SDS等;这篇文章后面将重点介绍Redis中数据存储的细节。
Redis主进程本身运行肯定需要占用内存,如代码、常量池等等;这部分内存大约几兆,在大多数生产环境中与Redis数据占用的内存相比可以忽略。这部分内存不是由jemalloc分配,因此不会统计在used_memory中。
补充说明:除了主进程外,Redis创建的子进程运行也会占用内存,如Redis执行AOF、RDB重写时创建的子进程。当然,这部分内存不属于Redis进程,也不会统计在used_memory和used_memory_rss中。
缓冲内存包括客户端缓冲区、复制积压缓冲区、AOF缓冲区等;其中,客户端缓冲存储客户端连接的输入输出缓冲;复制积压缓冲用于部分复制功能;AOF缓冲区用于在进行AOF重写时,保存最近的写入命令。在了解相应功能之前,不需要知道这些缓冲的细节;这部分内存由jemalloc分配,因此会统计在used_memory中。
内存碎片是Redis在分配、回收物理内存过程中产生的。例如,如果对数据的更改频繁,而且数据之间的大小相差很大,可能导致redis释放的空间在物理内存中并没有释放,但redis又无法有效利用,这就形成了内存碎片。内存碎片不会统计在used_memory中。
内存碎片的产生与对数据进行的操作、数据的特点等都有关;此外,与使用的内存分配器也有关系:如果内存分配器设计合理,可以尽可能的减少内存碎片的产生。后面将要说到的jemalloc便在控制内存碎片方面做的很好。
如果Redis服务器中的内存碎片已经很大,可以通过安全重启的方式减小内存碎片:因为重启之后,Redis重新从备份文件中读取数据,在内存中进行重排,为每个数据重新选择合适的内存单元,减小内存碎片。
在一个论坛的首页,展示最新发布的20片帖子,我们在每一篇帖子发布后,将帖子的简要信息及id存储在Redis-List里面,用lpush的命令,这样保证了从左至右就是按照帖子的发布时间倒序排列的;这样无论取最新20,50,100都可以用命令lrange来读取,或者用命令ltrim对list进行削整,保存最新的帖子,淘汰较早的帖子
lpush post "生活大爆炸大结局"
lpush post "华为备胎芯片转正"
lpush post "滴滴回收违规单车"
lpush post "合照杀手王祖贤"
lpush post "Evil Genius 战队勇夺TI5冠军"
lrange post 0 2
-------
1) "Evil Genius 战队勇夺TI5冠军"
2) "合照杀手王祖贤"
3) "滴滴回收违规单车"
-------
llen post
(integer) 5
ltrim post 0 2
llen post
(integer) 3
zadd ranking 9987 "miracle"
zadd ranking 9762 "天命"
zadd ranking 9521 "高手坟墓"
zadd ranking 9785 "RAMZES666"
zadd ranking 9589 "中华小当家"
zadd ranking 9129 "fly"
zadd ranking 9899 "Arteezy"
// 降序输出天梯排名前五(降序)
zrevrange ranking 0
// 升序输出
zrange ranking 0 4
注意: 这里放进set中的值key不是分数而是内容, 所以当我们放入已经存在的值时, 会触发重新排序;