《Spring Cloud微服务实战》

基础知识

微服务架构

微服务是系统架构中的一种设计风格。

将一个原本独立的系统拆分成多个小型服务,这些小型服务都在各自独立的进程中运行,服务之间通过基于
HTTP的REST API进行通信协作。

每个小型服务维护自身的数据存储,业务开发,独立测试案例以及独立部署机制。
轻量级的通信协作基础,使得这些服务可以用不同语言编写。

微服务架构的九大特性

2.1服务组件化

组件,是一个可以独立更换和升级的单元,且不会影响到其他单元。

在微服务架构中,需要我们对服务进行组件化分解。服务,是一种进程外的组件。
每一个服务独立开发和部署,可以有效避免一个服务的修改引起整个系统的重新部署。

2.2按业务组织团队

由于每一个微服务都是针对特定业务的宽栈或全栈实现,既要负责数据的持久化存储,又要负责用户的接口定义等各种跨专业领域的职能。

因此,面对大型项目时,对于微服务团队的拆分更加建议按业务线的方式进行拆分。一方面可以有效减少服务内部修改所产生的内耗,另一方面,团队边界可以变得更为清晰。

2.3做“产品”的态度

在实施微服务架构的团队中,每个小团队都应该以做产品的方式,对其产品的整个生命周期负责。

开发团队通过了解服务在具体生产环境中的情况,可以增加对具体业务的理解。
对待每一个微服务,持续关注服务的运作情况,并不断分析以帮助用户来改善业务功能。

2.4智能端点与哑管道

在微服务中,由于服务不在一个进程中,组件间的通信模式发生了改变,若仅仅将原本在进程内的方法调用改为RPC方式的调用,会导致微服务之间产生烦琐的通信。

所以需要更粗粒度的通信协议,通常使用:
1、使用HTTP的RESTful API或轻量级的消息发送协议,实现信息传递与服务调用的触发。
2、通过在轻量级消息总线上传递消息,类似RabbitMQ等一些提供可靠异步交换的中间件。

2.5去中心化治理

在实施服务架构时,通常采用轻量级的契约定义接口,使得服务本身的具体技术平台不再那么敏感,这样整个微服务架构系统中的各个组件就能针对其不同的业务特点选择不同的技术平台。

不是每个问题都是钉子,不是每个解决方案都是锤子。

2.6去中心化管理数据

在实施微服务架构时,都希望每一个服务来管理其自有的数据库,这就是数据管理去中心化。

去中心化过程中,除了将原数据库中的存储内容拆分到新的同平台的其他数据库事例中(如将一个MySQL实例中的表,拆分到多个不同的MySQL实例中),也可以将一些具有特殊结构或业务特性的数据存储到一些其他技术的数据库实例中(MongoDB,Redis)。

数据管理的去中心化,可以让数据管理更加细致化,采用更合适的技术可以让数据存储好性能达到最优。

但,由于数据存储于不同数据库实例中后,数据的一致性成了有待解决的问题之一。

所以在微服务架构中能够,强调在各服务之间进行”无事务”的调用,而对于数据的一致性,只要求数据在最后的处理状态一致即可。
若在过程中发现错误,通过补偿机制进行处理,使得错误数据能够达到最终的一致性。

2.7基础设施自动化

在微服务架构中,务必从一开始就构建起”持续交付”平台来支撑整个实施过程,该平台需要两大内容:

1、自动化测试:
每次不输钱的强心剂,尽可能地获得对正在运行的软件的信心。
2、自动化部署:
解放烦琐枯燥的重复操作以及对多环节的配置管理。

2.8容错设计

在微服务架构中,由于服务都是运行在独立的进程中,所以存在部分服务出现故障,而其他服务正常运行的情况。
调用:
A -> B -> C -> D
如果D出现故障,调用链的上游所有服务都会等待下游的响应,造成连锁反应的蔓延,故障的蔓延

