Redis

我的另一篇: 简要介绍

 

单线程模型,避免了加锁,避免了线程切换开销,降低了数据结构开发难度(不用线程安全数据结构了)。缺点:一个命令如果执行时间较长会阻塞其他命令。

 

各个数据类型应用场景:

类型简介特性场景
String(字符串)二进制安全可以包含任何数据,比如jpg图片或者序列化的对象,一个键最大能存储512M---
Hash(字典)键值对集合,即编程语言中的Map类型适合存储对象,并且可以像数据库中update一个属性一样只修改某一项属性值(Memcached中需要取出整个字符串反序列化成对象修改完再序列化存回去)存储、读取、修改用户属性
List(列表)链表(双向链表)增删快,提供了操作某一段元素的API1,最新消息排行等功能(比如朋友圈的时间线) 2,消息队列
Set(集合)哈希表实现,元素不重复1、添加、删除,查找的复杂度都是O(1) 2、为集合提供了求交集、并集、差集等操作1、共同好友 2、利用唯一性,统计访问网站的所有独立ip 3、好友推荐时,根据tag求交集,大于某个阈值就可以推荐
Sorted Set(有序集合)将Set中的元素增加一个权重参数score,元素按score有序排列数据插入集合时,已经进行天然排序1、排行榜 2、带权重的消息队列

HyperLogLog: (实现也是String值类型)模糊统计集合里unique元素的数目(比如大网站统计某网页的当日用户访问量UV, 不是总访问次数PV)。存在一定错误率。

位数组表示一组数据的状态(实际值还是String类型,命令是setbit等操作):通过redis setbit方式高效统计网站当前在线人数

georadius等命令可范围查找带经纬度的东西,用zset实现,geohash就是分数;

可以通过String类型序列化其他数据结构来实现。适用于不要频繁读写其中小部分数据的情况。要读全部读。

值类型往往可以是其他类型,从而实现嵌套,例如:List元素的值可以是Hash类型的。

List的一种实现quicklist: 用双链表,每个节点是ziplist,减少指针域开销,减少小节点造成的内存碎片。

 

Key可以设置过期时间(单位秒或毫秒);在Redis内部用的是一个大字典存储key及其过期时间戳(宕机再重启后仍有效!) 原理

三种不同的删除策略:
(1):立即删除。在设置键的过期时间时,创建一个回调事件,当过期时间达到时,由时间处理器自动执行键的删除操作。
(2):惰性删除。键过期了就过期了,不管。每次从dict字典中按key取值时,先检查此key是否已经过期,如果过期了就删除它,并返回nil,如果没过期,就返回键值。
(3):定时删除。每隔一段时间,对expires字典进行检查,删除里面的过期键。

Redis使用的是惰性删除+定时删除;立即删除的CPU压力太大所以不采用。惰性删除积累久了会浪费内存,所以用定时删除来补充。

 

setnx命令实现分布式锁:根据返回值1还是0来判断有没有置成功。所有竞争者里只能有一个返回1。由于 Redis 采用单进程单线程模型,所以不需要担心并发的问题。可应付挂掉的进程没机会释放锁的问题:方法1: 配合getset命令实现超时失效锁。方法2: 用set的nx和ex放到同一行

用String也可以存储Json等结构的数据,尽量只读,要读全读;用Hash等结构存储经常变化或者只读一部分的数据;

Value里的数据量很小时,退化为Ziplist以减少内存的额外开销

 

publish/subscribe: 实现上:是用channel作key, clients的地址做value;publish消息的时候,根据channel找到其上的所有clients,把消息挨个发给这些clients; 也就是Redis本身不存储任何消息;(Kafka可以实现消息堆积和读指针回退重读)

 

事务:事务中任意命令执行失败,其余的命令依然被执行(也不回滚)。Redis 没有在事务上增加任何维持原子性的机制,所以 Redis 事务的执行并不是原子性的。事务可以理解为一个打包的批量执行脚本,但批量指令并非原子化的操作,中间某条指令的失败不会导致前面已做指令的回滚,也不会造成后续的指令不做。

