Redis

Redis:

原理:数据结构、过期机制、淘汰机制

实践:内存分析、最佳实践


数据结构:

最基本的数据结构(最基本、最简洁)

RedisObject:

typedef struct redisObject{
    unsigned type:4;        #  type  4bit
    unsigned encoding:4;  # encoding  4bit 
    unsigned lru:24;     #类似于  最近访问时间这种  用于做key淘汰
    int refcount;       #引用计数 redis里面的数据可以通过引用计数进行共享
    void *ptr;        #  数据结构可以存任意类型的数据
}robj;

redis 里面有不超过16种的类型;有不超过16种的编码方式,一种类型可能有多种编码方式,共享的数据是可以共享的

数据结构的第一个成员:type

redis 实际上有五种数据类型:  List、 Set、 String、 Sorted Set、Hash

编码方式:encoding

数据类型与编码方式的对应关系:

数据类型都是O1的

String - RAW(裸的--纯字符串的结构)

RedisObject【type: String/Encoding:RAW/LUR/RefCount/ptr】ptr-->指向一个叫sds的数据结构:sds是redis里面保存一般字符串的数据结构,sds包含了(头):Len:45/Free:3

redis里面字符串比较小(仅限 <= 39 )的时候的优化

String - EMBSTR【type: String/Encoding:EMBSTR/ptr sds:len/sds:free/sds:buf】

obj和字符串在同一个连续的内存块上

String - INT【type:String/Encoding:INT...ptr(INT值)】ptr的值直接代表对应的Value

共享条件(0-10000,LUR无意义)

 String编码方式的总结:

  *整数类型的value比普通的value节省内存

  *0-10000,LUR无效的情况下String Object无需额外创建,既省内存又省时间

   *长度<=39的使用EMBSTR编码,效率更高

ZIPLIST/INTSET:压缩列表(数据量小的时候用)

*紧凑连续的一段内存;5-10倍的压缩比

RedisObject【type:List/Set/Hash/Sorted Set,Encoding:ZIPLIST/INTSET...ptr】

  ptr-->ziplist[zlbytes/zltail/entrey1/entry2...entryn/zlend]

HT SIZE&REHASH

*HT默认初始大小为4

*负载因子超出合理范围(0.1-5)时进行扩缩容(rehash)

*HT[0]、HT[1]

*一次性rehash太多的key可能导致服务长时间不可用,resdis采用渐进式rehash分批进行

DICT

*DICT对HT进行封装

*读写均为O(1)

*Redis中广泛存在

  哈希对象,集合对象和有序集合对象

  db[n]->dict,db[n]->expire

DICT渐进式rehash

*每次对字典执行添加、删除、查找或者更新操作时,除了执行指定的操作外,还会顺带将HT[0],哈希表在rehashindex索引上的所有键值对rehash到HT[1]上,并将rehashindex的值增1;

*直到整个HT[0]全部完成rehash后,rehashindex设置为-1,释放HT[0],HT[1]置为HT[0],在HT[1]创建一个新的空白表

SKIPLIST :跳跃表(有序集合)

*跳跃表:随机化的平衡树

*while ((random) < (0.25 * 0xFFFF)) level += 1

*第n+1层的节点数目为第n层的1/4

*性能与平衡树近似O(lgn)

性能:

Redis的过期机制:

*失效性数据,比如限时优惠活动,缓存或者验证码可以采用Redis过期机制进行管理

*expire|pexpire key ttl

*typedef struct redisDb{

  dict *dict;//所有的 k v

  dict *expire;//设置过期时间的kv

  }redisDb;

*db->expire会复用db->dict中的key,value对象

*访问key时:expireIFNeeded(db,key);

*一次事件循环结束,进入事件侦听前:

  activeExpireCycle(ACTIVE_EXPIRE_CYCLE_FAST)

  ACTIVE_EXPIRE_CYCLE_SLOW

