SpringCloud 基础入门

一.SpringCloud介绍

1.1微服务架构

微服务架构的提示者: 马丁福勒

Microservices

In short, the microservice architectural style [1] is an approach to developing a single application as a suite of small services, each running in its own process and communicating with lightweight mechanisms, often an HTTP resource API. These services are built around business capabilities and independently deployable by fully automated deployment machinery. There is a bare minimum of centralized management of these services, which may be written in different programming languages and use different data storage technologies.

1.微服务架构只是一个样式,一个风格

2.将一个完成的项目, 拆分成多个模块去分别开发。

3.每一个模块都是单独的运行在自己的容器中。

4.每一个模块都是需要相互通讯的。 http, RPC, MQ

5.每一个模块之间是没有依赖关系的,单独的部署。

6.可以使用多种语言去开发不同的模块。

7.使用MySql数据库, Redis, ES 去存储数据, 也可以使用多个MySql数据库。

总结: 将复杂臃肿的单体应用进行细粒度的划分,每个拆分出来的服务各自打包部署。

1.2SpringCloud介绍

SpringCloud是微服务架构落地的一套技术栈。

SpringCloud中的大多数技术都是基于Netflix公司的技术进行二次研发。

  1. springcloud的中文社区网站:http://springcloud.cn/

    2.springcloud的中文网: Spring Cloud中文网-官方文档中文版

    八个技术点:

  2. Eureka - 服务的注册与发现

  3. Robbin - 服务之间的负载均衡

  4. Feign - 服务之间的通讯

  5. Hystrix - 服务的线程隔离以及断路器

  6. Zuul - 服务网关

  7. Stream - 实现MQ的使用

  8. Config - 动态配置

  9. Sleath - 服务追踪

二.服务的注册与发现-Eureka

2.1引言

Eureka就是帮助我们维护所有服务的信息,以便服务之间的相互调用

2.2 Eureka的快速入门

2.2.1创建EurekaServer

1、创建一个父工程,并且在父工程中指定SpringCloud的版本,并且将pageage指定为pom

<packaging>pom</packaging>
<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>

#####

创建SpringBoot工程, 并且导入依赖,在启动类中添加注解,编写yml文件

2、导入依赖

<dependencies>
    <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>
</dependencies>

3、启动类添加注解

@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {
    public static void main(String[] args)
    {
        SpringApplication.run(EurekaApplication.class, args);
    }
}

4、编写 yml配置文件

server:
  port: 8761   #端口号
​
eureka:
  instance:
    hostname: localhost    #localhost
  client:
    #当前的eureka服务是单机版
    registerWithEureka: false
    fetchRegistry: false
    serviceUrl:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

2.2.2创建EurekaClient

1.创建Maven工程,修改为SpringBoot

2.导入依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

3.在启动类上添加注解

@SpringBootApplication
@EnableEurekaClient
public class CustomerApplication {
    public static void main(String[] args){
        SpringApplication.run(CustomerApplication.class, args);
    }
}

4.编写配置文件

#指定Eureka服务地址
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka
​
spring:
  application:
    name: CUSTOMER

2.2.3 测试Eureka

1.创建一个Search搜索模块,并且注册到Eureka

2.使用到EurekaClient的对象去获取服务信息

@Autowired
private EurekaClient eurekaClient;

3.正常调用RestTemplate即可


    @GetMapping("/customer")
    public String customer(){
        //"SEARCH";
        //1.通过eurekaClienta获取到SEARCH服务的信息
        InstanceInfo info = eurekaClient.getNextServerFromEureka("SEARCH", false);
        //2.获取到访问的地址
        String url = info.getHomePageUrl();
        System.out.println("url: " + url);
        //3.通过restTemplate访问
        String result = restTemplate.getForObject(url+"/search", String.class);
        //4.返回
        return result;
    }

2.3Eureka的安全性

实现Eureka认证

  1. 导入依赖

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

  2. 编写配置类

    @EnableWebSecurity
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            //忽略掉/eureka/**
            http.csrf().ignoringAntMatchers("/eureka/**");
            super.configure(http);
        }
    }

  3. 编写配置文件

    #指定用户名和密码
    spring:
      security:
        user:
          name: root
          password: root4

    4.其他服务注册到Eureka需要添加用户名和密码

#指定Eureka服务地址
    eureka:
         client:
             service-url:
                defaultZone: http://用户名:密码@localhost:8761/eureka

2.4 Eureka的高可用

如果程序正在运行,突然Eureka宕机。

1、如果调用方访问过一次被调用方,Eureka的宕机不会影响到功能。

2、如果调用方没有访问过被调用方,Eureka的宕机就会造成当前功能不可用。

搭建Eureka高可用

1、准备多台Eureka

采用了复制的方式,删除iml 和target 文件,并且修改pom.xml中的项目名称,再给父工程添加一个module

2、让服务注册到多台Eureka

eureka:
  client:
    service-url:
      defaultZone: http://root:root@localhost:8761/eureka,http://root:root@localhost:8762/eureka

3、让多台Eureka之间相互通讯