事务是用Watch key的方式,能实现写操作的一致性。原理

 

你想知道的,都在info命令里 :端口,redis-server路径, redis.conf路径;内存使用情况;rdb(全量)持久化和aof(增量)持久化的情况;key命中次数和miss次数;各种命令被调用次数的统计(get, set, ...)

实时显示请求数和连接信息:redis-cli --stat

DEBUG OBJECT key : 获取key的调试信息(值类型, 压缩后的字节数,TTL等信息)

shutdown : 优雅的关闭服务器(可以用选项指定先存快照完了再关)

redis-benchmark命令可以测试Redis的性能

redis-server --test-memory 1024 : 测一下OS能不能分配1GB内存给Redis

slowlog命令可以查询到执行时间较长的命令(慢查询),用于分析系统拥塞的原因。只包括执行时间,不包括网络传输和排队等待时间。

redis-cli --bigkeys : 找到value较大的key们;大的数据项会导致单线程拥塞,吞吐量降低等问题;删除bigkey的时候有可能造成阻塞,可使用scan(每次指定扫描多少个,返回cursor,下次拿这个cursor做入参在上次尾巴后继续扫描)边扫边删除,避免阻塞(或者新版的lazy delete free模式)

monitor命令:实时看到正在执行的redis命令们(get, set, ...)

client list命令:查看所有的客户端连接(检查是否有未关闭的连接)

 

实际运维案例:

内存溢出问题:注意看看是不是输入缓冲区或者输出缓冲区太满造成的(info clients或client list)

客户端周期性超时:可能是周期性执行某个查询,该查询又是慢查询,造成的周期性阻塞(慢查询用slowlog命令查看)

排查大对象:大对象会时一个操作的时长太长,拥塞其他命令;使用bigkeys查看所有大对象;

monitor命令会造成Redis性能下降;短期调试可以,不适合长期开启;

redis-cli --stat 查看实时请求压力;

不要把Redis和其他CPU密集任务部在同一台机器上,以防CPU竞争。绑定CPU会导致fork子进程竞争CPU,绑定了CPU就别再开启持久化。

内存交换会带来严重性能下降:redis-cli info server|grep process_id来查看redis-server的进程id, 然后cat /proc/ID/smaps|grep Swap来查看内存交换情况,都接近0表示正常。尽量设置maxmemory来防止Redis使用过多内存导致内存换出到硬盘。

除了Redis自己用maxclients参数控制客户端连接上限,linux系统对进程打开文件有上限,用limit -u来查看。网络连接也定义为文件句柄,所以网络连接数上限也受这个制约。可以用limit -n 65535来设置。

 

数据持久化:

RDB: save命令备份数据到文件里,文件的位置用CONFIG GET dir命令来查看。(save会阻塞库,建议使用bgsave起后台进程来异步保存)

AOF: 默认不开启;用linux系统调用write实现写文件, fsync实现向磁盘同步;默认情况下linux会最长30秒同步一次;可以配置成每次写操作同步一次或每秒同步一次;直接把写操作的命令append到AOF文件来实现;

当AOF的sync太慢时,会阻塞Redis主线程;

 

Master-Slave主从复制(一般用于读多写少的应用,扩展并发读取的性能):

主节点可写,从节点只读不可写;主节点的任何写命令都会单向流入从节点;用TCP_NODELAY的开启来降低延迟;

全量复制:主节点保存RDB文件,与此同时过来的写命令会保存到缓冲区,RDB发送完成后把缓冲区也发给从节点;

部分复制:主节点缓冲区里的写命令,会有一个当前偏移量,每次自动复制从节点都会更新该偏移量;当网络问题断开后,从节点偏移量落后于主节点,重新建立连接后,会根据此偏移量将主节点缓冲区该偏移量之后的写命令复制给从节点;

缓冲区里的写命令是异步复制的(执行命令和复制到从节点是异步的),因此从节点读到的数据可能是有较大的延迟。可以自己写监控进程查看主从节点的写命令复制缓冲区的偏移量之差来报警。

