前话
- 大家都知道redis是一个可以高速运行在缓存级别的数据库, 他的高速原因主要有几个原因
- 绝大部分请求是纯粹的内存操作(非常快速),避免了与硬盘的接触
- 丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除
- 使用大量的hash思想的k v键值对, 获取效率为O(1)
- 依靠非阻塞的IO多路复用原则,使redis形成单线程去执行命令的服务器, 避免了不必要的阻塞和上下文切换和竞争条件
redis采用IO复用实现单线程的方式, 将命令任务分装在队列中让一个线程去串行化执行, 自然避免了线程安全问题, 这也是为什么我们常说redis是基于原子操作的原因
跟多线程相比较,线程切换需要切换到内核进行线程切换,需要消耗时间和资源.而I/O多路复用不需要切换线/进程,效率相对较高,特别是对高并发的应用nginx就是用I/O多路复用,故而性能极佳
总结Redis特点
redis快是因为他是基于内存进行操作的,并且使用了大量的kv数据结构和很多特殊的数据类型,还使用了io多路复用避免了阻塞,所以使用多线程就可以避免上下文切换和锁机制和线程的创建和销毁,而且说他是单线程但是其他操作比如持久化,连接管理都是多线程式的,最主要的是由于上述条件,redis的性能瓶颈不在线程上,而是在内存容量和网络带宽上
- 但是还有一个问题, 上面说redis是单线程的, 那么就不会有线程安全问题, 那为什么还有让redis支持事务, 还要 要求他使用redis分布式事务锁
没错,大家所熟知的 Redis 确实是单线程模型,指的是执行 Redis 命令的核心模块是单线程的,而不是整个 Redis 实例就一个线程,Redis 其他模块还有各自模块的线程的。
比如socket连接, 这就是一个多线程式的
在redis事务中或有一个watch乐观锁去监视者数据是否被改动, 如果真的是完全单线程, 那么就不需要有这个监视的存在
本质原因就是redis的socket连接是多线程的, 我们在开始redis事务, 往里面添加命令其实并没有真正的执行这些命令, 所以需要watch的监视, 防止其他socket连接进行了数据的操作
下面看看redis的真实运行环境
- 它的组成结构为4部分:多个套接字、IO多路复用程序、文件事件分派器、事件处理器。
因为文件事件分派器队列的消费是单线程的,所以Redis才叫单线程模型。 - 所以Redis 不仅仅是单线程, 他需要事务的支持, 就是因为多客户端对Redis的连接并不存在竞争关系。
- 所以在单机服务器,出现资源的竞争,一般使用synchronized 还可以使用前的redis的事务就可以解决,但是在分布式的服务器上,多个系统或者说多个节点同时访问你这个redis节点,synchronized 和redis事务就无法解决这个问题,这就需要一个分布式事务锁。
讲分布式事务锁前的几个概念
- 分布式:简单来说就是将业务进行拆分,部署到不同的机器来协调处理。比如用户在网上买东西,大致分为:订单系统、库存系统、支付系统、、、、这些系统共同来完成用户买东西这个业务操作。
- 集群:同一个业务,通过部署多个实例来完成,保证应用的高可用,如果其中某个实例挂了,业务仍然可以正常进行,通常集群和分布式配合使用。来保证系统的高可用、高性能。
- 分布式事务:按照传统的系统架构,下单、扣库存等等,都是在单机系统中, 这一系列的操作都是一个数据库中完成的,也就是说保证了事务的ACID特性。如果在分布式应用中就会涉及到跨应用、跨库。这样就涉及到了分布式事务,就要考虑怎么保证这一系列的操作要么都成功要么都失败。保证数据的一致性。
- 分布式锁:因为资源有限,要通过互斥来保持一致性,比如下订单的数据库操作和支付的数据库操作就是一个保证互斥性, 不能同时去执行, 不然那就会出现一旦支付出现失败, 那么下订单也得重新下订单, 这就不合理了, 所以引入分布式事务锁。
Redis分布式事务锁原理
- 分布式锁是控制分布式系统之间同步访问共享资源的一种方式。如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要互斥来防止彼此干扰来保证一致性,在这种情况下,便需要使用到分布式锁。
- 简单来说就是好几个节点访问一个资源, 那我就是使用额外的锁机制互斥的只让其中一个能进行访问
核心思想
- 在被保护的redis节点加一把锁, 让这把锁和被保护的redis节建立直接映射
- 在访问这个redis之前都去看看这把锁在不在
- 如果不存在锁,说明没有客户端使用,可以执行任务,执行完毕,解锁,删除锁 (并且要保证判断有无锁和加锁是原子操作)
- 如果锁从在则认为有其他客户端在使用,等待锁消失
- Redis中可以使用SETNX命令实现分布式锁,(因redis执行命令是单线程所以这个命令是院子的, 完全可以放心去使用)。
- SETNX——SET if Not eXists(如果不存在,则设置):
setnx key value
- 如果需要解锁,使用
del key
命令就能释放锁
问题一
- 当一个客户端上锁之后服务宕机,由于锁是他上的只有他可以进行redis访问的,别人无法访问,所以导致锁无法被删除.
解决思路
- 给锁设置一个过期时间,可以通过两种方法实现:通过命令 “setnx 键名 过期时间 “;或者通过设置锁的expire(失效)时间,让Redis去删除锁。
问题一
- 当一个客户端设置了锁的失效时间, 但是这个客户端并没有宕机, 只是真的需要那么多时间来进行操作
- 也就是任务执行过长,超过过期时间。
解决思路
这就是上图那个看门狗的作用可, 这条狗看到时间快到了, 就大喊一声让时间倒流~
- 实际是通过客户端的一个守护线程,大概时间快到的时间给线程续命.
问题三
- 任务执行造成死循环,会造成无限续命
解决思路
设置最大续命时间, 或者设置最大续命次数