eureka:
  client:
    registerWithEureka: true   #注册到Eureka上
    fetchRegistry: true        #从Eureka拉取信息
    serviceUrl:
      defaultZone: http://root:root@localhost:8762/eureka/

2.5 Eureka的细节

1.EurekaClient启动时,将自己的信息注册到EurekaServer上,EurekaServer就会存储上EurekaClient的注册信息。

2.当EurekaClient调用服务时,去EurekaServer中去获取注册信息

3.当EurekaClient会通过心跳的方式去和EurekaServer进行连接,(默认30s EurekaClient会发送一次心跳请求,如果超过了90s还没有发送心跳信息的话,EurekaServer就认为宕机,将当前EurekaClient从注册表中移除)

eureka:
  instance:
    lease-renewal-interval-in-seconds: 30    #心跳的间隔
    lease-expiration-duration-in-seconds: 90  #多久没有发送,就认为你宕机了

4.EurekaClient会每隔30去EurekaServer中去更新本地的注册表

eureka:
  client:
    registry-fetch-interval-seconds: 30     #更新注册表的时间

5.Eureka的自我保护机制,统计15分钟内,如果一个服务的心跳发送比例低于85%, EurekaServer就会开启自我保护机制

1.不会从EurekaServer中去移除长时间没有收到心跳的服务

2.EurekaServer还是可以正常提供服务的。

3.网络比较稳定时,EurekaServer才会开始将自已的信息被其他节点同步过去

eureka:
  server:
     enable-self-preservation: true     #开启自我保护机制
  1. CAP定义,C - 一致性,A-可用性,P-分区容错性, 这三个特性在分布式环境下,只能满足2个,而且分区容错性在分布式环境下,是必须要满足的,只能在AC之间进行权衡。

    如果选择CP,保证了一致性,可能会造成系统在一定时间内是不可用的,如果同步数据的时间比较长,造成的损失大。

    Eureka就是一个AP的效果,高可用的集群,Eureka集群是无中心,Eureka即便宕机几个也不会影响系统的使用,不需要重新的支推举一个master, 也会导致一定时间内数据是不一致。

三.服务间的负载均衡-Robbin

3.1引言

Robbin是帮助我们实现服务和服务负载均衡,Robbin属于客户端负载均衡

客户端负载均衡: customer客户模块, 将2个Search模块信息全部拉取到本地的缓存,在customer中自己做一个负载均衡的策略,选中某一个服务。

服务端负载均衡:在注册中心中,直接根据你指定的负载均衡策略,帮你选中一个指定的服务信息,并返回。

三种负载均衡 Nginx、Dubbo、Ribbon 区别_washingtin的博客-CSDN博客_dubbo和ribbon

3.1.1三种负载均衡 Nginx、Dubbo、Ribbon 区别

描述

1.Dubbo负载均衡:支持4种(随机,轮循,最少活跃,hash),引入了JVM预热时间加权、权重自定义配置的规则,同时支持控制台动态配置权重值参数,所以是最灵活的。

2.Nginx负载均衡:支持4种,自带 轮询(支持权重)、IP_Hash(避免Session共享的问题)、最少连接数策略,可以扩展fair(响应时间)策略,更专注于功能。

3.Ribbon负载均衡:支持6种,不支持权重:轮询、随机、最少连接数、最短响应时间(随机+响应时间加权)、过滤异常节点+轮询,负载策略最全的

Dubbo 负载均衡(2.6.x)

Dubbo提供4种负载均衡算法,引入了JVM预热时间加权、权重自定义配置的规则,同时支持控制台动态配置权重值参数,所以最灵活。

  • Random LoadBalance:按照权重随机分配Provider,比如随机且权重Node1:Node2= 2:1,那么运行30次,大约有20次在Node1上,10次在Node2上。

  • RoundRobin LoadBalance:按照权重轮询分配。比如权重Node1:Node2= 20:10,那么运行30次:前20次里面轮询Node1和Node2大家各10次,第20次到30次,全部选择Node1。因为Dubbo默认是不会做公约数的处理,只有完成一个完整的20+10次运算,才能保证负载均衡的权重比例准确,如果Consumer只调用了20次,那么这里配置的权重的结果就是1:1了,该算法很不平滑。在2.6.5版本中修复了,跟Nginx的实现方法一样。

  • LeastActive LoadBalance:节点处理越快分配更多,避免慢节点堆积,每次筛选Provider的时候,都只取Active值最小的节点,如果最小Active值的节点有多个,则按照权重随机选取。Provider每获取到一个任务Active值++,每结束一个任务Active值--。

  • ConsistentHash LoadBalance:唯一忽略权重配置和JVM预热的算法。先把所有Provider都分配160个虚拟节点,通过Hash算法,全部分散到Hash圆上。每次Consumer调用时,会根据参数值做Hash换算,最后映射到Hash圆上,找到邻近的虚拟节点,最终获取到提供服务的Provider。但是Dubbo在实现的时候违背了Hash一致性的原则,每次Porvider发生改变的时候(新增或者剔除),都会重新创建一个Hash圆,而不是在之前的Hash圆上新增或者剔除不合格的Porvider。

Nginx 负载均衡算法

