Redis------连接

Redis连接

  • 客户端和服务器端正常连接后才能实现彼此的交互、通信。

  • redis通过RESP实现C/S之间的连接通信,该协议包含两个部分:
    网络模型:负责数据交互的组织方式;
    序列化协议:实现了数据的序列化;

  • C以序列号后的协议数据向S发出请求,S也以RESP后的数据返给C

  • 命令:redis-cli//开启c-s连接

Redis安全策略

  • C要连接S,为保证数据安全,客户端连接S时要验证密码;
  • 指令安全:如flushdb、flushall会让redis的所有数据清空,所以redist提供了rename-command用于将某些危险的指令修改成特别的名称。
  • 端口安全:redis的配置文件中要绑定外网的ip,目的为了监听,防止redis的服务地址被外网直接访问,其内部的数据失去了安全性,黑客就可以通过redis执行Lua脚本拿到服务器权限,为所欲为。
  • SSL代理:redis不支持ssl连接,因此C/S交互的数据不能在公网上传输,若想在公网上传输,可以使用ssl代理---->常见的有ssh

IO(补充)

  1. 同步、异步描述的是用户线程-----内核(操作系统资源)的交互方式。
    同步:用户线程发起io请求后需要等待、或者轮询 等内核操作完成后才能继续执行;
    异步:用户发起io请求后,可以继续执行,内核操作完成后会通知用户线程,或者调用用户线程注册的回调函数。

  2. 阻塞、非阻塞指的是用户线程调用内核io操作的方式;
    阻塞:io操作彻底完成后,才能返回给用户线程;
    非阻塞:内核io操作后会返给用户线程一个状态值,无须等到io操作彻底完成。

  3. 同步阻塞io
    用户线程系统调用read(socket,buffer)发起read服务器端的数据请求,(用户线程通过LWP连接到内核线程,然后由内核控制内核线程,对资源操作)在内核等待服务器端的数据包到达的这段时间+内核将接收到的数据拷贝到用户空间,完成read操作,用户线程是处于阻塞的状态

  4. 同步非阻塞io
    用户线程系统调用read(socket,buffer)发起read服务器端的数据请求,(用户线程通过LWP连接到内核线程,然后由内核控制内核线程,对资源操作),用户线程发起read请求后若服务器端的数据还未到达,可以立即返回,接着不断地发起请求,尝试读取socket里面的数据,直到数据到达,内核将数据拷贝到用户空间,用户线程读取数据到自个的缓存区成功,再继续处理接收到的数据。
    缺点:轮询期间,占有CPU,耗能

  5. io多路复用
    前提:建立在由内核(或者同步事件多路分离器----->Synchronous event Demutiplexer)提供的多路分离函数select基础上,使用该函数可以避免同步非阻塞io中用户线程不断轮询等待的问题。
    优点:用户可以注册多个socket,不断调用select获取被激活了的socket们,实现了在一个线程内处理多个socket的io请求,而在同步阻塞io中,要使用多线程才能做到(一个用户线程只能轮询一个指定的socket,不能解决处理多个io请求)。
    实现原理:用户线程将进行io操作的socket添加到select函数中,目的用于监视被激活的socket(因为当来(C or S的)请求来了或者当用户线程等待的数据到了,接收请求方的socket才会被唤醒),接着循环调用select来获取被唤醒的socket,(注意:selecth函数被调用期间,用户线程一直是阻塞的),一旦发现socket被唤醒------>表明数据可以读取了,select函数返回结果给用户线程,用户线程正式发起read请求、读取socket里的数据并继续往下执行。
    用户线程使用select函数的伪代码描述:
    {
    select(socket);
    while(1){
    sockets=selerct();//select返回结果
    for(socket in sockets){
    if(can_read(socket)){
    read(socket,buffer);
    process(buffer);
    }
    }
    }
    }
    select缺点:
    注意:1)如果任何一个socket被唤醒,select仅仅会返回,但不会告诉你是哪个socket上有数据,你需要循环去寻找,开销颇大
    2)select只能监听1024个socket连接;
    3)select不是线程安全的。
    4) 需要进行两次遍历文件描述符集合—>存放的是已连接的Socket,目的时查找发生了网络事件的socket;一次:是select函数将文件描述符拷贝到内核里,由内核检查是否有网络事件,若有,则将socket标记为可读/可写;再将整个文件描述符拷贝到用户态;下一次:由用户态(用户线程在用户空间)遍历整个文件描述符,找到可读、可写的socket;
    5)线性结构存储文件描述符集合,O(n)
    poll
    1)修复了select的很多问题
    2)socket连接没限制;
    3)不再修改传入的参数数组;
    4)依然不是线程安全的,只可以在一个线程里面处理一组I/O流;
    5)遍历文件描述符同上述。
    epoll
    1)线程安全的;
    2)会告诉用户线程哪个socket里面有数据;
    3)只有linux支持
    4)内核里使用红黑树跟踪所有待检测的文件描述符,CID为O(n),每次只传入一个需要监控的Socket,减少了内核和用户空间大量的拷贝。
    5)经过红黑树检测,当某个socket发生事件,通过回调函数内核将其加入一个由内核维护的就绪事件列表,当用户调用epoll_wait(),时,只返回有事件发生了的socket,而不是像那两个一顿夸夸复制过去轮询整个文件描述符集合。
    通过epoll监听,及时要监听的socket越来越多,效率也不会降低。其被称为解决C10K问题的利器。
    以上这三个都是IO多路复用的具体实现。
    结论:在不断执行selecth函数为了读取可唤醒的socket,知道返回期间,用户线程一直是阻塞的,也就是说,在每个io请求的过程中是阻塞的。
    但是,如果用户只注册自己感兴趣的socket或者io请求,然后不必阻塞,仍然可以去做自己的其他事情,等到数据来了再去处理,提高了cpu的利用率。
    如何解决:引入一个中间体Reactor,去执行本该用户线程调用select函数的职责
    |>

  6. 采取了reactor设计模式的io多路复用模型(又叫异步阻塞io模型)
    用户线程注册io事件处理器(EventHandle----->拥有io文件句柄是通过get_handle()获取的)(该事件处理器交由Reactor管理),Reactor线程受理该事件处理器,接着,该线程会调用内核的select函数检查socket状态,并处于阻塞状态,当有socket被唤醒时(即io文件句柄被激活),(数据来了)select会返回给Reactor线程,Reactor线程就会去通知相应的用户线程(即Reacrotl类的handle_events()会调用与那个io文件句柄相关联的io事件处理器的handle_event()进行读写),由用户线程执行注册了的io事件处理器的handle_event进行数据的读取、处理工作,(还是由内核将数据拷贝到用户空间)。
    用户线程注册自己的io事件处理器到Reactor,由其来对事件处理器进行注册、删除伪代码:
    {
    Reactor.register(new UserEventHandler(socket));
    }
    用户线程使用IO多路复用模型的伪代码描述为:
    void UserEventHandler::handle_event() {
    if(can_read(socket)) {
    read(socket, buffer);
    process(buffer);

    }
    }
    Reactor类的handel_events事件循环的代码如下:
    Reactor::handle_events(){
    while(1){
    sockets=select();
    for(socket in sockets){
    get_event_handle(socket).hanle_event();
    }
    }
    }
    优点:即上面"但是"描述的;
    由于使用了会阻塞线程的select系统调用(系统调用由内核线程可直接调用,凡不是内核线程调用系统调用的话,都相当于用户级线程,是通过LWP连接到内核线程,和内核通信,都会阻塞用户级线程),所以该模型只能被称为异步阻塞io,并非真正的异步IO。

  7. 异步io
    “真正”的异步IO需要操作系统更强的支持。
    与io多路复用模型不同的是,handle_events事件循环将将socket的激活状态通知给用户线程,由用户线程的io事件处理器自行读取数据、处理数据(不明白:处理数据指什么?)
    而异步io模型中,当用户线程收到通知时,内核已经将数据读取完毕,并放在了用户线程指定的缓冲区内,用户线程只需要直接使用即可。
    以上实现是使用了Proactor设计模式实现的。
    过程:用户线程要先将AsynchonousOperation、proctor(持有handle_events())、CompletionHandler(持有handle_event())(注意:用户线程需要先重写CompetionHandle的handle_event函数进行处理数据的工作,具体就是process(buffer)------->原来是用户线程自己读取数据到buffer,现在,这个buffer表示是Proactor已经准备好的数据)注册到Asynchronous Operation Processor(相当于内核)中,其使用Facade模式提供了一组异步操作API(主要是读写等)供用户线程使用,接着,用户线程直接使用这组由内核提供的异步IO API发起read请求,发起后立即返回,可继续执行用户线程的代码;Asynchronous Operation Processor会开启独立的内核线程去处理异步io操作------->即:当read请求的数据到达时,由内核负责读取socket中的数据,并写入用户指定的缓冲区里。接着,Asynchronous Operation Processor(或者说是内核)将read到的数据和用户线程注册到的CompletionHandle一起转发给内部Proactor,接着,proactor的handle_events()负责调用用户线程注册的完成处理事件----即CompletionHandel的handle_event(),用来将IO操作完成的信息通知给用户线程,就此,read完成。
    伪代码:
    用户线程使用异步IO模型的伪代码描述为:
    void UserCompletionHandler::handle_event(buffer) {
    process(buffer);}
    {//用户线程使用内核提供的异步ioAPI发起read请求;
    aio_read(socket, new UserCompletionHandler);
    }
    用户需要重写CompletionHandler的handle_event函数进行处理数据的工作,参数buffer表示Proactor已经准备好的数据,用户线程直接调用内核提供的异步IO API,并将重写的CompletionHandler注册即可。
    总结:相比io多路复用模型,异步io并不十分常用,取决于os对异步io的支持度;许多高性能并发服务程序使用 io多路复用模型+多线程任务处理的架构基本可以满足需求。

  8. 线程和io
    一个线程的执行,通常需要3个资源:cpu—>负责计算、执行指令;内存------>负责存放即时数据;IO负责和磁盘、数据库、网络等做数据交换。
    cpu:一个物理cpu只有一个核,即单核cpu----->在单位时间内,cpu只能处理一个线程,由于采用了时间片划分,实现了多线程。多核cpu下,每个核的处理情况和单核cpu一样。
    逻辑cpu:即一个物理cpu抽象为多个cpu核心,一个物理cpu可以支持多线程,但并不是前面所说的时间片实现的,它是真正意义上的单位时间的多线程,即逻辑cpu的个数和系统单位时间内可执行的线程数量一样。
    I/O:常见的io如:文件流、数据库连接、网络连接;注意:单位时间内只能为一个线程服务,它并不能像单核cpu那样,通过切片,执行多线程;另外,当一个线程在使用I/O时,在使用的这段时间内,线程不能进行计算、读写内存。即:一个线程在使用I/O时,可以把cpu执行权交给别的线程,可以充分的利用系统资源。