所以,在微服务架构中,快速检测出故障源并尽可能地自动恢复服务是必须被设计和考虑的。
通常,我们都希望在每个服务中实现监控和日志记录的组件,比如服务状态、断路状态,吞吐量,网络延迟等关键数据的仪表盘等。

2.9演进式设计

通过以上的几点特征,发现,要实施一个完美的微服务架构,需要考虑的设计与成本并不小。

所以,在很多情况下,架构师都会以演进的方式进行系统的架构。

在初期,以单体系统的方式来设计和实施:
1、系统体量初期并不会很大,构建和维护成本都不高
2、初期的核心业务在后期通常也不会发生巨大的改变

随着系统的发展或者业务的需要,架构师会将一些经常变动或是有一定时间效应的内容进行微服务处理,并逐渐将
原来在单体系统中多变的模块逐步拆分出来,而稳定不太变化的模块就形成一个核心微服务存在于整个架构中。

Spring Cloud 简介

Spring Cloud是一个基于Spring Boot实现的微服务架构开发工具。

它为微服务架构中涉及的配置管理、服务治理、断路器、智能路由、微代理、控制总线、全局锁、决策竞选、分布式回话和集群状态管理等操作提供了一种简单的开发方式。

Spring Cloud包含了多个子项目(针对分布式系统中设计的多个不同开源产品,还可能会新增),如下:

1、Spring Cloud Config:
配置管理工具,支持使用Git存储配置内容,可以使用它实现应用配置的外部化存储,并支持
客户端配置信息刷新、加密/解密配置内容等。

2、Spring Cloud Netflix:核心组件,对多个Netflix OSS开源套件进行整合
Eureka: 服务治理组件,包含服务注册中心、服务注册与发现机制的实现。
Hystrix: 容错管理组件,实现断路器模式,帮助服务依赖中出现的延迟和为故障提供强大的容错能力。
Ribbon:客户端负载均衡的服务调用组件。
Feign:基于Ribbon和Hystrix的声明式服务调用组件。
Zuul:网关组件,提供智能路由、访问过滤等功能。
Archaiux:外部化配置组件

3、Spring Cloud Bus
事件、消息总线,用于传播集群中的状态变化或事件,以触发后续的处理,比如用来冬天刷新配置等。

4、Spring Cloud Cluster
针对ZooKeeper、Redis、Hazelcast、Consul的选举算法和通用状态模式的实现。

5、Spring Cloud CloudFoundry
与Pivotal Cloud foundry 的整合支持。

6、Spring Cloud Consul
服务发下和配置管理工具

7、Spring Cloud Stream
通过Redis、Rabbit或者Kafka实现的消费微服务,可以通过简单的声明式模型来发送和接收消息。

8、Spring Cloud AWS
用于简化整合Amazon Web Service 的组件。

9、Spring Cloud Security
安全工具包,提供在Zuul代理中对OAuth2客户端请求的中继器。

10、Spring Cloud Sleuth
Spring Cloud应用的分布式跟踪实现,可以完美整合Zipkin

11、Spring Cloud ZooKeeper
基于ZooKeeper的服务发现与配置管理组件

12、Spring Cloud Starters
Spring Cloud 的基础组件,它基于Spring Boot风格项目的基础依赖模块

13、Spring Clod CLI
用于在Groovy中快速创建Spring Cloud应用的 Spring Boot CLI插件

Spring Boot

1.Spring Boot简介

1.1简化配置

通过设计大量的自动化配置等方式来简化Spring原有样板化的配置,使得开发者可以快速构建应用。

1.2简化依赖管理

Spring Boot 通过一系列Starter POMs的定义,让我们整合各项功能的时候,不需要再Maven的pom.xml中
维护那些错中复杂的依赖关系,而是通过类似模块化的Starter模块定义来引用,使得依赖管理工作变得更为简单。

1.3简化部署