Nginx目前有4种负载均衡配置:

  • round_robin,加权轮询,是默认的HTTP负载均衡算法,适用于知道机器的性能,且默认所有的请求对于服务器而言,处理的时间相差不大。比如我Server1 比Server2的配置要高一倍,我设置为2:1的权重,可以实现比较科学的负载。算法实现上,简单的轮询很简单,给每个Server依次编号,然后只要记录一个调用index,既可以实现轮询。

  • ip_hash,IP哈希,可保持会话

  • least_conn; 避免了慢堆积,会取连接数最小的server提供服务,可以避免有些请求耗时长,有些耗时端的情况。根据实际的连接数选择服务器。

  • fair,需要插件扩展该功能,根据后端服务器的响应时间来分配请求,响应时间短的优先分配,避免慢堆积。

    权重配置:而且采用的是平滑的负载均衡算法,比如node1:node2:node3=1:2:5 --> node3,node3,node2,node3,node1,node3,node2,node3。

Ribbon 负载均衡概述

  • RoundRobinRule:轮询。默认超过10次获取到的server都不可用,会返回一个空的server

  • RandomRule:随机,如果随机到的server为null或者不可用的话,会while不停的循环选取

  • RetryRule:一定时限内循环重试。默认继承RoundRobinRule,也支持自定义注入,RetryRule会在每次选取之后,对选举的server进行判断,是否为null,是否alive,并且在500ms内会不停的选取判断。而RoundRobinRule失效的策略是超过10次,RandomRule是没有失效时间的概念,只要serverList没都挂。

@Bean
public IRule ribbonRule() {
     return new RetryRule(new BestAvailableRule());//这里配置策略,和配置文件对应
}
  • BestAvailableRule:最小连接数。遍历serverList,选取出可用的且连接数最小的一个server。该算法里面有一个LoadBalancerStats的成员变量,会存储所有server的运行状况和连接数。如果选取到的server为null,那么会调用RoundRobinRule重新选取。

  • WeightedResponseTimeRule:最小响应时间。这个策略整合了随机算法和响应时间加权算法。会开启定时任务,每30秒计算一次所有Provider的响应时间,以响应时间作为权重,响应时间越短的服务器被选中的概率越大。比如Node1:node2:node3的平均响应时间为100ms:200ms:300ms,那么nodes的的权重值是300:500:600,每次以600为基础*随机值,那么落在 0--300的概率为50%,300--500的概率33%,100--600的概率为17%,也就是平均响应时间越短的节点,被选中的概率越大。

double totalResponseTime = 0;
//遍历获取所有节点的总的平均响应时间
for (Server server : nlb.getAllServers()) {
    ServerStats ss = stats.getSingleServerStat(server);
    totalResponseTime += ss.getResponseTimeAvg();
}
​
Double weightSoFar = 0.0;
​
//然后从第一个节点开始设置自增的weightSoFar,每个结点的权重值weight为总响应时间-自己的相应时间,也就是说,node的响应时间越短,weight的值就越大,就越有可能被随机数命中
List<Double> finalWeights = new ArrayList<Double>();
​
for (Server server : nlb.getAllServers()) {
   ServerStats ss = stats.getSingleServerStat(server);
    double weight = totalResponseTime - ss.getResponseTimeAvg();
    weightSoFar += weight;
    finalWeights.add(weightSoFar);   
}
​
setWeights(finalWeights);
  • AvailabilityFilteringRule 过滤+轮询策略,先过滤出故障的或并发请求大于阈值一部分服务实例,然后再轮询。

private boolean shouldSkipServer(ServerStats stats) {        
   if ((CIRCUIT_BREAKER_FILTERING.get() && stats.isCircuitBreakerTripped()) || stats.getActiveRequestsCount() >= activeConnectionsLimit.get()) {
       return true;
   }
   return false;
}
  • ZoneAvoidanceRule 扩展了轮询策略,继承了2个过滤器:ZoneAvoidancePredicate和AvailabilityPredicate,除了过滤超时和链接数过多的server,还会过滤掉不符合要求的zone里面的所有节点。

 

3.1.2 Ribbon与Nginx区别

服务器端负载均衡Nginx

Nginx是客户端所有请求统一交给Nginx,由Nginx进行实现负载均衡请求转发,属于服务器端负载均衡。

既请求由nginx服务器端进行转发。

Nginx介绍请点击这里

客户端负载均衡Ribbon

Ribbon是本地负载均衡(客户端),在调用接口的时候,会在Eureka注册中心上获取注册信息服务列表,缓存到本地,然后在本地实现负载均衡策略。

Eureka和Zuul中默认开启了ribbon负载均衡

应用场景的区别:

Nginx适合于服务器端实现负载均衡 比如Tomcat ,Jetty

Ribbon适合与在微服务中RPC远程调用实现本地服务负载均衡,比如Dubbo、SpringCloud中都是采用本地负载均衡。

3.2 Robbin的快速入门

新版robbin的使用: 深入学习springCloud——Ribbon/OpenFeign负载均衡服务接口调用 - SegmentFault 思否

1.启动两个seach模块

 

2.在customer导入robbin

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>

3.配置整合RestTemplate和Robbin

