微服务概述
- 微服务的核心就是将传统的一站式应用,根据业务拆分成一个一个的服务,彻底地去耦合,每一个微服务提供单个业务功能的服务,一个服务做一件事,从技术角度看就是一种小而独立的处理过程,类似进程概念,能够自行单独启动或销毁,拥有自己独立的数据库
- 微服务架构是一种架构模式或者说是一种架构风格,它提倡将单一应用程序划分成一组小的服务,每个服务运行在其独立的自己的进程中,服务之间互相协调、互相配合,为用户提供最终价值。服务之间采用轻量级的通信机制互相沟通( 通常是基干HTTP的RESTful API) 。每个服务都围绕着具体业务进行构建,并且能够被独立地部署到生产环境类生产环境等 。另外,应尽量避免统一的、集中式的服务管理机制,对具体的一个服务而言,应根据业务上下文,选择合适的语言、工具对其进行构建,可以有一个非常轻量级的集中式管理来协调这些服务,可以使用不同的语言来编写服,也可以使用不同的数据存储
- 优点
- 每个服务足够内聚,足够小
- 开发简单、效率高,一个服务专注于一件事
- 微服务是松耦合的,是有功能意义的服务,无论是在开发阶段还是部署阶段都是独立的。独立部署,独立运行
- 可以使用不同的语言开发,易于和第三方集成
- 每个微服务都有自己的存储能力,可以有自己的数据库,也可以有统一数据库
- 缺点
- 运维复杂,成本高、服务间通信成本高、数据一致性、部署依赖…
- 微服务技术栈SpringCloud:多种技术的集合体(是一个完整的微服务架构)
- 服务开发:springBoot、spring、springmvc
- 服务配置与管理:Archaius、Diamond
- 服务注册与发现:eureka、Consul、Zookeeper
- 服务调用:Rest、RPC、gRPC
- 服务熔断器:Hystrix、Envoy
- 负载均衡:Ribbon、Nginx
- 服务接口调用:Feign
- 消息队列:Kafka、RabbitMQ、ActiveMQ等
- 配置中心:SpringClougConfig、chef等
- 路由:zuul
- 服务监控:Zabbix,Nagios、Metrics、Spectator等
- 全链路追踪:Zipkin、Brave、Dapper等
- 服务部署:Docker、OpenStack、Kubernetes等
- 数据流操作开发包:springCloud Stream
- 消息总线:Spring Cloud Bus
- 批量处理:Quartz
SpringCloud概述
- 基于SpringBoot提供了一套微服务解决方案,包括服务注册与发现,配置中心,全链路监控,服务网关,负载均衡,熔断器等组件,除了基于NetFlix的开源组件做高度抽象封装之外,还有一些选型中立的开源组件
- SpringCloud利用SpringBoot的开发便利性巧妙地简化了分布式系统基础设施的开发,SpringCloud为开发人员提供了快速构建分布式系统的一些组件,包括配置管理,服务发现,断路器,路由,微代理,事件总线,全局锁,决策竞选,分布式会话等等,它们都可以用SpringBoot的开发风格做到一键启动和部署
- SpringBoot并没有重复制造轮子,她只是将目前各家公司开发的比较成熟,经得起实际考验的服务框架组合起来,通过SpringBoot风格进行再封装屏蔽掉了复杂 的配置和实现原理,最终给开发者留出了一套简单易懂,易部署和易维护的分布式系统开发工具包
- 分布式微服务架构下的一站式解决方案,是各个微服务架构落地技术的集合体,俗称微服务全家桶
- 与SpringBoot区别
- SpringBoot可以不依赖SpringCloud单独使用,但是SpringCloud必须依赖于SpringBoot才可以使用
- SpringBoot专注于快速方便的开发单个个体微服务
- SpringCloud是关注全局的微服务协调整理治理框架,它将SpringBoot开发的一个个单体微服务整合并管理起来,为各个微服务之间提供,配置管理,服务发现,断路器,路由,微代理,事件总线,全局锁,决策竞选,分布式会话等等集成服务
- 与Dubbo的区别
- SpringCloud抛弃了Dubbo的RPC通信,采用的是基于HTTP的REST方式等等
- SpringCloud分布式开发五大神兽
- 服务注册与发现:Netflix Eureka
- 客服端负载均衡:Netflix Ribbon
- 断路器:Netflix Hystrix
- 服务网关:Netflix Zuul
- 分布式配置:SpringCloud Config
- 集成组件
服务注册与发现
Eureka
- 是Netflix的子模块,也是核心模块之一,基于RESTful的服务,用于定位服务,实现云端中间层发现和故障转移,功能类似于dubbo的注册中心
- 由两个组件组成:EurekaServer服务器和EurekaClient客户端。
- EurekaServer服务器用作服务注册服务器,各个节点启动后会在EurekaServer中进行注册, EurekaServer中的服务注册表存储可用的服务节点信息
- EurekaClient客户端是一个java客户端,用来简化与EurekaServer的交互,同时具备一个内置的轮询负载均衡器,提供服务的故障切换支持(应用启动后,将会向EurekaServer发送心跳,默认周期30秒,若多次未接收某个节点的心跳,则会在服务注册表中移除)。Netflix在其生产环境中使用的是另外的客户端,它提供基于流量、资源利用率以及出错状态的加权负载均衡
建立服务器EurekaServer
- maven依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter</artifactId>
<dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
<dependency>
- yml配置
server:
port: 8761
eureka:
instance:
hostname: localhost #eureka服务器端的实例名称
client:
register-with-eureka: false #表示不向注册中心注册自己
fetch-registry: false #表示自己就是注册中心,维护服务实例,并不需要去检索服务
service-url:
dafaultZone: http://localhost:8761/eureka/ #设置与EurekaServer交互的地址查询服务和注册服务
spring:
application:
name: eureka
- 主启动类标注@EnableEurekaServer注解,表示接受其它微服务注册
建立客户端EurekaClient(在已有微服务中)
- maven依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<dependency>
- yml配置及主程序
eureka:
client: #客户端注册进服务列表中
service-url:
dafaultZone: http://localhost:8761/eureka/ #配置eureka服务端的url,如果服务端为高可用,则可以配置多个,中间用逗号隔开
instance:
instance-id: dept #自定义服务名称信息
prefer-ip-address: true #访问路径可以显示ip地址
spring:
application:
name: client #对外暴露微服务名
- 主启动类标注@EnableDiscoveryClient注解
监控信息完善(在已有微服务中)
- SpringBoot Actuator监控 SpringBoot 应用的运行情况
- maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<dependency>
- parent项目POM文件添加build配置
<build>
<finalName>microservicecloud</finalName>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<configuration>
<delimiters>
<delimit>$</delimit>
</delimiters>
</configuration>
</plugin>
</plugins>
</build>
- 服务端application.yml添加info
info:
app.name: atguigu-microservicecloud
company.name: www.atguigu.com
bulid.artifactId: $project.artifactId$
bulid.version: $project.version$
Eureka自我保护机制
- Eureka Server 在运行期间会去统计心跳失败比例在 15 分钟之内是否低于 85%,如果低于 85%,Eureka Server 会将这些实例保护起来,让这些实例不会过期,但是在保护期内如果服务刚好这个服务提供者非正常下线了,此时服务消费者就会拿到一个无效的服务实例,此时会调用失败,对于这个问题需要服务消费者端要有一些容错机制,如重试,断路器等
- 我们在单机测试的时候很容易满足心跳失败比例在 15 分钟之内低于 85%,这个时候就会触发 Eureka 的保护机制,一旦开启了保护机制,则服务注册中心维护的服务实例就不是那么准确了
- 解决方式1:EurekaServer的高可用yml配置
eureka:
client: #客户端注册进服务列表中
service-url:
dafaultZone: http://localhost:8761/eureka/,http://localhost:8762/eureka/ #集群配置
register-with-eureka: false #表示不向注册中心注册自己
server:
enable-self-preservation=false #关闭保护机制,确保注册中心中不可用的实例被及时的剔除(不推荐)
spring:
application:
name: eureka
- 方式二
- Eureka Server 界面两个参数
- Renews threshold:Eureka Server 期望每分钟收到客户端实例续约的总数
- Renews (last min):Eureka Server 最后 1 分钟收到客户端实例续约的总数
- 自我保护模式被激活的条件是:在 1 分钟后,Renews (last min) < Renews threshold
- 降低renewalPercentThreshold的比例(eureka.server.renewal-percent-threshold设置为0.5以下,比如0.49),不推荐
- Eureka Server 界面两个参数
- 方式三:部署多个 Eureka Server 并开启其客户端行为(eureka.client.register-with-eureka不要设为false,默认为true),推荐
集群配置
- 映射localhost(修改C:\Windows\System32\drivers\etc路径下的host)
127.0.0.1 eureka7001.com
127.0.0.1 eureka7002.com
127.0.0.1 eureka7003.com
- 修改yml文件
eureka:
instance:
hostname: eureka7001.com
client: #客户端注册进服务列表中
service-url:
dafaultZone: http://eureka7002.com:7002/eureka/,http://eureka7002.com:7003/eureka/ #集群配置
eureka:
instance:
hostname: eureka7002.com
client: #客户端注册进服务列表中
service-url:
dafaultZone: http://eureka7001.com:7001/eureka/,http://eureka7003.com:7003/eureka/ #集群配置
eureka:
instance:
hostname: eureka7003.com
client: #客户端注册进服务列表中
service-url:
dafaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/ #集群配置
- 修改服务yml文件
eureka:
client:
register-with-eureka: false #表示不向注册中心注册自己
fetch-registry: false #表示自己就是注册中心,维护服务实例,并不需要去检索服务
service-url:
dafaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
与Zookeeper区别
- CAP原则:在一个分布式系统中,一致性Consistency、可用性Availability、分区容错性Partition tolerance,三者不可兼得,由于分区容错性是分布式系统中必须保证的,因此只能在A与C之间选择
- Zookeeper保证的是CP,不能接受服务直接down掉,而Eureka保证AP,各个节点都是平等的,因此有自我保护机制
- Eureka不会从注册列表中移除长期没有收到心跳而过期的应用
- Eureka会接受新服务的注册与查询操作,但不会同步到其他节点,网络稳定后新的注册才同步到其他节点
负载均衡
- 负载均衡(Load Balance)在微服务或分布式集群中的一种应用,它将用户的请求平摊的分配到多个服务上,从而达到系统的HA,常见的Nginx(进程式),LVS,硬件F5(集中式)等,相应的中间件有dubbo和SpringCloud,SpringCloud的负载均衡算法可自定义
Ribbon简介
- 主要提供客户端的软件负载均衡算法,Ribbon客户端组件提供一套完善的配置选项,比如连接超时、重试、重试算法等。Ribbon内置可插拔、可定制的负载均衡组件。
- 负载均衡策略
- 简单轮询负载均衡
- 加权响应时间负载均衡
- 区域感知轮询负载均衡
- 随机负载均衡
- 其他功能
- 易于与服务发现组件(比如Netflix的Eureka)集成
- 使用Archaius完成运行时配置
- 使用JMX暴露运维指标,使用Servo发布
- 多种可插拔的序列化选择
- 异步和批处理操作(即将推出)
- 自动SLA框架(即将推出)
- 系统管理/指标控制台(即将推出)
Ribbon使用
- 配置客户端pom.xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
- 修改yml文件
server:
port: 9001
eureka:
client:
register-with-eureka: false #作为消费者不提供服务,不应该注册自己
service-url:
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
- 对ConfigBean进行注解@LoadBalanced ,获得Rest时加入Ribbon的配置
@Configuration
public class ConfigBean
{
@Bean
@LoadBalanced
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
- 主启动类中标注@EnableEurekaClient注解
- 上述步骤完成后即可在controller中直接通过服务名访问系统中的微服务,服务名作为URI
/* 原服务url */
//private static final String REST_URL_PREFIX = "http://localhost:8001";
/* 以服务名作为URI */
private static final String REST_URL_PREFIX = "http://SPRINGCLOUDDEMO-PROVIDER-DEPT";
@Autowired
private RestTemplate restTemplate;
@RequestMapping(value = "/dept/list")
public List<Dept> list() {
return restTemplate.getForObject(REST_URL_PREFIX + "/provider/dept/list", List.class);
}
- 完成以上步骤就已经实现客户端负载均衡功能(此处使用的是默认轮询的规则进行负载均衡),可以访问客户端进行测试(略过)
Ribbon核心组件IRule
- 根据特定算法从服务列表中选取一个需要访问的服务,其中IRule是一个接口,有七个自带的落地实现类,可以实现不同的负载均衡算法规则
- RoundRobinRule:轮询规则(默认方法)
- RandomRule:随机
- AvailabilityFilteringRule:先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,还有并发的连接数量超过阈值的服务,然后对剩余的服务进行轮询
- WeightedResponseTimeRule:根据平均响应时间计算服务的权重。统计信息不足时会按照轮询,统计信息足够会按照响应的时间选择服务
- RetryRule:正常时按照轮询选择服务,若过程中有服务出现故障,在轮询一定次数后依然故障,则会跳过故障的服务继续轮询
- BestAvailableRule:先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务
- ZoneAvoidanceRule:默认规则,符合判断server所在的区域的性能和server的可用性选择服务
- 切换Ribbon中赋值均衡的规则:只需在客户端(consume)工程中的SpringBoot配置类ConfigBean配置一个返回具体方法的bean即可
@Bean
public IRule MyRule(){
/* RandomRule为Ribbon中自带规则实现之一,随机规则 */
return new RandomRule();
}
- 自定义Ribbon负载均衡算法
- 自定义的Ribbon算法类不能放在主启动类所在的包及子包下(确切来说是不能放在@ComponentScan注解的包及子包下),否则会被全局应用到Ribbon服务中。 应该把自定义算法类放在另外新建的包下,且这个类应该是为【配置类】即加上注解@Configuration
- 步骤
- 将原来consumer工程中的配置类ConfigBean中返回 IRule 的Bean删掉,然后新建一个包,定义配置类,返回自定义的负载均衡规则的实现类
@Configuration public class MySelfRule { @Bean public IRule myRule() { /* 此处返回自定义的负载算法规则 */ return new RandomRule_Myself(); } } ``` + 实现自定义规则实现类RandomRule_Myself(此处实现每台服务器调用5次后,再继续轮询下一台服务器) ```java public class RandomRule_Myself extends AbstractLoadBalancerRule { /* total = 0 // 当total==5以后,我们指针才能往下走, index = 0 // 当前对外提供服务的服务器地址, total需要重新置为零,但是已经达到过一个5次,我们的index = 1 */ /* 总共被调用的次数,目前要求每台被调用5次 */ private int total = 0; /* 当前提供服务的机器号 */ private int currentIndex = 0; public Server choose(ILoadBalancer lb, Object key) { if (lb == null) { return null; } Server server = null; while (server == null) { if (Thread.interrupted()) { return null; } List<Server> upList = lb.getReachableServers(); List<Server> allList = lb.getAllServers(); int serverCount = allList.size(); if (serverCount == 0) { return null; } if (total < 5) { server = upList.get(currentIndex); total++; } else { total = 0; currentIndex++; if (currentIndex >= upList.size()) { currentIndex = 0; } } if (server == null) { Thread.yield(); continue; } if (server.isAlive()) { return (server); } server = null; Thread.yield(); } return server; } @Override public Server choose(Object key) { return choose(getLoadBalancer(), key); } @Override public void initWithNiwsConfig(IClientConfig clientConfig) {} }
- 在启动类上加上注解 @RibbonClient(name = “微服务名”,configuration = XXX.class) 注解指定需要用到负载均衡的微服务名及自定义算法的class对象
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class}) @EnableEurekaClient @RibbonClient(name="SPRINGCLOUDDEMO-PROVIDER-DEPT",configuration=MySelfRule.class) public class DeptConsumer_9001_App { public static void main(String[] args) { SpringApplication.run(DeptConsumer_9001_App.class, args); } }
Feign简介
- Fegin是一个声明似的web服务客户端,它使得编写web服务客户端变得更加容易,使用Fegin创建一个接口并对它进行注解
- Feign是一种负载均衡的HTTP客户端, 使用Feign调用API就像调用本地方法一样,从而避免了调用目标微服务时,需要不断的解析/封装json 数据的繁琐。Feign集成了Ribbon,利用Ribbon维护了MicroServiceCloud-Dept的服务列表信息,并且通过轮询实现了客户端的负载均衡。而与Ribbon不同的是,通过feign只需要定义服务绑定接口且以声明式的方法,优雅而简单的实现了服务调用。Ribbon+eureka是面向微服务编程,而Feign是面向接口编程
- 用途
- Feign旨在使编写Java Http客户端变得更容易。在使用Ribbon+RestTemplate时,利用RestTemplate对http请求的封装处理,形成了一套模版化的调用方法。但是在实际开发中,由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用。所以,Feign在此基础上做了进一步封装,由他来帮助我们定义和实现依赖服务接口的定义。在Feign的实现下,我们只需创建一个接口并使用注解的方式来配置它(以前是Dao接口上面标注Mapper注解,现在是一个微服务接口上面标注一个Feign注解即可),即可完成对服务提供方的接口绑定,简化了使用Spring cloud Ribbon时,自动封装服务调用客户端的开发量
Feign使用
- maven依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
- 主程序
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients(basePackages= {"com.atguigu.springcloud"})
@ComponentScan("com.atguigu.springcloud")
public class DeptConsumer80_Feign_App{
public static void main(String[] args){
SpringApplication.run(DeptConsumer80_Feign_App.class, args);
}
}
- 接口+注解(一般在公共接口中定义,方面其他微服务调用)
@FeignClient(value = "MICROSERVICECLOUD-DEPT") //该接口对应于应用id为MICROSERVICECLOUD-DEPT的微服务
public interface DeptClientService{
@RequestMapping(value = "/dept/get/{id}",method = RequestMethod.GET)
public Dept get(@PathVariable("id") long id);
@RequestMapping(value = "/dept/list",method = RequestMethod.GET)
public List<Dept> list();
@RequestMapping(value = "/dept/add",method = RequestMethod.POST)
public boolean add(Dept dept);
}
- 使用Ribbon+Eureka时
@RestController
public class DeptController_Consumer{
private static final String REST_URL_PREFIX = "http://MICROSERVICECLOUD-DEPT";
@Autowired
private RestTemplate restTemplate;
@RequestMapping(value = "/consumer/dept/add")
public boolean add(Dept dept){
return restTemplate.postForObject(REST_URL_PREFIX + "/dept/add", dept, Boolean.class);
}
@RequestMapping(value = "/consumer/dept/get/{id}")
public Dept get(@PathVariable("id") Long id){
return restTemplate.getForObject(REST_URL_PREFIX + "/dept/get/" + id, Dept.class);
}
@SuppressWarnings("unchecked")
@RequestMapping(value = "/consumer/dept/list")
public List<Dept> list(){
return restTemplate.getForObject(REST_URL_PREFIX + "/dept/list", List.class);
}
}
- 使用Feign,面向接口编程
@RestController
public class DeptController_Consumer{
@Autowired
private DeptClientService service;
@RequestMapping(value = "/consumer/dept/get/{id}")
public Dept get(@PathVariable("id") Long id){
return this.service.get(id);
}
@RequestMapping(value = "/consumer/dept/list")
public List<Dept> list(){
return this.service.list();
}
@RequestMapping(value = "/consumer/dept/add")
public Object add(Dept dept){
return this.service.add(dept);
}
}
Ribbon和Feign调用其他服务的方式区别
- Spring cloud 中服务之间通过restful方式调用有两种方式:restTemplate+Ribbon ,feign
- 启动类使用的注解不同,Ribbon用的是@RibbonClient,Feign用的是@EnableFeignClients
- 服务的指定位置不同,Ribbon是在@RibbonClient注解上声明,Feign则是在定义抽象方法的接口中使用@FeignClient声明
- 调用方式不同,Ribbon需要自己构建http请求,模拟http请求然后使用RestTemplate发送给其他服务,步骤相当繁琐。Feign则是在Ribbon的基础上进行了一次改进,采用接口的方式,将需要调用的其他服务的方法定义成抽象方法即可,不需要自己构建http请求。不过要注意的是抽象方法的注解、方法签名要和提供服务的方法完全一致
Feign的自带断路器
- yml配置
feign: #开启熔断
hystrix:
enabled: true
ribbon: #feign的ribbon的超时配置
Connection: 500 #连接超时
ReadTimeout: 300 #读取超时
- 服务消费者类
@FeignClient(value = "YunCai-SearchServer", fallback=ServiceHystrix.class)
public interface SchedualServiceHi {
@RequestMapping(value = "/luke/itemsearch",method = RequestMethod.GET)
ResponseMap sayHiFromClientOne(@RequestParam(value = "start") Integer start,
@RequestParam(value = "rows") Integer rows,
@RequestParam(value = "tenantId") Integer tenantId,
@RequestParam(value = "status") Integer status,
@RequestParam(value = "key") String key);
}
- 断路器处理类
@Component
public class ServiceHystrix implements SchedualServiceHi {
@Override
public ResponseMap sayHiFromClientOne(Integer start, Integer rows, Integer tenantId, Integer status, String key) {
SearchResult result = new SearchResult(0, new ArrayList<>(Arrays.asList(1L,2L,3L)));
return new ResponseMap(false, "111", 506, result);
}
}
断路器
- 扇出:多个微服务互相调用的时候,如果A调用B,而B又继续调用C,这就是扇出(像一把扇子一样慢慢打开)
- 服务雪崩:如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用会占用越来越多的系统资源,进而引起系统崩溃,就是雪崩效应
- 对于高流量的应用来说,单一的后端依赖会导致服务器所有的资源都在几秒钟内饱和。比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联故障。这些都表示需要【对故障和延迟进行隔离和管理】,以便单个依赖关系的失败,不能影响整个应用程序或系统
Hystrix简介
- Hystrix是一个用于处理分布式系统延迟和容错的开源库。分布式系统中,依赖避免不了调用失败,比如超时,异常等。Hystrix能保证在出现问题的时候,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性
- Hystrix类似一个“断路器”,当系统中异常发生时,断路器给调用返回一个符合预期的,可处理的FallBack,这样就可以避免长时间无响应或抛出异常,使故障不能再系统中蔓延,造成雪崩
服务熔断
- 熔断机制是应对雪崩效应的一种微服务链路保护机制,当扇出链路的某个微服务不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回错误的响应信息。当检测到该节点微服务调用响应正常后,恢复调用链路。 在Spring Cloud框架里,熔断机制通过Hystrix实现。Hystrix会监控微服务间调用的状况,当失败的调用到一定阈值,缺省是5秒内20次调用失败,就会启动熔断机制。熔断机制的注解是@HystrixCommand
- 在Hystrix里面,熔断又分为三种情况:半熔断、熔断打开、熔断关闭
- 熔断打开:请求不再进行调用当前服务,内部设置时钟一般为MTTR(平均故障处理时间),当打开时长达到所设时钟则进入半熔断状态
- 半熔断: 部分请求根据规则调用当前服务,如果请求成功且符合规则则认为当前服务恢复正常,关闭熔断
- 熔断关闭: 熔断关闭不会对服务进行熔断
- maven依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
- yml配置:和其他provider模块的配置基本一致,就是在【instance-id】加了个hystrix标识
eureka:
client: #客户端注册进eureka服务列表内
service-url:
#单机版配置
#defaultZone: http://eureka7001.com:7001/eureka
#集群版配置
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
instance:
instance-id: springclouddemo-dept8003-hystrix #注册实例的id标识
prefer-ip-address: true #访问路径可以显示IP地址
- 控制层和业务层直接参照其他provider模块
- controller类中,在需要配置Fallback的方法上加入@HystrixCommand(fallbackMethod = “XXX”)注解,XXX为FallBack方法名
@RestController
@RequestMapping("/provider")
public class DeptController{
/* 为了演示hystrix,刻意在方法中抛出异常 当出现异常后方法会由Hystrix注解指定的方法处理 */
@RequestMapping(value = "/dept/hystrix")
@HystrixCommand(fallbackMethod = "handleByHystrix")
public String testHystrix(@PathVariable("id") Long id){
if(id == 1){
return "id = 1 ---> success";
} else {
throw new RuntimeException("出现异常id != 1");
}
}
public String handleByHystrix(@PathVariable("id") Long id){
return "出现异常,已经被Hystrix断路器处理完毕";
}
}
- 主启动类中加入@EnableCircuitBreaker注解
@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker//对hystrixR熔断机制的支持
public class DeptProvider_8003_Hystrix_App
{
public static void main(String[] args){
SpringApplication.run(DeptProvider_8003_Hystrix_App.class, args);
}
}
解耦与降级处理
- 服务降级
- 当系统整体资源快不够的时候,忍痛将部分服务暂时关闭,待渡过难关后,再重新开启
- 降级处理是在【客户端】完成的,与服务端没有关系
- 降级一般是从【整体负荷】考虑,当某个服务熔断之后,服务器将不再被调用,此时客户端可以自己准备一个本地的FallBack回调,返回一个缺省值。这样做虽然服务水平下降,但好歹可用,比直接挂掉好
- 解耦
- 如果按照上面的熔断案例来做的话,Controller下的每个方法,都要给其编写一个FallBack方法,当方法慢慢变多,就会造成代码膨胀,一个是增加编写的工作量,另外一个也会增大维护的难度,代码的耦合度也会高,是十分不合理的,所以要将其解耦
- 解耦思路:因为服务端的是通过实现接口访问服务端的,如果在父接口上实现了FallBack方法,通过这样一种方式去维护起来就能实现解耦,也顺便完成了降级的机制
- 使用
1.在公共模块【springclouddemo-api】中,Feign接口上的注解增加属性 @FeignClient(fallbackFactory = T.class)
/* 将接口FeignClientService的熔断处理统一到DeptClientServiceFallbackFactory处理(服务降级处理) */
@FeignClient(value = "SPRINGCLOUDDEMO-PROVIDER-DEPT",fallbackFactory=FeignClientServiceFallbackFactory.class)
public interface FeignClientService{
@RequestMapping(value = "/provider/dept/get/{id}", method = RequestMethod.GET)
Dept get(@PathVariable("id") long id);
@RequestMapping(value = "/provider/dept/list", method = RequestMethod.GET)
List<Dept> list();
}
2.新建FeignClientServiceFallbackFactory类,并实现FallbackFactory接口。需要实现其create方法,在create方法中返回实现了T的对象,使用匿名内部类实现T。(注意:这个类一定要加@Component注解!)
@Component //不要忘记添加
public class FeignClientServiceFallbackFactory implements FallbackFactory<FeignClientService> {
@Override
public FeignClientService create(Throwable throwable) {
return new FeignClientService() {
@Override
public Dept get(long id) {
return new Dept().setDeptno(id).setDname("该ID:" + id + "没有没有对应的信息,Consumer客户端提供的降级信息,此刻服务Provider已经关闭")
.setDb_source("no this database in MySQL");
}
@Override
public List<Dept> list() {
List<Dept> list= new ArrayList<Dept>();
list.add(new Dept().setDname("Consumer客户端提供的降级信息,此刻服务Provider已经关闭")
.setDb_source("no this database in MySQL"));
return list;
}
};
}
}
3.修改【springclouddemo-consume-feign-9002】模块的application.yml文件,开启hystrix(注:在IDEA中可能没有代码提示,开启的true也没有正常高亮,但好像不需要做额外操作也不影响结果)
feign:
hystrix:
enabled: true
服务监控
- Hystrix Dashboard是Hystrix提供的实时监控系统,Hystyix会持续记录所有通过Hystrix发起的请求执行信息,并通过统计报表和图形的形式展示给用户,Hystrix Dashboard是一个单独的微服务,SpringCloud提供了Dashboard的整合,对监控内容转化成可视化界面
创建服务
- maven依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
</dependency>
- yml配置文件
server:
port: 9002
- 启动类
@SpringBootApplication
@EnableHystrixDashboard //开启HystrixDashboard支持
public class HystrixDashboard {
public static void main(String[] args) {
SpringApplication.run(HystrixDashboard.class,args);
}
}
- 测试:在浏览器中输入:http://localhost:9002/hystrix可以访问
服务网关
Zuul简介
- springcloud zuul包含了对请求的路由和过滤两个功能
- 路由功能负责将外部的请求转发到具体的微服务实例上,是实现外部访问统一入口的基础上,而过滤功能则负责对请求的处理过程进行干预,是实现请求校验,服务聚合等功能的基础。
- Zuul和Eureka进行整合,将Zuul自身注册为Eureka服务治理下的应用,同时从Eureka中获取其他微服务的信息,也即以后访问微服务都是通过Zuul跳转后获得
- zuul服务最终会注册进Eureka,提供代理,路由,过滤三大功能
Zuul使用
- maven依赖,需要和Eureka客户端结合使用
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--zuul的依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
- yml配置
server:
port: 9001
eureka:
client:
serviceUrl:
defaultZone: http://localhost:7001/eureka # eureka的暴露地址,直接注册
instance:
instance-id: zuul.com
prefer-ip-address: true
spring:
application:
name: zuul #应用的名称,在同一个eureka中必须不重复
- 在主启动类上添加@EnableZuulProxy这个注解
@SpringBootApplication
@EnableEurekaClient //开启eurkea客户端
@EnableZuulProxy //开启zuul
public class DeptGetWayZuul9001Application {
路由映射规则
代理名称
- 若不想微服务的实例名称暴露,那么此时就需要使用代理名称替代
- 使用ignored-services忽略真实的服务名称访问,可以同时指定多个,其中服务名称必须和服务配置文件中一样
- 在routes下指定多个路由映射规则
zuul: #忽略真实的服务名称实例访问,是一个Set集合,可以指定多个,取消全部使用 "*"即可 ignored-services: - order-provider routes: #routes下面指定代理规则,可以同时指定多个 api-order: #指定第一个规则,这里的名称任意 serviceId: order-provider #指定的实例名称 path: /api-order/** #指定可以访问的路由
设置统一前缀
zuul:
#加上统一的前缀,那么访问的时候一定要加上这个前缀才可以访问到
prefix: /gc
# 忽略真实的服务名称实例访问,是一个Set集合,可以指定多个,取消全部使用 "*"即可
ignored-services:
- order-provider
#routes下面指定代理规则,可以同时指定多个
routes:
#指定第一个规则,这里的名称任意
api-dept:
#指定的实例名称
serviceId: order-provider
#指定可以访问的路由
path: /api-order/**
某个uri取消路由
zuul:
ignored-patterns:
- /api-order/order/list # 取消指定的一个
- /api-order/order/** # 使用通配符去掉order下的全部接口
传递敏感头信息
zuul:
routes:
#指定第一个规则,这里的名称任意
api-order:
#指定的实例名称
serviceId: order-provider
#指定可以访问的路由
path: /api-order/**
sensitive-headers: # 设置为空即可,那么就可以传递敏感头信息了
过滤器
- 生命周期
- PRE:这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、鉴权、限流、参数校验、请求转发,在集群中选择请求的微服务、记录调试信息等
- ROUTING:这种过滤器将请求路由到微服务。这种过滤器用于构建发送给微服务的请求,并使用 Apache HttpClient 或 Netfilx Ribbon 请求微服务
- POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的 HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等
- ERROR:在其他阶段发生错误时执行该过滤器。 除了默认的过滤器类型,Zuul 还允许我们创建自定义的过滤器类型。例如,我们可以定制一种 STATIC 类型的过滤器,直接在 Zuul 中生成响应,而不将请求转发到后端的微服务
- 前置过滤器的使用
/**
* 自定义过滤器,用于实现鉴权,前置过滤器
* 继承ZuulFilter
*/
@Component //一定要注入到ioc容器中
public class TokenFilter extends ZuulFilter {
/**
* 判断过滤器是否被执行,返回true表示被会被执
* 在这里我们可以限制过滤器的执行范围,可以根据指定的条件判断这个请求是否被过滤
*/
@Override
public boolean shouldFilter() {
return true;
}
/**
* 过滤器的具体实现逻辑
* @return
* @throws ZuulException
*/
@Override
public Object run() throws ZuulException {
RequestContext requestContext = RequestContext.getCurrentContext(); //获取请求上下文
HttpServletRequest request = requestContext.getRequest(); //获取HttpServletRequest
String token = request.getParameter("token"); //获取传递过来的请求参数
//如果token是空的,返回权限不足,一般返回的状态码是401
if (StringUtils.isEmpty(token)) {
requestContext.setSendZuulResponse(false); //设置false,此时的zuul不对此路由
requestContext.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value()); //设置401
// requestContext.setResponseBody("no power"); //设置响应的消息
}
return null;
}
/**
* 指定过滤器的类型,前置,后置.................
* 1、其中FilterConstants这个常量类中定义了过滤器常用的变量
* public static final String ERROR_TYPE = "error";
public static final String POST_TYPE = "post";
public static final String PRE_TYPE = "pre";
public static final String ROUTE_TYPE = "route";
* @return
*/
@Override
public String filterType() {
return FilterConstants.PRE_TYPE; //前置过滤器 pre
}
/**
* 过滤器执行的顺序,数字越小优先级越高
* @return
*/
@Override
public int filterOrder() {
//一般前置过滤器放在org.springframework.cloud.netflix.zuul.filters.pre.PreDecorationFilter这个过滤器之前即可,只需要将其对应的顺序-1
return FilterConstants.PRE_DECORATION_FILTER_ORDER-1;
}
}
- 后置过滤器的使用
/**
* 后置过滤器,在响应头中添加一些内容
*/
@Component //注入
public class AddResponseHeaderFilter extends ZuulFilter {
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
RequestContext requestContext = RequestContext.getCurrentContext(); //获取请求上下文
HttpServletResponse response = requestContext.getResponse(); //获取HttpServletResponse
response.addHeader("X-Foo", "add header"); //添加头信息
return null;
}
@Override
public String filterType() {
return FilterConstants.POST_TYPE; //后置过滤器
}
@Override
public int filterOrder() {
//在org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter#filterOrder()这个过滤一起之前执行即可
return FilterConstants.SEND_RESPONSE_FILTER_ORDER-1;
}
}
禁用某种过滤器
zuul:
TokenFilter: # 类的名字
pre: # 类型
disable: true
限流
令牌桶算法
- 系统按照恒定的速率往指定大小的桶里添加令牌,每来一个请求就消耗一个令牌,如果桶内没有令牌表示此事的请求流量已经超过设置的大小了,应该做出相应的响应或者直接抛出异常
- 使用前置过滤器,在请求被转发之前调用,限流的过滤器应该是所有过滤器中优先级最大的,使用google开源的组件Guava
/**
* 限流 ,前置过滤器
* 限流的过滤器的优先级应该是最高,数字最小
*/
@Component
public class RateFilter extends ZuulFilter {
private static final RateLimiter RATE_LIMITER=RateLimiter.create(100); //程每秒钟往桶里放置100个令牌
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
/**
* tryAcquire():如果获取不到一个令牌,表示流量超时了,没有等待时间
* tryAcquire(int permits, long timeout, TimeUnit unit):获取permits个令牌,如果在指定的时间timeout内,还是没有获取到指定的permits个令牌,那么就返回false
*/
if (!RATE_LIMITER.tryAcquire()) {
RequestContext requestContext = RequestContext.getCurrentContext();
requestContext.setSendZuulResponse(false); //不路由
requestContext.setResponseStatusCode(HttpStatus.FORBIDDEN.value()); //403拒绝访问
}
//也可以直接抛出异常
// if (!RATE_LIMITER.tryAcquire()) {
// throw new RuntimeException(); //抛出异常
// }
return null;
}
@Override
public String filterType() {
return FilterConstants.PRE_TYPE; //前置
}
@Override
public int filterOrder() {
//org.springframework.cloud.netflix.zuul.filters.pre.ServletDetectionFilter#filterOrder()这个过滤器的优先级是最高的,只需要-1即可
return FilterConstants.SERVLET_DETECTION_FILTER_ORDER-1;
}
}
超时时间设置
zuul:
host: # 配置zuul的超时时间
connect-timeout-millis: 60000 # 默认2秒,
socket-timeout-millis: 60000
ribbon: # zuul使用服务发现的时候,要想让上面的配置生效,必须配置ribbon的超时时间
ReadTimeout: 60000 # 请求处理时间。
ConnectTimeout: 60000 # 请求连接时间。
服务熔断
- 当请求的服务响应时间超时或者服务不可用的时候zuul会直接响应异常,我们可以设置熔断,只需要在zuul的服务中配置即可
/**
* 设置zuul的熔断
* 实现FallbackProvider接口
* 出现熔断的情况如下:
* 1、当请求的服务响应超时
* 2、当请求的服务不能正常提供服务
*/
@Component //注入到IOC容器
public class OrderFallback implements FallbackProvider {
/**
* 这个方法返回的是serviceId,如果返回的单个服务,那么只针对一个服务熔断
* 如果想要针对所有的服务进行配置熔断,只需要返回*即可
*/
@Override
public String getRoute() {
return "order-provider";
}
/**
* 发生熔断的响应方法
*/
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
return new ClientHttpResponse() {
@Override
public HttpStatus getStatusCode() throws IOException {
return HttpStatus.OK;
}
@Override
public int getRawStatusCode() throws IOException {
return 200;
}
@Override
public String getStatusText() throws IOException {
return "OK";
}
@Override
public void close() {
}
//设置响应的内容
@Override
public InputStream getBody() throws IOException {
return new ByteArrayInputStream("fallback".getBytes());
}
@Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return headers;
}
};
}
}
zuul的重试
- 有时候因为网络或者其它原因,服务可能会暂时的不可用,这个时候我们希望可以再次对服务进行重试,Zuul也帮我们实现了此功能,需要结合Spring Retry 一起来实现
- maven依赖
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
- yml配置
zuul:
retryable: true # 开启重试机制
ribbon: # zuul内部使用的是ribbon实现负载均衡的,因此配置ribbon的重试次数
MaxAutoRetries: 2 # 同一个服务的最大重试次数
MaxAutoRetriesNextServer: 2 # 对于切换的下一个实例的重试次数
Zuul的高可用
zuul:
# TokenFilter:
# pre:
# disable: true
# 忽略真实的服务名称实例访问,是一个Set集合,可以指定多个,取消全部使用 "*"即可
sensitive-headers: # 设置所有的服务都取消敏感头信息
ignored-services:
- order-provider
# ignored-patterns:
# - /api-order/order/list # 取消指定的一个
# - /api-order/order/** # 使用通配符去掉order下的全部接口
#routes下面指定代理规则,可以同时指定多个
routes:
#指定第一个规则,这里的名称任意
api-order:
#指定的实例名称
serviceId: order-provider
#指定可以访问的路由
path: /api-order/**
消费端的使用
- 前提
- zuul微服务(zuul-server)注册到eureka注册中心
- 微服务提供者注册到Eureka注册中心,zuul-server配置的路由是api-order
- 服务消费者注册到Eureka中
- 消费者想用通过zuul-server访问到服务提供者,那么可以直接写http://zuul-server/api-order/order/{id}
@RestController
public class OrderController {
private final static String URI_PRFIX="http://zuul-server/api-order"; //直接使用zuul网管连接订单的服务提供者
@Resource
private RestTemplate restTemplate;
@GetMapping("/order/{id}")
public Order getOrder(@PathVariable("id")Integer id) {
return restTemplate.getForObject(URI_PRFIX+"/order/"+id, Order.class);
}
}
分布式配置
SpringCloud Config简介
- 微服务意味着要将单体应用中的业务拆分成一个个子服务,每个服务的粒度相对较小,因此系统中会出现大量的服务。由于每个服务都需要必要的配置信息才能运行,所以一套集中式的、动态的配置管理设施是必不可少的。SpringCloud提供了ConfigServer来解决这个问题
- SpringCloud Config为微服务架构中的微服务提供集中化的外部配置支持,配置服务器为各个不同微服务应用的所有环境提供了一个中心化的外部配置
- SpringCloud Config分为服务端和客户端两部分
- 服务端也称为分布式配置中心,它是一个独立的微服务应用,用来连接配置服务器并为客户端提供获取配置信息,加密/解密信息等访问接口
- 客户端则是通过指定的配置中心来管理应用资源,以及与业务相关的配置内容,并在启动的时候从配置中心获取和加载配置信息配置服务器默认采用git来存储配置信息,这样就有助于对环境配置进行版本管理,并且可以通过git客户端工具来方便的管理和访问配置内容
SpringCloud Config服务端配置
- pom文件
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>springcloud_config_service</groupId>
<artifactId>config_service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>config_service</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Finchley.SR1</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 配置中心是svn的话需要导入此包 -->
<dependency>
<groupId>org.tmatesoft.svnkit</groupId>
<artifactId>svnkit</artifactId>
<version>1.8.10</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
- 在本地仓库新建一个git仓库(github和码云都可以),然后在里面建立两个文件application-dev.properties和application-test.properties,然后推送到远程的git仓库,由于我考虑到后续可能会有多个项目,所以我新建了一个二级目录springcloud_demo,里面存放当前项目的配置,根据自己需求选择可以不建
- 本地项目的 application.properties配置
server.port=6010
#服务的git仓库地址
spring.cloud.config.server.git.uri=https://gitee.com/hwm0717/springcloud_config.git
#配置文件所在的目录
spring.cloud.config.server.git.search-paths=/springcloud_demo
#配置文件所在的分支
spring.cloud.config.label=master
#git仓库的用户名
spring.cloud.config.username=
#git仓库的密码
spring.cloud.config.password=
#服务的svn仓库地址
#使用svn作为配置仓库,必须显示声明profiles.active=subversion,不然还是用的git
#spring.profiles.active=subversion
#spring.cloud.config.server.svn.uri=svn://192.168.2.200/dev/code/web/springcloud_config
#spring.cloud.config.server.svn.search-paths=/springcloud_demo
#spring.cloud.config.server.svn.default-label=springcloud_demo
#spring.cloud.config.server.svn.username=
#spring.cloud.config.server.svn.password=
- 在启动类上开启配置中心的注解:@EnableConfigServer
@SpringBootApplication
@EnableConfigServer
@Controller
public class ConfigServiceApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServiceApplication.class, args);
}
@GetMapping("/")
@ResponseBody
public String hello(){
return "Hello springConfigService";
}
}
使用svn作为配置信息读取
- maven依赖
<!-- 配置中心是svn的话需要导入此包 -->
<dependency>
<groupId>org.tmatesoft.svnkit</groupId>
<artifactId>svnkit</artifactId>
<version>1.8.10</version>
</dependency>
- 把上面application.properties配置文件git的配置注释,把svn注释打开,并在svn仓库创建配置目录和配置文件