首页 文章 精选 留言 我的

精选列表

搜索[SpringCloud],共1288篇文章
优秀的个人博客,低调大师

java B2B2C Springcloud电子商务平台源码

鸿鹄云商大型企业分布式互联网电子商务平台,推出PC+微信+APP+云服务的云商平台系统,其中包括B2B、B2C、C2C、O2O、新零售、直播电商等子平台。愿意了解源码的朋友直接求求交流分享技术:二一四七七七五六三三 分布式、微服务、云架构电子商务平台 java b2b2c o2o 技术列表: Spring Cloud Config配置管理工具包,让你可以把配置放到远程服务器,集中化管理集群配置,目前支持本地存储、Git以及Subversion Spring Cloud Bus事件、消息总线,用于在集群(例如,配置变化事件)中传播状态变化,可与Spring Cloud Config联合实现热部署 Eureka云端服务发现,一个基于 REST 的服务,用于定位服务,以实现云端中间层服务发现和故障转移。 Hystrix熔断器,容错管理工具,旨在通过熔断机制控制服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力。 ZuulZuul 是在云平台上提供动态路由,监控,弹性,安全等边缘服务的框架。Zuul 相当于是设备和 Netflix 流应用的 Web 网站后端所有请求的前门。 Spring Cloud Security基于spring security的安全工具包,为你的应用程序添加安全控制。 FeignFeign是一种声明式、模板化的HTTP客户端。 通用架构:springmvc,spring boot,spring security,Oauth2.0,mybatis,mybatis plus 、kafka、zookeepre 前端框架:Bootstrap 4 、html5、css3 扁平化风格APP界面截图:源码下载地址

优秀的个人博客,低调大师

SpringCloud+SpringBoot+mybatis分布式微服务云架构开发Web应用

