聊聊单线程的Redis为何会快到飞起

目录
  • Redis为什么用单线程?
    • 多线程的开销
  • Redis使用单线程为什么还这么快?
  • 网络与IO操作的潜在阻塞点
  • 基于多路复用的高性能IO模型
  • 回调机制
  • Redis的性能瓶颈点
    • 1.耗时操作
    • 2.高并发场景
  • 其他Redis相关的有趣问题

实际上它确实也很快 : ),但Redis底层却是单线程的!有同学可能就要有疑问了,为什么单线程的Redis却能够快到飞起?

别急,我尽量用通俗易懂的语言来给各位说道说道~~

Redis是单线程,主要是指Redis的网络IO和读写是由一个线程来完成的,但Redis的其他功能,比如持久化、异步删除、集群数据同步等,其实是由额外的线程执行的。这不是本文讨论的重点,有个印象即可

Redis为什么用单线程?

多线程的开销

通常情况下,在采用多线程后,如果没有良好的系统设计,其实是右图所展示的那样(注意纵坐标)。刚开始增加线程数时,系统吞吐率会增加,再进一步增加线程时,系统吞吐率就增长迟缓了,甚至还会出现下降的情况。

关键瓶颈在于: 系统中通常会存在会被多线程同时访问的共享资源,为了保证共享资源的正确性,就需要有额外的机制保证线程安全性,例如加锁,这会带来额外的开销。

比如拿最常用的List类型来举例吧,假设Redis采用多线程设计,有两个线程A和B分别对ListLPUSHLPUSH操作,为了使得每次执行都是相同的结果,即【B线程取出A线程放入的数据】就需要让这两个过程串行执行。这就是多线程编程模式面临的共享资源的并发访问控制问题。

并发访问控制一直是多线程开发中的一个难点问题:如果只是简单地采用一个互斥锁,就会出现即使增加了线程,大部分线程也在等待获取互斥锁,并行变串行,系统吞吐率并没有随着线程的增加而增加。

同时加入并发访问控制后也会降低系统代码的可读性和可维护性,所以Redis干脆直接采用了单线程模式。

Redis使用单线程为什么还这么快?

之所以使用单线程是Redis设计者多方面衡量的结果。

  • Redis的大部分操作在内存上完成
  • 采用了高效的数据结构,例如哈希表和跳表
  • 采用了多路复用机制,使其在网络IO操作中能并发处理大量的客户端请求,实现高吞吐率

既然Redis使用单线程进行IO,如果线程被阻塞了就无法进行多路复用了,所以不难想象,Redis肯定还针对网络和IO操作的潜在阻塞点进行了设计。

网络与IO操作的潜在阻塞点

在网络通信里,服务器为了处理一个Get请求,需要监听客户端请求(bind/listen),和客户端建立连接(accept),从socket中读取请求(recv),解析客户端发送请求(parse),最后给客户端返回结果(send)。

最基本的一种单线程实现是依次执行上面的操作。

上面标红的accept和recv操作都是潜在的阻塞点:

  • 当Redis监听到有连接请求,但却一直不能成功建立起连接时,就会阻塞在accept()函数这里,其他客户端此时也无法和Redis建立连接
  • 当Redis通过recv()从一个客户端读取数据时,如果数据一直没有到达,也会一直阻塞

基于多路复用的高性能IO模型

为了解决IO中的阻塞问题,Redis采用了Linux的IO多路复用机制,该机制允许内核中,同时存在多个监听套接字和已连接套接字(select/epoll)。

内核会一直监听这些套接字上的连接或数据请求。一旦有请求到达,就会交给Redis处理,这就实现了一个Redis线程处理多个IO流的效果。

此时,Redis线程就不会阻塞在某一个特定的客户端请求处理上,所以它可以同时和多个客户端连接并处理请求。

回调机制

select/epoll一旦监测到FD上有请求到达时,就会触发相应的事件被放进一个队列里,Redis线程对该事件队列不断进行处理,所以就实现了基于事件的回调。

例如,Redis会对Accept和Read事件注册acceptget回调函数。当Linux内核监听到有连接请求或读数据请求时,就会触发Accept事件和Read事件,此时,内核就会回调Redis相应的acceptget函数进行处理。

