优化的一些建议
1, 尽量使用短的 key
当然在精简的同时, 不要完了 key 的 "见名知意". 对于 value 有些也可精简, 比如性别使用 0,1.
2, 避免使用 keys*
keys *, 这个命令是阻塞的, 即操作执行期间, 其它任何命令在你的实例中都无法执行. 当 Redis 中 key 数据量小时到无所谓, 数据量大就很糟糕了. 所以我们应该避免去使用这个命令. 可以去使用 SCAN, 来代替.
3, 在存到 Redis 之前先把你的数据压缩下
Redis 为每种数据类型都提供了两种内部编码方式, 在不同的情况下 Redis 会自动调整合适的编码方式.
4, 设置 key 有效期
我们应该尽可能的利用 key 有效期. 比如一些临时数据(短信校验码), 过了有效期 Redis 就会自动为你清除!
5, 选择回收策略(maxmemory-policy)
当 Redis 的实例空间被填满了之后, 将会尝试回收一部分 key. 根据你的使用方式, 强烈建议使用 volatile-lru(默认) 策略 -- 前提是你对 key 已经设置了超时. 但如果你运行的是一些类似于 https://www.centos.bz/tag/cache/ 的东西, 并且没有对 key 设置超时机制, 可以考虑使用 allkeys-lru 回收机制, 具体讲解查看 .maxmemory-samples 3 是说每次进行淘汰的时候 会随机抽取 3 个 key 从里面淘汰最不经常使用的(默认选项)
maxmemory-policy 六种方式 :
volatile-lru: 只对设置了过期时间的 key 进行 LRU(默认值)
allkeys-lru : 是从所有 key 里 删除 不经常使用的 key
volatile-random: 随机删除即将过期 key
allkeys-random: 随机删除
volatile-ttl : 删除即将过期的
noeviction : 永不过期, 返回错误
6, 使用 bit 位级别操作和 byte 字节级别操作来减少不必要的内存使用.
bit 位级别操作: GETRANGE, SETRANGE, GETBIT and SETBIT
byte 字节级别操作: GETRANGE and SETRANGE
7, 尽可能地使用 hashes 哈希存储.
8, 当业务场景不需要数据持久化时, 关闭所有的持久化方式可以获得最佳的性能.
9, 想要一次添加多条数据的时候可以使用管道.
10, 限制 Redis 的内存大小(64 位系统不限制内存, 32 位系统默认最多使用 3GB 内存)
数据量不可预估, 并且内存也有限的话, 尽量限制下 Redis 使用的内存大小, 这样可以避免 Redis 使用 swap 分区或者出现 OOM 错误.(使用 swap 分区, 性能较低, 如果限制了内存, 当到达指定内存之后就不能添加数据了, 否则会报 OOM 错误. 可以设置 maxmemory-policy, 内存不足时删除数据.)
11,SLOWLOG [get/reset/len]
slowlog-log-slower-than 它决定要对执行时间大于多少微秒 (microsecond,1 秒 = 1,000,000 微秒) 的命令进行记录.
slowlog-max-len 它决定 slowlog 最多能保存多少条日志, 当发现 Redis 性能下降的时候可以查看下是哪些命令导致的.
优化实例分析
管道性能测试
Redis 的管道功能在命令行中没有, 但是 Redis 是支持管道的, 在 java 的客户端 (jedis) 中是可以使用的:
示例代码
- // 注: 具体耗时, 和自身电脑有关(博主是在虚拟机中运行的数据)
- /**
- * 不使用管道初始化 1W 条数据
- * 耗时: 3079 毫秒
- * @throws Exception
- */
- @Test
- public void NOTUsePipeline() throws Exception {
- Jedis jedis = JedisUtil.getJedis();
- long start_time = System.currentTimeMillis();
- for (int i = 0; i <10000; i++) {
- jedis.set("aa_"+i, i+"");
- }
- System.out.println(System.currentTimeMillis()-start_time);
- }
- /**
- * 使用管道初始化 1W 条数据
- * 耗时: 255 毫秒
- * @throws Exception
- */
- @Test
- public void usePipeline() throws Exception {
- Jedis jedis = JedisUtil.getJedis();
- long start_time = System.currentTimeMillis();
- Pipeline pipelined = jedis.pipelined();
- for (int i = 0; i < 10000; i++) {
- pipelined.set("cc_"+i, i+"");
- }
- pipelined.sync();// 执行管道中的命令
- System.out.println(System.currentTimeMillis()-start_time);
- }
hash 的应用
示例: 我们要存储一个用户信息对象数据, 包含以下信息:
key 为用户 ID,value 为用户对象 (姓名, 年龄, 生日等) 如果用普通的 key/value 结构来存储, 主要有以下 2 种存储方式:
将用户 ID 作为查找 key, 把其他信息封装成一个对象以序列化的方式存储
缺点: 增加了序列化 / 反序列化的开销, 引入复杂适应系统 (Complex adaptive system, 简称 CAS) 修改其中一项信息时, 需要把整个对象取回, 并且修改操作需要对并发进行保护.
用户信息对象有多少成员就存成多少个 key-value 对
虽然省去了序列化开销和并发问题, 但是用户 ID 为重复存储.
Redis 提供的 Hash 很好的解决了这个问题, 提供了直接存取这个 Map 成员的接口. Key 仍然是用户 ID, value 是一个 Map, 这个 Map 的 key 是成员的属性名, value 是属性值.( 内部实现: Redis Hashd 的 Value 内部有 2 种不同实现, Hash 的成员比较少时 Redis 为了节省内存会采用类似一维数组的方式来紧凑存储, 而不会采用真正的 HashMap 结构, 对应的 value redisObject 的 encoding 为 zipmap, 当成员数量增大时会自动转成真正的 HashMap, 此时 encoding 为 ht ).
Instagram 内存优化
Instagram 可能大家都已熟悉, 当前火热的拍照 App, 月活跃用户 3 亿. 四年前 Instagram 所存图片 3 亿多时需要解决一个问题: 想知道每一张照片的作者是谁(通过图片 ID 反查用户 UID), 并且要求查询速度要相当的块, 如果把它放到内存中使用 String 结构做 key-value:
- HSET "mediabucket:1155" "1155315" "939"
- HGET "mediabucket:1155" "1155315"
- "939"
测试: 1 百万数据会用掉 70MB 内存, 3 亿张照片就会用掉 21GB 的内存. 当时 (四年前) 最好是一台 EC2 的 high-memory 机型就能存储(17GB 或者 34GB 的, 68GB 的太浪费了), 想把它放到 16G 机型中还是不行的.
Instagram 的开发者向 Redis 的开发者之一 Pieter Noordhuis 询问优化方案, 得到的回复是使用 Hash 结构. 具体的做法就是将数据分段, 每一段使用一个 Hash 结构存储.
由于 Hash 结构会在单个 Hash 元素在不足一定数量时进行压缩存储, 所以可以大量节约内存. 这一点在上面的 String 结构里是不存在的. 而这个一定数量是由配置文件中的 hash-zipmap-max-entries 参数来控制的. 经过实验, 将 hash-zipmap-max-entries 设置为 1000 时, 性能比较好, 超过 1000 后 HSET 命令就会导致 CPU 消耗变得非常大.
- HSET "mediabucket:1155" "1155315" "939"
- HGET "mediabucket:1155" "1155315"
- "939"
测试: 1 百万消耗 16MB 的内存. 总内存使用也降到了 5GB. 当然我们还可以优化, 去掉 mediabucket:key 长度减少了 12 个字节.
- HSET "1155" "315" "939"
- HGET "1155" "315"
- "939"
启动时 WARNING 优化
在我们启动 Redis 时, 默认会出现如下三个警告:
一, 修改 https://www.centos.bz/tag/linux/ 中 TCP 监听的最大容纳数量
- WARNING: The TCP backlog setting of 511 cannot be enforced because
- /proc/sys.NET/core/somaxconn is set to the lower value of 128.
在高并发环境下你需要一个高 backlog 值来避免慢客户端连接问题. 注意 Linux 内核默默地将这个值减小到 / proc/sys.NET/core/somaxconn 的值, 所以需要确认增大 somaxconn 和 tcp_max_syn_backlog 两个值来达到想要的效果.
echo 511> /proc/sys.NET/core/somaxconn
注意: 这个参数并不是限制 Redis 的最大链接数. 如果想限制 Redis 的最大连接数需要修改 maxclients, 默认最大连接数为 10000
二, 修改 Linux 内核内存分配策略
错误日志: WARNING overcommit_memory is set to 0! Background save may fail under low memory condition.
- To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or
- run the command 'sysctl vm.overcommit_memory=1'
原因:
Redis 在备份数据的时候, 会 fork 出一个子进程, 理论上 child 进程所占用的内存和 parent 是一样的, 比如 parent 占用的内存为 8G, 这个时候也要同样分配 8G 的内存给 child, 如果内存无法负担, 往往会造成 Redis 服务器的 down 机或者 IO 负载过高, 效率下降. 所以内存分配策略应该设置为 1(表示内核允许分配所有的物理内存, 而不管当前的内存状态如何).
内存分配策略有三种
可选值: 0,1,2.
0, 表示内核将检查是否有足够的可用内存供应用进程使用; 如果有足够的可用内存, 内存申请允许; 否则, 内存申请失败, 并把错误返回给应用进程.
1, 不管需要多少内存, 都允许申请.
2, 只允许分配物理内存和交换内存的大小(交换内存一般是物理内存的一半).
三, 关闭 Transparent Huge Pages(THP)
THP 会造成内存锁影响 Redis 性能, 建议关闭
Transparent HugePages : 用来提高内存管理的性能
Transparent Huge Pages 在 32 位的 RHEL 6 中是不支持的
执行命令 echo never> /sys/kernel/mm/transparent_hugepage/enabled
把这条命令添加到这个文件中 / etc/rc.local
来源: https://www.cnblogs.com/alterem/p/11298476.html