socket

  1. 实现客户端和服务端在网络中通信,特别:可以跨主机间通信;

  2. 双方在网络通信前,都要各自创建一个socket,创建时要指定网络层使用的是iPV4/iPV6,传输层用的是TCP/UDP;
    服务端:要先跑起来,等待客户端的连接请求和数据。
    1)服务端的socket要bind一个IP地址和端口,当内核收到tcp报文,要通过tcp里面的端口来找到应用程序,然后将数据传给我们;一台机器有多个网卡,每个网卡都有对应的ip地址,内核在收到该ip对应的网卡上的数据包时,才会发给我们。
    2)接下来,服务器端进入了监听状态,之后调用accept(),来从内核获取客户端的连接,一直到客户端的连接来,是处于阻塞的状态。
    客户端:创建的socket,通过connect()发起连接,通过该函数的参数,要指明服务端的ip、端口。接着,三次握手就开始了。

  3. 在tcp连接中,服务端的内核实际上为每个socket维护了两个队列,一个队列里是没有完成三次握手连接的socket,此时,服务端处于syn_rcvd状态。一个队列里都是完成了三次握手连接的socket,此时的服务端状态是established状态。
    服务端的accept函数就会从这个全连接的队列中拿出一个已连接的socket返回应用程序(或者用户线程),后续的数据传输都用这个Socket.
    注意:上述用于监听的socket和真正用来传输数据的socket是两个东西。

  4. 连接建立后,就可以传输数据了,双方都可以通过read()、write()来读写数据。