在如今容器化大行其道的时代,Spring Boot除了可以很好融入Docker之外,其自身就支持嵌入式大的Tomcat、Jetty等容器。
所以,通过 Spring Boot 构建的应用不再需要安装Tomcat,只需要将Spring Boot应用打成jar包,并通过
java -jar 命令直接运行就能启动一个标准化的Web应用,这使得Spring Boot应用变得非常轻便。

1.4优化开发环节

整个Spring Boot的生态系统都使用到了Groovy,可以通过使用Gradle和Groovy来开发Spring Boot应用。

2.快速入门

2.1构建Maven项目

访问 http://start.spring.io/ ,该页面提供了以Maven 或 Gradle构建Spring Boot 项目的功能。

2.2目录介绍

src/main/java: 代码目录
src/main/resources:配置目录。
该目录用来存放应用的一些配置信息,比如应用名,服务端口,数据库链接等。
该目录还有 static目录和templates目录。
static : 存放静态资源,如图片、CSS、JavaScript等。
templates:存放Web页面的模板文件
src/test/:单元测试目录。
通过JUnit 4实现测试类,可以直接用运行Spring Boot应用的测试。

2.3 Maven配置
<?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>com.study</groupId>
    <artifactId>springboot</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>springboot</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.8.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>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

2.4 构建和启动

项目构建的build部分,引入了Spring Boot的Maven插件,该插件可以帮助我们方便地启停应用,
这样在开发时就不用每次去找主类或是打包成jar来运行微服务。
只需要通过 mvn spring-boot:run 命令就可以快速启动Spring Boot应用。

在服务器上部署运行时,通常先使用 mvn install 将应用打包成jar包,在通过 java -jar xxx.jar来启动应用

2.4编写单元测试

功能实现之后,要随手写配套单元测试的习惯,这在微服务架构中尤为重要。
在src/test/下写测试类:

    @RunWith(SpringJUnit4ClassRunner.class)
    @SpringBootTest(classes=HelloController.class)
    @WebAppConfiguration
    public class HelloApplicationTests {

    private MockMvc mvc;

    @Before
    public void setUp()  throws Exception {
        mvc = MockMvcBuilders.standaloneSetup(new HelloController()).build();
    }

    @Test
    public void hello() throws Exception {
        mvc.perform(MockMvcRequestBuilders.get("/hello").accept(MediaType.APPLICATION_JSON))
        .andExpect(status().isOk())
        .andExpect(content().string(equalTo("Hello World")));
    }
}

2.5配置文件

Spring Boot 的默认配置文件位置为src/main/resources/application.properties
关于Spring Boot应用的配置内容都可以集中在该文件中,根据我们引入的不同Starter模块,
可以在这里定义容器端口号、数据库连接信息、日志级别等各种配置信息。

2.5.5多环境配置

对于多环境的配置,各种项目构建工具或是框架的基本思路是一致的,通过配置多份不同环境的配置文件,
再通过打包命令指定需要打包的内容之后进行区分打包。

在Spring Boot中,多环境配置的文件名需要满足 application-{profile}.properties的格式,其中
{profile}对应环境标识,如:

application-dev.properties 开发环境
application-test.properties 测试环境
application-prod.properties 生产环境
至于具体哪个配置文件会被加载,需要在 application.properties 文件中通过
spring.profiles.active 属性来设置,其值对应配置文件中的{profile}值。

如spring.profiles.active=test就会加载 application-test.properties配置文件内容。

服务治理

服务注册
每个服务去构建中心中注册自己提供的服务,将主机,端口号、版本号通信协议等一些附加信息告知注册中心,注册中心按服务名分类组织服务清单。

一个例子:

服务名位置
服务A192.168.0.100:8000 192.168.0.101:8000
服务B1192.168.0.100:9000 192.168.0.101:9000

服务中心还要以心跳机制不断检测清单中服务是否可用并剔除不可用服务。

服务发现
服务间调用不再指定具体的实例地址,而是通过向服务名发起请求调用实现。

