-
目录
-
首先贴出redis内存优化结论
- @redis中文官网
- 如若有错,或者有更好的方案,不吝赐教
- 根据公司业务选择实时数据库redis
- 起初用户较少qps和内存占用非常可观没有感觉到危机感,
- 肆无忌惮的使用内存丝毫没有感觉到危险的降临,依然洋洋得意的享受着内存服务提供的舒适与快感
- 当用户逐渐积累达到10亿的时候,我开始慌了
- qps请求过慢(要求30ms),已然达到100+,高峰期可达到上千,还实时服务个鸡儿
- 内存占用过高,毫无节制的使用内存,造成服务重启,内存缓冲区溢出
- 这样->就应该心安理得的承受服务器的怒火,以及……领导的白色的眼球
- 想想办法吧,经过一番周折硬生生将内存占用降低50%,回头想一想是浪费了多少资源
-
糟糕的问题
- 没有使用过redis,认为可以提供ctr实时访问,反作弊实时请求就完了,速度和qps都很可观,根本没有去理会key的设计,数据结构的存储,怼就完了,当ctr和反作弊上线并且有了效果,(公司做广告的)有效点击率翻倍的时候老板的眼神是发光的
- 当然开发之前阅读过一些关于redis的使用文档,但都不是很深刻,大概了解之后就开始部署搭建,使用,上线,没有规范key的设计,value的大小,以及大key的产生
-
言归正传
- 根据业务数据分为:imei,imei_md5,idfa,idfa_md5,ck……等等
- 起初的选型,当时已经晓得redis的string类型kv很占用内存,这种类型适合数据量小,提供实时访问的请求可以满足,内存占用不大的需求
- 根据公司业务以及日数据量,日用户增长量,独立用户增长曲线,判断设计了10w个key供业务数据使用,但是到最后却产生了大key,还是太年轻
- 根据业务数据分为:imei,imei_md5,idfa,idfa_md5,ck……等等
-
redis Key设计理念
- 首先redis支持的数据类型为:String,Hash列表,List,Set……
- key设计规范:简介,高效,可维护
- redis命名须具有可读性以及可管理型,key的长度不宜太长,含义清楚明了
- 尽量不要包含下划线,空格,换行单引号以及其他特殊字符
- 不同的业务模块,不同的逻辑含义使用引文半角冒号分隔(:),redis会自动识别分类,同逻辑不同含义的字符建议使用点号(.)分隔
- string的值的大小建议不要超过10kb,hash,set元素不要超过1000
- 禁止大key,关于大key的性能计算
- redis支持512Mb大小的string,如果1mb的string,在高并发得场景下,1s内并发访问10次,将会造成10mb的IO
- 读写大key容易造成带宽沾满,超时访问,甚至堵塞服务
- 根据业务需求,会有大量的用户识别码,有数十亿的key,所以选择hash方式存储,使用device+hashcode定义key值,将用户识别码当做hashKey的Field进行存储,大量减少了key的个数,同时也会有大key的产生
- 在定义key的时候需要想到和预留下之后的新增数据的容量,根据用户增长曲线,预算处在用户增长到多少的时候会有下滑,确定最大用户数
- 然后根据最大用户数,确定key的个数,根据默认配置
- hash-max-ziplist-entries=512 默认为512,可适当调节,
- hash-max-ziplist-value 64 默认64k,单个hash表中所有数据的总和
- 当单个hash中的field个数不超过512个,或者单个hash中说有数据的总和超过64后,以OBJ_ENCODING_ZIPLIST形式存储,超过512后以hash表的形式OBJ_ENCODING_HT存储,两个的不同在于前者使用压缩方式在获取数据的时候需要解压,后者直接在内存中存储hash表,直接从内存中获取数据,速度差肉眼无法识别,业务方面前者也满足返回时长在50ms以内
- OBJ_ENCODING_ZIPLIST编码
- Ziplist压缩链表,是一种紧凑编码格式,总体思想是时间换空间
- OBJ_ENCODING_HT 编码
- 这种编码方式内部才是真正的hash表结构(也成为字典结构),可以实现O(1)复杂的的读写,性能较高
@详情请见
- 这种编码方式内部才是真正的hash表结构(也成为字典结构),可以实现O(1)复杂的的读写,性能较高
- field个数计算
- 默认配置field个数小于512时采用内存压缩格式存储数据,属于时间换空间理论
- 业务数据10独立用户,设计field个数不可超过200(需要预留出来避免求余倾斜,产生大key)
- 需要的keys=10亿/200=500w个key根据阿里内存选型,服务器内存规格为64G,可用<2000w个key
- 根据默认大小64kb
- 计算独立用户数:数据格式为"{"imei":"xxxxxxxxxxxxx","字段1":"xxxxxxx","字段2":"xxxxxxx","字段3":1,}"
- 根据字节数计算数据格式为全英文(一个英文字母占用一个字节):64kb=65536b
- 每个key含有200个field,每个field分配的空间为:65536/200=327.68b
- 字段名为9字节,可用最大字段数为:327/9-15=21个字段(-15为json串存在value中是以string类型存储,{}和\转义符占用的字节数)
- 根据业务计算字段1,2,3,均为人群包包名,目前规划人群包数量不会超过15个,所以存储方式足以
- 上面计算方式仅供参考,已经降低了大key的生成概率,有效果
- 默认配置field个数小于512时采用内存压缩格式存储数据,属于时间换空间理论
-
redis持久化
- RDB生成方式:Redis*.rdb文件是内存存储的二进制表示,官网给出已经针对rdb文件读写速度进行了优化,在可能的情况下使用LZF压缩来减少文件大小@redis-rdb工具
- 通过命令手动生成
- 执行BGSAVE,执行命令之后没有延迟,会在后台生成RDB文件,期间不会造成堵塞
- 通过配置SAVE自动生成
- 可以设置save选项,
- save 900 1 -->900s之内对数据进行了一次修改
- save 300 10 -->300s内对数据进行了10次修改
- save 60 10000 -->60秒内对数据库修改了1w次
- 可以设置save选项,
- 通过命令手动生成
- AOF方式:以纯文本的形式记录了redis执行的命令
- 可以进行配置文件配置(默认为no):appendonly yes
- 配置详解
- appendfsync always -->每提交一个修改命令就会调用fsync到AOF文件,频率高,速度慢,安全性较高
- appendfsync everysec -->每秒都调用fstnc说新到AOF文件,速度较快,安全性最差丢失一秒数据
- appendfsync no -->依靠OS刷新,redis不主动刷新AOF,速度快,安全性较差
- 建议使用第二种方式每秒刷新一次,
-
数据恢复
-
RDB持久化方式加载数据时会占用大量内存缓冲区,对服务器空闲内存要求较高,但是速度较快
-
AOF持久化方式,子进程向磁盘写数据时会带来IO压力,但是对性能要求不需要太高
-
- RDB生成方式:Redis*.rdb文件是内存存储的二进制表示,官网给出已经针对rdb文件读写速度进行了优化,在可能的情况下使用LZF压缩来减少文件大小@redis-rdb工具
-
内存碎片
- 计算内存碎片率
- 内存碎片率产生的原因,主要是redis存储更新数据,每次更新数据之前,redis会删除旧的数据,实际上由于redis释放了内存块,但内存分配器并没有返回内存给操作系统,这个内存分配器实在编译时指定的(libc,jemalloc或者tcmalloc),当redis中used_memory_rss会越来越大,导致mem_fragmentation_ratio越来越高
- 官网给出,内存碎片无法避免,也给出了一个正常区间:1.0-1.5内属于正常,根据上面的优化之后,我们的内存碎片率为1.01
- 计算内存碎片率
Redis性能优化
最新推荐文章于 2025-04-29 18:30:00 发布