Redis 是(key-value)的 NoSQL 数据库,所有的 key 都是 String ,它的 value 可以是 String、hash、list、set、zset(有序集合)、Bitmaps(位图)、HyperLogLog、GEO(地理信息定位)等数据类型,这些类型都支持 push/pop、add/remove 及取交集和差集。而且这些操作都是原子性的。
Redis 的数据是缓存在内存中,但是 Redis 会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件中。在此基础上实现了 master-slave (主从)同步
主从复制
Redis 提供复制功能,能实现多个相同数据的 Redis 副本,复制功能是分布式 Redis 的基础
删除数据命令
# 删除指定的 key 数据 del key # 根据 value 选择非阻塞删除,也就是现在是将 keys 从 keyspace 元数据中删除,真正的删除会在后续异步操作 unlink key
redis 中最基本的数据结构,所有的 key 都是 String 。String 类型的 Value 可以是 String、数字、jpg图片或者序列化的对象(值不能超过 512MB)
set key value [ex seconds][px milliseconds][nx|xx]
: 设置给定键和值
get key
: 获取值
del key
:删除存储在给定键中的值
incr key
: 将 key 对应的值加1
decr key
: 将 key 对应的值减1
incrby key amount
: 将key 对应的值加上整数
decrby key amount
:将key 对应的值减去整数
计数器: 实现快速计数、查询缓存,同时数据可以异步落地到其他数据源。
共享Session:分布式服务器将用户的 Session 进行集中的管理,每次用户更新或者查询登录信息都直接从 Redis 中集中获取。
哈希类型指的是 value 本身又是一个键值对结构,比如 value = {{field1, value1}, ... {fieldN, valueN}}。
hset hash-key sub-key1 value1
:添加键值对hget hash-key key1
: 获取制定散列键的值hgetall hash-key
:获取哈希中包含的所有键值对hdel hash-key sub-key1
: 在哈希中移除这个键Redis 中的 List 采用双端链表来实现,可以用来存储多个有序的字符创,列表最多可以存储 2^32 - 1 个元素(element)。可以对列表两端插入(push)和弹出(pop),还可以获取制定范围的元素列表,获取指定索引下标的元素等。列表是一种比较灵活的数据结构,它可以充当栈和队列的角色。
rpush, lpush 分别是右边和左边插入,linsert 命令会从列表中找到等于某个值的元素,在其前或者后插入新的元素。可以转换成其他的数据结构:
lpush + brpop
组合可以实现阻塞队列,生产者使用 lpush 从左侧插入元素,多个消费者使用 brpop 阻塞式抢列表尾部的元素。保证消费的负载均衡和高可用性set 类型是用来保存多个字符串元素,但是 set 中不允许有重复元素,并且集合中的元素是无序的,不能通过索引下标获取元素。它的底层是通过哈希表来实现的,因此添加、删除、查找的复杂度都是 O(1)
sadd key value
: 向集合中添加一个或者多个成员scard key
: 获取集合中的成员数smember key member
: 返回集合中的所有成员sismember key member
: 判断 member 元素是否是集合 key 的成员有序集合 zset 相对于 set 而言,其内部的元素可以进行排序,它是通过给每个元素设置一个分数来作为排序的依据。
zadd zset-key int member1
: 将一个带有给定分值的成员添加到有序的集合中zrange zset-key 0-1
: 根据元素在有序集合中所处的位置,从有序集合中获取对应的元素zrem zset-key member1
: 如果给定元素存在于有序集合中,就移除该元素HyperLogLog并不是一种新的数据结构(实际类型为字符串类 型),而是一种基数算法,通过HyperLogLog可以利用极小的内存空间 完成独立总数的统计,比如注册IP u数,每日访问IP 数等等。它是一个基于基数估算的算法,只能比较准确的估算出基数,可以使用少量固定的内存去存储并识别集合中的唯一元素。而且这个估算的技术并不一定准确,它是一个带有0.81%标准错误的近似值(对于一些可以接受容错的业务场景可以忽略不计)
例如2016-03-06的访问用户是 uuid-1、uuid-2、uuid-3、uuid-4,2016-03-05的访问用户是uuid-4、uuid-5、uuid-6、uuid-7,如图所示。
pfadd : 用于在基数统计中添加元素,添加成功会返回1
pfcount:用于计算一个或者多个 HyperLogLogs 的独立总数
pfmerge:求出多个HyperLogLogs 的并集并赋值给 destkey
它本身不是一种数据结构,实际上就是字符串,但是它可以对字符串的位进行操作
Bitmaps 相当于一个以位为单位的数组,数组的每个单元只能存储0 和 1 , 数组的下标在 Bitmaps 中叫做偏移量。
setbit key offset value
: 设置键和偏移量的值getbit key offset
: 获取键的第 offset 位的值bitcount key
: 统计该键的次数值Redis3.2版本提供了GEO(地理信息定位)功能,支持存储地理位 置信息用来实现诸如附近位置、摇一摇这类依赖于地理位置信息的功能
geopos: 获取地理位置信息
geodist: 获取两个地理位置的距离
georadius: 获取范围内的信息位置集合-->附近的人
geohash: 将二维经纬度转换为一维的字符串
zrem: 删除地理位置信息(实际上是利用 zset 中的命令实现对位置信息的删除)
为什么 Redis 会设计 RedisObject 对象,因为操作数据类型的命令除了要对键的类型进行检查以外,还需要根据数据类型的不同编码进行多态处理,所以 Redis 构建了自己的类型系统,主要有:
/* * Redis对象 */ typedef struct redisObject { //类型 unsigned type:4; //编码方式 unsigned encoding:4; //LRU 记录最后一次访问时间 unsigned lru:LRU_BITS; //引用计数 int refcount; //指向底层数据结构实例 void *ptr } robj;
记录了对象所保存的值类型,也就是常用的五个数据类型
/* * 对象类型 */ #define OBJ_STRING 0 // 字符串 #define OBJ_LIST 1 // 列表 #define OBJ_SET 2 // 集合 #define OBJ_ZSET 3 // 有序集 #define OBJ_HASH 4 // 哈希表
记录了对象所保存的值的编码,表示数据类型对应的编码类型
/* * 对象编码 */ #define OBJ_ENCODING_RAW 0 /* Raw representation */ #define OBJ_ENCODING_INT 1 /* Encoded as integer */ #define OBJ_ENCODING_HT 2 /* Encoded as hash table */ #define OBJ_ENCODING_ZIPMAP 3 /* 注意:版本2.6后不再使用. */ #define OBJ_ENCODING_LINKEDLIST 4 /* 注意:不再使用了,旧版本2.x中String的底层之一. */ #define OBJ_ENCODING_ZIPLIST 5 /* Encoded as ziplist */ #define OBJ_ENCODING_INTSET 6 /* Encoded as intset */ #define OBJ_ENCODING_SKIPLIST 7 /* Encoded as skiplist */ #define OBJ_ENCODING_EMBSTR 8 /* Embedded sds string encoding */ #define OBJ_ENCODING_QUICKLIST 9 /* Encoded as linked list of ziplists */ #define OBJ_ENCODING_STREAM 10 /* Encoded as a radix tree of listpacks */
它指向实际保存值的数据结构,而数据结构类型是由前面的 encoding 和 type 两个属性来决定。如下图,数据类型和编码类型决定指向实际的数据结构。
记录的是对象最后一次被命令程序访问的时间,那么如何实现对对象的回收,这里引入一个概念:空转时长
空转时长,也就是当前系统时间减去 键的值对象的 LRU 时间。如果服务器用于回收内存的算法是 Volatile-lru 或者 allkeys-lru。那么当服务器占用的内存树超过了 maxmemory 选项所设置的上限值时,空转时长较高的那部分键会优先被服务器所释放。
用于计数,对指向这个对象的引用计数。
比如创建了一个值为 100 的 key A ,使用 OBJECT REFCOUNT 命令查看 key A 的值对象的引用计数 refcount ,发现引用计数为 2,说明这个值对象被两个程序所引用,两个程序共享了这个值对象的 key
那么当对象的 refcount 值为 0 时,这个对象将会被内存回收释放,这也是对象的销毁机制。(对应 JVM 里面的引用计数法标记)
redis 当执行一个处理数据类型命令时,比如 LPOP key
命令redis 执行的步骤:
共享对象的出现是为了避免重复分配的麻烦。通过 refcount 来表示对象所引用的次数。比如创键一个 值为 100 的 key A,然后再创建一个值为 100 的 key B ,这个时候共享对象的引用计数值变为了 3
redis> SET A 100 OK redis> SET B 100 OK redis> OBJECT REFCOUNT A (integer) 3
此外共享对象不单单只有字符串键可以使用, 那些在数据结构中嵌套了字符串对象的对象(linkedlist
编码的列表对象、 hashtable
编码的哈希对象、 hashtable
编码的集合对象、以及 zset
编码的有序集合对象)都可以使用这些共享对象。
当服务器考虑将一个共享对象设置为键的值对象时, 程序需要先检查给定的共享对象和键想创建的目标对象是否完全相同, 只有在共享对象和目标对象完全相同的情况下, 程序才会将共享对象用作键的值对象, 而一个共享对象保存的值越复杂, 验证共享对象和目标对象是否相同所需的复杂度就会越高, 消耗的 CPU 时间也会越多:
因此, 尽管共享更复杂的对象可以节约更多的内存, 但受到 CPU 时间的限制, Redis 只对包含整数值的字符串对象进行共享。
前面谈到过,redisObject 中带有一个 refcount 属性,表示这个对象被引用了多少次。