例如服务C想要调用服务A,就向注册中心询问,得到服务A下面的两个地址,然后通过某种轮询策略(客户端负载均衡),取出一个位置来进行服务调用。

Eureka详解

服务提供者

  • 服务注册:“服务提供者”在启动的时候会通过发送REST请求的方式将自己注册到Eureka
    Server上,同时带上了自身服务的一些元数据信息。Eureka Server接收到这个REST请求之后,将元数据信息存储在一个双层结构Map中,其中第一层的key是服务名,第二层的key是具体服务的实例名。Eureka server会维护一份只读的服务清单来返回给服务消费者,同时改缓存清单会每隔30秒更新一次。
  • 服务同步:当服务提供者发送注册请求到一个服务注册中心时,该注册中心会将请求转发给集群中相连的其他注册中心,从而实现注册中心之间的服务同步。
  • 服务续约:在注册完服务之后,服务提供者会维护一个心跳来告诉Eureka Server:“我还活着”,防止Eureka Server的“剔除任务”将该服务实例从服务列表中排除出去,我们称该操作为服务预约(Renew)。可以通过一下参数进行相关配置。

服务消费者

  • 获取服务:服务消费者会发送一个REST请求给服务注册中心,来获取注册中心的服务清单。

  • 服务调用:服务消费者获取到服务清单后,通过服务名获得具体提供服务的实例名和该实例的元数据信息。再根据自己的需要决定具体调用那个实例。对于访问实例的选择,Eureka中有Region和Zone的概念,一个Region包含多个Zone,每个服务客户端需要注册到一个Zone中,所以客户端对应一个Region和一个Zone。在进行服务调用的时候,优先访问同处一个Zone中的服务提供方,若访问不到,就访问其他的Zone。

  • 服务下线:当服务实例进行正常的关闭操作时,它会触发一个服务下线的REST请求给Eureka Server,告诉服务注册中心:“我要下线了”。服务端接收到请求以后,将该服务状态设置为下线(DOWN),并把改下线事件传播出去。

服务注册中心

  • 失效剔除:对于非正常关闭的服务,Eureka Server会启动一个定时任务,每60S(默认)将清单没有续约的服务(90秒没有收到服务提供者的心跳)移除。
  • 自我保护:Eureka Server每30S接收其他注册服务的心跳,同时Eureka
    Server会统计心跳失败的比例(15分钟之内是否低于85%),如果低于85%,Eureka
    Server会将当前的实例注册信息保护起来,让这些实例不会过期。可以通过一下配置确保注册中心可以将不可用的实例正确移除。低于85%时,访问Eureka
    Server会出现:EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES AREUP WHEN THEY’RE NOT. RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUST TO BE SAFE.报错

配置详解

客户端负载均衡-Spring Cloud Ribbon

客户端负载均衡

负载均衡是对系统的高可用、网络压力的缓解和处理能力扩容的重要手段之一。我们常说的负载均衡是指服务端负载均衡,其中分为硬件负载均衡和软件负载均衡。硬件负载均衡主要通过在服务器节点中回见安装专门用于负载均衡的设备,比如F5等;而软件负载均衡则是通过在服务器上安装一些具有负载均衡功能或模块的软件来完成请求分发工作,比如Nginx等,不论是采用硬件负载均衡还是软件负载均衡,只要是服务端负载均衡都能以类似如下图的架构方式构建起来。

客户端负载均衡和服务端负载均衡最大的不同点在于上面所提及的服务清单所存储的位置。在客户端负载均衡中,所有客户端节点都维护这自己要访问的服务端清单,而这些服务端的清单来自于服务注册中心。

负载均衡

负载均衡策略