Redis的性能瓶颈点

经过上面的分析,虽然通过多路复用机制可以同时监听多个客户端的请求,但Redis仍然有一些性能瓶颈点,这也是我们平时编程需要极力避免的情况。

1. 耗时操作

任意一个请求在Redis中一旦耗时较久,都会影响整个server的性能。后面的请求都要等前面这个耗时请求处理完成,自己才能被处理到。

这一点需要我们在设计业务场景时去规避;Redis的lazy-free机制也把释放内存的耗时操作放在了异步线程中去执行了。

2. 高并发场景

并发量非常大时,单线程读写客户端IO数据存在性能瓶颈,虽然采用IO多路复用机制,但还是只能单线程依次读取客户端的数据,无法利用到CPU多核。

Redis在6.0可以利用CPU多核多线程读写客户端数据,但只是针对客户端的读写是并行的,每个命令的真正操作还是单线程。

其他Redis相关的有趣问题

借此机会也提几个和redis相关的有意思的问题。

  • 为什么要用Redis,直接访问内存不好吗?

这一条其实并没有很明确的界定,对于一些不经常变动的数据,可以直接放到内存里,不一定要放到Redis里,可以放到内存里。一致性问题:如果一个数据被修改了,数据在本地内存里的话,可能只有一台服务器上的数据被修改了。如果用Redis里面的话,我们访问Redis服务器,可以解决一致性问题。

  • 数据太多内存放不下怎么办?比如我要缓存100G的数据,怎么办?

这里也要打一个广告Tair是淘宝开源的分布式KV缓存系统,它从Redis继承了丰富的操作,理论上总数据量无限制,针对可用性、可扩展性、可靠性也进行了升级,感兴趣的小伙伴们可以了解一下~

