Nacos1.4X注册表性能优化方案
1.问题
- 注册表就是一个Map,客户端有多个,并发访问注册表,需要加锁否则会出现线程安全问题,性能差
- 哪怕是用高性能的ConcurrentHashMap在激烈的并发冲突下,性能也不好
2.优化思路
- 核心就是优化对于Map的读写性能
- 写性能提升:内存队列+异步任务
- 读性能提升:写时复制实现读写非阻塞
3.内存队列+异步任务
图示核心思想
具体工作流程
首先 Nacos 客户端发起实例注册,Nacos 服务端把接收的参数包装成一个对象,最后放入到BlookingQueue 里面,注册结束
这个时候,实例注册接口已经结束了,Nacos 客户端已经拿到服务端返回的消息了,然后 Nacos 服务端会在后台新开启一个单线程异步任务,这个任务在不断地取 BlookingQueue 队列中的内容,把这个队列中的内容取出来之后,再把信息写入到注册表中,从而完成注册
优点
- 接口响应时效更快:其实 Nacos 服务端实例注册接口,它并没有做真正注册的动作,而只是把信息包装好,放入到队列中,接口就结束了。代码逻辑很简单,所以响应时效会更快
- 保证服务稳定性:哪怕同时有 1 千个、1 万个客户端同时发起实例注册请求接口,最后只是把任务放入到队列中。这个就好比如我们使用消息队列流量削峰一样,后续复杂逻辑的,由消费者慢慢处理,异步任务就相当于消费者
- 解决写时并发冲突:Nacos 服务端后台,其实是只有一个单线程在处理队列中的任务,把队列中注册实例信息,同步到 Nacos 注册表中,既然是单线程操作,那么在操作写的时候,其实是不用考虑多线程并发写的问题。因为只有一个线程在写,所以不用考虑,但是读操作是由其他线程来操作,会存在读写并发冲突,这个问题是用了写时复制实现读写非阻塞
4.写时复制
图示核心思想
注意:这里用临时复制表只是为了好理解,实际上力度上比这个细,比如临时的Set<Instance>然后覆盖注册表里面某个Cluster对象的Set<Instance>
优点
实现了读写非阻塞,大大提高了性能
缺点
会有短暂的数据不一致情况,在线程还没有把临时注册表覆盖回去,读取的客户端就暂时无法可见