主从节点之间通过心跳来知晓对方状态

客户端在连接不上某节点时,可以切换到另外一个节点去连接,由程序自动做可以实现高可用性。

主节点挂了以后,再重启,默认他的ID会变化,从节点看到主节点ID变了会认为爹换人了,会触发不必要的全量复制;避免方法可以手动让主节点重启后仍然用老的ID,更好的办法是手动/哨兵/集群方式让从节点自动升级为主节点。

复制积压缓冲区太小(默认1MB),也会造成从节点跟不上主节点的步伐而不得已进行全量复制。解决:增大该缓冲区大小(repl_backlog_size)

 

客户端连接时,服务器做3件事:(epoll,I/O多路复用技术)

1. 设置客户端连接为非阻塞模式

2. 设置TCP_NODELAY, 禁用Nagle算法 (防止发送很多小数据包的算法,攒多了或者超时或者上一个数据包的ACK到达了,再发送)

3. 创建一个可读的文件事件用于监听这个客户端 socket 的数据发送

 

Pipeline加速的原理,主要是打包发送减少了往返交互的次数。如果不使用Pipeline, 客户端每次发完请求要阻塞等待回应,回应到了才能发送下一个请求。实测降低了5倍延迟。将多次RTT减少为1次RTT。RTT: Round Trip Time,消息在网络上发送和返回的时间,不包括排队等待和命令执行的时间。

原生批量命令是原子的,pipeline不是原子的。原生批量命令是一个命令对应多个Key,pipeline是一批执行多个命令。

连接池:客户端开一个连接池,每次从连接池里拿现成的连接,可以减少新建TCP连接的开销;尤其适合很多短连接。

 

内存原理:

info memory显示的碎片,是操作系统角度的进程内存/Redis自己和数据和缓冲占用的内存,相当于碎片比例,大于1时越低越好;小于1说明发生了内存换出,很不好;底层用的jemalloc; 对数据的频繁更新,或者大量删除过期键,会导致碎片问题;值尽量能对齐,比如采用数字或者长度相等的字符串;

maxmemory默认是不设置,也就是用超了物理内存就换出;如果设置了,6种内存溢出控制策略要设置(默认是增加输入请求来了直接返回错误码OOM):1. OOM; 2.按LRU策略删设置了TTL的(没有则OOM); 3.按LRU策略删键(不管有没有设置TTL);4.随机删除键; 5. 随机删除过期键; 6.删除最近将要过期的数据(没有则OOM)

redisObject对象封装了值类型;里面有type,encoding,lru(最近一次访问的时间戳),refcount(引用计数),*ptr(整数类型则直接是整数)

对象可以序列化后以字符串编码放到值里去(java自带的效率低,高效序列化工具有protostuff, kryo);长文本信息建议压缩后存入(例如Gzip)

共享对象池:维护0~9999的整数们,利用好可以节约30%内存;不光是值,list,hash,set,zet内部也可使用;可用object refcount命令查看key的应用计数,大于1则表示用了共享对象池;与maxmemory+LRU策略冲突,因为共享了对象就没法记录每个对象的最近访问时间戳了;

字符串结构:(已用字节长度,未用字节长度,字节数组); 惰性删除机制:字符串缩短后空间不释放,以防后面马上再append;预分配机制:新插入时空间开辟刚刚够,只要有一次追加操作,就分配min(新串长度*2,新串长度+1MB)的空间,以防后面还要append避免反复开辟空间;

object encoding查看值的编码;一旦用了复杂编码,则即使数据删减也无法回到简单编码,除非Redis重启;

ziplist编码:一串数组,省空间,查询慢O(N); 

intset编码:set里放整数时可用,排好序的数组,增删O(N),查询O(lgN),小而快;

巨量小对象哈希表:为了节省内存,可以考虑哈希分组到N个key,每个value是一个百量级的哈希表;这样value可以用ziplist编码,使内存更加紧凑;(P223页)

 

Redis分区:原理介绍

好资料:

https://www.jianshu.com/c/28cac9d01df6

书:Redis开发与运维

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值