降低Redis内存的占用

1.原理

短结构

Redis为列表、集合、散列和有序集合提供了一组配置选项,让Redis以跟节约内存的方式存储这些变量

阿里云主从版提供的选项(全是和短结构有关的,ziplist可以说是除了hash字典之外最常用的结构)

image

在列表、散列、有序集合的长度较短或者体积较小的时候,redis会使用压缩列表(ziplist)代替通常情况下这三种结构的底层实现方式作为这几种结构的底层实现。

配置:

  • entries:最大元素数量
  • value:压缩列表的每个节点的最大体积

当任一条件被突破时,Redis就会把底层实现改为通常的结构,而且当数据被删除之后也不会再次使用ziplist

体积较小的集合使用intset来作为底层实现

条件:

  • 整数集合包含的所有成员都可以被解释为十进制数
  • 这些整数又处于平台的有符号整数范围之内
  • 集合成员数量足够少

短结构是以上几种结构的紧凑表示方式

减少键的长度也是应该注意的一个点。

短结构ziplist

Redis中ziplist的定义(序列化字符串)和接口

unsigned char *ziplistNew(void);
unsigned char *ziplistPush(unsigned char *zl, unsigned char *s, unsigned int slen, int where);
unsigned char *ziplistIndex(unsigned char *zl, int index);
unsigned char *ziplistNext(unsigned char *zl, unsigned char *p);
unsigned char *ziplistPrev(unsigned char *zl, unsigned char *p);
unsigned int ziplistGet(unsigned char *p, unsigned char **sval, unsigned int *slen, long long *lval);
unsigned char *ziplistInsert(unsigned char *zl, unsigned char *p, unsigned char *s, unsigned int slen);
unsigned char *ziplistDelete(unsigned char *zl, unsigned char **p);
unsigned char *ziplistDeleteRange(unsigned char *zl, unsigned int index, unsigned int num);
unsigned int ziplistCompare(unsigned char *p, unsigned char *s, unsigned int slen);
unsigned char *ziplistFind(unsigned char *p, unsigned char *vstr, unsigned int vlen, unsigned int skip);
unsigned int ziplistLen(unsigned char *zl);
size_t ziplistBlobLen(unsigned char *zl);

压缩列表由节点序列和四个控制位组成。

影响性能的因素:每次读取数据时都需要对进行解码,写入也需要进行局部的重新编码,并且有可能触发连锁更新,但是这些操作在对小列表进行处理时对性能的损耗并不很明显(平均O(N))。

短结构intset

Redis中整数集合的定义和接口,可见其最底层实现就是一个有序的C语言数组。

typedef struct intset {
    
    // 编码方式
    uint32_t encoding;

    // 集合包含的元素数量
    uint32_t length;

    // 保存元素的数组
    int8_t contents[];

} intset;

intset *intsetNew(void);
intset *intsetAdd(intset *is, int64_t value, uint8_t *success);
intset *intsetRemove(intset *is, int64_t value, int *success);
uint8_t intsetFind(intset *is, int64_t value);
int64_t intsetRandom(intset *is);
uint8_t intsetGet(intset *is, uint32_t pos, int64_t *value);
uint32_t intsetLen(intset *is);
size_t intsetBlobLen(intset *is);

影响性能的因素:对整数集合进行插入或者删除操作时需要进行数据移动,并有可能引起底层升级。

随着短结构的体积变得越来越大,操作这些结构的速度也会变得越来越慢,所以可以引入分片技术以追求性能和空间之前的平衡。

2.分片结构

  • 特别适合分片的场景:执行分片版命令的时候只需要访问指定分片即可。
  • 特别不适合分片的场景:需要访问指定分片,完事还得进行归并聚合的操作。
  • 可以通过分片提升性能的场景:搜索
  • 替代分片的一种场景:只对前n位和后n位操作的有序集合(用单独维护的两个最大有序集合和最小有序集合实现)。
  1. 对列表进行分片

  2. 有序集合进行分片:
    有序集合的大部分操作都属于特别不适合分片的场景。

  3. 分片式散列:将字符串键存储到散列里面也可以明显降低内存占用

    1. 实现分片方法即可
asbtract class ShardHlist { 
/*
** $base 基础散列 
** $key 将要保存到分片散列里面的键
** $total_elements 预计元素总量
** $shard_size 预期分片大小
*/
abstract public function shard_key($base, $key, $total_elements, $shard_size);
abstract public function shard_hset($shard_key, key, value, $total_elements, $shard_size);
abstract public function shard_hset($shard_key, key, $total_elements, $shard_size);
}

如果不是辅助缓存那么不应该随便更改$total_elements 和 $shard_size,一旦不得不更改需要resharding方法将数据从旧的分片迁移到新的分片

  1. 分片集合
    shard_add、shard_rem

3.二进制位和字节打包用来进一步降低存储空间

推荐阅读更多精彩内容