redis 单线程的理解

单线程模型nginx

  Redis客户端对服务端的每次调用都经历了发送命令,执行命令,返回结果三个过程。其中执行命令阶段,因为Redis是单线程来处理命令的,全部每一条到达服务端的命令不会马上执行,全部的命令都会进入一个队列中,而后逐个被执行。而且多个客户端发送的命令的执行顺序是不肯定的。可是能够肯定的是不会有两条命令被同时执行,不会产生并发问题,这就是Redis的单线程基本模型。redis

 

1. redis单线程问题数据库

  单线程指的是网络请求模块使用了一个线程(因此不需考虑并发安全性),即一个线程处理全部网络请求,其余模块仍用了多个线程。编程

2. 为何说redis可以快速执行api

(1) 绝大部分请求是纯粹的内存操做(很是快速)安全

(2) 采用单线程,避免了没必要要的上下文切换和竞争条件服务器

(3) 非阻塞IO - IO多路复用,Redis采用epoll作为I/O多路复用技术的实现,再加上Redis自身的事件处理模型将epoll中的链接,读写,关闭都转换为了时间,不在I/O上浪费过多的时间。网络

  Redis采用单线程模型,每条命令执行若是占用大量时间,会形成其余线程阻塞,对于Redis这种高性能服务是致命的,因此Redis是面向高速执行的数据库。多线程

3. redis的内部实现并发

  内部实现采用epoll,采用了epoll+本身实现的简单的事件框架。epoll中的读、写、关闭、链接都转化成了事件,而后利用epoll的多路复用特性,毫不在io上浪费一点时间 这3个条件不是相互独立的,特别是第一条,若是请求都是耗时的,采用单线程吞吐量及性能可想而知了。应该说redis为特殊的场景选择了合适的技术方案。

4. Redis关于线程安全问题

   redis其实是采用了线程封闭的观念,把任务封闭在一个线程,天然避免了线程安全问题,不过对于须要依赖多个redis操做的复合操做来讲,依然须要锁,并且有多是分布式锁。

 

另外一篇对redis单线程的理解:Redis单线程理解

我的理解

        redis分客户端和服务端,一次完整的redis请求事件有多个阶段(客户端到服务器的网络链接-->redis读写事件发生-->redis服务端的数据处理(单线程)-->数据返回)。平时所说的redis单线程模型,本质上指的是服务端的数据处理阶段,不牵扯网络链接和数据返回,这是理解redis单线程的第一步。接下来,针对不一样阶段分别阐述我的的一些理解。

1:客户端到服务器的网络链接

首先,客户端和服务器是socket通讯方式,socket服务端监听可同时接受多个客户端请求,这点很重要,若是不理解可先记住。注意这里能够理解为本质上与redis无关,这里仅仅作网络链接,或者能够理解为,为redis服务端提供网络交互api。

        假设创建网络链接须要30秒(为了更容易理解,因此时间上扩大了N倍)

2:redis读写事件发生并向服务端发送请求数据

        首先肯定一点,redis的客户端与服务器端通讯是基于TCP链接(不懂去看,基础很重要),第一阶段仅仅是创建了客户端到服务器的网络链接,而后才是发生第二阶段的读写事件。

        完成了上一个阶段的网络链接,redis客户端开始真正向服务器发起读写事件,假设是set(写)事件,此时redis客户端开始向创建的网络流中送数据,服务端能够理解为给每个网络链接建立一个线程同时接收客户端的请求数据。

        假设从客户端发数据,到服务端接收完数据须要10秒。

3:redis服务端的数据处理

        服务端完成了第二阶段的数据接收,接下来开始依据接收到的数据作逻辑处理,而后获得处理后的数据。数据处理能够理解为一次方法调用,带参调用方法,最终获得方法返回值。不要想复杂,重在理解流程。

        假设redis服务端处理数据须要0.1秒