@Bean
@LoadBalanced
public RestTemplate restTemplate(){
    return new RestTemplate();
}

4.在customer中去访问search

@RestController
public class CustomerController {
​
    @Autowired
    private RestTemplate restTemplate;
​
    @GetMapping("/customer")
    public String customer(){
        String result = restTemplate.getForObject("http://SEARCH/search", String.class);
        return result;
    }
}

3.3Robbin配置负载均衡

1.负载均衡策略

  1. RandomRule: 随机策略

  2. RoundRobbinRule: 轮询策略

  3. WeightedResponseTimeRule: 默认会采用轮询的策略,后续会根据服务的响应时间,自动给你分配权重

  4. BestAvailableRuel: 根据被调用方并发数量最小的去分配

2.采用注解的形式

@Bean
public IRule robbinRule() {
    return new RandomRule();
}

3.配置文件去指定负载均衡

#指定具体服务的负载均衡策略
SEARCH:   #编写服务名称
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.WeightedResponseTimeRule #具体负载均衡使用的类

四.服务间的调用-Feign

4.1 引言

Feign可以帮助我们实现面向接口编程,就直接调用其他的服务,简化开发。

4.2 Feign的快速入门

1.导入依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

2.添加一个注解

@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class CustomerApplication {
    public static void main(String[] args){
        SpringApplication.run(CustomerApplication.class, args);
    }
​
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}

3.创建一个接口, 并且和Search模块做映射

@FeignClient("SEARCH")    //指定服务名称
public interface SearchClient {
    // value -> 目标服务的请求路径, method -> 映射请求方式
    @RequestMapping(value = "/search", method = RequestMethod.GET)
    String search();
}

4.测试使用

@Autowired
private SearchClient searchClient;
​
@GetMapping("/customer")
public String customer(){
    String result = searchClient.search();
    return result;
}

4.3 Feign的传递参数方式

1.注意事项

1.如果传递的参数,比较复杂时,默认会采用POST的请求方式

2.传递单个参数时,推荐使用@PathVariable,如果传递的单个参数比较多,这里也可以采用@RequestParam,不要省略value属性

3.传递对象信息时,统一采用json方式,添加@RequestBody

4.Client接口必须采用@RequestMapping

2.在Search模块下准备三个接口

@GetMapping("/search/{id}")
public Customer findById(@PathVariable Integer id)
{
    return new Customer(1, "张三", 23);
}
​
@GetMapping("/getCustomer")
public Customer getCustomer(@RequestParam Integer id, @RequestParam String name)
{
    return new Customer(id, name, 23);
}
​
@PostMapping("/save")   //会自动转为POST请求,405
public Customer save(@RequestBody Customer customer) {
    return customer;
}

3.封装Customer模块下的Controller

@
GetMapping("/customer/{id}")
public Customer findById(@PathVariable Integer id)
{
    return searchClient.findById(id);
}
​
@GetMapping("/getCustomer")
public Customer getCustomer(@RequestParam Integer id, @RequestParam String name)
{
    return searchClient.getCustomer(id, name);
}
​
@GetMapping("/save")   //会自动转为POST请求,405
public Customer save(Customer customer) {
    return searchClient.save(customer);
}
​
//Josn使用如下方式:
@PostMapping("/save")  会自动转为POST请求, 405
public Customer save(@RequestBody Customer customer){
    return customer;
}

4.再封装Client接口

@RequestMapping(value="/search/{id}",  method = RequestMethod.GET)
Customer findById(@PathVariable(value="id") Integer id);
​
@RequestMapping(value="/getCustomer",  method = RequestMethod.GET)
Customer getCustomer(@RequestParam(value="id") Integer id, @RequestParam(value="name") String name);
​
@RequestMapping(value="/save",  method = RequestMethod.POST)   //会自动转为POST请求,405
Customer save(@RequestBody Customer customer);

4.4 Feign的Failback

Failback可以帮助我们在使用Feign去调用另外一个服务时,如果出现了问题,走服务降级,返回一个错误数据,避免功能因为一个服务出现问题,全部失效。

  1. 创建一个POJO类,实现Client接口

    @Component
    public class SearchClientFallBack implements SearchClient {
        @Override
        public String search() {
            return "出现问题了";
        }
    ​
        @Override
        public Customer findById(Integer id) {
            return null;
        }
    ​
        @Override
        public Customer getCustomer(Integer id, String name) {
            return null;
        }
    ​
        @Override
        public Customer save(Customer customer) {
            return null;
        }
    }

  2. 修改Client接口中的注解,添加一个属性

    @FeignClient(value="SEARCH", fallback = SearchClientFallBack.class)

  3. 添加一个配置文件。

    #feign和hystrix组件整合
    feign:
      hystrix:
        enabled: true

    调用方无法知道具体的错误信息是什么,通过FallBackFactory的方式去实现这个功能。

    1.FallBackFactory基于Fallback

    2.创建一个POJO类,实现FallBackFactory<Client>

    @Component
    public class SearchClientFallBackFactory implements FallbackFactory<SearchClient> {
    ​
        @Autowired
        private SearchClientFallBack searchClientFallBack;
        @Override
        public SearchClient create(Throwable throwable)
        {
            throwable.printStackTrace();
            return searchClientFallBack;
        }
    }

    3.修改Client接口中的属性

    @FeignClient(value="SEARCH", 
                      fallbackFactory = SearchClientFallBackFactory.class)

