Redis学习

Reids

Reference

[1] https://zhuanlan.zhihu.com/p/62608469

解决问题
主要为了解决性能问题. 
  • 计数器&统计数据: 排行榜
    • 列出前100名用户 zset
    • 列出某用户当前名词
  • 任务队列: 定时任务(把时间戳和用户一起做为key放到list里被消费),推送
    • 针对不同类型的用户有不同的任务组合 (把任务放到list列表里,消费者不断消费列表)
  • 关注
    • 粉丝列表
    • 关注列表
  • 限流器

Redis技术

事件循环

redis是单线程的,整个redis服务在启动之后会陷入一个巨大的while循环,不停地处理文件事件(先处理)和时间事件(后处理).
文件事件: 新建链接等响应请求事件.epoll方式,在多个客户端中实现多路复用,接受它们发来的命令请求,并将命令的执行结果返回给客户端.
时间事件: 记录那些要在指定时间点运行的事件,多个时间事件以无序链表的形式保存在服务器状态中.
内部定时的任务如: 
  • 更新服务器各类统计信息,如事件、内存占用等
  • 数据库后台操作,key过期清理、数据库rehash等
  • 关闭、清理失效的客户端连接
  • 检查是否需要RDB dump(全部数据拷贝的磁盘,会新建进程去做),AOF重写
  • 主节点,对从节点定期同步
  • 集群模式,集群定期同步信息和连接测试
beforesleep阶段 =》 进入event loop前执行
  • cluster集群状态检查,ok-》fail, fail-》ok
  • 处理被block住的client,如一些阻塞请求BLPOP等
  • 将AOF buffer持久化到AOF文件
redis大体流程: beforeSleep-》epollwait-》处理请求-》定时事件-》...
建议: 减少大key(> 1M),减少耗时命令...

过期&逐出

逐出
当执行write但内存达到上限时,强制将一些key删除.删除key的策略:
  • allkeys - 所有key
  • volatile - 设置了过期的key
  • LRU - 最久未被使用
  • random - 随机
  • ttl - 最快过期的
特点
  • 不是精准算法,而是抽样比对
  • 每次写入操作前判断
  • 逐出会block当前请求
建议: 关注逐出qps,过高会影响正常请求处理.redis内部会用hash来记录一些内部关系,达到一定容量限制后会翻倍的rehash,这个阶段持续的几秒钟有可能造成写入key时大量逐出.
过期
当某个key到达了ttl事件,认为该key已经失效.
两种方式:
  • 惰性删除 - 读写操作前判断ttl,如过期则删除
  • 定期删除 - 在redis定时事件中随机抽取部分key判断ttl
特点
  • 并不一定是按设置时间准时的过期
  • 定期删除的时候会判断过期比例,达到阈值才退出
建议:打散key的过期时间,避免大量key在同一时间点过期

持久化

将内存中的数据dump到磁盘文件,两种方式
  • RDB持久化到文件中
    • 经过压缩的二进制格式
    • fork子进程dump可能造成瞬间卡顿
  • AOF持久化(推荐)
    • 保存所有修改数据库的命令
    • 先写aof缓存,再同步到aof文件(异步)
    • aof重写,达到阈值时触发,减小文件大小
应用:利用AOF文件备灾
  • 可将数据恢复到最近3天任意小时粒度

主从复制

主从模式
  • 主、从节点都可以挂从节点(扛读流量)
  • 最终一致性
全量同步
  • 传递RDB文件&restore命令重建kv
  • 传递在RDB dump过程中的写入数据
部分同步
  • 根据offset传递积压缓存中的部分数据

pipeline&mget

pipeline

  • Client: 将多个命令缓存起来,缓冲区满了就发送
  • Redis: 处理一个tcp连接发来的多个命令;处理完一个就发一个
  • Twemproxy: 既要处理一个client连接发来的多个命令,又要将同一个下游redis server的命令缓存起来一起发送
信息链路:
Redis client -》 proxy -》redis server
优点:
  • 节省往返时间
  • 减少proxy,redis server的IO次数

Mget

  • Client: 使用mget命令
  • Redis: 一个命令中处理多个key;等所有key处理完后组装回复一起发送
  • Twemproxy: 拆key分发到不同redis server;需要等待、缓存mget中全部回复
优点:
  • 节省往返时间
缺点:
  • proxy缓存mget结果
  • mget延时是最后一个key回复时间,前面的key需要等待
建议:利用pipeline代替mget,且控制一次请求的命令数量(建议50以内)

redis集群

  • 一致性hash方式
实例宕机、加节点容易造成数据丢失
  • 原生redis cluster方式:
每个实例都有主有从,两两(master)之间进行通信,有节点数量限制

为什么Redis单线程却能支撑高并发

I/O多路复用, Redis 服务采用 Reactor 的方式来实现文件事件处理器(每一个网络连接其实都对应一个文件描述符). 文件事件处理器使用 I/O 多路复用模块同时监听多个 FD,当 accept、read、write 和 close 文件事件产生时,文件事件处理器就会回调 FD 绑定的事件处理器。虽然整个文件事件处理器是在单线程上运行的,但是通过 I/O 多路复用模块的引入,实现了同时对多个 FD 读写的监控,提高了网络通信模型的性能,同时也可以保证整个 Redis 服务实现的简单。
 