全局策略设置
策略类命名描述
RandomRule随机策略随机选择server
RoundRobinRule轮询策略按顺序选择server
RetryRule重试策略在一个配置时间段内当选择的server不成功,则继续轮训,一直尝试选择一个可用的server
BestAvailableRule最低并发策略先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务。
AvailabilityFilteringRule可用过滤策略先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,以及并发连接数超过阈值的服务,剩下的服务,使用轮询策略
ResponseTimeWeightedRule响应时间加权策略根据server的响应时间分配权重。响应时间越长,权重越低,被选择到的概略就越低,权重越高,被选择到的概率就越高
ZoneAvoidanceRule区域权衡策略综合判断server所在的区域的性能和server的可用性轮询选择server,并且判定一个AWS Zobe的运行性能是否可用,提出不可用的Zone中所有server
局部策略设置

如果在项目中你想对某些服务使用指定的负载均衡策略,那么可以如下配置:

@Configuration
@RibbonClients({
        @RibbonClient(name = "user-service",configuration = UserServiceConfig.class),
    	@RibbonClient(name = "order-service",configuration = OrderServiceConfig.class)
})
public class RibbonConfig {
}
超时重试

HTTP请求不免的会有网络不好的情况出现超时,Ribbon提供了超时重试机制,提供如下参数可以设置:

ribbon-client:
  ribbon:
    ConnectTimeout: 3000
    ReadTimeout: 60000
    MaxAutoRetries: 1 #对第一次请求的服务的重试次数
    MaxAutoRetriesNextServer: 1 #要重试的下一个服务的最大数量(不包括第一个服务)
    OkToRetryOnAllOperations: true
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

饥饿加载
Ribbon在进行客户端负载均衡的时候并不是在启动的时候就加载上下文的,实在实际请求的时候才加载,有点像servlet的第一次请求的时候才去生成实例,这回导致第一次请求会比较的缓慢,甚至可能会出现超时的情况。所以我们可以指定具体的客户端名称来开启饥饿加载,即在启动的时候便加载素养的配置项的应用上下文。

ribbon:
  eager-load:
    enabled: true
    clients: ribbon-client-1, ribbon-client-2, ribbon-client-3

客户端负载均衡-Spring Cloud Ribbon

快速入门

引入Spring Cloud Hystrix依赖

<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-hystrix</artifactId>
	<version>1.4.6.RELEASE</version>
</dependency>

在客户端工程的主类上添加@EnableCircuitBreaker注解
改造服务消费方式,在业务方法上添加@HystrixCommand(fallbackMethod = “helloFallback”,commandKey = “helloKey”)

@Service
public class HelloService {
    private final java.util.logging.Logger logger = java.util.logging.Logger.getLogger(String.valueOf(HelloService.class));

    @Autowired
    RestTemplate restTemplate;

    @HystrixCommand(fallbackMethod = "helloFallback",commandKey = "helloKey")
    public String helloService(){
        long start = System.currentTimeMillis();
        String result = restTemplate.getForEntity("http://HELLO-SERVICE/hello",String.class).getBody();
        long end = System.currentTimeMillis();
        logger.info("SpendTime = " + (end - start));
        return result;
    }

    public String helloFallback(){
        return "error";
    }
}

服务容错保护:Spring Cloud Hystrix

微服务架构中,系统拆分成了多个服务单元,各单元的应用间通过服务注册与订阅的方式互相依赖.由于每个单元都在不同的进程中运行,依赖通过进程调用的方式执行,这样就有可能因为网络原因或是依赖服务自身问题出现调用故障或延迟,而这些问题会直接导致调用方的对外服务也出现延迟,若此时调用方的请求不断增加,最后就会因等待出现故障的依赖方响应形成任务积压,最终导致自身服务的瘫痪.
一句话就是,服务提供方出现问题,服务调用方就有可能瘫痪

快速入门

引入Spring Cloud Hystrix依赖

<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-hystrix</artifactId>
	<version>1.4.6.RELEASE</version>
</dependency>

在客户端工程的主类上添加@EnableCircuitBreaker注解
改造服务消费方式,在业务方法上添加@HystrixCommand(fallbackMethod = “helloFallback”,commandKey = “helloKey”)

