本文将从源码的角度带大家学习Eureka服务器接收和注册的过程。此外,我还从源码中发现了一些值得借鉴的地方,比如Eureka存储了注册表的数据结构,使用读写锁控制更细粒度的并发,提高了程序的运行效率。接下来将从以下几个方面进行说明:客户端发送注册请求。Eureka注册表接收注册请求。服务器将客户端注册信息保存在一个Map中。直接去官网下载源码就可以了。https://github.com/Netflix/eureka这篇文章已经收录在我的github中:https://github.com/Jackson0714/PassJava-Learning1.注册入口在上一讲中我们知道EurekaClient是由http请求注册了,那么肯定有一个地方可以接收这个http请求,也就是注册入口。这是如何运作的?其实就是用到了jersey框架。没有必要深入研究这个框架。我们只需要知道这个框架在哪里被引用,它做了什么。您可以将jersey与mvc框架进行比较。球衣有处理http请求的servlet。jerseyframework引用的地方:\eureka\eureka-server\src\main\webapp\WEB-INF\web.xml以及处理HTTP请求的controller在哪里?其实在eureka-core项目的resources目录下定义了很多以Resource结尾的类,用于处理HTTP请求。\eureka\eureka-core\src\main\java\com\netflix\eureka\resources通过XxResource类的英文注释我们也可以知道这个jersey资源类是用来处理HTTP请求的。ajerseyresourcethathandlesrequestApplicationsResource最后发现ApplicationResource类的addInstance方法就是我们要找的处理注册请求的方法。2.接收注册请求的整体流程如下:2.1接收注册请求的方法addInstance方法中的核心代码是registry.register(info,true);registry是PeerAwareInstanceRegistryImpl的实例对象。它实现了PeerAwareInstanceRegistry接口。调用它的register()方法后,会调用抽象类AbstractInstanceRegistry的register()方法,核心代码就在这个抽象类的register()方法中。另外要说的是,上面的抽象类和接口分别实现和继承了接口InstanceRegistry。接口和类的关系如下:那么注册信息会放在哪里呢?3.注册信息存放的地方我们看到在源码中定义了一个gNewMap,就是ConcurrentHashMap,然后赋值给gMap变量ConcurrentHashMap>gNewMap,所以gMap变量其实就是用于存放注册信息。我们来分析一下gMap的结构。首先,gMap是一个ConcurrentHashMap结构,所以是一个键值对。key是一个唯一的id,String类型。该值类似于:Lease存储在i-00000004值中。Lease是一个包含instanceInfoholder的类。这个instanceInfo就是注册的服务实例信息,包括ip地址,端口号等。把服务实例信息放到gMap中也很简单,调用put方法即可。gMap.put(registrant.getId(),lease);下面是我注册的两个服务实例的状态:4.值得学习的东西4.1ConcurrentHashMap?上面说了ConcurrentHashMap,为什么不用hashmap呢?ConcurrentHashMap>()原因:在并发编程中使用HashMap可能会造成死循环(JDK1.7和1.8可能会造成数据丢失)HashTable效率很低。简单说下ConcurrentHashMap的底层原理是什么?ConcurrentHashMap内部又细分了几个小的HashMap,称为segment。默认情况下,一个ConcurrentHashMap被进一步细分为16段,这就是锁的并发。如果需要在ConcurrentHashMap中增加一个新的entry,不是锁定整个HashMap,而是先根据hashcode获取entry应该存放的segment,然后锁定segment,完成put操作。在多线程环境下,如果多个线程同时进行put操作,只要添加的条目不存储在同一个段中,线程之间就可以真正并行。4.2读写锁?我们在源码中看到读锁ReentrantReadWriteLock是有用的,如下图readWriteLock=newReentrantReadWriteLock();Lockread=readWriteLock.readLock();read.lock();...read.unlock();4.2.1Why分为读锁和写锁?原因:在没有读写锁之前,假设使用普通的ReentrantLock,虽然保证了线程安全,但是还是浪费了一定的资源,因为如果同时进行多个读操作,其实是没有线程安全问题可以允许的并行执行多个读取操作以提高程序效率。但是写操作不是线程安全的。如果多个线程同时写,或者边写边读,都会造成线程安全问题。读写锁解决了这个问题。它设置了一套规则,既能保证多线程同时读的效率,又能保证有写操作时的线程安全。读锁:允许多个线程获取读锁,同时访问同一个资源。读锁和写锁:只允许一个线程获取写锁,不允许同时访问同一个资源。写锁的整体思路:它有两个锁。第一个锁是写锁。获得写锁后,就可以读取和修改数据了。第二个锁是读锁。获得读锁后,只能查看数据,不能修改数据。读锁可以同时被多个线程持有,所以多个线程可以同时查看数据。在读的地方合理使用读锁,在写的地方合理使用写锁,灵活控制,可以提高程序的执行效率。4.2.2读写锁的获取规则使用读写锁时遵循以下获取规则:如果一个线程已经占用了读锁,此时其他线程如果想申请读锁,可以申请读锁.如果一个线程已经占用了读锁,此时如果其他线程要申请写锁,申请写锁的线程会等待读锁释放,因为读锁不能在此时进行操作同时。如果一个线程已经占用了写锁,如果此时其他线程申请写锁或者读锁,必须等待前一个线程释放写锁。也是因为读和写不能同时进行,两个线程也不应该同时写。读写锁互斥总结:读写共享。写写互斥,读写互斥,写读互斥。五、总结本文从源码的角度分析了Eureka服务器接收注册信息的过程。核心逻辑是将服务实例的注册信息放到ConcurrentHashMap中,使用读锁控制细粒度的并发注册。另外引入了我们不熟悉的Jersey框架,用来处理HTTP请求,比如客户端注册的HTTP请求。通过源码分析了解到Eureka使用的数据结构ConcurrentHashMap来存储注册表。转载本文请联系悟空聊天架构公众号。