4:数据返回

        这一阶段很简单,当reids服务端数据处理完后 就会当即返回处理后的数据,没什么特别须要强调的。

        假设服务端把处理后的数据回送给客户端须要5秒。

那么什么是Reids的单线程

        第一阶段说过,redis是以socket方式通讯,socket服务端可同时接受多个客户端请求链接,也就是说,redis服务同时面对多个redis客户端链接请求,而redis服务自己是单线程运行。

        假设,如今有A,B,C,D,E五个客户端同时发起redis请求,A优先稍微一点点第一个到达,而后是B,C,D,E依次到达,此时redis服务端开始处理A请求,创建链接须要30秒,获取请求数据须要10秒,而后处理数据须要0.1秒,回送数据给客户端须要5秒,总共大概须要45秒。也就是说,下一个B请求须要等待45秒,这里注意,也许这五个几乎同时请求,因为socket能够同时处理多个请求,因此创建网络链接阶段时间差可忽略,可是在第二阶段,服务端须要什么事都不干,坐等10秒中,对于CPU和客户端来讲是没法忍受的。因此说单线程效率很是,很是低,可是正是由于这些相似问题,Redis单线程本质上并非如此运行。接下来讨论redis真正的单线程运行方式。

        客户端与服务端创建链接交由socket,能够同时创建多个链接(这里应该是多线程/多进程),创建的链接redis是知道的(为何知道,去看socket编程,再次强调基础很重要),而后redis会基于这些创建的链接去探测哪一个链接已经接收完了客户端的请求数据(注意:不是探测哪一个链接创建好了,而是探测哪一个接收完了请求数据),并且这里的探测动做就是单线程的开始,一旦探测到则基于接收到的数据开始数据处理阶段,而后返回数据,再继续探测下一个已经接收完请求数据的网络链接。注意,从探测到数据处理再到数据返回,全程单线程。这应该就是所谓的redis单线程。至于内部有多复杂咱们无需关心,咱们追求的是理解流程,苛求原理,但不能把内脏都挖出来。

        从探测到接受完请求数据的网络链接到最终的数据返回,服务器只须要5.1秒,这个时间是我放大N倍后的数据,实际时间远远小于这个,多是5.1的N万分之一时间,为何这么说,由于数据的处理是在本地内存中,速度有多快任你想象,最终的返回数据虽然牵扯到网络,可是网络链接已经创建,这个速度也是很是很是快的,只是比数据处理阶段慢那么一点点。所以单线程方式在效率上其实并不须要担忧。

 

IO多路复用

  参考:https://www.zhihu.com/question/32163005

 要弄清问题先要知道问题的出现缘由

缘由:

  因为进程的执行过程是线性的(也就是顺序执行),当咱们调用低速系统I/O(read,write,accept等等),进程可能阻塞,此时进程就阻塞在这个调用上,不能执行其余操做.阻塞很正常.

  接下来考虑这么一个问题:一个服务器进程和一个客户端进程通讯,服务器端read(sockfd1,bud,bufsize),此时客户端进程没有发送数据,那么read(阻塞调用)将阻塞,直到客户端调用write(sockfd,but,size)发来数据.在一个客户和服务器通讯时这没什么问题;

  当多个客户与服务器通讯时当多个客户与服务器通讯时,若服务器阻塞于其中一个客户sockfd1,当另外一个客户的数据到达套接字sockfd2时,服务器不能处理,仍然阻塞在read(sockfd1,...)上;此时问题就出现了,不能及时处理另外一个客户的服务,咋么办?

  I/O多路复用来解决!

I/O多路复用:

  继续上面的问题,有多个客户链接,sockfd1,sockfd2,sockfd3..sockfdn同时监听这n个客户,当其中有一个发来消息时就从select的阻塞中返回,而后就调用read读取收到消息的sockfd,而后又循环回select阻塞;这样就不会由于阻塞在其中一个上而不能处理另外一个客户的消息

  “I/O多路复用”的英文是“I/O multiplexing”,能够百度一下multiplexing,就能获得这个图:

