2----------原理篇

Redis事单个线程程序

Nginx也是单线程,但是他们都是服务器高性能的典范

redis为什么这么快?

所有数据都在内存中,所有的运算都是内存级别的运算

Redis既然是单线程,如何能处理那么多的并发客户连接?

多路复用

非阻塞IO

当我们调用套接字(socket网络编程)默认是阻塞的;

阻塞:非要读到数据才返回,非要写完才返回

非阻塞IO:在套接字对象上提供一个选项Non_Blocking,当这个选项打开时,读写方法不会阻塞,而是能读多少度多少,能写多少写多少。

  1. 能读多少:取决于内核为套接字分配的读缓冲区内部的数据字节数
  2. 能写多少:取决于内核为套接字分配写缓冲区的空闲空间字节数
  3. 返回值高数程序实际读写了多少字节

事件轮询(多路复用)

简单的轮询API是select函数,操作系统提供给用户程序的API。来一个处理一个,来了事件就立即返回,死循环。-----不是很懂,大概跟底层相关吧。

指令队列

Redis会将每个客户端套接字都关联一个指令队列。客户端的指令通过队列来排队进行顺序处理,先到先服务。

相应队列

Redis为每一个客户端套接字关联一个响应队列。rRedis服务器通过响应队列来讲指令的返回结果回复给客户端。

定时任务

假如线程阻塞在select系统调用上,定时任务无法准时调度怎么办

定时任务记录在一个最小堆的数据结构中。快执行的任务放在堆的最上方。

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------

通信协议

RESP

Redis序列化协议------文本协议

就是将客户端发送给服务器的指令按照一定的规则转换为文档;服务器将返回结果经过编码发送回来。说白了就是序列化协议。

持久化

Redis的数据全部在内存里,突然宕机,数据就会全部丢失,因此需要一种机制来保证Redis的数据不会因为故障而丢失,这个叫作Redis的持久化。

持久化方式:

  1. 快照
  2. AOF日志

快照原理

原理:将内存数据的二进制序列化,然后复制一份到文件中

简单的:fork一个子进程,将此时的内存数据复制一份在内存中,然后进行快照。

那么产生的问题就是:在复制完成的过程中,内存发生的数据改变是无法感知的。跟CopyOnWriteList一摸一样

AOF日志

记录指令,恢复Redis时需要进行重放这些指令,很慢。

Redis每次执行指令结束之后,执行完的指令再放入日志存盘。

所以:这个日志会越来越长,而且恢复会很耗时,导致Redis长时间无法对外提供服务,所以需要AOF定期瘦身

------Redis提供了bgrewriteaof指令用来给AOF文件日志瘦身

fsync

保证机器宕机的时候,也保证指令的不丢失

运维

通常Redis的主节点不会进行持久化操作,持久化操作主要在从节点进行,从节点是备份节点,没有来自客户端请求的压力,它的操作系统资源往往比较充沛。

混合持久化

前半段用快照,后半段用最新的AOF文件

管道

就是将连续的几个任务请求一起发送过去,然后结果一起发送过来。是客户端提供的功能,其实没东西的

事务

Redis对应的事务指令为:multi、exec、discard。

multi

incr books

incr books

exec

 

所有指令在exec之前不执行,而是缓存在服务器的一个事务队列中,服务器一旦收到exec指令,才开始执行整个事务队列,执行完毕后一次性返回所有指令的运行结果。

注意:假如事务的中间有一条指令出错,那么后续的还是能执行的。所以说redis并不能保证原子性。

discard 丢弃

跟exec是对立的

优化

Redis事务在发送每个指令到事务缓存队列时都要经历一次网络读写,当一个事务内部的指令较多时,需要的网络IO就会变长,所以通常Redis的客户端在执行事务时都会结合管道一起使用。

watch

乐观锁

watch books

incr books

multi

incr books

incr books

exec

 

上述代码执行失败,因为books在事务之前改变了。

PubSub消息队列

上一篇讲了Redis消息队列的使用方法,但是没有提到Redis消息队列的不足,不保证准确性和多播机制

消息多播允许生产者只生产一次信息,由中间件负责将信息复制到多个消息队列,每个消息队列由相应的消费组进行消费。

为了解决多播问题:Redis提供了PubSub(PublisherSubscriber)发布者/订阅者模式。

 

消费者

订阅了name这个主题

import redis
import time

client = redis.StrictRedis()
p = client.pubsub()
p.subscribe("name")
while true:
    msg = p.get_message()
    if  not msg:
        time.sleep(1)
        continue;
    print msg

 

生产者

import redis

client = redis.StrictRedis()
client.publish("name", "java")
client.publish("name", "python")
client.publish("name", "go")

 

所有的消费者都会收到相同的信息

但是休眠也不是个事,用监听来代替休眠

import redis
import time

client = redis.StrictRedis()
p = client.pubsub()
p.subscribe("name")
for msg in p.listen():
    print msg

 

当信息来了,就知道了

模式订阅

一次订阅多个主题

缺点

无法确认消费者是否收到

并且不会持久化

小对象的压缩

Redis是一个非常耗费内存的数据库,它的所有数据都放在内存里

zipList

内部管理的集合数据结构很小,他会使用紧凑存储形式压缩存储

本来时Hashmap存储的,改用一维数组存储下。

如果hash结构,那么key和value会作为两个entry被相邻存储

object encoding key:查看是用什么格式存储的

intset

当set元素少的时候且都是整数的时候,紧凑的整数数组存储

 

总结:小对象超过一定的数量之后就必须使用标准的存储结构

内存回收机制

页回收,只要页上还有一个key,就不会回收,但是会重新使用那些尚未回收的空闲内存

内存分配算法

内存分配是一个非常复杂的课题,需要适当的算法划分内存页,需要考虑内存碎片,需要平衡性能和效率。

redis不管内存的,但是有第三方库管理:

  1. jemalloc(facebook)
  2. tcmalloc(google)

jemalloc的性能好一些,所以redis默认使用了jemalloc

 

posted @ 2020-07-14 11:25  梦想成为DALAO  阅读(105)  评论(0编辑  收藏  举报