*系统空闲时后台定期任务

  activeExpireCycle(ACTIVE_EXPIRE_CYCLE_SLOW)

  25%CPU时间限制

后台定期任务 serverCron

*执行时间间隔1/hz 默认100ms(1s执行10次)

  过期key清理

  rehash全局dict

  关闭超时客户端

  主从同步相关操作

过期key清理算法

*依次遍历所有的db

*从db中随机取20个key,判断是否过期,若过期则逐出

*若5个以上key过期,则重复执行遍历,否则遍历下一个db

*在清理过程中,若达到了时间限制退出清理过程

过期key清理算法的特点

*这是一个基于概率的简单算法,假设是抽出的样本能够代表整个key空间

*单次运行时间有25% cpu时间限制

*redis持续清理过期的数据直置将过期的key的百分比降到25%以下

*长期来看任何给定的时刻已经过期的数据仍占着内存空间的key的量最多为每秒的写操作量除以4

*永远都不可能清理干净

*清理过程以key为单位,如果有大key存在,删除耗时太长有可能导致长时间服务不可用

*调高HZ参数

  可以提升淘汰过期key的频率

  相应的,每一次淘汰最大时间限制将减少(可使系统响应时间变快)

  在一般情况下并不能降低过期key所占比率

  会导致空闲时cpu的占用率提高

淘汰机制:

*执行命令式,先判断是否设置了最大允许内存(server.maxmermy)

*若是,调用freeMemoryIfNeeded,先判断使用内存是否超出最大内存机制

*若是,按照设置的淘汰策略淘汰key直到使用内存小于最大内存

*volatile-lru:从已设置过期时间的数据中挑选最近最少使用的数据淘汰

*volatile-ttl:从已设置过期时间的数据中挑选将要过期的数据淘汰

*volatile-random:从已设置过期时间的数据集中任意选择数据淘汰

*allkeys-lru: 从数据集中挑选最近最少使用的数据淘汰

*allkeys-random:从数据集中任意选择数据淘汰

*no-enviction:禁止淘汰数据

*大Key

  删除大key耗时长

  写入大key导致内存超出太多,下次淘汰需要淘汰很多内存

*内存长期100%问题

  每一次执行命令都需要淘汰一些key

*内存100%且无key可淘汰的情况

  OOM command not allowed when used memory > 'maxmemory'

内存分析:提升业务特点、了解业务瓶颈、发现业务bug

离线内存分析:生成rdb文件(bgsave)、生成内存快照、分析内存快照

redis(rdb文件) 分析工具:redis-rdb-tools

使用PYPI安装 :pip install rdbtools

源码安装:git clone https://github.com/sripathikrishnan/redis-rdb-tools

  cd redis-rdb-tools

  sudo python setup.py install

  rdb -c memory dump.rdb > memory.csv

*生成内存快照为csv格式,包含

  Database:数据库ID

  Type:数据类型

  key:

  size_in_bytes:理论内存值

  Encoding:编码方式

  num_elements:成员个数

  len_largest_element:最大成员长度

数据分析: 

数据导入数据库:

*sqlite3 memory.db

*sqlite > create table

  memory(database int,type varchar(128),key varchar(128),

  size_in_bytes int,encoding varchar(128),num_elsments int,

  len_largest_element varchar(128));

*sqlite > .mode csv memory

*sqlite > .import memory.csv memory

查询key的个数

  sqlite > select count(*) from memory;

查询总的内存占用

  sqlite > select sum(size_in_bytes) from memory;

查询内存占用最高的十个key

  sqlite > select * from memory order by size_in_bytes desc limit 10;

查询成员个数1000以上的list

  sqlite > select * from memory where type='list' and num_elsments > 1000;

在线内存分析:

*查看client占用内存

  对比master,slave内存情况

  client list 查看 idle,multi

*排除大的dict  rehash占用

  静态分析出大key 若有dict类型的数据

  脚本访问触发rehash,看内存是否变化

原文地址:https://www.cnblogs.com/mosson/p/6217134.html