NetFlix 网飞的SpringCloud、服务发现组件 Eureka
当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。此时,用于简化增删改查工作量的数据访问框架**(ORM)是影响项目开发的关键**。
存在的问题:
· 代码耦合,开发维护困难
· 无法针对不同模块进行针对性优化
· 无法水平扩展
· 单点容错率低,并发能力差
当访问量逐渐增大,单一应用无法满足需求,此时为了应对更高的并发和业务需求,我们根据业务功能对系统进行拆分:
优点:
· 系统拆分实现了流量分担,解决了并发问题
· 可以针对不同模块进行优化
· 方便水平扩展,负载均衡,容错率提高
缺点:
· 系统间相互独立,会有很多重复开发工作,影响开发效率
当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,
使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的分布式调用是关键。
优点:
· 将基础服务进行了抽取,系统间相互调用,提高了代码复用和开发效率
缺点:
· 系统间耦合度变高,调用关系错综复杂,难以维护
当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的资源调度和治理中心(SOA)是关键
Dubbo Service的远程调用
Zookeeper 实现 注册中心
以前出现了什么问题?
· 服务越来越多,需要管理每个服务的地址
· 调用关系错综复杂,难以理清依赖关系
· 服务过多,服务状态难以管理,无法根据服务情况动态管理
服务治理要做什么?
· 服务注册中心,实现服务自动注册和发现,无需人为记录服务地址
· 服务自动订阅,服务列表自动推送,服务调用透明化,无需关心依赖关系
· 动态监控服务状态监控报告,人为控制服务状态
缺点:
· 服务间会有依赖关系,一旦某个环节出错会影响较大
· 服务关系复杂,运维、测试部署困难,不符合DevOps(开发部署等一体化)思想
缺点:
服务间依赖关系、某个环节出错影响较大
服务关系复杂、运维测试部署难度大、不符合服务一体化Devops
目前的微服务并没有一个统一的标准,一般是以业务来划分将传统的一站式应用,拆分成一个个的服务,彻底去耦合,一个微服务就是单功能业务,只做一件事。
微服务是一种架构模式或者一种架构风格,提倡将单一应用程序划分成一组小的服务独立部署,服务之间相互配合、相互协调,每个服务运行于自己的进程中。
服务与服务间采用轻量级通讯,如HTTP的RESTful API等
避免统一的、集中式的服务管理机制 去中心化
优点
每个服务足够内聚,足够小,比较容易聚焦
开发简单且效率高,一个服务只做一件事情
开发团队小,一般2-5人足以(当然按实际为准)
微服务是松耦合的,无论开发还是部署都可以独立完成
微服务能用不同的语言开发 go语言,javascript==> node.js --> vue.js
1 ~ 3年的沉淀期 ==> java范围,扩展 go语言,javascript ==> node.js
4 ~ 6 年发展期 ==> 管理方向,把控到整个软件的开发周期 ==> 项目经理,项目总监
技术方向,大牛 ==> 架构师 ==> CTO
7 ~ 9 年瓶颈期 ==> 管理层,创业
易于和第三方集成,微服务允许容易且灵活的自动集成部署(持续集成工具有Jenkins,Hudson,bamboo等)
微服务易于被开发人员理解,修改和维护,这样可以使小团队更加关注自己的工作成果,而无需一定要通过合作才能体现价值
微服务允许你融合最新的技术
微服务只是业务逻辑的代码,不会和HTML,CSS或其他界面组件融合。
每个微服务都可以有自己的存储能力,数据库可自有也可以统一,十分灵活。
缺点
发展方向
1-3 年沉淀期:Java 范围、扩展:**go 语言**、nodejs、python
4-6 年发展期:明显划分方向。
管理方向 ==>把控整个软件的开发周期 分配任务 项目经理、项目总监
35 岁之前进入管理层 或者 自我创业 就不会出局、对于软件行业、软件成熟的架构有更深入的了解
技术方向 ==> 大牛、架构师、CTO
7-9 年瓶颈期:管理层、创业
微服务还是SOA
RPC:远程过程调用、RMI 远程方法调用、webservice、dubbo
Http:基于TCP、缺点消息封装臃肿(消息头、消息体)、Rest 风格
概念解释:
RPC,即 Remote Procedure Call(远程过程调用),是一个计算机通信协议。 该协议允许运行于一台计算机的程序调用另一台计算机的子程序,而程序员无需额外地为这个交互作用编程。
说得通俗一点就是:A计算机提供一个服务,B计算机可以像调用本地服务那样调用A计算机的服务。
作用体现:
通过上面的概念,我们可以知道,实现RPC主要是做到两点:
问题思考:
SpringBoot 一站式开发 一般情况下 Service 和 Controller 不分开
Dubbo 更适用于 SSM 的开发、Service 注册在 Dubbo、Controller 远程调用 注册的 service、xml 文件 服务注册
网络通信:底层TCP 的协议
RPC 对调用的过程封装
动态代理方式、隐藏实现的细节
封装接口
序列化工具 JDK-serializer 序列化与反序列化
序列化为字节网络传输
概念解释:
超文本协议:应用层的协议 。规定了网络传输的请求格式、响应格式、资源定位和操作的方式等。但是底层采用什么网络传输协议,并没有规定,不过现在都是采用TCP协议作为底层传输协议。
http和rpc差别:
优缺点:
浏览器 就是 Http 协议的实现 请求和响应
对效率要求更高、并且开发过程使用统一的技术栈、RPC
需要更加灵活、跨语言、跨平台、http 更合适
微服务 强调 独立、自制、灵活 基于 Http 的 Rest 风格服务
RPC 方式限制较多
Spring 的三大模块:SpringBoot (构建)、SpringCloud(协调)、SpringCloudDataFlow(连接)
Spring Cloud是一系列框架的有序集合。它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、熔断器、数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署。Spring并没有重复制造轮子,它只是将目前各家公司开发的比较成熟、经得起实际考验的服务框架组合起来,通过Spring Boot风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包。
Spring Cloud项目的官方网址:https://spring.io/projects/spring-cloud
Spring Boot 是 Spring 的一套快速配置脚手架,可以基于Spring Boot 快速开发单个微服务,Spring Cloud是一个基于Spring Boot实现的云应用开发工具;
Spring Boot专注于快速方便开发单个微服务个体,Spring Cloud关注全局的服务治理框架,它将SpringBoot开发的一个个微服务整合并管理起来,为各个服务之间提供 服务发现、负载均衡、断路器、路由、配置管理、微代理,消息总线、全局锁、分布式会话等集成服务;
Spring Boot使用了默认大于配置的理念,很多集成方案已经帮你选择好了,能不配置就不配置,Spring Cloud很大的一部分是基于Spring Boot来实现,可以不基于Spring Boot吗?不可以。
Spring Boot可以离开Spring Cloud独立使用开发项目,但是Spring Cloud离不开Spring Boot,属于依赖的关系。
Spring Boot 使用 Hoxton 版本
服务发现 Netflix Eureka
服务调用 Netflix Feign
熔断器 Netflix Hystrix
服务网关 Netflix Zuul(舍弃) SpringCloud Gateway(推荐)(支持异步、匹配规则更灵活、依赖SpringCloud)
分布式配置 SpringCloudConfg
相较于 Zookeeper的 Dubbo 和 Nacos,无需搭建注册中心,注册中心作为微服务存在
Eureka 是Spring Cloud Netflix 微服务套件中的一部分, 它基于Netflix Eureka 做了二次封装, 主要负责完成微服务架构中的服务治理功能。我们只需通过简单引入依赖和注解配置就能让Spring Boot 构建的微服务应用轻松地与Eureka 服务治理体系进行整合。
Eureka包含两个组件:Eureka Server和Eureka Client。
Eureka Server提供服务注册服务。
Eureka Client是一个java客户端,用来简化与Eureka Server的交互、客户端同时也就是一个内置的、使用轮询(round-robin)负载算法的负载均衡器。
Eureka 服务器做集群 防止宕机
新建父工程 eureka-demo
依赖
SpringBoot 版本2.3.9.RELEASE 不使用阿里系、依赖管理只需要一个 SpringCloud Hoxton
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.9.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.bh</groupId>
<artifactId>eureke-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>eureke-demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<packaging>pom</packaging>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
一个注册中心的情况下
定义子模块 EurekaServer01
依赖
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
配置文件
server:
port: 8888
spring:
application:
name: EurekaServer01
eureka:
client:
# 当前的注册中心不做为客户端注册到其他的注册中心中(只有一个注册中心)
register-with-eureka: false
# 不从其他的注册中心中获取数据
fetch-registry: false
# 注册中心 注册地址
service-url:
defaultZone: http://localhost:8888/erureka
主启动类
@SpringBootApplication
@EnableEurekaServer
public class EurekaService01App {
public static void main(String[] args) {
SpringApplication.run(EurekaService01App.class);
}
}
二个注册中心的情况下
新增 子 EurekaServer02
修改配置
注册中心的地址必须指向其他的注册中心、不能是自己
允许注册到其他注册中心 和从其他注册中心获取数据 这就构成了注册同步CAP 的 C 一致性
server:
port: 9999
spring:
application:
name: EurekaServer02
eureka:
client:
# 当前的注册中心做为客户端注册到其他的注册中心中(只有一个注册中心)
register-with-eureka: true
# 从其他的注册中心中获取数据
fetch-registry: true
# 注册中心 注册地址
service-url:
defaultZone: http://localhost:8888/eureka
效果
新建提供者模块 EurekaProvider
新增依赖
<dependencies>
<!-- web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
配置
实现了 注册一致性 defaultZone 写一个也可以
server:
port: 9001
spring:
application:
name: provider
eureka:
client:
service-url:
defaultZone: http://localhost:8888/eureka,http://localhost:9999/erueka
主启动类
@SpringBootApplication
//@EnableDiscoveryClient // 开启 Spring Cloud 所有客户端
@EnableEurekaClient // 开启 Eureka 客户端
public class ProviderApp {
public static void main(String[] args) {
SpringApplication.run(ProviderApp.class);
}
}
Controller
@RestController
public class HelloController {
@RequestMapping("/hello")
public String sayHello() {
return "Hello Provider!";
}
}
注解
@EnableDiscoveryClient
开启 SpringCLoud 所有客户端
@EnableEurekaClient
只开启 Euerka 客户端
新建消费者模块 EurekaConsumer
需要的依赖、配置和提供者大体相似 就不记录了
Controller
@RestController
public class HiController {
@RequestMapping("/hi")
public String sayHi() {
return "hi";
}
}
1.服务剔除
注册到eureka的服务可能由于内存溢出或网络故障等原因使得服务不能正常的工作,而服务注册中心并未收到“服务下线”的请求。服务注册中心在启动时会创建一个定时任务,默认每隔一段时间(默认为60秒)将当前清单中超时(默认为90秒)没有续约的服务剔除,这个操作被称为失效剔除。
2.服务保护
我们关停一个服务,很可能会在Eureka面板看到一条警告:
这是触发了Eureka的自我保护机制。当服务未按时进行心跳续约时,Eureka会统计服务实例最近15分钟心跳续约的比例是否低于了85%。
在生产环境下,因为网络延迟等原因,心跳失败实例的比例很有可能超标,但是此时就把服务剔除列表并不妥当,因为服务可能没有宕机。
Eureka在这段时间内不会剔除任何服务实例,直到网络恢复正常。生产环境下这很有效,保证了大多数服务依然可用,不过也有可能获取到失败的服务实例,因此服务调用者必须做好服务的失败容错。
可以通过下面的配置来关停自我保护:
90s 剔除、低于85 保护
eureka:
server:
# 关闭自我保护机制
enable-self-preservation: false
有三种远程调用的实现方式:基于LoadBalanceClient、Ribbon、Feign
增加一个基础类 基础包
EurekaCommon
依赖 lombok
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
实体类 UserEntity
@Data
public class UserEntity implements Serializable {
private long id;
private String name;
private Integer age;
}
service
```java
public interface UserService {
List<UserEntity> findAll();
}
服务提供者、消费者 添加依赖
<dependency>
<groupId>com.bh</groupId>
<artifactId>EurekaCommon</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
编写提供者的数据
UserServiceImpl
@Service
public class UserServiceImpl implements UserService {
@Override
public List<UserEntity> findAll() {
List<UserEntity> list = new ArrayList<>();
list.add(new UserEntity(20210903L,"xx",12));
list.add(new UserEntity(20210904L,"xx",18));
list.add(new UserEntity(20210905L,"xx",19));
return list;
}
}
Controller
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/findAll")
public List<UserEntity> findAll() {
return userService.findAll();
}
}
编写 消费者
主启动类 添加 Bean
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
UserServiceImpl
@Service
public class UserServiceImpl implements com.offcn.service.UserService {
// 远程调用
@Autowired
private RestTemplate restTemplate;
@Autowired
LoadBalancerClient loadBalancerClient;
/*
获取访问路径
*/
public String getServiceUrl() {
ServiceInstance provider = loadBalancerClient.choose("PROVIDER");
String ip = provider.getHost();
int port = provider.getPort();
String url = "http://" + ip +":" + port;
return url;
}
@Override
public List<UserEntity> findAll() {
String url = getServiceUrl();
List<UserEntity> list = restTemplate.getForObject(url + "/user/findAll", List.class);
return list;
}
}
Controller
@RestController
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/showUser")
public List<UserEntity> showUser() {
return userService.findAll();
}
}
实现远程调用
默认是轮询形式的负载均衡
依赖
<!-- web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- ribbon-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
</dependencies>
配置
server:
port: 9102
spring:
application:
name: consumerByRibbon
eureka:
client:
service-url:
defaultZone: http://localhost:8888/eureka
服务提供者
// 获取 端口地址
@Value("${server.port}")
private Integer port;
入口地方创建对象加Bean、加注解LoadBalance(负载均衡)
new RestTemplate
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
路径 自定义 "http://PROVIDER"
实现负载均衡
@LoadBalanced 对应 PROVIDER、loclahost 会出错
1.规则分类
默认情况下Ribbon的负载均衡策略是轮询,Ribbon常用的负载均衡规则:
1、简单轮询策略(RoundRobinRule) 默认
以轮询的方式依次将请求调度不同的服务器
2、随机策略 (RandomRule)
随机选择状态为UP的Server
3、加权响应时间策略 (WeightedResponseTimeRule)
根据响应时间分配一个weight,响应时间越长,weight越小,被选中的可能性越低。
4、区域权重策略(ZoneAwareRule)
综合判断server所在区域的性能和server的可用性选择server
5、最低并发策略(BestAvailableRule)
逐个查看server,选择一个并发连接最低的server
6、重试策略(RetryRule)
对选定的负载均衡策略机上重试机制
7、可用过滤策略(AvailabilityFilteringRule)
过滤掉一直失败并被标记为circuit tripped的server
8、ResponseTimeWeightedRule
已废弃,作用同WeightedResponseTimeRule
2.规则配置
yml文件配置指定服务的负载均衡策略
# com.netflix.loadbalancer.RandomRule #配置规则 随机
# com.netflix.loadbalancer.RoundRobinRule #配置规则 轮询
# com.netflix.loadbalancer.RetryRule #配置规则 重试
# com.netflix.loadbalancer.WeightedResponseTimeRule #配置规则 响应时间权重
# com.netflix.loadbalancer.BestAvailableRule #配置规则 最空闲连接策略
针对每个服务来在消费端如UserWeb02的application.yml中配置负载均衡规则
PROVIDER: #服务名称
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
1.重试机制理解
当我们选择轮询负载均衡策略的时候,如果一个服务停掉,因为服务剔除的延迟,消费者并不会立即得到最新的服务列表,此时再次访问你会得到错误提示:
Spring Cloud 整合了Spring Retry 来增强RestTemplate的重试能力,当一次服务调用失败后,不会立即抛出一次,而是再次重试另一个服务。
2.重试机制实现
1.修改配置文件application.yml
spring:
#开启Spring Cloud的重试功能
cloud:
loadbalancer:
retry:
enabled: true
PROVIDER:
ribbon:
#配置指定服务的负载均衡策略
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule
# 是否对所有操作都进行重试
OkToRetryOnAllOperations: true
ConnectTimeout: 250 # Ribbon的连接超时时间
ReadTimeout: 1000 # Ribbon的数据读取超时时间
# 切换实例的重试次数
MaxAutoRetriesNextServer: 1
# 对当前实例的重试次数
MaxAutoRetries: 1
注意:使用重试,要设置负载均衡策略为轮询com.netflix.loadbalancer.RoundRobinRule
2.修改pom文件引入依赖
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
3.修改主启动类方式
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
// 单位毫秒
requestFactory.setReadTimeout(200);
requestFactory.setConnectTimeout(200);
return new RestTemplate(requestFactory);
}
4.测试效果
重启项目UserWeb02,
不断刷新页面,可以看到用户服务显示内容在变化,负载均衡效果已经生效,到此ribbon消费者成功
如图:
关闭生产者项目ProviderService002
重试机制生效不会再产生刚才的调用错误。
注意:重试过于频繁后台也会出现异常,但是页面不会再次出现错误。
Feign是一个声明式的web服务客户端,它使编写web服务客户端变得更加容易。创建一个接口并添加一个Fegin的注解@FeignClient,就可以通过该接口调用生产者提供的服务。Spring Cloud对Feign进行了增强,使得Feign支持了Spring MVC注解
两点:1、Feign采用的是接口加注解的声明式服务调用;
2、Fegin整合Ribbon及Eureka,支持负载均衡
依赖
spring-boot-starter-web
spring-cloud-starter-netflix-eureka-client
spring-cloud-starter-openfeign
主启动类 Feign
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients(basePackages = "com.offcn.consumer.feign")
接口+注解
@FeignClient(value = "PROVIDER")
public interface ProviderService {
@GetMapping("/user/findAll")
public Map<String,Object> findAll();
}
使用 AutoWired 注解
a、b、c 级联调用调用出错、防止调用失败,返回fallback数据。
熔断依赖
<!-- 熔断依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
开启熔断器
@EnableCircuitBreaker
回调降级方法
@HystrixCommand(fallbackMethod = "findAllFallback")
降级服务方法写同一个类里面
@FeignClient(value = "PROVIDER",fallback = ProviderServiceFallback.class)
创建回调降级方法的实现类继承远程调用类
@Component
public class ProviderServiceFallback implements ProviderService {
@Override
public Map<String, Object> findAll() {
Map<String, Object> map = new HashMap<String, Object>();
map.put("List",new ArrayList<>());
map.put("msg","feign 远程调用失败");
return map;
}
}
配置文件开启熔断器
feign:
hystrix:
enable: true
断路器是根据一段时间窗内的请求情况来判断并操作断路器的打开和关闭状态的。而这些请求情况的指标信息都是HystrixCommand和HystrixObservableCommand实例在执行过程中记录的重要度量信息,它们除了Hystrix断路器实现中使用之外,对于系统运维也有非常大的帮助。这些指标信息会以“滚动时间窗”与“桶”结合的方式进行汇总,并在内存中驻留一段时间,以供内部或外部进行查询使用,Hystrix Dashboard就是这些指标内容的消费者之一。
监测组件
<dependencies>
<!-- 熔断监控监测组件-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- 熔断依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!-- 监测面板-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
</dependencies>
主启动类加入注解
@EnableHystricDashboard
豪猪Logo
修改 Feign 的主启动类
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients(basePackages = "com.offcn.consumer.feign")
@EnableCircuitBreaker
@EnableHystrixDashboard
public class ConsumerFeignApp {
public static void main(String[] args) {
SpringApplication.run(ConsumerFeignApp.class);
}
@Bean
public ServletRegistrationBean getServlet(){
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
registrationBean.setLoadOnStartup(1); //系统启动时加载顺序 越小越优先
registrationBean.addUrlMappings("/hystrix.stream");//路径
registrationBean.setName("HystrixMetricsStreamServlet"); // 访问名字
return registrationBean;
}
}