SpringCloudNetflix
一.应用架构的演变
单体——>分布式——>SOA——>微服务——>…
1. 单体应用架构
1.1. 概念
单体架构:所有的模块,组件…都在一个应用中应用最终打成一个(war,jar)包使用一个容器(Tomcat)进行部署,通常一个应用享用一个数据库。
1.2. 单体架构的优缺点
优点:
- 易于开发 :架构简单,技术成本低
- 易于测试 :所有功能在一个项目,方便测试
- 易于部署 :一个Tomcat就可以实现部署,简单方便
缺点:
- 代码臃肿不方便开发维护(代码可读性差)
- 代码编译系统启动变慢
- 系统扩展性能变差(牵一发而动全身)
- 无法针对某一个业务做扩展(集群)
- 对大数据量,高并发量的处理不占优势
- 技术选型单一
- 模块/业务耦合度高
2. 分布式与SOA
2.1.分布式架构
2.2.面向服务的架构SOA
优点:
- 模块拆分,使用API通信,降低模块之间的耦合度
- 项目拆分多个子应用,每个子应用业务简单,代码简单,方便维护开发。
- 不同技术人员可以负责不同的子应用
- 提高服务之间的重用性,业务逻辑可组合。
缺点:
- 服务之间的API接口开发增加了工作量,
- SOA服务之间的网络通信调用对性能有一定的影响(尽管很小)
- 相对于单体应用来说,技术,人力成本较高。
- 部署和运维相对麻烦
3. 微服务架构
3.1. 概念
一种架构风格,它提倡将单一应用程序划分成一组小的服务,每个服务运行独立的自己的进程中,服务之间互相协调、互相配合,为用户提供最终价值
3.2. 微服务的优缺点
优点:
- 单个服务业务简单,代码简单方便开发维护
- 服务之间无耦合,服务之间升级维护互不影响
- 轻量级HTTP通信机制,使得的不同的服务可以采用不同的编程语言
- 微服务有极强的扩展能力,业务量大的服务可以再次拆分服务,也可以进行集群部署,剔除服务也很方便
- 更大的系统负载能力和容错能力(集群)
- 对于开发人员来说,通常只需要关注单一服务,新员工上手也比较快
- 微服务架构对现在流行的敏捷开发支持优化
缺点:
- 分布式事务 :服务通信机制增加了事务的复杂性,架构师要选择合适的分布式方案(CAP理论)
- 部署麻烦 :微服务众多,部署麻烦,需要借助容器技术和自动化部署工具,这又增加了开发人员的学习成本。
- 技术成本高 :微服务架构本身比较复杂,技术成本高,开发人员需要花更多的时间学习相关技术。
- 服务通信对性能的损耗 : 微服务架构一定要考虑服务通信延迟对服务调用性能的损耗问题,开发人员需要选择合适的通信方式解决这一问题。
二.SpringCloud介绍
1. 认识SpringCloud
1.1.概念
Spring cloud是一个基于Spring Boot实现的服务治理工具包
,用于微服务架构中管理和协调服务的
。Spring Cloud是一系列框架的有序集合。它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等
,都可以用Spring Boot的开发风格做到一键启动和部署。通过Spring Boot风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包。有了SpringCloud之后,让微服务架构的落地变得更简单。
1.2. 常用组件
`Netflix Eureka
微服务过多时,管理服务的通信地址非常麻烦,Eureka就是用来管理微服务的通信地址清单的,有了Eureka之后我们通过服务的名字就能实现服务的调用。
Netflix Ribbon\Feign : 客户端负载均衡
Ribbon和Feign都是客户端负载均衡器,它的作用是在服务发生调用的时候帮我们将请求按照某种规则分发到多个目标服务器上,简单理解就是用来解决微服务之间的通信问题。
Netflix Hystrix :断路器
微服务的调用非常复杂,有的时候一个请求需要很多的微服务共同完成,那么一旦某个服务发生故障,导致整个调用链上的微服务全都出现异常,甚至导致整个微服务架构瘫痪。Hystrix就是用来解决微服务故障,保护微服务安全的组件。
Netflix Zuul : 服务网关
zuul作为服务网关,所有的请求都需要经过zuul之后才能到达目标服务,把微服务公共事件交给zuul统一处理,如:用户鉴权,请求监控等。
Spring Cloud Config :分布式配置
微服务架构中的服务实例非常的多,服务的配置文件分散在每个服务中,每次修改服务的配置文件和重新服务实例都是一个很麻烦的工作,Spring Cloud Config作为分布式配置管理中心就是用来统一的管理服务的配置文件。
Spring Cloud Bus : 消息总线
消息总线是在微服务中给各个微服务广播消息的一个组件,我们使用消息总线构建一个消息中心,其他微服务来接入到消息中心,当消息总线发起消息,接入的微服务都可以收到消息从而进行消费。
Spring Cloud sleuth :微服务链路追踪
当我们的应用采用微服务架构之后,后台可能有几十个甚至几百个服务在支撑,一个请求请求可能需要多次的服务调用最后才能完成,链路追踪的作用就是来监控维护之间的调用关系,让程序员方便直观的感受到一个请求经历了哪些微服务,以及服务的请求时间,是否有异常等。
2. SpringCloud与Dubbo
2.1.Dubbo简介
Dubbo最早是有阿里巴巴提供的一个服务治理和服务调用框架,现在已经成为Apache的顶级项目,Dubbo跟SpringCloud最显著的区别是Dubbo的定位只是一个RPC框架,相比SpringCloud来说它缺少很多功能模块,如:网关,链路追踪等,所以往往在使用Dubbo作为微服务开发框架的时候,还需要去配合其他的框架一起使用,如:加入zookeeper作为注册中心。
2.2.SpringCloud与Dubbo的区别
三. 服务注册与发现
1. Eureka介绍
1.1. 什么是Eureka
管理服务的通信地址
Netflix公司提供的服务注册与发现
组件。
同类型的组件还有 zookeeper,consul等
1.2. Eureka的工作原理
1.2.1.服务注册
Eureka是一个服务注册与发现组件,简单说就是用来统一管理微服务的通信地址的组件,它包含了EurekaServer 服务端(也叫注册中心)和EurekaClient客户端两部分组成,EurekaServer是独立的服务,而EurekaClient需要集成到每个微服务中。
微服务(EurekaClient)在启动的时候会向EurekaServer提交自己的服务信息(通信地址如:服务名,ip,端口等),在 EurekaServer会形成一个微服务的通信地址列表存储起来。 — 这叫服务注册
1.2.2.服务发现
微服务(EurekaClient)会定期(RegistryFetchIntervalSeconds:默认30s)的从EurekaServer拉取一份微服务通信地址列表缓存到本地。当一个微服务在向另一个微服务发起调用的时候会根据目标服务的服务名找到其通信地址,然后基于HTTP协议向目标服务发起请求。
1.2.3.服务续约
微服务(EurekaClient)采用定时(LeaseRenewalIntervalInSeconds:默认30s)发送“心跳”请求向EurekaServer发请求进行服务续约,其实就是定时向 EurekaServer发请求报告自己的健康状况,告诉EurekaServer自己还活着,不要把自己从服务地址清单中剔除掉,那么当微服务(EurekaClient)宕机未向EurekaServer续约,或者续约请求超时,注册中心机会从服务地址清单中剔除该续约失败的服务。
1.2.4.服务下线
微服务(EurekaClient)关闭服务前向注册中心发送下线请求,注册中心(EurekaServer)接受到下线请求负责将该服务实例从注册列表剔除
2. EurekaServer实战
2.1. 搭建父项目:
springboot 、springcloud、公共依赖:
<modules>
<module>springcloud-netfix-service-order</module>
<module>springcloud-netfix-service-user</module>
<module>springcloud-netfix-eureka</module>
<module>springcloud-netfix-pojo-user</module>
</modules>
<!--公共的一些配置-->
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<!--1.管理 SpringBoot的jar包-->
<!--SpringBoot-->
<parent>
<groupId> org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.5.RELEASE</version>
</parent>
<!--2.管理 SpringCloud的jar包
-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!--3.这里是所有子项目都可以用的jar包-->
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
2.2. 搭建子项目
2.2.1. 依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
2.2.2. yml配置
server:
port: 10010 #端口
eureka:
instance:
hostname: localhost #主机
client: #客户端配置
registerWithEureka: false #EurekaServer自己不要注册到EurekaServer自己 ,只有EurekaClient才注册
fetchRegistry: false #EurekaServer不要拉取服务的通信地址列表 ,只有EurekaClient才拉取地址列表
serviceUrl: #注册中心的注册地址
defaultZone: http://localhost:10010/eureka/ #http://${eureka.instance.hostname}:${server.port}/eureka/
server:
enable-self-preservation: false #关闭自我保护警告
2.3.![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/ba969434cf02b6f093c02a0504cc8c51.png)
2.3.1. 依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
2.3.2. yml配置
#注册到EurekaServer
eureka:
client:
serviceUrl:
defaultZone: http://localhost:10010/eureka/
instance:
prefer-ip-address: true #使用ip地址进行注册
instance-id: user-server:10020 #实例ID
spring:
application:
name: service-order
server:
port: 10020
2.3 测试
启动订单服务,访问Eureka Server的监控界面:http://localhost:10010,可以看到订单服务也注册进去了。
四.RestTemplate服务通信
1.如何实现服务通信
用户服务需要提供User对象——>编写Controller接口,编写相关方法返回User,订单服务需要从用户服务获取到User对象,而浏览器需要访问订单服务获取到User对象——>订单服务编写Controller接口供浏览器来调用。
我们发现不管是用户服务还是订单服务都需要用到User对象,那么是不是在用户服务和订单服务都需要创建User的模型?当然没必要,公共的东西就是要抽取,所以我们会把User对象封装在一个公共的模块 springcloud-netfix-pojo-user中然后让用户服务和订单服务都去依赖这个模块。
2.编码实战
2.1 公共的实体类模块-User
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Long id;
private String username;
private String content;
}
2.2. 订单服务和用户服务 : 导入实体类模块的.jar
<dependency>
<groupId>com.huawei</groupId>
<artifactId>springcloud-netfix-pojo-user</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
2.3. 用户服务:编写controller,返回一个User
@RestController
public class UserController {
// value自动读yml配置 server.port
@Value("${server.port}")
private int port;
// 用户微服务:根据id查询user
@GetMapping("/user/{id}")
public User getById(@PathVariable Long id) {
return new User(id,"Edsen","端口号为:"+port);
}
}
2.4. 订单服务:编写一个controller,远程调用User
@RestController
public class OrderController {
@Autowired
private RestTemplate restTemplate;
// 订单微服务,根据id查询user
@GetMapping("/order/{id}")
public User getById(@PathVariable Long id) {
// 远程调用User服务,拿到User
// return restTemplate.getForObject("http://localhost:10020/user/"+id,User.class);
return restTemplate.getForObject("http://SERVICE-USER/user/"+id,User.class);
}
}
五.Ribbon客户端负载均衡
1.概念
1.1.引入
防止应用出现单节点故障问题,同时为了提高应用的作业能力,我们需要对应用做集群 ,如果我们对user-server(用户服务)做了集群 ,那么这个时候回衍生出一些问题:现在有两个user-server(用户服务)就意味着有两个user-server(用户服务)的通信地址,我的order-server(订单服务)在向user-server(用户服务)发起调用的时候该访问哪个?如何访问?这个时候就需要有一个组件帮我们做请求的分发,即:负载均衡器,而Ribbon就是一个 - 客户端负载均衡器。
1.2.什么是Ribbon
Ribbon是Netflix发布的云中间层服务开源项目,主要功能是提供客户端负载均衡算法
。Ribbon客户端组件提供一系列完善的配置项,如,连接超时,重试
等。简单的说,Ribbon是一个客户端负载均衡器,Ribbon可以按照负载均衡算法(如简单轮询,随机连接等)向多个服务发起调用
(正好可以解决上面的问题),我们也很容易使用Ribbon实现自定义的负载均衡算法
。
1.3.Ribbon的工作机制
我们将user-server(用户服务)做集群处理,增加到2个节点(注意:两个user-server(用户服务)的服务名要一样,ip和端口不一样),在注册中心的服务通信地址清单中user-server(用户服务)这个服务下面会挂载两个通信地址 。
order-server(订单服务)会定时把服务通信地址清单拉取到本地进行缓存
, 那么当order-server(订单服务)在向user-server(用户服务)发起调用时,需要指定服务名为 user-server(用户服务)
;那么这个时候,ribbon会根据user-server(用户服务)这个服务名找到两个order-server的通信地址
, 然后ribbon会按照负载均衡算法(默认轮询)选择其中的某一个通信地址,发起http请求实现服务的调用
2.提供者user-server(用户服务)集群
@RestController
public class UserController {
// value自动读yml配置 server.port
@Value("${server.port}")
private int port;
// 用户微服务:根据id查询user
@GetMapping("/user/{id}")
public User getById(@PathVariable Long id) {
return new User(id,"Edsen","端口号为:"+port);
}
}
这样就可以使用一个tomcat服务器开启多个不同的端口号
3.消费者Order-server集成Ribbon
3.1. 导入Ribbon的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
3.2. 定义RestTemplate的方法加:@LoadBalanced
//配置一个RestTemplate ,Spring封装的一个机遇Restful风格的http客户端 工具
//@LoadBalanced :让RestTemplate有负载均衡的功能
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
3.3. 修改RestTemplate调用方式:把ip:端口改成服务名
@RestController
public class OrderController {
@Autowired
private RestTemplate restTemplate;
// 订单微服务,根据id查询user
@GetMapping("/order/{id}")
public User getById(@PathVariable Long id) {
// 远程调用User服务,拿到User
// return restTemplate.getForObject("http://localhost:10020/user/"+id,User.class);
return restTemplate.getForObject("http://SERVICE-USER/user/"+id,User.class);
}
}
3.4. 测试:
每次刷新页面 端口号都不同,这里因为停了服务(懒…),就不测试了
4.负载均衡算法
4.1.Ribbon内置算法
4.2.配置负载均衡算法
4.2.1.注解全局配置
/**
* 订单的启动类
*/
@SpringBootApplication
@EnableEurekaClient
public class OrderServerApplication1030
{
//配置一个RestTemplate ,Spring封装的一个机遇Restful风格的http客户端 工具
//@LoadBalanced :让RestTemplate有负载均衡的功能
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
//负载均衡算法
@Bean
public RandomRule randomRule(){
return new RandomRule();
}
//省略...
4.2.2.yml方式配置负载均衡算法
配置全局Ribbon算法
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
配置某个服务的Ribbon算法
user-server:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
5.Ribbon调优配置
5.1.超时配置
使用Ribbon进行服务通信时为了防止网络波动造成服务调用超时,我们可以针对Ribbon配置超时时间以及重试机制
ribbon:
ReadTimeout: 3000 #读取超时时间
ConnectTimeout: 3000 #链接超时时间
MaxAutoRetries: 1 #重试机制:同一台实例最大重试次数
MaxAutoRetriesNextServer: 1 #重试负载均衡其他的实例最大重试次数
OkToRetryOnAllOperations: false #是否所有操作都重试,因为针对post请求如果没做幂等处理可能会造成数据多次添加/修改
5.2.饥饿加载
我们在启动服务使用Ribbon发起服务调用的时候往往会出现找不到目标服务的情况,这是因为Ribbon在进行客户端负载均衡的时候并不是启动时就创建好的,而是在实际请求的时候才会去创建,所以往往我们在发起第一次调用的时候会出现超时导致服务调用失败,我们可以通过设置Ribbon的饥饿加载来改善此情况,即在服务启动时就把Ribbon相关内容创建好。
ribbon:
eager-load:
enabled: true #开启饥饿加载
clients: user-server #针对于哪些服务需要饥饿加载