最近学习了一下Redis写一篇文章来总结一下学习成果,学习的方式主要是看书,看的是Redis 5设计与源码分析;想系统学习的同学,可以好好看看很推荐这本书,那么,为什么标题选择Redis为什么会那么快?因为,我在学习的过程中,感受到Redis的精髓就是快,为了快这个属性,它有了很多自己特殊设计及实现;
Redis快,我主要是基于三大部分的理解
下面分别对这2,3部分进行展开:
首先,先要知道Redis工作线程是单线程的,但是,整个Redis来说,是多线程的;
Redis事件处理 :
Redis 服务器是典型的事件驱动程序,而事件又分为文件事件(socket 的可读可写事件)与时间事件(定时任务)两大类。已经注册的文件事件存储在event[]数组中, 时间事件形成链表;Redis 底层可以使用4中I/O多路复用模型(kqueue、epoll、select等)根据 *** 作系统的不同选择不同, 关于,多路复用模型相关内容可以查看我的另一篇文章 *** 作系统IO进化史 所以,epoll本身就效率很高了;但是,随着我们网卡的不断升级,在Redis 60之后的版本中,对IO的处理变成了多线程;
为什么对IO的处理变成了多线程能提高速度?
下面是Redis60之前的情况:
如果到了Redis60之后:
所以,这也是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是比较慢的,所以,在40以后,AOF就包含了RDB全量,和增加的新的写 *** 作。这样来提高速度;
缺点 :由于,AOF是增加的方式,所以,如果一直增加的话,就会有 1体量无限变大 2恢复慢 的缺点;为了解决这个问题,需要设计出一个方案让日志AOF足够小;这个,就有了 重写 的方案;40之前,重写方案是将AOF进行瘦身,比如说:把创建key和删除key的命令进行抵消删除;40之后,就采用 混合持久化 比如说:我这个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定义:
Redis是由意大利人SalvatoreSanfilippo(网名:antirez)开发的一款内存高速缓存数据库。Redis全称为:RemoteDictionaryServer(远程数据服务),该软件使用C语言编写,Redis是一个key-value存储系统,它支持丰富的数据类型,如:string、list、set、zset(sortedset)、hash。
②Redis特点
Redis以内存作为数据存储介质,所以读写数据的效率极高,远远超过数据库。以设置和获取一个256字节字符串为例,它的读取速度可高达110000次/s,写速度高达81000次/s。
Redis跟memcache不同的是,储存在Redis中的数据是持久化的,断电或重启后,数据也不会丢失。因为Redis的存储分为内存存储、磁盘存储和log文件三部分,重启后,Redis可以从磁盘重新将数据加载到内存中,这些可以通过配置文件对其进行配置,正因为这样,Redis才能实现持久化。
Redis支持主从模式,可以配置集群,这样更利于支撑起大型的项目,这也是Redis的一大亮点。
③Redis应用场景,它能做什么
众多语言都支持Redis,因为Redis交换数据快,所以在服务器中常用来存储一些需要频繁调取的数据,这样可以大大节省系统直接读取磁盘来获得数据的I/O开销,更重要的是可以极大提升速度。
拿大型网站来举个例子,比如a网站首页一天有100万人访问,其中有一个板块为推荐新闻。要是直接从数据库查询,那么一天就要多消耗100万次数据库请求。上面已经说过,Redis支持丰富的数据类型,所以这完全可以用Redis来完成,将这种热点数据存到Redis(内存)中,要用的时候,直接从内存取,极大的提高了速度和节约了服务器的开销。
总之,Redis的应用是非常广泛的,而且极有价值,真是服务器中的一件利器,所以从现在开始,我们就来一步步学好它。
11 锁需要具备唯一性12 锁需要有超时时间,防止死锁
13 锁的创建和设置锁超时时间需要具备原子性
14 锁的超时的续期问题
15 B的锁被A给释放了的问题
16 锁的可重入问题
17 集群下分布式锁的问题
问题讲解:
首先分布式锁要解决的问题就是分布式环境下同一资源被多个进程进行访问和 *** 作的问题,既然是同一资源,那么肯定要考虑数据安全问题其实和单进程下加锁解锁的原理是一样的,单进程下需要考虑多线程对同一变量进行访问和修改问题,为了保证同一变量不被多个线程同时访问,按照顺序对变量进行修改,就要在访问变量时进行加锁,这个加锁可以是重量级锁,也可以是基于cas的乐观锁
解决方案:
使用redis命令setnx(set if not exist),即只能被一个客户端占坑,如果redis实例存在唯一键(key),如果再想在该键(key)上设置值,就会被拒绝
问题讲解:
redis释放锁需要客户端的 *** 作,如果此时客户端突然挂了,就没有释放锁的 *** 作了,也意味着其他客户端想要重新加锁,却加不了的问题
解决方案:
所以,为了避免客户端挂掉或者说是客户端不能正常释放锁的问题,需要在加锁的同时,给锁加上超时时间
即,加锁和给锁加上超时时间的 *** 作如下 *** 作:
>setnx lockkey true #加锁 *** 作
ok
>expire lockkey 5 #给锁加上超时时间
do something critical
>del lockkey #释放锁
(integer) 1
问题讲解:
通过23加锁和超时时间的设置可以看到,setnx和expire需要两个命令来完成 *** 作,也就是需要两次RTT *** 作,如果在setnx和expire两次命令之间,客户端突然挂掉,这时又无法释放锁,且又回到了死锁的问题
解决方案:
使用set扩展命令
如下:
>set lockkey true ex 5 nx #加锁,过期时间5s
ok
do something critical
>del lockkey
以上的set lockkey true ex 5 nx命令可以一次性完成setnx和expire两个 *** 作,也就是解决了原子性问题
问题讲解:
redis分布式锁过期,而业务逻辑没执行完的场景,不过,这里换一种思路想问题,把redis锁的过期时间再弄长点不就解决了吗?那还是有问题,我们可以在加锁的时候,手动调长redis锁的过期时间,可这个时间多长合适?业务逻辑的执行时间是不可控的,调的过长又会影响 *** 作性能。
解决方案:
使用redis客户端redisson,redisson很好的解决了redis在分布式环境下的一些棘手问题,它的宗旨就是让使用者减少对Redis的关注,将更多精力用在处理业务逻辑上。redisson对分布式锁做了很好封装,只需调用API即可。RLock lock = redissonClientgetLock("stockLock");
redisson在加锁成功后,会注册一个定时任务监听这个锁,每隔10秒就去查看这个锁,如果还持有锁,就对过期时间进行续期。默认过期时间30秒。这个机制也被叫做:“看门狗”
问题讲解:
A、B两个线程来尝试给key myLock加锁,A线程先拿到锁(假如锁3秒后过期),B线程就在等待尝试获取锁,到这一点毛病没有。那如果此时业务逻辑比较耗时,执行时间已经超过redis锁过期时间,这时A线程的锁自动释放(删除key),B线程检测到myLock这个key不存在,执行 SETNX命令也拿到了锁。但是,此时A线程执行完业务逻辑之后,还是会去释放锁(删除key),这就导致B线程的锁被A线程给释放了。
解决方案:
一般我们在每个线程加锁时要带上自己独有的value值来标识,只释放指定value的key,否则就会出现释放锁混乱的场景一般我们可以设置value为业务前缀_当前线程ID或者uuid,只有当前value相同的才可以释放锁
问题讲解:
上面我们讲了,为了保证锁具有唯一性,需要使用setnx,后来为了与超时时间一起设置,我们选用了set命令。 在我们想要在加锁期间,拥有锁的客户端想要再次获得锁,也就是锁重入
解决方案:
给锁设置hash结构的加锁次数,每次加锁就+1
问题讲解:
这一问题是在redis集群方案时会出现的事实上,现在为了保证redis的高可用和访问性能,都会设置redis的主节点和从节点,主节点负责写 *** 作,从节点负责读 *** 作,也就意味着,我们所有的锁都要写在主redis服务器实例中,如果主redis服务器宕机,资源释放(在没有加持久化时候,如果加了持久化,这一问题会更加复杂),此时redis主节点的数据并没有复制到从服务器,此时,其他客户端就会趁机获取锁,而之前拥有锁的客户端可能还在对资源进行 *** 作,此时又会出现多客户端对同一资源进行访问和 *** 作的问题
解决方案:
使用redlock,原理与zookeeper分布式锁原理相同多台主机超过半数设置成功则获取锁成功,要注意下主机个数必须是奇数,不过这有效率问题构建 Redis
redis 目前没有官方 RPM 安装包,需要从源代码编译,而为了要编译就需要安装 Make 和 GCC。
如果没有安装过 GCC 和 Make,那么就使用 yum 安装。
yum install gccmake
从官网下载 tar 压缩包。
curl >
为了将一台服务器进行关机和重启,您可以按照以下步骤进行 *** 作:
关闭所有正在运行的 Redis 实例。
使用以下命令将需要关机的服务器加入集群:
Copy coderedis-cluster add-node <服务器 IP>:<端口>其中 <服务器 IP> 是需要加入集群的服务器 IP 地址,<端口> 是该服务器的 Redis 端口号。
使用以下命令将需要重启的服务器从集群中移除:
Copy coderedis-cluster remove-node <服务器 IP>:<端口>其中 <服务器 IP> 是需要移除集群的服务器 IP 地址,<端口> 是该服务器的 Redis 端口号。
关机并重启需要关机的服务器。
Copy codesudo systemctl stop <服务器名称> sudo systemctl restart <服务器名称>其中 <服务器名称> 是需要关机和重启的服务器名称。
检查 Redis 集群是否正常工作。
您可以使用以下命令检查 Redis 集群是否正常工作:
Copy coderedis-cluster status如果一切正常,则应该可以看到所有服务器的状态都为 "Online"。
请注意,在 *** 作 Redis 集群时必须非常小心,以免误 *** 作导致数据丢失。如果您不确定如何 *** 作 Redis 集群,请寻求专业人员的帮助。
Try again
7站VPS导航为您回答 选购VPS上>
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)