Redis 会优先选择时间复杂度为 $O(1)$ 的 I/O 多路复用函数作为底层实现,包括 Solaries 10 中的 evport、Linux 中的 epoll 和 macOS/FreeBSD 中的 kqueue,上述的这些函数都使用了内核内部的结构,并且能够服务几十万的文件描述符。
但是如果当前编译环境没有上述函数,就会选择 select 作为备选方案,由于其在使用时会扫描全部监听的描述符,所以其时间复杂度较差 $O(n)$,并且只能同时服务 1024 个文件描述符,所以一般并不会以 select 作为第一方案使用。

Redis使用规范

一、键值设计

1、key名设计
可读性和可管理性
以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如业务名:表名:id
ugc:video: 1
简洁性
保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视,例如:
user:{uid}:friends:messages:{mid}简化为u:{uid}:fr:m:{mid}。
不要包含特殊字符
反例:包含空格、换行、单双引号以及其他转义字符
2、value设计
拒绝bigkey
防止网卡流量、慢查询,string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000。
反例:一个包含200万个元素的list。
非字符串的bigkey,不要使用del删除,使用hscan、sscan、zscan方式渐进式删除,同时要注意防止bigkey过期时间自动删除问题(例如一个200万的zset设置1小时过期,会触发del操作,造成阻塞,而且该操作不会不出现在慢查询中(latency可查)),查找方法和删除方法
选择适合的数据类型
例如:实体类型(要合理控制和使用数据结构内存编码优化配置,例如ziplist,但也要注意节省内存和性能之间的平衡)。了解下,Redis 为什么这么快?
反例:
set user: 1 :name tom set user: 1 :age 19 set user: 1 :favor football
正例:
hmset user: 1 name tom age 19 favor football
控制key的生命周期
redis不是垃圾桶,建议使用expire设置过期时间(条件允许可以打散过期时间,防止集中过期),不过期的数据重点关注idletime。

二、命令使用


1、O(N)命令关注N的数量
例如hgetall、lrange、smembers、zrange、sinter等并非不能使用,但是需要明确N的值。有遍历的需求可以使用hscan、sscan、zscan代替。
2、禁用命令
禁止线上使用keys、flushall、flushdb等,通过redis的rename机制禁掉命令,或者使用scan的方式渐进式处理。一个致命的 Redis 命令,导致公司损失 400 万!!关注Java技术栈微信公众号,在后台回复关键字:redis,可以获取更多栈长整理的 Redis 系列技术干货。
3、合理使用select
redis的多数据库较弱,使用数字进行区分,很多客户端支持较差,同时多业务用多数据库实际还是单线程处理,会有干扰。
4、使用批量操作提高效率

  1. 原生命令:例如mget、mset。
  2. 非原生命令:可以使用pipeline提高效率。

但要注意控制一次批量操作的元素个数(例如500以内,实际也和元素字节数有关)。
注意两者不同:

  1. 原生是原子操作,pipeline是非原子操作。
  2. pipeline可以打包不同的命令,原生做不到
  3. pipeline需要客户端和服务端同时支持。

5、不建议过多使用Redis事务功能
Redis的事务功能较弱(不支持回滚),而且集群版本(自研和官方)要求一次事务操作的key必须在一个slot上(可以使用hashtag功能解决)。
6、Redis集群版本在使用Lua上有特殊要求
1、所有key都应该由 KEYS 数组来传递,redis.call/pcall 里面调用的redis命令,key的位置,必须是KEYS array, 否则直接返回error,"-ERR bad lua script for redis cluster, all the keys that the script uses should be passed using the KEYS arrayrn"
2、所有key,必须在1个slot上,否则直接返回error, "-ERR eval/evalsha command keys must in same slotrn"
7、monitor命令
必要情况下使用monitor命令时,要注意不要长时间使用。

三、客户端使用

1、避免多个应用使用一个Redis实例
不相干的业务拆分,公共数据做服务化。
2、使用连接池
可以有效控制连接,同时提高效率,标准使用方式:
Jedis jedis = null; try { jedis = jedisPool.getResource(); //具体的命令 jedis.executeCommand() } catch (Exception e) { logger.error("op key {} error: " + e.getMessage(), key, e); } finally { //注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。 if (jedis != null) jedis.close(); }
3、熔断功能
高并发下建议客户端添加熔断功能(例如netflix hystrix)
4、合理的加密
设置合理的密码,如有必要可以使用SSL加密访问(阿里云Redis支持)
5、淘汰策略
根据自身业务类型,选好maxmemory-policy(最大内存淘汰策略),设置好过期时间。
默认策略是volatile-lru,即超过最大内存后,在过期键中使用lru算法进行key的剔除,保证不过期数据不被删除,但是可能会出现OOM问题。
其他策略如下:

  • allkeys-lru:根据LRU算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止。
  • allkeys-random:随机删除所有键,直到腾出足够空间为止。
  • volatile-random:随机删除过期键,直到腾出足够空间为止。
  • volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据。如果没有,回退到noeviction策略。
  • noeviction:不会剔除任何数据,拒绝所有写入操作并返回客户端错误信息"(error) OOM command not allowed when used memory",此时Redis只响应读操作。

四、相关工具

1、数据同步
redis间数据同步可以使用:redis-port
2、big key搜索
redis大key搜索工具
3、热点key寻找
内部实现使用monitor,所以建议短时间使用facebook的redis-faina
阿里云Redis已经在内核层面解决热点key问题

五、删除bigkey

    1. 下面操作可以使用pipeline加速。
    2. redis 4.0已经支持key的异步删除,欢迎使用。
 
原文地址:https://www.cnblogs.com/codingforum/p/13253608.html