五.服务的隔离及断路器-Hystrix

5.1引言

 

5.2降级机制实现

1.导入依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>

2.添加一个注解

@SpringBootApplication
@EnableCircuitBreaker
public class CustomerApplication {
    public static void main(String[] args){
        SpringApplication.run(CustomerApplication.class, args);
    }
}

3.针对某一个接口去编写他的降级方法

//findById的降级方法,方法的描途要和接口一致
public Customer findByIdFallBack(@PathVariable Integer  id)
{
    return new Customer(-1, "", 0);
}

4.在接口上添加注解

@GetMapping("/customer/{id}")
@HystrixCommand(fallbackMethod = "findByIdFallBack")
public Customer findById(@PathVariable Integer id)
{
    int i = 1/0;
    return searchClient.findById(id);
}

5.测试

5.3线程隔离

如果使用Tomcat的线程池去接收用户的请求,使用当前线程去执行其他的服务的功能,如果某一个服务出现了故障,导致tomcat的线程大量的堆积,使得Tomcat无法处理其他的业务功能。

Hystrix的线程池(默认),接收用户请求采用tomcat的线程沁,执行业务代码,调用其他服务时,采用Hystrix的线程池。

信号量,使用的还是Tomcat的线程池,帮助我们去管理Tomcat的线程池。

1.Hystrix的线程池的配置

(具体的配置name属性需要去查看HystrixCommandProperties类)

1.线程隔离策略: name='hystrix.command.default.execution.isolation.strategy', value='THREAD,SEMAPHORE'

2.指定超时时间(针对线程池)

name='hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds', value=1000

3.是否开启超时时间配置:name=‘hystrix.command.default.execution.timeout.enabled’,value=‘true’

4.超时之后是否中断线程:

name=‘hystrix.command.default.execution.isolation.thread.interruptOnTimeout’, value=‘true’

5.取消任务后,是否中断线程

name=‘hystrix.command.default.execution.isolation.thread.interruptOnCancel’, value=‘false’

@GetMapping("/customer/{id}")
@HystrixCommand(fallbackMethod = "findByIdFallBack", commandProperties = {
        @HystrixProperty(name="execution.isolation.strategy", value="THREAD"),
        @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds", value="3000")
})
public Customer findById(@PathVariable Integer id) throws InterruptedException {
    System.out.println(Thread.currentThread().getName());
    Thread.sleep(3000);
    return searchClient.findById(id);
}

2.信号量的配置信息

1.线程隔离策略: name='hystrix.command.default.execution.isolation.strategy', value='THREAD,SEMAPHORE'

2.指定信号量的最大并发请求数:

name=hystrix.command.default.execution.isolation.semaphore.maxConcurrentRequest value=10

5.4 断路器

5.4.1断路器介绍

在调用指定服务时,如果说这个服务的失败率达到输入的一个阈值, 将断路器从close状态,转变为open状态,指定服务时无法被访问的,如果你访问就直接走fallback方法,在一定的时间内,open状态会再次转变为half open状态,允许一个请求发送到我的指定的服务,如果成功,转变为close,如果失败,服务再次转变为open状态,会再次循环到half open,直到断路器回到一个close状态。

 

5.4.2配置断路器的监控界面

1.导入依赖

  <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-dashboard</artifactId>
   </dependency>

2.在启动类中添加注解, 引入servlet

@EnableHystrixDashboard
@Bean
public ServletRegistrationBean getServlet() {
    HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
    ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
    registrationBean.setLoadOnStartup(1);
    registrationBean.addUrlMappings("/actuator/hystrix.stream");//访问路径
    registrationBean.setName("hystrix.stream");
    return registrationBean;
}

hystrix:
  dashboard:
    proxy-stream-allow-list: "localhost"

如果引用下面的servlet, 则不需要上面的Bean,需添加 servlet扫描注解

@ServletComponentScan("com.study.servlet") 

3.配置一个Servlet

@WebServlet("/actuator/hystrix.stream")
public class HystrixServlet extends HystrixMetricsStreamServlet {
}

4.测试

直接方问http://host:port/hystrix

 

在当前位置输入映射好的servlet路径:

 

5.4.3配置断路器的属性

断路器的属性(10秒中之内):

1.断路器的开关: name=hystrix.command.default.circuitBreaker.enabled, value=true

2.失败阈值的总请求数: name=hystrix.command.default.circuitBreaker.requestVolumeThreshold, value=20

3.请求失败率达到%多少时:

name=hystrix.command.default.circuitBreaker.errorThresholdPercentage, value=50

4.断路器open状态后,多少秒是拒绝请求的:

name=hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds, value=5000

5.强制让服务拒绝请求:name=hystrix.command.default.circuitBreaker.forceOpen, value=false

6.强制让服务接收请求:name=hystrix.command.default.circuitBreaker.forceClosed, values=false

具体配置方式:

