redis面试题3
redis的key如何命名
Redis的key虽然可以自定义,但最好遵循下面几个最佳实践约定
- 遵循基本格式:[业务名称]:[数据名称]:[id]
- 长度不超过44字节
- 不包含特殊字符
key的格式不一定非要三断,也可以四段,5断,不要太长即可,列入存储用户登录信息
优点
- 可读性强
- 避免key冲突
- 方便管理
- 更节省内存:key是string类型,底层编码包含int,embstr,raw三种.embstr小于44字节使用,采用连续内存空间,内存占用更小
拒绝使用bigkey
bigkey通常以key的大小和key中成员的数量来总和判定
- key本身的数据过大:一个String类型的key,它的值为5MB
- Key中的成员过多,一个zset类型的key,成员数量为10000个
- key中成员的数量过大,一个Hash类型的key,他的成员虽然只有1000个但这些成员的value值为100mb
推荐值
单个key的value小于10kb
对于集合类型的key建议元素数量小于1000(redis默认是500,可以修改为1000,hash-max-ziplist-entries)
bigkey的危害
- 网络阻塞:对bogkey执行读请求时,少量的qps就看你被沾满,导致redis实例乃至所在物理机变慢
- 数据倾斜:bigkey所在的redis实例内存使用率远超其他实例,无法使数据被分片的内存资源达到均衡
- Redis阻塞:对于元素比较多的hash,list,zset等做运算会耗时很久,让主线程被阻塞
- cpu压力:对bigkey的序列化和反序列化,会导致cpu占用率飙升,影响redis实例和本机其他应用
如何发现bigkey
redis-cli --bigkeys:利用redis-cli提供的--bigkeys参数,可以遍历分析所有key,并返回Key的整体统计信息与每个数据的Top1的big key;
scan扫描:自己编程,利用scan扫描Redis中的所有key,利用strlen、hlen等命令判断key的长度(此处不建议使用MEMORY USAGE);
如何删除bigkey
bigkey内存占用较多,即便是删除这样的key也需要很长的时间,导致Redis主线程阻塞,引发一系列问题
redis3.0以下版本
如果是集合类型,则便利bigKey的元素,逐个删除子元素,最后删除BigKey
Redis4.0以后
Redis在4.0以后提供了异步删除的命令:unlink
使用恰当的数据结构
- json字符串,简单粗暴,但数据耦合不够灵活
- 将字段打散:可以灵活访问对象,但是占用空间大,没办法做统一控制
- hash存储最优选择
hash存储底层使用ziplist,空间占用小,可灵活访问对象的任意字段,缺点是代码相对复杂
Hash类型的key数据量过大怎么办
假如有hash类型的key,其中有100万对field和value,field是自增id,这个key存在什么问题?如何优化?
存在的问题
hash的entry数量超过500时,会使用哈希表而不是ziplist,内存占用较多
可以通过hash-max-ziplist-entries配置entry上限。但是如果entry过多就会导致BigKey问题
拆分为string类型
- string结构底层没有太多内存优化,内存占用较多。
- 想要批量获取这些数据比较麻烦
比使用一个hash更大了
拆分为小哈希
拆分为小的hash,将 id / 100 作为key, 将id % 100 作为field,这样每100个元素为一个Hash
- 合理的拆分数据,拒绝BigKey
- 选择合适数据结构
- Hash结构的entry数量不要超过1000(默认为500)
- 设置合理的超时时间
redisKey的编码
字符串类型编码
- int:存储8个类型的长整型(long 2^63-1) 四个字节
- embstr:代表embstr:代表embstr格式的SDS(Simple Dynamic String 简单动态字符串),存储小于44个字节的字符串,只分配一次内存空间(Redis Object和SDS是连续的)
- row:存储大于44个字节的字符串(3.2版本之前是39个字节),需要分配俩次内存空间(分别为 Redis Object 和 SDS 分配空间)。
embstr结构
今天主要是来研究一下,embstr
编码的问题,先来看一下embstr的内存结构:
embstr分配的是连续的一块内存,包含redisObjec
和sds
,redisObject占用了16个字节,先来看一下redisObject的存储结构:
typedef struct redisObject {
/*对象的类型*/
unsigned type:4;
/*具体的数据结构,embstr、sds、*/
unsigned encoding:4;
/* 24位,对象最后一次被命令程序访问的时间,与内存回收有关*/
/* LRU time (relative to global lru_clock) or
* LFU data (least significant 8 bits frequency
* and most significant 16 bits access time). */
unsigned lru:LRU_BITS;
/*引用计数。当refcount为0的时候,表示该对象已经不被任何对象引用,则可以进行垃圾回收了*/
int refcount;
/*指向对象实际的数据结构*/
void *ptr;
} robj;
Reids3.2之前版本
因为在之前很多书上都会说存储小于39个字节,编码为embstr,超过39个字节,编码为raw。这是在3.2
版本之前。下面我们就先来看一下之前的版本:
/*
* 保存字符串对象的结构
*/
struct sdshdr {
// buf 中已占用空间的长度
unsigned int len;
// buf 中剩余可用空间的长度
unsigned int free;
// 数据空间
char buf[];
};
先来剖析一下为什么之前embstr在存储小于39个字符都是embstr
的,大于39个之后就是raw
的了。
sdshdr = unsigned int * 2 = 4 * 2 = 8
embstr : redisObject(16)+sds{char(39个) + 8 + 1} =64
此时可以看出如果是39个字符就是64个字节,那么也就是说只要是小于39个字符的,分配的空间都是64个字节。超过64个字节就为raw。
Reids3.2之后版本
本文选用的源代码是5.0.8版本,在 3.2 以后的版本中,SDS 又有多种结构(sds.h文件中):sdshdr5、sdshdr8、sdshdr16、sdshdr32、sdshdr64,用于存储不同的长度的字符串,分别代表 25=32byte,28=256byte,216=65536byte=64KB,232byte=4GB。先来查看一下sds的结构:
/* object.c */
#define OBJ_ENCODING_EMBSTR_SIZE_LIMIT 44 //制定长度
struct __attribute__ ((__packed__)) sdshdr8 {
//当前字符数组的长度
uint8_t len; /* used */
// 当前字符数组总共分配的内存大小
uint8_t alloc; /* excluding the header and null terminator */
// 当前字符数组的属性、用来标识到底是 sdshdr8 还是 sdshdr16 等
unsigned char flags; /* 3 lsb of type, 5 unused bits */
// 字符串真正的值
char buf[];
};
我们来看一下5.0的中的sds的结构,经过分析可以看到sds使用了uint8_t代替int,占用的内存空间更小了,下面计算一下sds的头部信息占用的空间大小:sdsdr8 = uint8_t (1个字节)* 2 + char(1个字节) = 3个字节
由于对sds的头部信息结构做了调整,所以在原来39的基础上又可以多存储了5个字节,为44个字节。
从2.4版本开始,redis开始使用jemalloc内存分配器。这个比glibc的malloc要好不少,还省内存。在这里可以简单理解,jemalloc会分配8,16,32,64等字节的内存。
所以embstr分配的最新内存为:
redisObject(16)+sds(uint8_t (1个字节)* 2 + char(1个字节)+8(最小分配内存) +1(0结束符))=28个字节;
总结
embstr
存储形式是这样一种存储形式,它将 RedisObject 对象头和 SDS 对象连续存在一起,使用 malloc 方法一次分配。embstr
的最小占用空间为19(16+3),而64-19-1(结尾的0)=44,所以empstr只能容纳44字节。
当字节数小于44时,分配的大小一直都是64个字节。一旦超过44个字节,整体的大小超过64个字节,在Redis中将认为是一个大的字符串,不再使用 emdstr 形式存储,存储结构将变为raw。
redis与memached的区别
redis和memached都是基于内存的数据库存储系统
Memmcached是高性能分布式内存缓存服务,本质上是一个内存key-value数据库,redis是一个开源的key-value存储系统
redis将大部分数据存储在内存中,支持的数据包括,字符串,set,list,zset,hash以及基于这些数据类型的相关操作
数据不同
A.memached仅支持key-value数据存储,redis支持String Hash List Set sortSet五中数据类型
B.memached不支持持久化,redis提供了持久化和复制等功能
内存管理机制
- 在redis中,并不是所有数据一直存储在内存中的,这是和memached最大的区别
- 当物理内存用完时,Redis可以将一些很久没用到的value交换到磁盘
- .Redis只会缓存所有的key的信息,如果Redis发现内存的使用量超过了某一个阀值,将触发swap的操作,Redis根据“swappability = age*log(size_in_memory)”计算出哪些key对应的value需要swap到磁盘。然后再将这些key对应的value持久化到磁盘中,同时在内存中清除。这种特性使得Redis可以保持超过其机器本身内存大小的数据。
- Memcached默认使用Slab Allocation机制管理内存,其主要思想是按照预先规定的大小,将分配的内存分割成特定长度的块以存储相应长度的key-value数据记录,以完全解决内存碎片问题。
- 从内存利用率来讲,使用简单的key-value存储的话,Memcached的内存利用率更高。而如果Redis采用hash结构来做key-value存储,由于其组合式的压缩,其内存利用率会高于Memcached。
性能
由于Redis只使用单核,而Memcached可以使用多核,所以平均每一个核上Redis在存储小数据时比Memcached性能更高。而在100k以上的数据中,Memcached性能要高于Redis,虽然Redis也在存储大数据的性能上进行了优化,但是比起Memcached,还是稍有逊色。
集群
memached本身不支持分布式,因此只能在客户端通过像一致性哈希这样的分布式算法来实现memcached的分布式存储
相较于Memcached只能采用客户端实现分布式存储,Redis更偏向于在服务器端构建分布式存储。最新版本的Redis已经支持了分布式存储功能。Redis Cluster是一个实现了分布式且允许单点故障的Redis高级版本,它没有中心节点,具有线性可伸缩的功能。下图给出Redis Cluster的分布式存储架构,其中节点与节点之间通过二进制协议进行通信,节点与客户端之间通过ascii协议进行通信。在数据的放置策略上,Redis Cluster将整个key的数值域分成4096个哈希槽,每个节点上可以存储一个或多个哈希槽,也就是说当前Redis Cluster支持的最大节点数就是4096。Redis Cluster使用的分布式算法也很简单:crc16( key ) % HASH_SLOTS_NUMBER。
Redis支持数据的备份,即master-slave模式的数据备份Memcached提供了cas命令保证多个并发访问操作同一份数据的一致性问题。
Redis提供了事务的功能,可以保证一串命令的原子性