@Service
public class HelloService {
    private final java.util.logging.Logger logger = java.util.logging.Logger.getLogger(String.valueOf(HelloService.class));

    @Autowired
    RestTemplate restTemplate;

    @HystrixCommand(fallbackMethod = "helloFallback",commandKey = "helloKey")
    public String helloService(){
        long start = System.currentTimeMillis();
        String result = restTemplate.getForEntity("http://HELLO-SERVICE/hello",String.class).getBody();
        long end = System.currentTimeMillis();
        logger.info("SpendTime = " + (end - start));
        return result;
    }

    public String helloFallback(){
        return "error";
    }
}

原理分析

在这里插入图片描述
1.创建HystrixCommand或HystrixObservableCommand对象
命令模式:将来自客户端的请求封装成一个对象,从而使用不同的请求对客户端进行参数化
2.命令执行:

  • HystrixCommand实现了两个方法:
    execute():同步执行,从依赖服务返回一个单一的结果对象,或是在发生错误的时候抛出异常
    queue():异步执行,直接返回一个Future对象,其中包含了服务执行结果时要返回的单一结果对象
  • HystrixObservableCommand实现了两个方法:
    observe():返回Observable对象,它代表了操作的多个结果,它是一个Hot Observable
    toObserve():返回Observable对象,代表了操作的多个结果,返回的是一个Cold Observable
    Hot Observable:不论"事件源"是否有"订阅者",都会在创建后对事件进行发布,Hot Observable的每一个"订阅者"有可能是从"事件源"的中途开始的,只能看到整个操作的局部过程
    Cold Observable:在没有"订阅者"的时候不会发布事件,直到有"订阅者"才会发布事件,可以保证从一开始就看到真个操作的全部过程

3.结果是否被缓存
4.断路器是否打开
5.线程池/请求队列/信号量是否占满
6.HystrixObservableCommand.construct()或HystrixCommand.run()
7.计算断路器的健康度
Hystrix会将"成功",“失败”,“拒绝”,“超时"等信息报告给断路器,而断路器会维护一组计数器来统计这些数据.断路器会根据这些数据进行判断是否熔断或者再次熔断
8.fallback处理
fallback处理也就是"服务降级”,能够引起服务降级处理的情况有:
1)第4步,当前命令处于"熔断/断路"状态,断路器是打开的时候
2)第5步,当前命令的线程池.请求队列或者信号量被占满的时候
3)第6步抛出异常的时候
9.返回成功的响应

命令模式

使用详解

创建请求命令
1.通过继承的方式,继承HystrixCommand
同步执行:
User u = new UserCommand(restTemplate,1L).execute();

异步执行
Future futureUser = new UserCommand(restTemplate,1L).queue();

异步执行可以通过futureUser的get方法获取结果

2.通过注解@HystrixCommand,优雅实现Hystrix命令
通过observableExecutionMode参数来控制是observe()还是toObservable()执行方式
@HystrixCommand(observableExecutionMode = ObservableExecution-Mode.EAGER):
EAGER表示使用observe()执行方式
@HystrixCommand(observableExecutionMode = ObservableExecution-Mode.LAZY):表示使用toObservable()执行方式

前者返回的是Hot Observable ,在observe()后立即执行;后者返回的是Cold Observable,只有所有订阅者订阅后才会执行。

定义服务降级
1.重载HystrixCommand中的getFallback()方法来实现服务降级
2.通过注解实现服务降级,使用@HystrixCommand中的fallbackMethod参数来指定具体的服务降级实现方法
不用实现服务降级的场景:
1)执行写操作的命令
2)执行批处理或离线计算的命令

异常处理
1.异常传播
通过设置@HystrixCommand注解的ignoreException参数

@HystrixCommand(ignoreExceptions = {BadRequestException.class})
public User getUserById(Long id){
	return restTemplate.getForObject("http://USER-SERVICE/users/{1}",User.class,id);
}