@GetMapping("/customer/{id}")
@HystrixCommand(fallbackMethod = "findByIdFallBack", commandProperties = {
        @HystrixProperty(name="circuitBreaker.enabled", value = "true"),
        @HystrixProperty(name="circuitBreaker.requestVolumeThreshold", value = "10"),
        @HystrixProperty(name="circuitBreaker.errorThresholdPercentage", value = "7"),
        @HystrixProperty(name="circuitBreaker.sleepWindowInMilliseconds", value = "5000")
})

5.5请求缓存:

5.5.1请求缓存介绍

1.请求缓存的声明周期胆一次请求

2.请求缓存是缓存当前线程中的一个方法,将方法参数作为key, 方法的返回结果作为value

3.在一次请求中,目标方法被调用过一次,以后就都会被缓存

 

5.5.1请求缓存的实现

1.创建一个Service, 在Service中调用Search服务

@Service
public class CustomerService {
​
    @Autowired
    private SearchClient searchClient;
​
    @CacheResult
    @HystrixCommand(commandKey="findById")
    public Customer findById(@CacheKey Integer id) throws InterruptedException {
        return searchClient.findById(id);
    }
​
    @CacheRemove(commandKey = "findById")
    @HystrixCommand()
    public void ClearFindById(@CacheKey Integer id){
        System.out.println("findById被清空");
    }
}

2.使用请求缓存的注解

@CacheResult: 帮助我们缓存当前方法的返回结果(必须@HystrixCommand配合使用)

@CacheRemove:帮助我们清除某一个缓存信息(基于commandKey)

@CacheKey:指定哪个方法参数作为缓存的标识

3.修改Search模块的返回结果

return new Customer(1, "张三", (int)(Math.random() * 100000));

4.编写Fitter,去构建HystrixRequestContext

@WebFilter("/*")
public class HystrixRequestContextInitFilter implements Filter {
​
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResonse, FilterChain filterChain) throws IOException, ServletException {
        HystrixRequestContext.initializeContext();
        filterChain.doFilter(servletRequest, servletResonse);
    }
}

修改启动类:

@ServletComponentScan({"com.study.filter"})

5.修改Controller

public Customer findById(@PathVariable Integer id) throws InterruptedException {
    System.out.println(customerService.findById(id));
    System.out.println(customerService.findById(id));
    customerService.ClearFindById(id);
    System.out.println(customerService.findById(id));
    System.out.println(customerService.findById(id));
    return searchClient.findById(id);
}

6.测试结果

 

六.服务的网关-Zuul

6.1引言

1客户端维护大量的IP和Port信息,直接访问指定服务

2.认证和授权操作,需要在每一个模块中都添加认证和授权的操作

3.项目的迭代,服务的拆分,服务的要合并,需要客户端进行大量的变化

4.统一的把安全性校验都放在Zuul中

 

6.2 Zull的快速入门

1.创建Maven项目, 修改为SpringBoot

2.导入依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>

3.创建启动类,添加注解

@SpringBootApplication
@EnableEurekaClient
@EnableZuulProxy
public class ZuulApplication {
    public static void main(String[] args)
    {
        SpringApplication.run(ZuulApplication.class, args);
    }
}

4.编写配置文件

#指定Eureka服务地址
eureka:
  client:
    service-url:
      defaultZone: http://root:root@localhost:8761/eureka,http://root:root@localhost:8762/eureka
#指定服务名称
spring:
  application:
    name: ZUUL
​
server:
  port: 80

5.测试

 

6.3 Zuul常用配置信息

6.3.1 Zuul监控界面

1.导入依赖

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

2.编写配置文件

#查看Zuul的监控界面(开发时,配置为*,上线时,不要配置)
management:
  endpoints:
    web:
      exposure:
        include: "*"

3.直接访问

http://localhost/actuator/routes

 

6.3.2忽略服务配置

