Redis中key的特点
Redis中的key是二进制安全的,意味着你可以使用任何二进制序列作为一个key,从“foo”这样的字符串到JPEG文件的内容。空字符串也是一个有效的key(Redis keys are binary safe, this means that you can use any binary sequence as a key, from a string like "foo" to the content of a JPEG file. The empty string is also a valid key)。
key的其他规则:
- 特别长的key不是一个好主意。例如,一个1024字节的key不仅在内存方面是一个坏主意,而且在数据集中查找key可能需要进行几次代价高昂的key的比较。即使现在的任务是匹配一个比较大的value是否存在,对它进行hash作为key是一个好主意,特别是从内存和带宽的角度来看。
- 非常短的key常常也不是一个好主意。例如"u1000flw"比 "user:1000:followers"更短,但是,后者可读性更好。尽管短key明显消费更好的内存,你的工作是找到合适的平衡。
- 尝试坚持一个模式。例如"object-type:id" 模式的例子:"user:1000"。
- key最大不能超过521MB
前言
字符串类型是Redis中最简单的数据类型,Redis中值类型为字符串时,最大不能超过512MB。Redis的字符串类型是二进制安全的,除了普通字符串(整型,浮点型,json,xml),还可以存储图片、音视频。
常用命令
设置值
set key value [expiration EX seconds|PX milliseconds] [NX|XX]
set命令的几个选项
-
EX seconds:为键设置秒级过期时间
-
PX milliseconds:为键设置ms级过期时间
-
NX:仅当key不存在时,可以设置成功,用于新增
-
XX:仅当key存在时,可以设置成功,用于更新
NX与XX参数示例:当key1不存在时,使用XX参数set key1失败,通过NX参数set key1成功,之后清空数据,再次使用NX参数,set key1成功
127.0.0.1:6379> flushall
OK
127.0.0.1:6379> set key1 value1 XX
(nil)
127.0.0.1:6379> set key1 value1
OK
127.0.0.1:6379> set key1 value2 NX
(nil)
127.0.0.1:6379> flushall
OK
127.0.0.1:6379> set key1 value2 NX
OK
EX参数示例:设置key1过期时间为3s,之后通过ttl key1获取key1的过期时间剩余1s,1s之后,使用keys *获取所有的key
127.0.0.1:6379> set key1 value1 EX 3
OK
127.0.0.1:6379> ttl key1
(integer) 1
127.0.0.1:6379> keys *
(empty list or set)
127.0.0.1:6379>
批量设置值
mset key value [key value ...]
示例:
127.0.0.1:6379> mset a 1 b 2 c 3
OK
127.0.0.1:6379> keys *
1) "a"
2) "b"
3) "c"
设置值及其过期时间
setex key seconds value
示例:
127.0.0.1:6379> setex key1 3 value1
OK
127.0.0.1:6379> ttl key1
(integer) 1
127.0.0.1:6379> keys *
(empty list or set)
如果key 不存在,则设置值
setnx key value
示例:
127.0.0.1:6379> set key1 value1
OK
127.0.0.1:6379> setnx key1 value2
(integer) 0
127.0.0.1:6379> flushall
OK
127.0.0.1:6379> setnx key1 value2
(integer) 1
获取值
示例:
get key
127.0.0.1:6379> set key1 value1
OK
127.0.0.1:6379> get key1
"value1"
批量获取值
mget key [key ...]
示例:
127.0.0.1:6379> mset a 1 b 2 c 3
OK
127.0.0.1:6379> mget a b c d
1) "1"
2) "2"
3) "3"
4) (nil)
计数操作(原子命令)
自增操作
incr key
示例:当key不存在时,默认从0递增,操作成功,返回最新的值
127.0.0.1:6379> flushall
OK
127.0.0.1:6379> incr key1
(integer) 1
127.0.0.1:6379> incr key1
(integer) 2
127.0.0.1:6379> incr key1
(integer) 3
incrby key increment
示例:当key不存在时,默认key的值为0,操作成功,返回最新的值
127.0.0.1:6379> flushall
OK
127.0.0.1:6379> incrby key1 3
(integer) 3
127.0.0.1:6379> incrby key1 3
(integer) 6
127.0.0.1:6379> incrby key1 4
(integer) 10
自减操作
decr key
decrby key increment
浮点类型自增操作
incrbyfloat key increment
bit操作
BITOP operation destkey key [key ...]
operation 可以是 AND 、 OR 、 NOT 、 XOR 四种操作中的任意一种
BITOP AND destkey key [key ...] ,对一个或多个 key 求逻辑与,并将结果保存到 destkey
BITOP OR destkey key [key ...] ,对一个或多个 key 求逻辑或,并将结果保存到 destkey
BITOP XOR destkey key [key ...] ,对一个或多个 key 求逻辑异或,并将结果保存到 destkey
BITOP NOT destkey key ,对给定 key 求逻辑非,并将结果保存到 destkey
AND 示例:a的ascii码为97(0110 0001),b的ascii码为98(0110 0010),所以求与的结果是0110 0000,即ascii码为96的字符("`")
127.0.0.1:6379> mset key1 a key2 b
OK
127.0.0.1:6379> BITOP AND key1 key1 key2
(integer) 1
127.0.0.1:6379> get key1
"`"
OR 示例:a|b=c
127.0.0.1:6379> mset key1 a key2 b
OK
127.0.0.1:6379> BITOP OR key1 key1 key2
(integer) 1
127.0.0.1:6379> get key1
"c"
XOR 示例:a^b=\x03 异或的规则是相同等于0,不同等于1,0110 0001^0110 0010=\x03
127.0.0.1:6379> mset key1 a key2 b
OK
127.0.0.1:6379> BITOP XOR key1 key1 key2
(integer) 1
127.0.0.1:6379> get key1
"\x03"
NOT 示例:a按位取反,结果是\x9e
127.0.0.1:6379> set key1 a
OK
127.0.0.1:6379> BITOP NOT key1 key1
(integer) 1
127.0.0.1:6379> get key1
"\x9e"
SETBIT操作
setbit key offset value
示例:将a的从左到右第6位设置为1,第7位设置为0,则a -> b
127.0.0.1:6379> set key1 a
OK
127.0.0.1:6379> setbit key1 6 1
(integer) 0
127.0.0.1:6379> setbit key1 7 0
(integer) 1
127.0.0.1:6379> get key1
"b"
GETBIT操作
getbit key offset
示例:
127.0.0.1:6379> set key1 a
OK
127.0.0.1:6379> getbit key1 6
(integer) 0
127.0.0.1:6379> getbit key1 7
(integer) 1
BITCOUNT操作
含义:计算给定字符串中,被设置为 1 的比特位的数量(索引的单位为byte)
bitcount key [start end]
示例:
127.0.0.1:6379> set key1 ab
OK
127.0.0.1:6379> bitcount key1 0 1 #a和b中bit位为1的数量
(integer) 6
127.0.0.1:6379> bitcount key1 0 0 #a中bit位为1的数量
(integer) 3
127.0.0.1:6379> bitcount key1 0 -1 #end为-1表示查找到最后一个字节
(integer) 6
不常用命令
追加命令
append key value
示例:
127.0.0.1:6379> set key1 a
OK
127.0.0.1:6379> append key1 2
(integer) 2
127.0.0.1:6379> get key1
"a2"
字符串长度
strlen key
示例:
127.0.0.1:6379> set key1 abc
OK
127.0.0.1:6379> strlen key1
(integer) 3
获取指定范围的字符
getrange key start end
示例:
127.0.0.1:6379> set key1 abcdefg
OK
127.0.0.1:6379> getrange key1 2 4
"cde"
设置指定范围的字符
setrange key offset value
示例:
127.0.0.1:6379> setrange key1 2 123
(integer) 7
127.0.0.1:6379> get key1
"ab123fg"
设置新值并返回旧值
getset key value
示例:
127.0.0.1:6379> set key1 abc
OK
127.0.0.1:6379> getset key1 123
"abc"
存储实现
Redis是KV数据库,通过hashtable实现,每个键值对都有一个dictEntry(dict.h)
typedef struct dictEntry {
void *key; //key定义
union {
void *val; //value定义
uint64_t u64;
int64_t s64;
double d;
} v;
struct dictEntry *next; //下一个键值对节点
} dictEntry;
redis中的字符串类型数据存储没有使用C语言中的字符串,而是存储在自定义的SDS(Simple Dynamic String)中。
redis中常用的5种数据类型的值都保存在redisObject中,其中的*ptr指向实际的数据结构,如果值时String,则指向的就是SDS
redisObject的定义在server.h中
typedef struct redisObject {
unsigned type:4; //对象的类型, 包括: OBJ_STRING、 OBJ_LIST、 OBJ_HASH、 OBJ_SET、 OBJ_ZSET
unsigned encoding:4; //具体的数据结构
unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or
* LFU data (least significant 8 bits frequency
* and most significant 16 bits access time). */
int refcount; //引用计数
void *ptr; //指向对象实际数据结构
} robj;
内部编码
字符串类型的内部编码有3种
- int,存储8个字节的长整型(long,2^63-1)
- embstr,存储小于44个字节的字符串(3.2 版本之前是 39 字节)
- raw,存储大于44个字节的字符串(3.2 版本之前是 39 字节)
其中,embstr和raw都是使用SDS存储
字符串的实现
SDS是redis中字符串的实现,在3.2之后的版本,SDS有多种结构(sds.h):sdshdr5、sdshdr8、sdshdr16、sdshdr32、sdshdr64。
/* Note: sdshdr5 is never used, we just access the flags byte directly.
* However is here to document the layout of type 5 SDS strings. */
struct __attribute__ ((__packed__)) sdshdr5 {
unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr8 {
uint8_t len; /* used */ //字符串长度,buf已用长度
uint8_t alloc; /* excluding the header and null terminator */ //为buf分配的总长度,剩余长度=alloc-len
unsigned char flags; /* 3 lsb of type, 5 unused bits */ //低3位表示类型标志
char buf[]; //保存具体的字符串
};
struct __attribute__ ((__packed__)) sdshdr16 {
uint16_t len; /* used */
uint16_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32 {
uint32_t len; /* used */
uint32_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr64 {
uint64_t len; /* used */
uint64_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[];
};
为什么使用SDS实现字符串?
C语言只能使用字符数组实现字符串
- 使用字符数组必须分配足够的空间,否则会内存溢出
- 如果要获取字符长度,必须遍历字符数组,时间复杂度是O(n)
- 对字符串的N次修改(导致字符串长度变化)操作必须重新分配N次内存空间
- 读取字符时,读取到第一个\0标记字符串结束,因此不能存储图片、视频等二进制文件,二进制不安全
SDS的特点:
- 不用担心内存溢出,SDS会自动扩容
- 获取字符串长度时间复杂度为O(1),因为保存了
len属性 - 减少修改字符串长度时所需的内存重分配次数,通过“空间预分配(sdsMakeRoomFor)”和“惰性空间释放”,防止多次重分配内存
- 通过
len属性判断是否结束,二进制安全
embstr和raw的区别?
- embstr的使用只分配1次内存空间(因为redisObject和SDS是连续的),而raw需要分配2次内存空间(分别为redisObject和SDS分配空间)
- embstr创建时分配1次内存空间,所以删除时也比raw少释放一次内存空间,embstr执行查找更方便
- embstr是只读的,如果字符串长度增加需要分配内存时,redisObject和SDS都需要重新分配内存
int、embstr和raw在什么情况下转换?
- 当int数据不再是整型,或int值超过long的范围(-2^63 ~ 2^63-1)时,自动转化为embstr
示例1:值超过int范围
127.0.0.1:6379> set key1 9223372036854775807
OK
127.0.0.1:6379> object encoding key
"int"
127.0.0.1:6379> set key1 9223372036854775808
OK
127.0.0.1:6379> object encoding key1
"embstr"
示例2: int数据不再是整型
127.0.0.1:6379> set key1 123
OK
127.0.0.1:6379> object encoding key1
"int"
127.0.0.1:6379> set key1 abc
OK
127.0.0.1:6379> object encoding key1
"embstr"
- 当对int或embstr执行追加操作时,自动转化为raw
示例3:
127.0.0.1:6379> set key1 123
OK
127.0.0.1:6379> object encoding key1
"int"
127.0.0.1:6379> append key1 a
(integer) 4
127.0.0.1:6379> object encoding key1
"raw"
127.0.0.1:6379> flushall
OK
127.0.0.1:6379> set key1 abc
OK
127.0.0.1:6379> object encoding key1
"embstr"
127.0.0.1:6379> append key1 d
(integer) 4
127.0.0.1:6379> object encoding key1
"raw"
注意:编码转换在redis写入数据时完成,且转换过程不可逆,只能从小编码转化为大编码(不包括重新set)
应用
Bit操作应用
- 统计活跃用户
- 用户在线状态
- 用户签到
Bitmap的优势
- 节约空间
- 位操作,运算速度快
- 更新和查询时间复杂度为O(1)
- 方便扩容
以用户签到为例:
如果使用int类型记录用户是否签到,则每个用户需要占用4个字节,如果是1亿用户量,每天占用的内存就是100000000*4/1024/1024=381MB,
如果使用bitmap来记录,每个用户占用1bit,8个用户占用一个字节内存,与一个用户占用4个字节的int相比,内存占用比是1:32,每天占用内存就是100000000/8/1024/1024=11.9MB;
如果要统计本周每天都有签到的用户,使用BITOP AND destkey key [key ...]命令(BITOP "AND" "7_days_both_online_users" "day_1_online_users" "day_2_online_users" ... "day_7_online_users)
bitmap的最大offset等于2^32-1(512MB)。在一台2010MacBookPro上,offset为2^32-1(分配512MB)需要~300ms,offset为2^30-1(分配128MB)需要~80ms,offset为2^28-1(分配32MB)需要~30ms,offset为2^26-1(分配8MB)需要8ms。
INCR 操作应用
- 用于生成全局ID,秒杀场景限流
- 统计文章阅读量,微博点赞数