当getUserById方法抛出类型为BadRequestException的异常时,Hystrix会将异常包装在HystrixBadRequestException中抛出,不会触发后续的fallback逻辑。
2.异常获取
1)传统方式继承,getFallback()方法通过Throwable getExecutionException()方法获取具体的异常
2)注解配置方式实现异常获取,在fallback实现方法的参数增加Throwable e

@HystrixCommand(fallbackMethod="fallback1")
User getUserById(String id){
	throw new RuntimeException("getUserById command failed");
}
User fallback1(String id,Throwable e){
	assert "getUserById command failed".equals(e.getMessage());
}

命令名称\分组以及线程池划分
在继承HystrixCommand的类的构造器中使用Setter静态类来设置。
多个不同的命令可能从业务逻辑上来看属于同一个组,但往往从实现本身上需要跟其他命令进行隔离,所以尽量通过HystrixThreadPoolKey的方式来指定线程池的划分

@HystrixCommand(commandKey="getUserById",groupKey="UserGroup",threadPoolKey="getUserByIdThread")
public User getUserById(Long id){
	return restTemplate.getForObject("http://USER-SERVICE/users/{1}",User.class,id);
}

请求缓存
分布式环境下,依赖服务会引起一部分性能损失
高并发环境下,http相比于其他高性能的通信协议在速度上处于劣势,容易成为系统瓶颈
1.开启请求缓存功能

public class UserCommand extends HystrixCommand<User>{
    private RestTemplate restTemplate;
    private Long id;
    public UserCommand(RestTemplate restTemplate,Long id){       super(HystrixCommand.Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("UserGroup")));
    }
   @Override
    protected User run() throws Exception {
        return restTemplate.getForObject("http://USER-SERVICE/users/{1}",User.class,id);
    }
    @Override
    protected String getCacheKey(){
        return String.valueOf(id);
    }
}

使用注解实现请求缓存
注解方式的几个用法:
1)设置请求缓存:
加上@CacheResult之后,Hystrix会将该结果置入请求缓存中,而key值使用所有的参数,这里就是Long id

@CacheResult
@HystrixCommand
public User getUserById(Long id){
   return restTemplate.getForObject("http://USER-SERVICE/users/{1}",User.class,id);
}

2)定义缓存Key:
第一种方式: 配置方式如同@HystrixCommand服务降级fallbackMethod的使用

@CacheResult(cacheKeyMethod="getUserByIdCacheKey")
@HystrixCommand
public User getUserById(Long id){
   return restTemplate.getForObject("http://USER-SERVICE/users/{1}",User.class,id);
}
private Long getUserByIdCacheKey(Long id){
   return  id;
}

第二种方式:通过@CacheKey注解实现,但是就如上面表格所说,如果已经使用了cacheKeyMethod的生成函数,则@CacheKey注解不会生效

@CacheResult
@HystrixCommand
public User getUserById(@CacheKey("id") Long id){
	return restTemplate.getForObject("http://USER-SERVICE/users/{1}",User.class,id);
}

请求合并
在高并发的情况下,通信次数增加,总的通信时间消耗也会变得不理想,同时依赖服务的线程池资源有限,将出现排队等待和响应延迟的情况,通过请求的合并,可以达到减少通信消耗和线程数占用的效果.
第一步:为请求合并的实现准备一个批量请求命令的实现

1.使用注解实现请求合并器

@Service
public class UserService {
    @Autowired
    private RestTemplate restTemplate;    
    @HystrixCollapser(batchMethod = "findAll", collapserProperties = {@HystrixProperty(name="timerDelayInMilliseconds",value="100")})
    public User find(Long id){
        return null;
    }    
    @HystrixCommand
    public List<User> findAll(List<Long> ids){
        return restTemplate.getForObject("http://USER-SERVICE/users?ids={1}",List.class, StringUtils.join(ids,","));
    }    
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值