#ZUUL的配置
zuul:
  ignored-services: eureka   #基于服务名忽略服务, 无法查看 (自定义的配置是无法忽略掉的)
  ignored-patterns: /**/search/**   #监控界面依然可以查看,在访问的时候, 404      如果要忽略全部的服务“*”, 默认配置的全部路径都会被忽略掉

6.3.3 自定义服务配置

zuul:
​
  #routes:  #指定自定义服务(方式一, key(服务名): value(映射路径))
  #  search: /ss/**
  # customer: /cc/**
  #指定自定义服务(方式二)
  routes:
    kehu:                        #自定义名称
      path: /ccc/**    #映射路径
      serviceId: customer   #服务名称

6.3.4 灰度发布

1.添加一个配置类

@Bean
public PatternServiceRouteMapper serviceRouteMapper() {
    return new PatternServiceRouteMapper(
            "(?<name>^.+)-(?<version>v.+$)",
            "${version}/${name}");
        //命名//服务名-v + number
        //访问// /v + number/路径
}

2.准备一个服务,提供2个版本

version: v1
#指定服务名称
spring:
  application:
    name: CUSTOMER-${version}

 

3.修改Zuul的配置

zuul:  #基于服务名忽略服务,无法查看,如果需要用到-v的方式,一定要忽略掉
  #ignored-services: "*"   #""   #基于服务名忽略服务, 无法查看

4.测试

 

6.4 Zuul的过滤器执行流程

客户端请求发送到Zuul服务上,首先通过PreFilter链,如果正常放行,会把请求再次转发给RoutingFilter,请求转发到一个指定的服务,在指定的服务响应一个结果之后,再次走一个PostFilter的过滤器链,最终再将响应信息交给客户端。

 

6.5 Zuul过滤器入门

1.创建POJO类,继承ZuulFilter抽象类

@Component
public class TestZuulFilter extends ZuulFilter {}

2.指定当前过滤器的类型

@Override
public String filterType() {
    return FilterConstants.PRE_TYPE;
}

指定过滤器的执行顺序

@Override
public int filterOrder() {
    return FilterConstants.PRE_DECORATION_FILTER_ORDER - 1;
}

配置是否启用

@Override
public boolean shouldFilter() {
    //开启当前过滤器
    return true;
}

指定过滤器中的具体业务代码

@Override
public Object run() throws ZuulException {
    System.out.println("prefix过滤器执行");
    return null;
}

模板代码如下:

@Component
public class TestZuulFilter extends ZuulFilter {
    @Override
    public String filterType() {
        //pre, routing, err, post
        return FilterConstants.PRE_TYPE;
    }
​
    @Override
    public int filterOrder() {
        return FilterConstants.PRE_DECORATION_FILTER_ORDER - 1;
    }
​
    @Override
    public boolean shouldFilter() {
        //true open当前过滤器, false close
        return true;
    }
​
    @Override
    public Object run() throws ZuulException {
        System.out.println("prefix过滤器111111111执行~~~~~");
        return null;
    }
}

测试

 

6.6 PreFilter实现token校验

1.准备访问路径,请求参数传递token(传递token方法很多,如header,方法参数,cookie)

http://localhost/v1/customer/version?token=123

2.创建AuthenticationFilter

@Component
public class AuthenticationFilter extends ZuulFilter {
    @Override
    public String filterType() {
        return FilterConstants.PRE_TYPE;
    }
​
    @Override
    public int filterOrder() {
        return FilterConstants.PRE_DECORATION_FILTER_ORDER - 2;
    }
​
    @Override
    public boolean shouldFilter() {
        return true;
    }
​
    @Override
    public Object run() throws ZuulException {
        System.out.println("com here >>>>>>>>>>>>>>>>>>>>>>>>>>>>");
        //1.获取Request对象
        RequestContext requestContext = RequestContext.getCurrentContext();
        HttpServletRequest request = requestContext.getRequest();
        //2.获取token参数
        String token = request.getParameter("token");
        //3. 对比token
        if (token == null || !"123".equalsIgnoreCase(token))
        {
            //4.token校验失败,直接响应数据
            requestContext.setSendZuulResponse(false);
            requestContext.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
        }
​
        return null;
    }
}

3.在run方法中编写具体的业务逻辑代码

@Override
public Object run() throws ZuulException {
    System.out.println("com here >>>>>>>>>>>>>>>>>>>>>>>>>>>>");
    //1.获取Request对象
    RequestContext requestContext = RequestContext.getCurrentContext();
    HttpServletRequest request = requestContext.getRequest();
    //2.获取token参数
    String token = request.getParameter("token");
    //3. 对比token
    if (token == null || !"123".equalsIgnoreCase(token))
    {
        //4.token校验失败,直接响应数据
        requestContext.setSendZuulResponse(false);
        requestContext.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
    }
​
    return null;
}

4.测试

 

6.7 Zuul的降级

  1. 创建POJO类, 实现接口 FallBackProvider接口

    @Component
    public class ZuulFallBack implements FallbackProvider {

  2. 重写两个方法

@Override
public String getRoute() {
    return "*";   //代表指定全部出现问题的服务,都走这个降级方法
}

@Override
    public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
        System.out.println("降级的服务: " + route);
        cause.printStackTrace();
        return new ClientHttpResponse() {
            @Override
            public HttpStatus getStatusCode() throws IOException {
                //指定具体的HttpStatus
                return HttpStatus.INTERNAL_SERVER_ERROR;
            }

            @Override
            public int getRawStatusCode() throws IOException {
                //返回的状态码
                return HttpStatus.INTERNAL_SERVER_ERROR.value();
            }

            @Override
            public String getStatusText() throws IOException {
                //指定错误信息
                return HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase();
            }

            @Override
            public void close() {

            }

            @Override
            public InputStream getBody() throws IOException {
                //给用户响应的信息
                String msg = "当前服务: " + route + "出现问题!!!!";
                return new ByteArrayInputStream(msg.getBytes());
            }

            @Override
            public HttpHeaders getHeaders() {
                //指定响应头信息
                HttpHeaders headers = new HttpHeaders();
                headers.setContentType(MediaType.APPLICATION_JSON);
             return headers;
            }
        };
    }

     3.测试

 

6.8 Zuul动态路由

1.创建一个过滤器

执行顺序最好放在Pre过滤器的最后面

@Component
public class DynamicRoutingFilter extends ZuulFilter {
    @Override
    public String filterType() {
        return FilterConstants.PRE_TYPE;
    }
​
    @Override
    public int filterOrder() {
        //执行顺序最好放在Pre过滤器的最后面
        return FilterConstants.PRE_DECORATION_FILTER_ORDER + 2;
    }
​
    @Override
    public boolean shouldFilter() {
        return true;
    }
    
    @Override
    public Object run() throws ZuulException {}
}

2.在run方法中编写业务逻辑

public Object run() throws ZuulException {
    //1.获取Request对象
    RequestContext context = RequestContext.getCurrentContext();
    HttpServletRequest request = context.getRequest();
​
    //2.获取参数
    String redisKey = request.getParameter("redisKey");
    //3.直接判断
    if (redisKey != null && redisKey.equalsIgnoreCase("customer"))
    {
        //http://localhost:8080/customer
        context.put(FilterConstants.SERVICE_ID_KEY, "customer-v1");
        context.put(FilterConstants.REQUEST_URI_KEY, "/customer");
    }else if(redisKey != null && redisKey.equalsIgnoreCase("search"))
    {
        //http://localhost:8080/search/1
        context.put(FilterConstants.SERVICE_ID_KEY, "search");
        context.put(FilterConstants.REQUEST_URI_KEY, "/search/1");
    }
    return null;
}

3.测试

 

七.多语言支持-Sidecar

7.1 引言

在SpringCloud的项目中,需要接入一些非java的程序,第三方接口,无法接入eureka, hystrix, feign等等组件,启动一个代理的微服务,代理微服务去和非java的程序或第三方接口交流,通过代理的微服务去计入SpringCloud的相关组件。

 

7.2 Sidecar实现

1.创建一个第三方的服务

创建一个Springboot工程,并且添加一个Controller

 

 

 

2.创建maven工程,修改为SpringBoot

 

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

3.导入依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-netflix-sidecar</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

4.添加注解

@EnableSidecar

5.编写配置文件

server:
  port: 81
eureka:
  client:
    service-url:
      defaultZone: http://root:root@localhost:8761/eureka,http://root:root@localhost:8762/eureka
​
spring:
    application:
      name: other-service
​
sidecar:
    port: 7001

6.通过customer通过Feign调用第三方服务

 

 

八.服务间消息传递-Stream

8.1 引言

 

8.2 Stream快速入门

1.启动RabbitMQ

docker-compose up -d

2.消费者-导入依赖(Search模块下)

Spring Cloud Stream RabbitMQ Binder Reference Guide

<dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>

3.消费者-配置文件

spring:
  rabbitmq: #连接RabbitMQ
    host: 192.168.1.201
    port: 5672
    username: test
    password: test
    virtual-host: /test

4.消费者-监听的队列

public interface StreamClient {
    @Input("myMessage")
    SubscribableChannel input();
}
//------------------------------------------------------
​
@Component
@EnableBinding(StreamClient.class)
public class StreamReceiver {
    @StreamListener("myMessage")
    public void msg(Object msg){
        System.out.println("接收到消息: " + msg);
    }
}

5.生产者-导入依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>

6.生产者-配置文件

spring:
  application:
    name: CUSTOMER-${version}
  rabbitmq: #连接RabbitMQ
    host: 192.168.199.109
    port: 5672
    username: test
    password: test
    virtual-host: /test

7.生产者-发面消息

public interface StreamClient {
    @Output("myMessage")
    MessageChannel output();
}
​
//---------------------------在启动类中添加注解 @EnableBinding(StreamClient.class)
​
@RestController
public class MessageController {
​
    @Autowired
    private StreamClient streamClient;
​
    @GetMapping("/send")
    public String send(){
        streamClient.output().send(MessageBuilder.withPayload("Hello Stream!").build());
        return "消息发送成功";
    }
}

8.3 重复消费问题

只需要添加一个配置, 指定消费者组(在消费组指定)

spring:
  cloud:
    stream:
      bindings:
        myMessage:           #队列名称
          group: customer    #消费者组

8.4 Stream的消费者手动ack

  1. 编写配置

     

    spring:
      cloud:
        stream:
          rabbit:
            bindings:
              myMessage:
                consumer:
                  acknowledgeMode: MANUAL  #手动ACK

  2. 修改(消费端)

    import com.rabbitmq.client.Channel;
    import com.study.springcloud.customer.stream.StreamClient;
    import org.springframework.amqp.support.AmqpHeaders;
    import org.springframework.cloud.stream.annotation.EnableBinding;
    import org.springframework.cloud.stream.annotation.StreamListener;
    import org.springframework.messaging.handler.annotation.Header;
    import org.springframework.stereotype.Component;
    ​
    import java.io.IOException;
    ​
    @Component
    @EnableBinding(StreamClient.class)
    public class StreamReceiver {
    ​
        @StreamListener("myMessage")
        public void msg(Object msg, @Header(name= AmqpHeaders.CHANNEL) Channel channel, @Header(name = AmqpHeaders.DELIVERY_TAG) Long deliveryTag) throws IOException {
            System.out.println("接收到消息: " + msg);
            channel.basicAck(deliveryTag, false);
        }
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值