到此这篇关于聊聊单线程的Redis为何会快到飞起的文章就介绍到这了,更多相关Java Redis内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java使用Redis及其优化详解

    目录 前言 开启远程连接 Jedis连接Redis 封装Jedis进行操作 前言 所有坚韧不拔的努力迟早会取得报酬的.-- 安格尔 开启远程连接 Redis默认是不支持远程连接的,这里需要手动开启远程连接. 关闭本机IP绑定,允许远程连接.找到redis.conf中的bind:127.0.0.1将其注释. 开启密码校验.找到redis.conf中的requirepass去掉其注释并设置密码. Jedis连接Redis 创建一个Maven项目,导入Jedis依赖. <dependency> &l

  • Redis五种数据结构在JAVA中如何封装使用

    目录 数据结构 string字符串 string字符串简介 string字符串在Java中的封装 list列表 list列表简介 队列 栈 list列表在Java中的封装 hash(字典) hash字典简介 hash字典在Java中的封装 set(集合) set集合简介 set集合在Java中的封装 zset(有序列表) zset有序列表 zset有序列表在Java中的封装 数据结构 Redis有五种基础数据结构,分别为: 1.string(字符串) 2.list(列表) 3.hash(字典)

  • Java手写Redis服务端的实现

    目录 零,起因 一,redis通讯与Netty 1,tcp 2,协议 3,编解码 4,命令处理 二,redis 的数据结构 1,底层主结构 2,key 3,list 4,set 5,hash 6,zset 三,redis AOF 持久化 1,aof线程与tcp线程解耦,即写缓冲 2,aof持久化协议 3,aof的加载与存储实现 4,内存文件映射与面向对象 四,redis 的集群特性 1,主从 2,主从复制 3,分片集群 五,redis 的压测与调优 1,aof内存泄漏 2,内存复用提升性能 3,

  • java秒杀之redis限流操作详解

    最近写到了一个秒杀的功能模块,为了保证高并发情况下不会宕机,要从多方面去考虑,当前的限流操作只是其中的一个方面,具体操作如下. 导入所需依赖 <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.targ

  • 详解Java redis中缓存穿透 缓存击穿 雪崩三种现象以及解决方法

    目录 前言 一.缓存穿透 二.缓存击穿 三.雪崩现象 总结 前言 本文主要阐述redis中的三种现象 1.缓存穿透 2.缓存击穿 3.雪崩现象 本文主要说明本人对三种情况的理解,如果需要知道redis基础请查看其他博客,加油! 一.缓存穿透 理解:何为缓存穿透,先要了解穿透,这样有助于区分穿透和击穿,穿透就类似于伤害一点一点的累计,最终打到穿透的目的,类似于射手,一下一下普通攻击,最终杀死对方,先上图 先来描述一下缓存穿透的过程: 1.由于我们取数据的原则是先查询redis上,如果redis上有

  • Java中操作Redis的详细方法

    目录 1.准备操作 1.1 新建工程 1.2 sca-jedis工程依赖 1.3 sca-tempalte工程依赖 1.4 测试是否可以连接Redis 1.5 修改redis.conf文件 2. 基于Jedis实现对redis中字符串的操作 3. 模式总结 4. 连接池JedisPool应用 5. 单例模式创建连接池 拓展:volatile关键字 6. 项目工程实践 6.1 分布式id 6.2 单点登陆 6.3 投票系统 7. StringRedisTemplate 应用 7.1 修改yml文件

  • Java SpringBoot 集成 Redis详解

    目录 1.概述 Redis是什么? Redis能该干什么? 特性 2.测试Redis 3.自定义redisTemplate 1.概述 Redis是什么? Redis(Remote Dictionary Server ),即远程字典服务. 是一个开源的使用ANSI C语言编写.支持网络.可基于内存亦可持久化的日志型.Key-Value数据库,并提供多种语言的API. 与memcached一样,为了保证效率,数据都是缓存在内存中.区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加

  • Java操作redis设置第二天凌晨过期的解决方案

    目录 Java操作redis设置第二天凌晨过期 场景 思路 代码 redis过期策略功能介绍 设置过期时间 内存淘汰 Java操作redis设置第二天凌晨过期 场景 在做查询数据的时候,遇到了需要设置数据在redis中第二天过期的问题,但是redis又没有对应的API,就只好自己来解决了 思路 计算出第二天凌晨与当前时间的时间差,将该时间差设置为redis的过期时间,就可以达到我们想要的效果 代码 /**      * 计算第二天凌晨与当前时间的时间差秒数      * @param      

  • 聊聊单线程的Redis为何会快到飞起

    目录 Redis为什么用单线程? 多线程的开销 Redis使用单线程为什么还这么快? 网络与IO操作的潜在阻塞点 基于多路复用的高性能IO模型 回调机制 Redis的性能瓶颈点 1.耗时操作 2.高并发场景 其他Redis相关的有趣问题 实际上它确实也很快 : ),但Redis底层却是单线程的!有同学可能就要有疑问了,为什么单线程的Redis却能够快到飞起? 别急,我尽量用通俗易懂的语言来给各位说道说道~~ Redis是单线程,主要是指Redis的网络IO和读写是由一个线程来完成的,但Redis

  • Redis为什么选择单线程?Redis为什么这么快?

    今天搞一下经典面试题Redis为什么选择单线程?Redis为什么这么快?,实现快速入门,丰富个人简历,提高面试level,给自己增加一点谈资,秒变面试小达人,BAT不是梦. 一.Redis版本迭代 Redis2.6,支持lua脚本: Redis3.0,支持集群: Redis4.0,混合持久化,多线程异步删除: Redis5.0,核心代码重构: Redis6.0,多线程IO: Redis7.0,Function.Multi-part-AOF: 二.Redis4.0之前为什么一直采用单线程? 1.R

  • 硬核!15张图解Redis为什么这么快(推荐)

    作为一名服务端工程师,工作中你肯定和 Redis 打过交道.Redis为什么快,这点想必你也知道,至少为了面试也做过准备.很多人知道Redis快仅仅因为它是基于内存实现的,对于其它原因倒是模棱两可. 那么今天就和小莱一起看看: 图注:- 思维导图 - 基于内存实现 这点在一开始就提到过了,这里再简单说说. Redis 是基于内存的数据库,那不可避免的就要与磁盘数据库做对比.对于磁盘数据库来说,是需要将数据读取到内存里的,这个过程会受到磁盘 I/O 的限制. 而对于内存数据库来说,本身数据就存在于

  • 浅谈为什么单线程的redis那么快

    目录 redis单机QPS 为什么这么快 内存型数据库 简单的数据结构 单线程 IO多路复用 总结 redis单机QPS ./redis-benchmark -t set,lpush -n 100000 -q SET: 82101.80 requests per second LPUSH: 82440.23 requests per second 在自己的电脑上测试SET和LPUSH10万次,可以发现每秒SET和LPUSH大概在8w多,接近官方说的单机10w qps的写. 为什么这么快 内存型数

  • redis实现分布式的方法总结

    一 为什么使用 Redis 在项目中使用 Redis,主要考虑两个角度:性能和并发.如果只是为了分布式锁这些其他功能,还有其他中间件 Zookpeer 等代替,并非一定要使用 Redis. 性能: 如下图所示,我们在碰到需要执行耗时特别久,且结果不频繁变动的 SQL,就特别适合将运行结果放入缓存.这样,后面的请求就去缓存中读取,使得请求能够迅速响应. 特别是在秒杀系统,在同一时间,几乎所有人都在点,都在下单...执行的是同一操作———向数据库查数据. 根据交互效果的不同,响应时间没有固定标准.在

  • Redis为什么快如何实现高可用及持久化

    前言 作为Java程序员,在面试过程中,缓存相关的问题是躲不掉的,肯定会问,例如缓存一致性问题,缓存雪崩.击穿.穿透等.说到缓存,那肯定少不了Redis,我在面试的时候也是被问了很多关于Redis相关的知识,但是Redis的功能太强大了,并不是一时半会儿能掌握好的,因为有些高级特性或是知识平时并不会用到. 所以回答的不好,人家就会觉得你对自己平时使用的工具都没有了解,自然就凉凉了.其实很早就有这个打算,打算好好总结一下Redis的知识,但也是由于自己都没有好好的了解Redis呢,所以一直没有开始

  • Redis凭啥可以这么快

    在日常开发中,为了保证数据的一致性,我们一般都选择关系型数据库来存储数据,如 MySQL,Oracle 等,因为关系型数据库有着事务的特性.然而在并发量比较大的业务场景,关系型数据库却又往往会成为系统瓶颈,无法完全满足我们的需求,所以就需要使用到缓存,而非关系型数据库,即 NoSQL 数据库往往又会成为最佳选择. NoSQL 数据库最常见的解释是 non-relational,也有人解释为 Not Only SQL.非关系型数据库不保证事务,也就是不具备事务 ACID 特性,这也是非关系型数据库

  • 详解Redis单线程的正确理解

    很多同学对Redis的单线程和I/O多路复用技术并不是很了解,所以我用简单易懂的语言让大家了解下Redis单线程和I/O多路复用技术的原理,对学好和运用好Redis打下基础. 一.Redis的单线程理解 Redis客户端对服务端的每次调用都经历了发送命令,执行命令,返回结果三个过程.其中执行命令阶段,由于Redis是单线程来处理命令的,所有到达服务端的命令都不会立刻执行,所有的命令都会进入一个队列中,然后逐个执行,并且多个客户端发送的命令的执行顺序是不确定的,但是可以确定的是不会有两条命令被同时

  • 关于Redis单线程的正确理解

    很多同学对Redis的单线程和I/O多路复用技术并不是很了解,所以我用简单易懂的语言让大家了解下Redis单线程和I/O多路复用技术的原理,对学好和运用好Redis打下基础. 一.Redis的单线程理解 Redis客户端对服务端的每次调用都经历了发送命令,执行命令,返回结果三个过程.其中执行命令阶段,由于Redis是单线程来处理命令的,所有到达服务端的命令都不会立刻执行,所有的命令都会进入一个队列中,然后逐个执行,并且多个客户端发送的命令的执行顺序是不确定的,但是可以确定的是不会有两条命令被同时

  • 为啥Redis使用pipelining会更快

    为啥Redis使用pipelining会更快? 这是一个很考究细节的问题,大部分人都会说:因为减少了网络开销,那么,看如下例子: import time import redis client = redis.Redis(decode_responses=True) count = 10000 def no_pipelining(): for i in range(count): client.set("test:nopp:{}".format(i), i, ex=100) def w

随机推荐