多进程模型

     上述的C---S通信基本是一对一,采用的是同步阻塞,S在没有处理完一个客户端的网络IO时,或者读写发生阻塞时,是没办法和其他的C连接的。
     服务器单机理论上可以连接的最大客户端tcpl连接数是客户端IP数*客户端端口数。但服务器肯定承载不了这么大的连接数,主要受两方面的限制:1)单个进程可以连接的socket是有限制的 ;2)系统内存:每个tcpl连接在内核中都有对应的数据结构,占有一定的内存。
     为了使服务端单机并发处理过万的客户端请求,先是提出了**多进程模型**------>
              1)为每个客户端请求分配一个进程来处理
              2)服务器端的主进程负责监听客户的连接,一旦连接建立,accept()会返回一个socket,接着主进程通过fork()创建一个子进程(继承了父进程的文件描述符----->每个socket都有一个文件描述符、内存地址空间、程序计数器、执行的代码等),直接使用这个socket和对应的客户端通信(进行read()、write()).
      缺点:客户端数量很多时,扛不住,进程的上下文切换包袱很重。

多线程模型

  1. 服务端将与客户端建立了连接的socket们统一放在一个队列里,通过phtrread_creat()创建线程,线程从这个队列里取出一个socket和客户端通信。
    注意:这里采用线程池的方式管理线程。
    但对与上万个线程,维护起来对os还是颇有压力。

Redis—IO多路复用

  1. redis通过监听tcp端口的方式来接受客户端的连接,客户端socket设置属性tcp_nodelay,目的是禁用nagle算法----->如果redis以命令的形式在客户端输入数据,由于数据量小,希望可以快速得到服务端的应答,即低延时,nagel算法并不适用,其适合传输较大的数据在广域网,减少分组的报文数。

  2. redis在网络事件处理上采用了io多路复用模型。
    1) 其底层是一个单线程模型,即使用一个线程来处理所有的网络事件,避免了线程切换导致的cpu开销,也不用考虑各种锁问题。
    2)redis采用的是epoll的方式监听多个socket,原理同上面讲述的一样;

  3. 命令使用:
    1)client kll 127.0.0.1:49502//关闭当前客户端连接
    2)client list//以列表的形式返回所有连接到redis服务器的客户端
    输出:id=2556 addr=127.0.0.1:64684 fd=30 name=age=2422 idle=339 flags=N db=0
    id=2557 addr=127.0.0.1:64684 …

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值