Q:

  那这样子,在读取socket1的数据时,若是其它socket有数据来,那么也要等到socket1读取完了才能继续读取其它socket的数据吧。那不是也阻塞住了吗?并且读取到的数据也要开启线程处理吧,那这和多线程IO有什么区别呢?

A:

  1.CPU原本就是线性的不论什么都须要顺序处理并行只能是多核CPU

  2.io多路复用原本就是用来解决对多个I/O监听时,一个I/O阻塞影响其余I/O的问题,跟多线程不要紧.

  3.跟多线程相比较,线程切换须要切换到内核进行线程切换,须要消耗时间和资源.而I/O多路复用不须要切换线/进程,效率相对较高,特别是对高并发的应用nginx就是用I/O多路复用,故而性能极佳.但多线程编程逻辑和处理上比I/O多路复用简单.而I/O多路复用处理起来较为复杂.

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

  I/O 指的是网络I/O。
  多路指的是多个TCP 链接(Socket 或Channel)。
  复用指的是复用一个或多个线程。


  它的基本原理就是再也不由应用程序本身监视链接,而是由内核替应用程序监视文件描述符。

  客户端在操做的时候,会产生具备不一样事件类型的socket。在服务端,I/O 多路复用程序(I/O Multiplexing Module)会把消息放入队列中,而后经过文件事件分派器(Fileevent Dispatcher),转发到不一样的事件处理器中。

  多路复用有不少的实现,以select 为例,当用户进程调用了多路复用器,进程会被阻塞。内核会监视多路复用器负责的全部socket,当任何一个socket 的数据准备好了,多路复用器就会返回。这时候用户进程再调用read 操做,把数据从内核缓冲区拷贝到用户空间。

  I/O 多路复用的特色是经过一种机制一个进程能同时等待多个文件描述符,而这些文件描述符(套接字描述符)其中的任意一个进入读就绪(readable)状态,select()函数就能够返回。

 

Redis 单线程 是否就表明线程安全?
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

class Demo extends Thread
{
    public void run()
    {
        Jedis jedis1 = new Jedis();
        for (int i=0;i<100;i++){
            int num = Integer.parseInt(jedis1.get("num"));// 1: 代码行1
            num = num + 1; // 2: 代码行2
            jedis1.set("num",num+"");
            System.out.println(jedis1.get("num"));
        }
    }
}

public class test{

    public static void main(String... args){
        Jedis jedis = new Jedis();
        jedis.set("num","1");
        new Demo().start();
        new Demo().start();
    }
}

  如代码所示,例如当线程1在代码行读取数值为99时候,此时线程2页执行读取操做也是99,随后同时执行num=num+1,以后更新,致使一次更新丢失,这就是这个代码测试的错误之处。因此Redis自己是线程安全的,可是你还须要保证你的业务必须也是线程安全的

 

注意:千万不要觉得原子操做是线程安全的,原子操做只能保证命令全执行或者全不执行,并不会保证线程安全操做。例如数据库中的事务就是原子的,依旧还须要提供并发控制!!!!

原子性操做是否线程安全?

  原文:https://stackoverflow.com/questions/14370575/why-are-atomic-operations-considered-thread-safe

 

  1. 原子操做是针对访问共享变量的操做而言的。涉及局部变量访问的操做无所谓是否原子的。
  2. 原子操做是从该操做的执行线程之外的线程来描述的,也就是说它只有在多线程环境下才有意义。


原子操做得“不可分割”包括两层含义
  1.访问(读、写)某个共享变量的操做从其执行线程之外的任何线程来看,该操做要么已经执行结束要么还没有发生,即其余线程不会“看到”该操做执行了部分的中间效果。

  2.访问同一组共享变量的原子操做是不可以被交错的。

 

此原子性与数据库原子性有区别:最主要区别是数据库的原子性,能够被其余线程看见中间状态,不然就不会有隔离级别的事了。

相关文章
相关标签/搜索