在完成配置之后,举一个简单的例子,在快速入门工程的基础上,举一个简单的示例来通过Thymeleaf渲染一个页面。 @Controller public class HelloController { @RequestMapping("/") public String index(ModelMap map) { // 加入一个属性,用来在模板中读取 map.addAttribute("host", "http://blog.didispace.com"); // return模板文件的名称,对应src/main/resources/templates/index.html return "index"; } } <!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8" /> <title></title> </head> <body> <h1 th:text="${host}">Hello World</h1> </body> </html> 如上页面,直接打开html页面展现Hello World,但是启动程序后,访问http://localhost:8080/,则是展示Controller中host的值:http://blog.didispace.com,做到了不破坏HTML自身内容的数据逻辑分离。 更多Thymeleaf的页面语法,还请访问Thymeleaf的官方文档查询使用。 Thymeleaf的默认参数配置 如有需要修改默认配置的时候,只需复制下面要修改的属性到application.properties中,并修改成需要的值,如修改模板文件的扩展名,修改默认的模板路径等。 # Enable template caching. spring.thymeleaf.cache=true # Check that the templates location exists. spring.thymeleaf.check-template-location=true # Content-Type value. spring.thymeleaf.content-type=text/html # Enable MVC Thymeleaf view resolution. spring.thymeleaf.enabled=true # Template encoding. spring.thymeleaf.encoding=UTF-8 # Comma-separated list of view names that should be excluded from resolution. spring.thymeleaf.excluded-view-names= # Template mode to be applied to templates. See also StandardTemplateModeHandlers. spring.thymeleaf.mode=HTML5 # Prefix that gets prepended to view names when building a URL. spring.thymeleaf.prefix=classpath:/templates/ # Suffix that gets appended to view names when building a URL. spring.thymeleaf.suffix=.html spring.thymeleaf.template-resolver-order= # Order of the template resolver in the chain. spring.thymeleaf.view-names= # Comma-separated list of view names that can be resolved. 支持JSP的配置Spring Boot并不建议使用,但如果一定要使用,可以参考此工程作为脚手架:JSP支持 完整项目的源码来源 技术支持1791743380

优秀的个人博客,低调大师

阿里云Kubernetes SpringCloud 实践进行时(4): 分布式链路追踪

简介 为了更好地支撑日益增长的庞大业务量,我们常常需要把服务进行整合、拆分,使我们的服务不仅能通过集群部署抵挡流量的冲击,又能根据业务在其上进行灵活的扩展。随着分布式的普及、服务的快速增长与云计算技术的进步,微服务架构也因其特有的优势而备受关注。微服务架构的本质,是把整体的业务拆分成很多有特定明确功能的服务,通过很多分散的小服务之间的配合,去解决更大,更复杂的问题。对被拆分后的服务进行分类和管理,彼此之间使用统一的接口来进行交互。 本系列讲述了在阿里云Kubernetes容器服务基础之上,如何快速搭建基于Spring Cloud的微服务架构中的基础设施: 第一篇:分布式服务注册与发现系统 第二篇:分布式配置管理系统 第三篇:API网关服务Zuul 系统 第四篇:分布式追踪系统 第五篇:分布式弹性服务与容错处理框架Hystrix及其监控仪表板 第六

优秀的个人博客,低调大师

阿里云Kubernetes SpringCloud 实践进行时(2): 分布式配置管理

简介 为了更好地支撑日益增长的庞大业务量,我们常常需要把服务进行整合、拆分,使我们的服务不仅能通过集群部署抵挡流量的冲击,又能根据业务在其上进行灵活的扩展。随着分布式的普及、服务的快速增长与云计算技术的进步,微服务架构也因其特有的优势而备受关注。微服务架构的本质,是把整体的业务拆分成很多有特定明确功能的服务,通过很多分散的小服务之间的配合,去解决更大,更复杂的问题。对被拆分后的服务进行分类和管理,彼此之间使用统一的接口来进行交互。 而随着服务数量的剧增,这些服务所依赖的配置文件则需要实现统一管理并且能够实时更新;否则对于分布式环境中多达上千、上百的服务实例来说,单单修改配置这项目工作就会耗费程序员很多时间和精力,甚至更为糟糕的是可能会带来配置不一致、错误的灾难。 本系列讲述了在阿里云Kubernetes容器服务基础之上,如何快速搭建基于

优秀的个人博客,低调大师

阿里云Kubernetes SpringCloud 实践进行时(5): 弹性服务与容错处理

简介 为了更好地支撑日益增长的庞大业务量,我们常常需要把服务进行整合、拆分,使我们的服务不仅能通过集群部署抵挡流量的冲击,又能根据业务在其上进行灵活的扩展。随着分布式的普及、服务的快速增长与云计算技术的进步,微服务架构也因其特有的优势而备受关注。微服务架构的本质,是把整体的业务拆分成很多有特定明确功能的服务,通过很多分散的小服务之间的配合,去解决更大,更复杂的问题。对被拆分后的服务进行分类和管理,彼此之间使用统一的接口来进行交互。 本系列讲述了在阿里云Kubernetes容器服务基础之上,如何快速搭建基于Spring Cloud的微服务架构中的基础设施: 第一篇:分布式服务注册与发现系统 第二篇:分布式配置管理系统 第三篇:API网关服务Zuul 系统 第四篇:分布式追踪系统 第五篇:分布式弹性服务与容错处理框架Hystrix及其监控仪表板 第六

优秀的个人博客,低调大师

springCloud(12):使用Hystrix实现微服务的容错处理-Hystrix的监控

一、简介 Hystrix提供了几乎实时的监控。HystrixCommand和HystrixObserv-ableCommand在执行时,会生成执行结果和运行指标,比如每秒执行的请求数、成功数等,这些监控数据对分析应用系统的状态很有用。 使用Hystrix的模块hystrix-metrics-event-stream,就可将这些监控的指标信息以text/event-stream的格式暴露给外部系统。spring-cloud-starter-hystrix已包含该模块,在此基础上,只须为项目添加spring-boot-starter-actuator,就可使用/hystrix.stream端点获得Hystrix的监控信息了。 我们以前的项目spring-hystrix-consumer中就已经包含了spring-cloud-starter-hystrix、spring-boot-starter-actuator,启动访问:http://localhost:8087/user/1后,再访问:http://localhost:8087/manage/hystrix.stream,会重复出现如下内容: 这是因为系统会不断地刷新以获取实时的监控数据.Hystrix的监控指标非常全面,例如HystrixCommand的名称、group名称、断路器状态、错误率、错误数等。 二、使用Hystrix Dashboard可视化监控数据 前面通过访问/hystrix.stream端点获得的数据很难一眼看出系统当前的运行状态,可是使用Hystrix Dashboard可以让监控数据图形化、可视化。 操作: 1、创建一个maven工程,加入Hystrix Dashboard依赖 1 2 3 4 <dependency> <groupId>org.springframework.cloud< / groupId> <artifactId>spring - cloud - starter - hystrix - dashboard< / artifactId> < / dependency> 2、编写启动类,添加@EnableHystrixDashboard注解 3、配置yml文件端口8086 测试: 1、访问http://localhost:8087/hystrix.stream,可看到Hystrix Dashbord的主页,如下: 2、随意设置一个title,并点击Monitor Stream,这里Title是:测试,URl是:http://localhost:8087/manage/hystrix.stream 注意:此处没有将Hystrix Dashboard注册到Eureka Server上,在生产环境中,为了更方便的管理Hystrix Dashboard,可将其注册到Eureka Server上。 三、使用Turbine聚合监控数据 前面使用的/hystrix.stream端点监控单个微服务。然而在使用微服务架构的应用系统一般会包含多个微服务,每个微服务通常都会部署多个实例。如果每次只能查看单个实例的监控数据,就必须在Hystrix Dashboard上切换想要监控的地址,这显然很不方便。 3.1、Turbine简介 Turbine是一个聚合Hystrix监控数据的工具,它可将所有相关/hystrix.stream端点的数据聚合到一个组合的/turbine.stream中,从而让集群的监控更加方便。 3.2、使用Turbine监控多个微服务 a、创建一个maven项目,添加turbine依赖: 1 2 3 4 5 <!--turbine--> < dependency > < groupId >org.springframework.cloud</ groupId > < artifactId >spring-cloud-starter-turbine</ artifactId > </ dependency > b、在启动类上添加@EnableTurbine注解 c、编写application.yml配置文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 spring: profiles: active: -dev application: name:hystrix-turbine eureka: client: service-url: defaultZone:http://liuy2:5010/eureka/ #设置与EurekaServer交互的地址,查询服务和注册服务都需要依赖这个地址,多个用逗号分隔 instance: prefer-ip-address:true turbine: app-config:hystrix-consumer-movie,ribbon-consumer-movie #参数指定了需要收集监控信息的服务名 cluster-name-expression: "'default'" #参数指定了集群名称为 --- spring: profiles: active:dev server: port:5014 说明:使用上面配置,Turbine会在Eureka Server中找到hystrix-consumer-movie和ribbon-consumer-movie这两个微服务,并聚合这两个微服务的监控数据。 d、测试 第一步:依次启动Eureka Server(4010)、provide-user(4011)、hystrix-consumer-movie(5012)、ribbon-consumer-movie(5011)、hystrix-turbine(5014)、hystrix-dashboard(5013) 第二步:访问http://localhost:5012/user/1,让hystrix-consumer-movie微服务产生监控数据 第三步:访问http://localhost:5011/user/1,让ribbon-consumer-movie微服务产生监控数据 第四步:打开Hystrix Dashboard首页http://localhost:5013/hystrix.stream,在URL栏填写http://localhost:5014/turbine.stream,随意填写title,点击Monitor Stream后出现如下图: 问题:在一些场景下,如微服务与Turbine网络不通,监控数据怎么处理? -- 使用消息中间件收集数据 3.3、使用rabbitmq收集监控数据 改造微服务:hystrix-consumer-movie a、添加以下依赖 1 2 3 4 5 6 7 8 < dependency > < groupId >org.springframework.cloud</ groupId > < artifactId >spring-cloud-netflix-hystrix-stream</ artifactId > </ dependency > < dependency > < groupId >org.springframework.cloud</ groupId > < artifactId >spring-cloud-starter-stream-rabbit</ artifactId > </ dependency > b、在application.yml中添加 1 2 3 4 5 6 spring: rabbitmq: host:192.168.175.13 port:5672 username:liuy password:123456 改造:hystrix-turbine a、添加以下依赖 1 2 3 4 5 6 7 8 9 < dependency > < groupId >org.springframework.cloud</ groupId > < artifactId >spring-cloud-starter-turbine-stream</ artifactId > </ dependency > <!--rabbit--> < dependency > < groupId >org.springframework.cloud</ groupId > < artifactId >spring-cloud-starter-stream-rabbit</ artifactId > </ dependency > 注意:此处删除spring-cloud-starter-turbine依赖 b、修改启动类,将@EnableTurbine改成@EnableTurbineStream c、修改application.yml 1 2 3 4 5 6 spring: rabbitmq: host:192.168.175.13 port:5672 username:liuy password:123456 同时删除:turbine 测试: 第一步:依次启动Eureka Server(4010)、provide-user(4011)、hystrix-dashboard(5013)、hystrix-consumer-movie-rabbitmq(5020)、hystrix-turbine-rabbitmq(5021) 第二步:访问http://localhost:5020/user/1,可正常获取结果 第三步:打开Hystrix Dashboard首页http://localhost:5013/hystrix.stream,在URL栏填写http://localhost:5021/,随意填写title,点击Monitor Stream后出现如下图: 本文转自我爱大金子博客51CTO博客,原文链接http://blog.51cto.com/1754966750/1949857如需转载请自行联系原作者 我爱大金子

优秀的个人博客,低调大师

VIPKID面试官:说说SpringCloud微服务架构在互联网的优势!

高并发微服务架构设计 作为一个 IT 从业人员,我们经常会碰到类似于下面的一些问题: 单个项目巨大而沉重,难以维护。 系统稳定性得不到更有效的保证。 怎样才能持续地提升系统的性能。 怎样才能快速地响应需求的变更,并且系统更新不会引起任何抖动。 怎样才能更好地适应系统规模化的扩张。 针对上面这些问题,我们无时无刻不在努力地进行各种各样的尝试和探索,寻求更好的解决方案,或者使用更先进的技术。 目前来看,在互联网环境之中产生的微服务架构设计是一个比较理想的解决方案。 Java学习笔记共享地址:spring cloud学习笔记 微服务总体架构设计 一个使用了微服务的电商平台的总体架构设计如图 2-1 所示。 这是一个典型的微服务总体架构设计图,自上往下看,可以分为前台应用层、 API 接入层、业务应用层、服务中心和基础资源层结构,每层结构都有其自身的功能和特别的设计。 前台应用层可支持任何应用的客户端,如物联网、微信小程序、移动 App API 开放平台等。 API 接入层可以使用 Spring Cloud Zuul Gateway 设计网关,起到一个承上启下的作用,并且具备鉴权、路由和流控等功能。 业务应用层是微服务 基本应用 ,它本身也是一个微服务。这里主要是电商平台的一些后台管理功能 服务中 就是使用微服务设计的各种 AP 接口服务,这些服务一般使用基于 ST削风格的设计,对外提供轻量 API 接口服务。另外,在基础服务中间件中,提供服务治理、消息队列、监控告警、配置管理等服务。 基础资源是我们组建的私有云或者租用的公有云,为微服务搭建容器平台,提供数据存储、 DevOps 镜像仓库等服务。 在这个架构设计图中,除了上面这些,在水平方向上,还可以看到负载均衡、日志记录、链路跟踪,以及基于大型电商平台的运营平台等服务。 下面介绍微服务架构设计的优势,以及它所表现出来的高并发、高性能的特点。 自然的压力分解。 可弹性伸缩的集群环境。 高度的独立性设计。 API 的分层调用关系。 高可用的基础资源支持。 快速响应的自动化基础设施。 完善的监控体系。 微服务的安全保障。 自然的压力分解 对于一个大型系统来说,在使用微服务架构设计之后,原来针对单独一个应用所承受的压力,就自然而然地分散到众多微服务中。各个微服务可以使用不同的数据库,并且可以分开部署在不同的服务器上,所有这些,都是一种压力分流的方法。 可弹性伸缩的集群环境 微服务的部署和发布,最终都将纳入微服务的治理环境之中。这种治理环境是一个分布式的集群管理体系,对于每一个微服务来说,都能对其实行动态路由、负载均衡、服务降级等一系列的管控措施。同时,允许每个微服务根据其所承受的压力情况,进行自由的扩展和收缩,即具备可弹性伸缩的特性。 图2-2 是微服务的一个运行环境示意图,不管是作为提供接口服务的阻STAPI 微服务,还是提供操作界面的 WebUI 微服务,都可以根据需要在云端服务器上很方便地增加其运行副本,从而扩展它的负载能力。而且,这种扩展并不局限于在一个虚拟环境中,它可以跨机房、跨地区,甚至跨国界。当然,微服务所使用的基础资源,同样具备自由扩展的能力。这样才能保证在整个系统平台中可以应对任何高并发的调用,而不存在性能瓶颈。 高度的独立性设计 微服务架构的去中心化的设计特点,为每个微服务的设计提供了高度的独立性。这样,我们就可以根据每个微服务的特点,选择数据库或者通信方式,以发挥其独特的效能。 比如,对于数据量不是很大,关联性不是很复杂的数据,可以使用传统的关系数据库,如MySQL 或者 Oracle 。对于数据量较大,更新不是很频繁的数据,可以使用 MongoDB NoSQL数据库。对于一些关系复杂,关联比较多的数据,则可以使用图数据库,如 Neo4j 等。这样针对不同的业务特性,使用合适的数据库,就可以充分发挥应用程序的性能。 对于通信方式来说,有些程序对实时数据很敏感,只能使用接口的方式进行实时调用:而有的程序对实时数据并没有太多要求,但是通信量很大,这时就可以使用异步消息进行调用。 这样,通过有针对性的独立设计,可以最大限度地发挥应用程序的效能。 API 的分层调用关系 微服务使用 GateWay 网关接口方式对外部环境提供服务。这种方式使用分层结构设计,在Gate Way 层既可以直接调用 RESTAPI 微服务接口服务,也可以再经过一层设计,即使用 Backendfor Frontend 层对复杂的调用进行一次包装设计。例如,当需要对多个微服务进行调用时,可以将多个调用组装成一个单一接口服务,从而避免微服务内部环境与外部环境的多重通信。另外,有的通信可以使用 MQ (Message Queue )方式以异步方式进行。 图2-3 是一个 GateWay 多层次调用关系示意图,在这个图中,每个层次都可以进行负载均衡设计,从而能够非常有效地提高这种调用关系的并发性。其中,微服务内部环境的负载均衡设计可以由服务治理进行处理,而处在外部环境中的 GateWay 的负载均衡设计则可以使用 Nginx等工具进行实施。 高可用的基础资源支持 在微服务架构设计中,通常是使用云服务来组建基础资源的。其中,云服务既可以租用云服务提供商的服务器,也可以自己组建私有云,或者两者兼而有之。 基于云端组建的基础资源,包括数据库、缓存和文件系统等,这些资源既可以使用云服务提供商提供的优质 RDBS 、分布式数据库和对象存储等服务,也可以自己搭建各种集群体系。 这样,就可以保证每一种基础资源的使用都不会成为另外一个系统的瓶颈,这是对高并发微服务架构设计的有力补充和支持。 快速晌应的自动化基础设施 自动化基础设施建设是微服务架构设计中的一项基本要求,涉及代码管理、代码检查、集成测试、自动化测试、持续交付、自动化部署等一系列问题。 不管是持续集成、持续交付,还是敏捷开发等,这些都是 DevOps 的一种管理机制。这种管理机制可以提高微服务架构设计中各个微服务应用的应变能力,可以快速响应整个系统的变更和更新,从而充分提升整个微服务架构的总体效能。 完善的监控体系 通过使用 Sp Cloud 工具套件并结合第三方工具,我们可以为微服务的运行环境构建一个完善的监控体系,从而有效保证微服务的稳定性和健壮性。 这一监控体系包括健康检查、告警系统、链路跟踪、日志记录和查询等内容。通过健康检查和告警系统,可以及时发现系统中可能存在的问题和隐患,从而减少事故的发生。链路跟踪和日志记录可以提供非常详细的服务调用轨迹,非常适合用来检验和查找复杂的系统内部问题,或者某些可能存在的隐藏错误。 微服务的安全保障 越大型的系统,系统的并发性越高,危险性越大,其安全保障也越重要。系统的安全设计包括防火墙设计、防攻击设计、访问控制设计、数据保密设计、数据备份及灾备等各个方面的内容。而安全防护是系统安全的第一道屏障,我们将使用防火墙及动态感知等设备,为微服务的服务器组建,提供一个安全可靠的分布式环境。 Spring Cloud面试真题共享地址:spring cloud答案解析。 如图 2-4 所示,是根据阿里云设计的一个安全管理架构,通过安全防护和安全预警 对不安全的访问或可能存在的攻击进行有效隔离,从而保证系统的安全和稳定。 如果需要进行跨机房或跨地区的微服务互联,则必须在保证安全的前提下,通过 VPC 专网使用专用通道进行通信。 小结 微服务架构设计风格本身就是一种高并发的机制。依靠云服务环境,我们可以把微服务使用的基础资源,通过自动化基础设施建设,提供成一种可伸缩、高并发、高可用的环境。同时,通过使用 Spring Cloud 工具套件和第三方库,充分保证微服务的高度可扩展性。不管是哪一种架构设计,系统的稳定性、健壮性和可靠性都缺一不可。

优秀的个人博客,低调大师

开发SpringCloud微服务三年,我才知道@EnableEurekaServer注解到底做了什么

全是干货的 Java 技术仓库: https://github.com/Wasabi1234/Java-Interview-Tutorial 当你在启动类上添加了启动 Eureka 服务注册中心注解时,到底发生了什么呢? 激活eureka服务器相关配置EurekaServerAutoConfiguration的注解 EurekaServerMarkerConfiguration点击到这里,我们发现 spring.factories文件 注意到如下注解 @ConditionalOnBean - 条件注入 也就是当有EurekaServerMarkerConfiguration.Marker.class时,才会注入 所以@EnableEurekaServer就是个开关,只要写了该注解,spring 就会帮你把EurekaServerAutoConfiguration类注入进来。 那么为什么注入他就行了? EurekaServerAutoConfiguration#jerseyFilterRegistration 注意如下类: ApplicationResource#addInstance 相当于 MVC 中的 controller /** * Registers information about a particular instance for an * {@link com.netflix.discovery.shared.Application}. * * @param info * {@link InstanceInfo} information of the instance. * @param isReplication * a header parameter containing information whether this is * replicated from other nodes. */ @POST @Consumes({"application/json", "application/xml"}) public Response addInstance(InstanceInfo info, @HeaderParam(PeerEurekaNode.HEADER_REPLICATION) String isReplication) { logger.debug("Registering instance {} (replication={})", info.getId(), isReplication); // validate that the instanceinfo contains all the necessary required fields if (isBlank(info.getId())) { return Response.status(400).entity("Missing instanceId").build(); } else if (isBlank(info.getHostName())) { return Response.status(400).entity("Missing hostname").build(); } else if (isBlank(info.getIPAddr())) { return Response.status(400).entity("Missing ip address").build(); } else if (isBlank(info.getAppName())) { return Response.status(400).entity("Missing appName").build(); } else if (!appName.equals(info.getAppName())) { return Response.status(400).entity("Mismatched appName, expecting " + appName + " but was " + info.getAppName()).build(); } else if (info.getDataCenterInfo() == null) { return Response.status(400).entity("Missing dataCenterInfo").build(); } else if (info.getDataCenterInfo().getName() == null) { return Response.status(400).entity("Missing dataCenterInfo Name").build(); } // handle cases where clients may be registering with bad DataCenterInfo with missing data DataCenterInfo dataCenterInfo = info.getDataCenterInfo(); if (dataCenterInfo instanceof UniqueIdentifier) { String dataCenterInfoId = ((UniqueIdentifier) dataCenterInfo).getId(); if (isBlank(dataCenterInfoId)) { boolean experimental = "true".equalsIgnoreCase(serverConfig.getExperimental("registration.validation.dataCenterInfoId")); if (experimental) { String entity = "DataCenterInfo of type " + dataCenterInfo.getClass() + " must contain a valid id"; return Response.status(400).entity(entity).build(); } else if (dataCenterInfo instanceof AmazonInfo) { AmazonInfo amazonInfo = (AmazonInfo) dataCenterInfo; String effectiveId = amazonInfo.get(AmazonInfo.MetaDataKey.instanceId); if (effectiveId == null) { amazonInfo.getMetadata().put(AmazonInfo.MetaDataKey.instanceId.getName(), info.getId()); } } else { logger.warn("Registering DataCenterInfo of type {} without an appropriate id", dataCenterInfo.getClass()); } } } registry.register(info, "true".equals(isReplication)); return Response.status(204).build(); // 204 to be backwards compatible } 注意如下继承体系图 InstanceRegistry#register PeerAwareInstanceRegistryImpl#register 这里取得微服务过期时间 90s,服务之间心跳请求 30s 一次,如果 90s 还没发生,就说明挂了 这就是责任链模式!!! AbstractInstanceRegistry#register 真正的注册 注册表K:微服务集群 V:微服务集群内的各个节点 当发生注册信息冲突时,咋办? 根据最后活跃时间,确定覆盖哪个 public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) { try { // 并发读锁 read.lock(); // 先获得微服务名,然后获得实例 Map<String, Lease<InstanceInfo>> gMap = registry.get(registrant.getAppName()); REGISTER.increment(isReplication); // 第一次注册时还不存在,所以 new 一个 if (gMap == null) { final ConcurrentHashMap<String, Lease<InstanceInfo>> gNewMap = new ConcurrentHashMap<String, Lease<InstanceInfo>>(); // 没有时才 put,有就不更新! 因为registry还是可能被写的!毕竟他不在读锁范围内! gMap = registry.putIfAbsent(registrant.getAppName(), gNewMap); if (gMap == null) { gMap = gNewMap; } } // 已存在的注册节点 Lease<InstanceInfo> existingLease = gMap.get(registrant.getId()); // Retain the last dirty timestamp without overwriting it, if there is already a lease if (existingLease != null && (existingLease.getHolder() != null)) { // 拿到已存在节点的注册时间 Long existingLastDirtyTimestamp = existingLease.getHolder().getLastDirtyTimestamp(); // 当前正在注册的节点的注册时间 Long registrationLastDirtyTimestamp = registrant.getLastDirtyTimestamp(); logger.debug("Existing lease found (existing={}, provided={}", existingLastDirtyTimestamp, registrationLastDirtyTimestamp); // this is a > instead of a >= because if the timestamps are equal, we still take the remote transmitted // InstanceInfo instead of the server local copy. // 注册ing 时,有更加新的节点注册了! if (existingLastDirtyTimestamp > registrationLastDirtyTimestamp) { logger.warn("There is an existing lease and the existing lease's dirty timestamp {} is greater" + " than the one that is being registered {}", existingLastDirtyTimestamp, registrationLastDirtyTimestamp); logger.warn("Using the existing instanceInfo instead of the new instanceInfo as the registrant"); // 那么就用已存在的替换当前注册节点,因为考虑到尽量保证可用性,因为既然还在活跃说明老的还能用 registrant = existingLease.getHolder(); } } else { // The lease does not exist and hence it is a new registration synchronized (lock) { if (this.expectedNumberOfRenewsPerMin > 0) { // Since the client wants to cancel it, reduce the threshold // (1 // for 30 seconds, 2 for a minute) this.expectedNumberOfRenewsPerMin = this.expectedNumberOfRenewsPerMin + 2; this.numberOfRenewsPerMinThreshold = (int) (this.expectedNumberOfRenewsPerMin * serverConfig.getRenewalPercentThreshold()); } } logger.debug("No previous lease information found; it is new registration"); } // 新的心跳续约对象,包括注册信息,最后操作时间,注册事件,过期时间,剔除时间 Lease<InstanceInfo> lease = new Lease<InstanceInfo>(registrant, leaseDuration); if (existingLease != null) { lease.setServiceUpTimestamp(existingLease.getServiceUpTimestamp()); } // 将新对象放入注册表 gMap.put(registrant.getId(), lease); synchronized (recentRegisteredQueue) { // 加入注册队列 recentRegisteredQueue.add(new Pair<Long, String>( System.currentTimeMillis(), registrant.getAppName() + "(" + registrant.getId() + ")")); } // This is where the initial state transfer of overridden status happens if (!InstanceStatus.UNKNOWN.equals(registrant.getOverriddenStatus())) { logger.debug("Found overridden status {} for instance {}. Checking to see if needs to be add to the " + "overrides", registrant.getOverriddenStatus(), registrant.getId()); if (!overriddenInstanceStatusMap.containsKey(registrant.getId())) { logger.info("Not found overridden id {} and hence adding it", registrant.getId()); overriddenInstanceStatusMap.put(registrant.getId(), registrant.getOverriddenStatus()); } } InstanceStatus overriddenStatusFromMap = overriddenInstanceStatusMap.get(registrant.getId()); if (overriddenStatusFromMap != null) { logger.info("Storing overridden status {} from map", overriddenStatusFromMap); registrant.setOverriddenStatus(overriddenStatusFromMap); } // Set the status based on the overridden status rules InstanceStatus overriddenInstanceStatus = getOverriddenInstanceStatus(registrant, existingLease, isReplication); registrant.setStatusWithoutDirty(overriddenInstanceStatus); // If the lease is registered with UP status, set lease service up timestamp if (InstanceStatus.UP.equals(registrant.getStatus())) { lease.serviceUp(); } registrant.setActionType(ActionType.ADDED); recentlyChangedQueue.add(new RecentlyChangedItem(lease)); registrant.setLastUpdatedTimestamp(); invalidateCache(registrant.getAppName(), registrant.getVIPAddress(), registrant.getSecureVipAddress()); logger.info("Registered instance {}/{} with status {} (replication={})", registrant.getAppName(), registrant.getId(), registrant.getStatus(), isReplication); } finally { read.unlock(); } } 服务续约 通知Eureka Server Service Provider还活着,避免服务被剔除。 和register实现思路基本一致 更新自身状态 再同步到其它Peer public boolean renew(String appName, String id, boolean isReplication) { RENEW.increment(isReplication); Map<String, Lease<InstanceInfo>> gMap = registry.get(appName); Lease<InstanceInfo> leaseToRenew = null; if (gMap != null) { // 拿到具体的注册节点 leaseToRenew = gMap.get(id); } if (leaseToRenew == null) { // 不存在,则重新注册续约 RENEW_NOT_FOUND.increment(isReplication); logger.warn("DS: Registry: lease doesn't exist, registering resource: {} - {}", appName, id); return false; } else { //存在 // 先得到节点信息 InstanceInfo instanceInfo = leaseToRenew.getHolder(); if (instanceInfo != null) { // touchASGCache(instanceInfo.getASGName()); InstanceStatus overriddenInstanceStatus = this.getOverriddenInstanceStatus( instanceInfo, leaseToRenew, isReplication); // 看是否处于宕机状态 if (overriddenInstanceStatus == InstanceStatus.UNKNOWN) { logger.info("Instance status UNKNOWN possibly due to deleted override for instance {}" + "; re-register required", instanceInfo.getId()); RENEW_NOT_FOUND.increment(isReplication); return false; } if (!instanceInfo.getStatus().equals(overriddenInstanceStatus)) { logger.info( "The instance status {} is different from overridden instance status {} for instance {}. " + "Hence setting the status to overridden status", instanceInfo.getStatus().name(), instanceInfo.getOverriddenStatus().name(), instanceInfo.getId()); instanceInfo.setStatusWithoutDirty(overriddenInstanceStatus); } } renewsLastMin.increment(); // 正常则续约 leaseToRenew.renew(); return true; } } public enum InstanceStatus { UP, // Ready to receive traffic DOWN, // Do not send traffic- healthcheck callback failed STARTING, // Just about starting- initializations to be done - do not // send traffic OUT_OF_SERVICE, // Intentionally shutdown for traffic UNKNOWN; public static InstanceStatus toEnum(String s) { if (s != null) { try { return InstanceStatus.valueOf(s.toUpperCase()); } catch (IllegalArgumentException e) { // ignore and fall through to unknown logger.debug("illegal argument supplied to InstanceStatus.valueOf: {}, defaulting to {}", s, UNKNOWN); } } return UNKNOWN; } } 续约,利用更新的持续时间,如果它是由相关联的指定T注册期间,否则默认持续时间是DEFAULT_DURATION_IN_SECS,即 90s 服务失效剔除 AbstractInstanceRegistry#postInit 剔除定时任务 public void evict(long additionalLeaseMs) { logger.debug("Running the evict task"); // 判断是否打开自我保护机制 if (!isLeaseExpirationEnabled()) { logger.debug("DS: lease expiration is currently disabled."); return; } // We collect first all expired items, to evict them in random order. For large eviction sets, // if we do not that, we might wipe out whole apps before self preservation kicks in. By randomizing it, // the impact should be evenly distributed across all applications. List<Lease<InstanceInfo>> expiredLeases = new ArrayList<>(); for (Entry<String, Map<String, Lease<InstanceInfo>>> groupEntry : registry.entrySet()) { Map<String, Lease<InstanceInfo>> leaseMap = groupEntry.getValue(); if (leaseMap != null) { for (Entry<String, Lease<InstanceInfo>> leaseEntry : leaseMap.entrySet()) { Lease<InstanceInfo> lease = leaseEntry.getValue(); if (lease.isExpired(additionalLeaseMs) && lease.getHolder() != null) { // 集合保存所有剔除节点 expiredLeases.add(lease); } } } } // To compensate for GC pauses or drifting local time, we need to use current registry size as a base for // triggering self-preservation. Without that we would wipe out full registry. // 注册表大小,即所有注册节点个数 int registrySize = (int) getLocalRegistrySize(); // 阈值,0.85 int registrySizeThreshold = (int) (registrySize * serverConfig.getRenewalPercentThreshold()); // 直接相减,得到剔除阈值 int evictionLimit = registrySize - registrySizeThreshold; // 将需要剔除节点个数 和 剔除阈值取最小值,作为自我保护机制下要剔除的节点个数。删多了还是 85 个开启,少了就降低点,这很合理! int toEvict = Math.min(expiredLeases.size(), evictionLimit); if (toEvict > 0) { logger.info("Evicting {} items (expired={}, evictionLimit={})", toEvict, expiredLeases.size(), evictionLimit); // 随机剔除这么多个 Random random = new Random(System.currentTimeMillis()); for (int i = 0; i < toEvict; i++) { // Pick a random item (Knuth shuffle algorithm) int next = i + random.nextInt(expiredLeases.size() - i); Collections.swap(expiredLeases, i, next); Lease<InstanceInfo> lease = expiredLeases.get(i); String appName = lease.getHolder().getAppName(); String id = lease.getHolder().getId(); EXPIRED.increment(); logger.warn("DS: Registry: expired lease for {}/{}", appName, id); internalCancel(appName, id, false); } } } 是否过期,该被剔除当前时间是否大于 最后操作时间+持续时间+服务集群之间同步的预留时间 自我保护机制 eureka 短时间内大量微服务被删除,会打开自我保护机制,避免自己宕机时疯狂删除 检查是否尤里卡服务器的自我保护功能。当启用时,服务器跟踪它应该从服务器接收更新的数量。 任何时候,续期次数低于按规定的阈值百分比getRenewalPercentThreshold() ,服务器关闭到期,以避免身处险境将有助于服务器维护的客户端和服务器之间的网络出现问题时注册表的信息。这些变化是在运行时有效15min 85%宕机则打开该机制 同步给其他peer 传入各种动作 InstanceInfo的状态有以下几种com.netflix.eureka.registry.PeerAwareInstanceRegistryImpl PeerAwareInstanceRegistryImpl#register InstanceInfo写入到本地registry之后,然后同步给其他peer节点 private void replicateToPeers(Action action, String appName, String id, InstanceInfo info /* 可选 */, InstanceStatus newStatus /* 可选 */, boolean isReplication) { Stopwatch tracer = action.getTimer().start(); try { if (isReplication) { numberOfReplicationsLastMin.increment(); } // 如果已经是副本,则不要再复制,因为这将创建有毒的复制! // 如果当前节点接收到的实例信息本就是另一个节点同步来的,则不会继续同步给其他节点,避免形成“广播效应”,造成死循环 if (peerEurekaNodes == Collections.EMPTY_LIST || isReplication) { return; } for (final PeerEurekaNode node : peerEurekaNodes.getPeerEurekaNodes()) { // If the url represents this host, do not replicate to yourself. if (peerEurekaNodes.isThisMyUrl(node.getServiceUrl())) { continue; } replicateInstanceActionsToPeers(action, appName, id, info, newStatus, node); } } finally { tracer.stop(); } } private void replicateInstanceActionsToPeers(Action action, String appName, String id, InstanceInfo info, InstanceStatus newStatus, PeerEurekaNode node) { try { InstanceInfo infoFromRegistry = null; CurrentRequestVersion.set(Version.V2); switch (action) { case Cancel: node.cancel(appName, id); break; case Heartbeat: InstanceStatus overriddenStatus = overriddenInstanceStatusMap.get(id); infoFromRegistry = getInstanceByAppAndId(appName, id, false); node.heartbeat(appName, id, infoFromRegistry, overriddenStatus, false); break; case Register: node.register(info); break; case StatusUpdate: infoFromRegistry = getInstanceByAppAndId(appName, id, false); node.statusUpdate(appName, id, newStatus, infoFromRegistry); break; case DeleteStatusOverride: infoFromRegistry = getInstanceByAppAndId(appName, id, false); node.deleteStatusOverride(appName, id, infoFromRegistry); break; } } catch (Throwable t) { logger.error("Cannot replicate information to {} for action {}", node.getServiceUrl(), action.name(), t); } } 我们看注册动作对应方法这里最终还是调用如下:只不过此时关键在于是否为副本节点

资源下载

更多资源
腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册