湖南省建设厅官方网站官网,宿州网站建设报价,怎么上国外购物网站,网站设计与管理方向目录
一、SpringCloudAlibaba 源码分析
1.1、SpringCloud SpringCloudAlibaba 常用组件
1.2、Nacos的服务注册表结构是怎样的#xff1f;
1.2.1、Nacos的分级存储模型#xff08;理论层#xff09;
1.2.2、Nacos 源码启动#xff08;准备工作#xff09;
1.2.…目录
一、SpringCloudAlibaba 源码分析
1.1、SpringCloud SpringCloudAlibaba 常用组件
1.2、Nacos的服务注册表结构是怎样的
1.2.1、Nacos的分级存储模型理论层
1.2.2、Nacos 源码启动准备工作
1.2.3、运行 Nacos
1.2.4、Nacos的分级存储模型源码层
1.3、Nacos如何支撑数十万服务注册压力
1.4、Nacos如何解决实例列表并发读写冲突问题
1.4.1、并发读写冲突
1.4.2、并发写冲突 一、SpringCloudAlibaba 源码分析 1.1、SpringCloud SpringCloudAlibaba 常用组件
我们脑海中因该出现一幅图 首先我们肯定有无数个小的微服务.这无数个微服务之间是不是要进行一个相互调用那么就会用到 OpenFeign 这样的组件.这么多服务要相互调用怎么去管理呢这就需要用到 nacos 组件去做注册中心那么所有的服务就会去找注册中心去注册自己的服务.那么我拉取到的服务可能是一个列表那么将来在远程调用的时候就需要做负载均衡就需要使用 LoadBalancer 这个组件.这么多服务将来要做统一配置的管理怎么办就需要引入 nacos 作为配置中心.这时候微服务集群就形成了将来对外提供服务是不是随便什么人都能访问呢显然不行所有在微服务群前面就需要有 gateway 网关 作为入口.那么就算你可以访问了万一流量激增引起微服务雪崩给我整个服务搞崩了肯定不行因此就需要 sentinel 来做限流、熔断降级保护.还有一个问题在分布式系统下就会引发分布式事务问题如何解决呢这就需要 Seata 上场了.
实际上微服务的组件远不止于此还有很多的组件但是以上呢就是我们最常用的几个组件啦~ 1.2、Nacos的服务注册表结构是怎样的 Tips要了解Nacos的服务注册表结构需要从以下两方面入手 1. Nacos的分级存储模型 2.Nacos的服务端源码分析 1.2.1、Nacos的分级存储模型理论层 aNacos 分级模型中最外层就是 namespace起到一个环境隔离的作用比如我们开发的时候会去区分开发环境、测试环境、生产环境...等等.
b现在环境隔离好了比如开发环境下我们肯定是有很多很多的服务这时候我们就可以业务模块进行分组比如交易模块分一个组里面就会有像 订单、支付有关的微服务. 像阿里这种服成千上万个服务进行分组管理就会很方便但是小型企业的就没必要了一般就使用默认组即可.
c那么分组下面就到了一个个微服务了而服务只是一个概念提供了这样一个功能将来为了保证服务的一个高可用肯定就需要把每个服务部署成集群而且部署的时候不能只是简单的说就整两太那么简单肯定要部署到全国各地不同的机房保证了异地容灾不至于一个机房毁了整个服务崩溃.
d集群的下面就是才是我们具体的实例可想而知一个集群下肯定也是有多个实例的.
问题来了这么一个分级存储的模型怎么用 java 代码来实现的呢如果让你来实现会用什么呢我们是不是可以用 map 的 key value 结构去存储接下来我们就来看看具体的源码怎么实现~
1.2.2、Nacos 源码启动准备工作
a想要进行到 Nacos 源码层面进行分析首先需要我们去官网下载好 Nacos 源码https://github.com/alibaba/nacos
这里以 1.4.2 的 Nacos 版本为例 b将 nacos 源码导入到工程当中将其修改为模块 cNacos底层的数据通信会基于 protobuf 对数据做序列化和反序列化。并将对应的 proto 文件定义在了consistency这个子模块中 d安装 protochttps://github.com/protocolbuffers/protobuf/releases
配置环境变量.
e进入 nacos-1.4.2 的 consistency 模块下的src/main目录下输入以下两个命令进行编译生成对应的 Java 文件.
protoc --java_out./java ./proto/consistency.proto
protoc --java_out./java ./proto/Data.proto entity 下就可以看到生成了如下代码 1.2.3、运行 Nacos
a添加 Nacos 的 SpringBoot 服务指定启动模式为单机 b运行的时候如果提到 Java 发行版本的问题记得去改一下 JDK 版本
1.2.4、Nacos的分级存储模型源码层 aNacos 的分级存储模型对应到源码中实际上就是一个多层嵌套 Mapkey 就是 String 类型的 namespace而他的 value 又是一个 Map.
b这个第二层的 Map 就表示 group 和 服务了key 就服务名称而 value 就是一个服务 service.
c服务实际上就是一个类由于一个服务往往是有多个集群的因此在 service 类中又维护了一个 mapkey 就是集群名称例如上海、广州、杭州....
d他的值 cluster 集群也是一个类这个类里面就维护了两个 Set 集合一个是临时实例另一个就是非临时实例. 1.3、Nacos如何支撑数十万服务注册压力
a首先 nacos 肯定是要做成一个集群的那么就可以对服务注册请求左负载均衡会大大减轻压力.
b其次nacos 内部接收到服务注册请求时不会立即更新到注册表中而是将服务注册的任务放到了一个阻塞队列中然后就响应给客户端了但是实际上在这个注册的动作还是没有完成的而是后续开启一个线程池写了一个死循环获取阻塞队列中的队头元素如果存在就获取不存在就阻塞等待直到阻塞队列中有新元素为止. 因此这个更新动作实际上是异步实现的.
如下源码 c无论是更新本地列表还是集群的一致性操作都是通过异步执行的. 当然这些都是临时实例啊非临时实例的话就不一样了因为要保证强一致性因此他的性能就难以保障了所以在默认请情况下所有实例都是临时的性能会更好一点. 1.4、Nacos如何解决实例列表并发读写冲突问题
1.4.1、并发读写冲突
a首先Nacos 的实例列表实际上也就是 Map这个集合里面装的就是旧的列表现在要对这个旧的列表做修改那一边写一边读可能会造成脏读的问题.
b因此处理这种问题我们最直接的可能就是想到使用加锁来处理但是加锁的开销也不小涉及到用户态到内核态的转换... 那么 Nacos 这里采取的是 CopyOnWrite 技术也就是说他不是直接来改这个集合中的数据而是先把这个集合中的数据拷贝了一份放到一个全新的集合中然后再在这个全新的集合中进行更新修改操作改完了之后再直接覆盖掉旧数据.
c而这个过程中读取的是旧的实例列表因此不会受到任何影响.
如下源码
public ListInstance updateIpAddresses(Service service, String action, boolean ephemeral, Instance... ips)throws NacosException {// 根据namespaceId、serviceName获取当前服务的实例列表返回值是Datum// 第一次来肯定是nullDatum datum consistencyService.get(KeyBuilder.buildInstanceListKey(service.getNamespaceId(), service.getName(), ephemeral));// 得到服务中现有的实例列表ListInstance currentIPs service.allIPs(ephemeral);// 创建map保存实例列表key为ip地址value是Instance对象MapString, Instance currentInstances new HashMap(currentIPs.size());// 创建Set集合保存实例的instanceIdSetString currentInstanceIds Sets.newHashSet();// 遍历要现有的实例列表for (Instance instance : currentIPs) {// 添加到map中currentInstances.put(instance.toIpAddr(), instance);// 添加instanceId到set中currentInstanceIds.add(instance.getInstanceId());}// 创建map用来保存更新后的实例列表MapString, Instance instanceMap;if (datum ! null null ! datum.value) {// 如果服务中已经有旧的数据则先保存旧的实例列表instanceMap setValid(((Instances) datum.value).getInstanceList(), currentInstances);} else {// 如果没有旧数据则直接创建新的mapinstanceMap new HashMap(ips.length);}// 遍历实例列表for (Instance instance : ips) {// 判断服务中是否包含要注册的实例的cluster信息if (!service.getClusterMap().containsKey(instance.getClusterName())) {// 如果不包含创建新的clusterCluster cluster new Cluster(instance.getClusterName(), service);cluster.init();// 将集群放入service的注册表service.getClusterMap().put(instance.getClusterName(), cluster);Loggers.SRV_LOG.warn(cluster: {} not found, ip: {}, will create new cluster with default configuration.,instance.getClusterName(), instance.toJson());}// 删除实例 or 新增实例 if (UtilsAndCommons.UPDATE_INSTANCE_ACTION_REMOVE.equals(action)) {instanceMap.remove(instance.getDatumKey());} else {// 新增实例instance生成全新的instanceIdInstance oldInstance instanceMap.get(instance.getDatumKey());if (oldInstance ! null) {instance.setInstanceId(oldInstance.getInstanceId());} else {instance.setInstanceId(instance.generateInstanceId(currentInstanceIds));}// 放入instance列表instanceMap.put(instance.getDatumKey(), instance);}}if (instanceMap.size() 0 UtilsAndCommons.UPDATE_INSTANCE_ACTION_ADD.equals(action)) {throw new IllegalArgumentException(ip list can not be empty, service: service.getName() , ip list: JacksonUtils.toJson(instanceMap.values()));}// 将instanceMap中的所有实例转为List返回return new ArrayList(instanceMap.values());
}
1.4.2、并发写冲突
a对于并发写冲突也就是说同时有多个线程来拷贝我们的实例即使通过 CopyOnWrite 技术你拷贝一份我也拷贝一份然后大家都各写各的最后都去覆盖同一个旧的列表这个时候还是会出现写冲突的问题.
b因此这里代码中的处理实际上就是直接给服务加锁因此访问同一个服务的多个实例就只能串行执行了.
如下源码