redis多线程(redis多线程还能保证线程安全吗)
本篇文章给大家谈谈redis多线程,以及redis多线程还能保证线程安全吗对应的知识点,希望对各位有所帮助,不要忘了收藏本站喔。
本文目录一览:
- 1、redisgetset如何防止多线程
- 2、Redis到底是多线程还是单线程
- 3、Redis的IO多路复用——单线程的理解(Redis6.0之后的多线程)
- 4、Redis为什么会那么快?
- 5、redis实现多个线程同时修改同一个数据,保证数据一致性
- 6、Redis 6.0多线程介绍
redisgetset如何防止多线程
1.将并行操作转化成串行操作,常用的清哪实现方式:
a.加锁,使临界区资源,只能有一个线程/进程可以访问。
b.执行业务逻辑的工作线程只分配一个,这也可以从根本上防止并发问题的产生。
2.基于操孙桐作系统提供给上层应用的原子操作能力,实现"CAS"的原子操作。
以上方案各有优劣,都有各自的使用场景,这里我们不做过多比较。其答凯码实工作遇到的问题,在一些开源软件中也会遇到,在一些比较著名开源软件中又是如何解决线程安全问题呢?或者能从中学到一些知识
Redis到底是多线程还是单线程
redis本身是单线程的 ,只不过很多时候一台机器上会部署很多redis做集群。
[img]Redis的IO多路复用——单线程的理解(Redis6.0之后的多线程)
Reactor 设计模式是一种 事件驱动 的设计模式,分发器(Dispatcher)使用多路分配器(Demultiplexer)监听多个客户端请求,当请求事件(Events)发生,分发器(Dispatcher)将一个或者多个客户端请求(Events)分发到不同的处理器(Event Handler)上塌斗,提升事件处理的效率。
下图为Reactor设计模式类图:
基于Reactor设行前计模式实现的IO多路复用
IO多路复用技术架构图如下
注:
多线程处理可能涉及锁,并且涉及切换线程的消耗。
耗时的命令会导团带磨致性能下降,而且无法发挥CPU多核的性能。
Redis多线程只用来处理网络数据的读写和协议解析,命令的执行仍旧是单线程。这样的设计改变是为了不想让Redis因为引入多线程变得复杂。而且过去单线程的使用主要考虑CPU不是Redis的瓶颈,不需要多条线程并发执行,所以多线程模型带来的性能提升不能抵消它带来的开发和维护成本。
而现在引入多线程模型解决的是网络IO操作的性能瓶颈。对于Redis基于内存的操作,仍然是很快的,而有时IO操作阻塞会影响着之后操作的效率。改为多线程并发进行IO操作,然后交由主线程进行内存操作,这样可以更好的缓解IO操作带来的性能瓶颈。
架构如下图:
Redis为什么会那么快?
最近学习了一下Redis写一篇文章来总结一下学习成果,学习的方式慎轮主要是看书,看的是Redis 5设计与源码分析;想系统学习的同学,可以好好看看很推荐这本书,那么,为什么标题选择Redis为什么如仔会那么快?因为,我在学习的过程中,感受到Redis的精髓就是快,为了快这个属性,它有了很多自己特殊设计及实现;
Redis快,我主要是基于三大部分的理解
下面分别对这2,3部分进行展开:
首先,先要知道Redis工作线程是单线程的,但是,整个Redis来说,是多线程的;
Redis事件处理 :
Redis 服务器是典型的事件驱动程序,而事件又分为文件事件(socket 的可读可写事件)与时间事件(定时任务)两大类。已经注册的文件事件存储在event[]数组中, 时间事件形成链表;Redis 底层可以使用4中I/O多路复用模型(kqueue、epoll、select等)根据操作系统的不同选择不同, 关于,多路复用模型相关内容可以查看我的另一篇文章 操作系统IO进化史 所以,epoll本身就效率很高了;但是,随着我们网卡的不断升级,在Redis 6.0之后的版本中,对IO的处理变成了多线程;
为什么对IO的处理变成了多线程能提高速度?
下面是Redis6.0之前的情渣孝汪况:
如果到了Redis6.0之后:
所以,这也是Redis快的一个主要原因;
由于,Redis中设计的话,主要分为底层设计结构以及一些相应的功能,所以,特定将其分为2部分来进行讲解;
Redis底层数据结构有简单动态字符串,跳跃表,压缩列表,字典,整数集合;针对,简单动态字符串,压缩列表,主要是考虑到节约内存;像跳跃表,字典,主要是考虑到查询速度,整数集合即考虑到了空间又考虑到了时间;其实像字典中的渐进式rehash,以及间断key查找,都是考虑到了节约时间;具体的内容可以查看我的另一篇文章, Redis底层数据结构
具体细节可查看官网
优点:最多有25%的过期key存在内存中,这种方法会比轮询更加省时间;就是稍微牺牲内存,来保证 redis的性能,就是快; 还是以空间换时间的思想;
注意 :个人觉得这里和 缓存雪崩 还能建立其联系,如果,一个大型的redis实例中所有的key在同一时间过期了,那么,redis会持续扫描keys 因为,一直大于25%;虽然,这是有扫描时间的上限的25ms;这个时候,刚好客户端请求过来了,如果,客户端将超时时间设置的比较短,比如说10ms,那么就会出现大量链接因为超时而关闭,业务端也会出现很多异常。(客户端超时时间,如果说设置得太小,那么容易导致访问redis失败,如果,设置太大,那么,在redis异常的时候,不容易及时作出切换;一般是通过网络延迟和redis慢日志来进行查看的)
redis的特点是快,它虽然有事务,但是,它是没有回滚的,事务的功能是不够完善的; 回滚:代表失败时,回滚到事务开始的时刻;
redis 是单线程的 如果,有多个客户端,一个客户端的事务 并不会阻塞到其他客户端; 客户端1 发送 开启事务的标记 客户端2 也开启事务 。随着时间发展;2又连续发了一些命令 1 也发了一些命令; 这时候,会先看谁的执行指令先到; 假设 2 先到达,这个时候,先执行2 的相关数据,在执行1相关的命令; 如果 1 先到达,这个时候,先执行1 的相关命令,再执行2;
事务失败处理
这个时候,会发现报错那条语句不执行,剩下的语句都会进行执行;也没有发生了回滚;
证明 :redis是不支持事务回滚的。在运行期错误,即使事务中有某条/某些命令执行失败了,事务队列中的其他命令仍然会继续执行 -- Redis 不会停止执行事务中的命令;
为什么Redis 不支持事务回滚?
总结 :Redis为了快,而不支持事务回滚;
在redis中,有两个东西 第一个为RDB , 第二个为AOF RDB为快照/副本相关内容, AOF为日志相关的内容;
RDB的特点 :1.需要时点性 (比如说:我有1G的内存,需要持久化到硬盘,比如说:一个小时持久化一次。那么,假设在8点,就需要进行持久化)
如何实现RDB持久化呢?
方法一:阻塞Redis ,Redis不再对外提供服务了,但是,这种方式是需要阻塞的,很显然,如果,这个持久化需要花费1s,那么,这个时候,Redis 不能被客户端进行使用;
方法二:非阻塞 Redis继续对外提供服务;
但是,这个时候会出现一个问题;比如说:8点开始RDB持久化,8点零1秒才持久化完,问题就来了:持久化的数据是8点的还是8点零1秒的呢?很显然,是8点的;那么,在8点到8点零1秒这个过程中,数据是会发生改变的,那么, 怎么解决这个数据不一致的问题呢? 比如说:8点的时候,b = 10 到 8点零1秒的时候,b =20;
为了解决这个读写并存 使用CopyOnWrite 的思想来进行实现;
就是,在操作系统中,先使用fork() 创建子线程来复制一份副本(注意:这里拷贝的是指针,所以,速度会很快)然后,这个副本,就保持在8点不变了。然后,复制的时候,就复制这份副本就行了,对数据增删改查就在父进程中更改。
但是,因为父子进程都指向的是同一个内存,所以,不能在这个内存中改,比如说:不能在原来key 8 中进行更改,比如说要改key = 10 那么,就得在内存中,再创建一块区域,然后,让父进程中指针指向新的key ,这样两个进程就不会相互影响了。
这里也验证了Redis是多线程的;
具体实现:
RDB的缺点
RDB的优点 :恢复数据的速度相对较快;
Redis内存大小选择 进程一般使用10G以内,因为从内存到磁盘持久化这个过程,如果说,10G需要写的时间比较久,那么,如何解决呢?1. 减少内存 2. 硬盘选择固态硬盘;
针对RDB容易丢失数据的问题,提出了AOF持久化机制
AOF : append on File 向文件中,进行追加;redis发生写操作时,会记录到文件中;
优点 :1.丢失的数据比较少
背景 :RDB和AOF可以同时开启,如果,开启了AOF只会用AOF来进行恢复,即便RDB也开启了,也不会使用它;因为,AOF的修复比较准确;但是,AOF是比较慢的,所以,在4.0以后,AOF就包含了RDB全量,和增加的新的写操作。这样来提高速度;
缺点 :由于,AOF是增加的方式,所以,如果一直增加的话,就会有 1.体量无限变大 2.恢复慢 的缺点;为了解决这个问题,需要设计出一个方案让日志AOF足够小;这个,就有了 重写 的方案;4.0之前,重写方案是将AOF进行瘦身,比如说:把创建key和删除key的命令进行抵消删除;4.0之后,就采用 混合持久化 比如说:我这个AOF已经到了100M文件了,这个时候,我先将老的数据变成RDB文件(二进制文件)然后,再存储到AOF中,再将增量以指令的方式Append 到AOF。所以,是一个混合体;这里的AOF日志不再是全量的日志,而是持久化开始到持久化结束这段时间的增量AOF日志通常很小;那么,它这么改变的 优点 是:在Redis重启时,可以先加载RDB的内容,在加载增量AOF日志,完全替代AOF全量日志重放,重启的效率将大幅度提升; 每次一重写完,就会变成RDB ;
脏数据刷入时机 :AOF日志是以文件形式存在的,当程序对AOF日志进行写操作时, 实际上是先将数据写到一个内存缓存中,然后,让内存再把脏数据写回到磁盘中 那么,什么时候写呢?如果,还没来的及写就宕机了,那么可能会出现日志丢失;这时候有三个级别可以调;
no : 不调用fsync 等到它满了再进行调用(fsync 可以将指定文件的内容,强制从内核缓存刷到磁盘) 一般生产环境不用
always :每写了一个数据,就调用一次fsync 一般生产环境不用
everysec: redis每一秒调用一次flush
一般Redis 的主节点不会进行持久化操作,持久化操作主要是在从节点中进行。因为,没有来自客户端请求的压力;
上面是Redis持久化的两种方式 由于,持久化过程需要花费的时间是比较多的,所以,一般由从节点来进行持久化操作; 主服务器发现需要执行完整重同步时,会fork子进程执行RDB持久化,并将持久化数据发送给从服务器。这时候,有两种选择 1. 直接通过Socket发送给从服务器(从服务器支持eof),2. 持久化数据到本地文件,待持久化完毕后再将该文件发送给从服务器。 默认第二种,具体情况是根据同步信息确定;但是,第一种效率会更高,速度会更快;
总结 :为了Redis快的特性,Redis在持久化的时候,使用fork()函数,新开线程来执行;同时,如果主从服务器的话,还提供了psync2来进行部分重同步;eof功能;
redis的特点就是快,在系统设计的方方面面都体现了这个快的特性;这是我自己在学习Redis相关知识时,了解到的内容,做个记录。如果,有偏差欢迎读者进行指正!
redis实现多个线程同时修改同一个数据,保证数据一致性
memcached在多线程情况下是支持并发操作的,但是如果使用redis的话就发现redis是单线程,所以涉及到了并发的问题.
GET 获得键值后保证该键值不被其他客户端修改,直到函数执行完虚亏成后才允许其他客户端修改该键键值,这样滑正也可以防止竞态条件.
watch可以监控一个或多个键,一旦其中有一个键被修改(或删除),之后的事务就不会执行。监控一直持续到EXEC命差让神令(事务中的命令是在EXEC之后才执行的,所以在MULTI命令后可以修改WATCH监控的键值)
Redis 6.0多线程介绍
Redis作为一个基于内存的缓存系统,一直以高性能著称,
在单线程处理情况下,读速度可达到11万次/s,写速度达到8.1万次/s。
官方曾做过类似问题的回复:使用Redis时,几乎不存在CPU成为瓶颈的情况, Redis主要受限于内存和网络。
但是,单线程的设计也给Redis带来一些问题:
针对上面问题,Redis在4.0版本以及6.0版本分别引入了Lazy Free以及多线程IO,逐步向多线程过渡。
Redis服务器是一个事件姿罩驱动程序,服务器需要处理以下两类事件:
Redis服务器通过套接字与客户端前册悔(或者其他Redis服务器)进行连接。
文件事件就是服务器对套接字操作的抽象 。
服务器与客户端的通信会产生相应的文件事件,而服务器则通过监听并处理这些事件来完成一系列网络通信操作。
(eg: 连接accept,read,write,close等)
Redis服务器中的一些操作(eg: serverCron函数)需要在给定的时间点执行。
时间事件就是服务器对这类定时操作的抽象
(eg: 过期键清理,服务状态统计等)
Redis将文件事件和时间事件进行抽象,时间轮询器会监听I/O事件表:
一旦有文件事件就绪,Redis就会优先处理文件事件,
接着处理时间事件。
在上述所有事件处理上,Redis都是以单线程形式处理,所以说Redis是单线程的。
处理过程见下图
Redis基于Reactor模式开发了自己的I/O事件处理器,也就是文件事件处理器。
Redis在I/O事件处理上,采用了I/O多路复用技术,同时监听多个套接字,
并为套接字关联不同的事件处理函数,通过一个线程实现了多客户端并发处理。
处理过程见下图
上述的设计,在数据处理上避免了加锁操作,既使得实现上足够简洁,也保证了其高性能。
当然, Redis单线程只慧正是指其在事件处理上 ,实际上,Redis也并不是单线程的,比如生成RDB文件,就会fork一个子进程来实现。
背景:
客户端向Redis发送一条耗时较长的命令,比如删除一个含有上百万对象的Set键,或者执行flushdb,flushall操作,
Redis服务器需要回收大量的内存空间,导致服务器卡住好几秒,对负载较高的缓存系统而言将会是个灾难。
为了解决这个问题,在Redis 4.0版本引入了Lazy Free, 将慢操作异步化 ,这也是在事件处理上向多线程迈进了一步。
将大键的删除操作异步化,采用非阻塞删除(对应命令UNLINK)。
大键的空间回收交由单独线程实现,主线程只做关系解除,可以快速返回,继续处理其他事件,避免服务器长时间阻塞。
意义:
Redis在4.0版本引入了Lazy Free,自此Redis有了一个 Lazy Free线程专门用于大键的回收 。
同时,也去掉了聚合类型的共享对象,这为多线程带来可能。
这为Redis在6.0版本实现了多线程I/O打下了基础。
Redis 6.0的多线程并未将事件处理改成多线程,而是在I/O上。
因为,如果把事件处理改成多线程,不但会导致锁竞争,而且会有频繁的上下文切换,
即使用分段锁来减少竞争,对Redis内核也会有较大改动,性能也不一定有明显提升。
流程简述如下:
见下图
Redis6.0的多线程默认是禁用的,只使用主线程。
如需开启需要修改redis.conf配置文件:
开启多线程后,还需要设置线程数,否则是不生效的。
同样修改redis.conf配置文件:
关于redis多线程和redis多线程还能保证线程安全吗的介绍到此就结束了,不知道你从中找到你需要的信息了吗 ?如果你还想了解更多这方面的信息,记得收藏关注本站。