本篇就来聊一聊读写锁。所谓读写锁就是把一个锁拆分成两个锁:读锁和写锁。那么在加锁的时候,可以加写锁,也可以加读锁。如下代码所示:如果一个线程有写锁,那么其他线程不能加写锁,一次只允许一个线程加写锁。因为加了写锁就意味着有人要写一个共享数据,同时不能让其他人写这个数据。同时,如果一个线程加了写锁,其他线程就不能加读锁,因为既然有人在写数据,其他人当然就读不到数据了!如果一个线程加读锁,其他线程可以随意同时加读锁,因为只有一个线程在读数据,此时其他线程也可以读数据!同理,如果一个线程加了读锁,其他线程此时不能加写锁,因为既然有人在读数据,那你就不能让你随意写数据!好吧!这是对读写锁使用的初步介绍。相信很多同学之前应该都知道,因为这是java开发中非常基础的一个知识点。微服务注册中心读写锁的优化现在进入正题。我们主要讲微服务注册中心读写锁的优化。我们为什么要谈这个问题?因为如果你出去面试,很可能会被问到读写锁。这时候自然就可以带出来了。之前了解过SpringCloud微服务技术架构,同时读写里面的微服务注册中心的注册中心。锁优化有自己的一些见解和看法。这样的话,比起单纯跟面试官讲读写锁的基本概念和用法,会精彩很多!首先,你需要了解一点微服务的整体架构。可以参考我之前写的一篇文章。拜托,请不要在面试中问我SpringCloud的底层原理!.同时,你还需要了解SpringCloudEureka(即微服务注册中心)的核心原理。这个可以参考我之前写的一篇文章【双十一背后】微服务注册中心是如何承载大型系统的千万级访问量的。好了,了解了这些前提知识之后,我们就正式开始了。让我们看看下面的图片。既然知道了微服务注册中心(可以是Eureka也可以是Consul也可以是你自己写的微服务注册中心),他肯定在记忆中会有一个服务注册中心的概念。这个服务注册中心存储了每个微服务在注册时发送的地址信息。它存储了每个服务有多少个服务实例,每个服务实例部署在哪台机器上,监听哪个端口号,主要是这样一些信息。OK,问题来了。服务注册中心的数据实际上是被别人读写的。比如有些服务在启动的时候会进行注册,这时候服务注册中心的数据就会被修改。这就是写作的过程。然后,其他服务也会读取服务注册表中的数据,因为每个服务都需要知道其他服务部署在哪些机器上。所以这块内存中的服务注册中心数据自然存在读写并发问题!可能有多个线程写,也可能有多个线程读!如果不对同一内存中的注册表数据添加任何保护措施,那么可能会出现多线程并发修改共享数据的问题,导致数据混乱,对吧?上面的过程可以看下图来理解。此时,如果在服务注册和读取服务注册的方法中都加上一个synchronized关键字,是不是就可以了呢?可能你会想,加上synchronized,直接让所有线程对服务注册中心进行读写操作,全部序列化。那样不就保证了内存中服务注册数据的安全吗?下面是一段伪代码,大家感受一下:上面代码中,写入(服务注册)和读取(读取服务注册中心)这两个方法直接加上了synchronized关键字,确实保证了数据在服务注册表中并不凌乱,但这绝对是不合适的。因为这样做的话,相当于所有线程读写服务注册中心的数据,都是序列化的。想一想,我们要的是什么效果?其实就是说,当有人向服务注册中心写入数据时,其他人不可以写入,同时也不允许读取!那么,当有人在读取服务注册中心的数据时,其他人可以同时读取,但是此时,其他人是不允许向服务注册中心写入数据的!对了,这不就是我们想要的吗,其实就是这个效果?经过思考,我们不应该暴力加一个synchronized来序列化所有的读写线程,这样会导致并发度很低。看下图,我们想要的第一个效果:一旦有人在写服务注册中心的数据,我们加一个写锁,这个时候别人不能写也不能读。那么如果有人在读取数据怎么办?这时候其他人可以读,但是不可以写。大家看下图。重点来了,这样做有什么好处呢?其实大部分时候都是读操作,所以使用读锁可以让大量线程同时读取数据,不会阻塞,不会排队,保证高并发读性能比较高。然后在少数时候,有服务上线的时候需要注册数据。写数据的场景比较少。这时候写数据的时候,只能一个一个加写锁,然后再写数据。读取数据。所以读写锁很适合这种读多写少的场景。另外,我们能不能尽量保证在写数据的同时,还能继续读数据呢?当大量加读锁时,会阻塞人写数据,长时间加写锁。这种情况可以避免吗?对,采用了多级缓存机制,本文分析了SpringCloudEureka微服务注册中心中的多级缓存机制。最后我们来看一下上面的伪代码,如果我们使用读写锁来优化它会是什么样子呢?
