spring cloud

一.Spring Cloud简介

     Spring Cloud服务间是通过http协议进行通信的

     region和zone

     查看:https://www.cnblogs.com/junjiang3/p/9061867.html

     分布式微服务的区别:

                最大的区别是微服务是个组件化的,有自己独立的资源,可以

                独立运行,可插拔。

一.Eureka

    1.Eureka简介

     (1)Eureka的基本原理

              关于Eureka的原理参看:

              https://blog.csdn.net/forezp/article/details/73017664

       

        

       由图可知,Eureka包含两个组件,Eureka Server和Eureka Client

       Eureka Server:提供服务注册服务,各个节点启动后会在Eureka Server进行注册。

                                  Eureka Server之间通过复制的方式完成数据的同步。在应用启动

                                  后,将会向Eureka Server发送心跳,默认周期为30秒,如果

                                  Eureka Server在多个心跳周期内没有接收到某个节点的心跳,将

                                  会重服务器注册表中把这个服务节点移除(开启自我保护除外)。

       Eureka Client:是一个Java客户端,用于简化与Eureka Server的交互,该客户端

                                具备一个内置的使用轮询负载均算法的负载均衡器。Eureka提供客

                                户端缓存机制,即使所有的Eureka Server都挂掉,客户端依然可以

                                利用缓存中的信息消费其它服务中的Api。

                                客户端缓存的实现原理:

                                Eureka Client缓存机制很简单,设置了一个每30秒执行一次的定时

                                任务,定时去服务端获取注册信息。获取之后,存入本地内存。

       综上,Eureka通过心跳检测、健康检查、客户端缓存等机制确保了系统的高可用性、

       灵活性和可伸缩性。

     (2)Eureka包含的核心功能

            (a)Register:服务注册

              当Eureka客户端向Eureka Server注册时,它提供自身的元数据,比如IP地

              址、端口,运行状况指示符URL,主页等。

            (b)Renew:服务续约

              Eureka客户会每隔30秒发送一次心跳来续约。 通过续约来告知

              Eureka Server该Eureka客户仍然存在,没有出现问题。 正常情况下,如果

              Eureka Server在90秒没有收到Eureka客户的续约,它会将实例从其注册表

              中删除。 建议不要更改续约间隔。

            (c)Fetch Registries:获取注册列表信息

              Eureka客户端从服务器获取注册表信息,并将其缓存在本地。客户端会使

              用该信息查找其他服务,从而进行远程调用。该注册列表信息定期(每30

              秒钟)更新一次。每次返回注册列表信息可能与Eureka客户端的缓存信息

              不同, Eureka客户端自动处理。如果由于某种原因导致注册列表信息不能

              及时匹配,Eureka客户端则会重新获取整个注册表信息。 Eureka服务器缓

              存注册列表信息,整个注册表以及每个应用程序的信息进行了压缩,压缩

              内容和没有压缩的内容完全相同。Eureka客户端和Eureka 服务器可以使用

              JSON / XML格式进行通讯。在默认的情况下Eureka客户端使用压缩JSON

              格式来获取注册列表的信息。

            (d)Cancel:服务下线

              Eureka客户端在程序关闭时向Eureka服务器发送取消请求。 发送请求后,

              该客户端实例信息将从服务器的实例注册表中删除。该下线请求不会自动

              完成,它需要调用以下内容:

              DiscoveryManager.getInstance().shutdownComponent();

       


       

    2.Eureka的一些使用示例

     (1)Eureka集群

Eureka服务器端application.yml配置(以两台为例):

server:
  port: 8761
spring:
  application:
    name: eureka-servier
  #Spring提供了profiles的功能,可以配置多套配置,用于区分不同的环境(开发、测试、生产)在运行时 
  #指定使用那套,这样代码只要一套,运行时加入不同参数就可以了。比如在UnitTest中,加入: 
  #@ActiveProfiles("dev"),即可使用dev的配置。也可以在运行jar的时候
  #加入:-Dspring.profiles.active=release。
  profiles: slave1
eureka:
  client:
    serviceUrl:
      #服务注册中心的配置内容,指定服务注册中心的位置
      defaultZone: http://localhost:8762/eureka
---
server:
  port: 8762
spring:
  application:
    name: eureka-servier
  profiles: slave2
eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka

Eureka客户端配置:

spring:
  application:
    name: consumer-demo
server:
  port: 9000
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka,http://localhost:8762/eureka

集群成功示例图:

 

     (2)健康检查:

   如果Eureka Server在一定时间内(默认90秒)没有接收到某个微服务实例的心跳,

   Eureka Server将会移除该实例。

健康检查配置(下面代码是一个eureka客户端而不是eureka server):

spring:
  application:
    name: consume-demo-hertbeat
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka
  instance:
      #eureka客户端发送给eureka服务器心跳的频率
      lease-renewal-interval-in-seconds: 5
      #表示eureka server至上一次收到client的心跳之后,等待下一次心跳的超时时间,在这个时间内若没收到下一次心跳,则将移除该instance。
      lease-expiration-duration-in-seconds: 10

 同时应该在eureka server中关闭自我保护,示例代码如下(下例是一个eureka server

 而不是eureka client):

spring:
  application:
    name: eurker-servier-heartbeat
server:
  port: 8761
eureka:
  client:
    register-with-eureka: false
    fetch-registry: false
  server:
    #自我保护配置(true 开启,false 关闭)
    enable-self-preservation: false
    #(清理服务列表的时间间隔)

  上面代码中为了测试方便还增加了清理服务列表的时间,即使满足“10秒没收到请求但不满足到

  了清理服务列表的时间,一样不会剔除该服务”。

     (3)自我保护:

   首先阐明存在的一个问题:  

   默认情况下,如果Eureka Server在一定时间内(默认90秒)没有接收到某个微服务实例的心跳,

   Eureka Server将会移除该实例。但是当网络分区故障发生时,微服务与Eureka Server之间无法

   正常通信,而微服务本身是正常运行的,此时不应该移除这个微服务,Eureka通过自我保护机制

   来解决该问题的。

  自我保护机制:当Eureka Server节点在短时间内丢失过多客户端时(15分钟内超过85%的客户端

  节点都没有正常的心跳),那么这个节点就会进入自我保护模式。一旦进入该模式,

  Eureka Server就会保护服务注册表中的信息,不再删除服务注册表中的据(也就是不会注销任

  何微服务)。当网络故障恢复后,该Eureka Server节点会自动退出自我保护模式。

  自我保护配置:

spring:
  application:
    name: eurker-servier-heartbeat
server:
  port: 8761
eureka:
  client:
    register-with-eureka: false
    fetch-registry: false
  server:
    #自我保护配置(true 开启,false 关闭)
    enable-self-preservation: false
    #(清理服务列表的时间间隔)
    eviction-interval-timer-in-ms: 10000

     (4)健康监控

            默认情况下注册到eureka server的服务是通过心跳来告知自己是UP还是DOWN,并不是

    通过spring-boot-actuator模块的/health端点来实现的,这样其实不是很合理。因为默认的心跳

    实现方式可以有效的检查eureka客户端进程是否正常运作,但是无法保证客户端应用能够正常

    提供服务(大多数微服务应用都会有一些其他的外部资源依赖,比如数据库,REDIS缓存等,

    如果我们的应用与这些外部资源无法连通的时候,实际上已经不能提供正常的对外服务了,但

    因为客户端心跳依然在运行,所以它还是会被服务消费者调用)。

           健康状态有UP和DOWN两种,如果Eureka中的是UP,则该服务可以正常调用;如果

    Eureka中的健康状态是DOWN则该服务不可调用

    问题场景:如果一个服务并没有死掉,但是其本身是有问题的,例如访问数据库的服务无法连接

    到数据库,这个时候需要使用健康监控。

    注意:健康监控监控的是客户端,所以健康指示器和健康处理器的代码只能写在需要健康的客户端。

 (a)引入jar包

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

 (b)编写健康指示器

package app;

import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.Status;
import org.springframework.stereotype.Component;

@Component
public class MyHealthIndicator implements HealthIndicator {
    public Health health() {
        if(ProviderMonitorController.canVisitDB)
        {
            return new Health.Builder(Status.UP).build();
        }else
        {
            return new Health.Builder(Status.DOWN).build();
        }
    }
}

(c)模拟数据库无法访问

package app;

import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ProviderMonitorController {
    public static boolean canVisitDB=true;
    @RequestMapping(value = "/setDB/{can}",method = RequestMethod.GET)
    public void setDB(@PathVariable boolean can)
    {
        canVisitDB=can;
    }
}

(d)测试

        (i)查看健康状态

            在浏览器里输入http://localhost:8080/health,返回状态为UP

        (ii)执行模拟数据库无法访问并查看健康状态

                浏览器中输入:http://localhost:8080/setDB/false 设置数据库无法访问

                浏览器中输入:http://localhost:8080/health 查看健康状态为 DOWN

                查看Eureka中的健康状态仍然为UP,这时需要使用健康检查处理器来改变Eureka中的状

                态(只有改变了Eureka中的状态,那么有问题的服务才不能被其它被访问)。

    (e)健康处理器代码(默认情况下这个处理器30秒执行一次):

                

package app;

import com.netflix.appinfo.HealthCheckHandler;
import com.netflix.appinfo.InstanceInfo;
import jdk.net.SocketFlow;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.boot.actuate.health.Status;
@Component
public class MyHealthCheckHandle implements HealthCheckHandler {
    @Autowired
    private MyHealthIndicator myHealthIndicator;
    public InstanceInfo.InstanceStatus getStatus(InstanceInfo.InstanceStatus instanceStatus) {
       Status status= myHealthIndicator.health().getStatus();
       if(status.equals(Status.UP))
       {
            return  InstanceInfo.InstanceStatus.UP;
       }else
       {
           return  InstanceInfo.InstanceStatus.DOWN;
       }
    }
}

      application.yml(主要关注健康处理器的执行频率)

spring:
  application:
    name: my-health-provider
endpoints:
  sensitive: false
eureka:
  client:
    #健康处理器执行频率。默认30秒执行一次,这里改成10秒执行一次
    instanceInfoReplicationIntervalSeconds: 10
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/

    (f)测试健康处理器

             浏览器中执行:http://localhost:8080/setDB/false,可以看到health端口和Eureka 中的状态

             全部变成DOWN

 

Ribbon

1.客户端负载均衡框架,支持可插拔式的负载均衡规则

2.支持多中协议,如HTTP、UDP

3.Ribbon支持的负载均衡规则

 

RoundRobinRule:轮询

AvailabilityFilteringRule:会过滤掉两类服务器。(1)短路状态(连续3次超时)(2)并发数过高

                                         的服务器。

WeightedResponseTimeRule:为每个服务器设置一个权重值,服务器的响应时间越长权重值越小。

                                                   这里是根据权重值随机选择服务器。

ZoneAvoidanceRule:复合判断server所在区域的性能和server的可用性选择server,使用区域对服

                                    务器进行选择。

BestAvailableRule:忽略短路的服务器并选择并发数最小的一个服务器

RandomRule:随机数

RetryRule:含有重试机制的选择逻辑。

 

NFLoadBalancerPingClassName:检查服务器是否存活

NFLoadBalancerClassName:负载均衡器的实现类

NIWSServerListClassName:服务器列表的处理类,用来维护服务器列表

NIWSServerListFilterClassName:实现服务器的拦截

 

 

Feign

1.Feign是一款rest客户端,底层使用了Ribbon的Api,天然具有负载均衡的功能;

   Feign默认使用HttpURLConnection连接HTTP服务。

2.Feign的一些模块介绍

(1)编码器和解码器

 

        (a)编码器:对请求内容进行处理。向服务发送请求的过程中,有些情况需要对请求的内容

                                进行处理。例如服务端发布的服务接收的是JSON格式参数,而客户端使用的

                                是对象,这种情况就可以使用编码器,将对象转换为JSON字符串。

                 JSON的编码器和解码器:GsonEncoder和GsonDecoder

                 示例代码:

                    服务器端:

/**
	 * 参数为JSON
	 */
	@RequestMapping(value = "/person/create", method = RequestMethod.POST, 
        consumes = MediaType.APPLICATION_JSON_VALUE)
	@ResponseBody
	public String createPerson(@RequestBody Person person) {
		System.out.println(person.getName() + "-" + person.getAge());
		return "Success, Person Id: " + person.getId();
	}

                   客户端:

public interface PersonClient {

    @RequestLine("POST /person/create")
    @Headers("Content-Type: application/json")
    String createPerson(Person person);

    @Data
    class Person {
        Integer id;
        String name;
        Integer age;
        String message;
    }
}

 

/**
 * 运行主类
 * @author 杨恩雄
 *
 */
public class EncoderTest {

	public static void main(String[] args) {
		// 获取服务接口
		PersonClient personClient = Feign.builder()
				.encoder(new GsonEncoder())
				.target(PersonClient.class, "http://localhost:8080/");
		// 创建参数的实例
		Person person = new Person();
		person.id = 1;
		person.name = "Angus";
		person.age = 30;
		String response = personClient.createPerson(person);
		System.out.println(response);
	}
}

 

        (b)解码器:对服务的响应内容进行处理。例如解析响应的JSON或者XML字符串,转换为

                               我们所需要的对象。

                代码示例:             

PersonClient personService = Feign.builder()
.decoder(new GsonDecoder())
.target(PersonClient.class, "http://localhost:8080/");

         (c)XML风格的编码器和解码器

                JAXBEncoder和JAXBDecoder

                具体代码查看:https://my.oschina.net/JavaLaw/blog/1555037     

        (d)自定义编码器和解码器

                实现接口Decoder和Encoder即可

    (2)自定义Feign的客户端

            (a)介绍:Feign的客户端默认使用HttpURLConnection连接HTTP服务

            (b)实现方式及代码示例

                    需要实现接口feign.Client,实现思路是请求转新的请求响应转新的响应。

                    代码示例:

                        自定义客户端:

package org.crazyit.cloud;

import java.io.IOException;
import java.net.URI;
import java.util.Collection;
import java.util.HashMap;

import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

import feign.Client;
import feign.Request;
import feign.Request.Options;
import feign.Response;

public class MyClient implements Client {

	public Response execute(Request request, Options options)
			throws IOException {
		System.out.println("this is my client");
		try {
			// 创建一个默认的客户端
			CloseableHttpClient httpclient = HttpClients.createDefault();
			// 获取调用的HTTP方法
			final String method = request.method();
			// 创建一个HttpClient的HttpRequest
			HttpRequestBase httpRequest = new HttpRequestBase() {
				public String getMethod() {
					return method;
				}
			};
			// 设置请求地址
			httpRequest.setURI(new URI(request.url()));
			// 执行请求,获取响应
			HttpResponse httpResponse = httpclient.execute(httpRequest);
			// 获取响应的主体内容
			byte[] body = EntityUtils.toByteArray(httpResponse.getEntity());
			// 将HttpClient的响应对象转换为Feign的Response
			Response response = Response.builder()
					.body(body)
					.headers(new HashMap<String, Collection<String>>())
					.status(httpResponse.getStatusLine().getStatusCode())
					.build();
			return response;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

}

                            发送请求代码:

package org.crazyit.cloud;

import feign.Feign;

public class MyClientTest {

	public static void main(String[] args) {
		HelloClient client = Feign.builder()
				.client(new MyClient())
				.target(HelloClient.class,
				"http://localhost:8080");
		String result = client.hello();
		System.out.println(result);
	}

}

(2)注解翻译器

         注解翻译器用来翻译注解,在spring cloud集成的时候会使用spring的注解,不要懵逼,其原

         理是使用了注解翻译器可自定义注解翻译器,但是个人认为基本不需要自定义注解翻译器,

         学习的目的只是要了解spring cloud能使用spring注解的原理。

          自定义注解翻译器:

        (a)定义注解

package org.crazyit.cloud.contract;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyUrl {

	String url();//该出叫属性不叫方法
	String method();//该出叫属性不叫方法
}

        (b)定义注解翻译器

package org.crazyit.cloud.contract;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

import feign.Contract.BaseContract;
import feign.MethodMetadata;

public class MyContract extends BaseContract {

	@Override
	protected void processAnnotationOnClass(MethodMetadata data, Class<?> clz) {
		// TODO Auto-generated method stub

	}

	@Override
	protected void processAnnotationOnMethod(MethodMetadata data,
			Annotation annotation, Method method) {
		// 注解是MyUrl类型的,才处理
		if(MyUrl.class.isInstance(annotation)) {
			MyUrl myUrl = method.getAnnotation(MyUrl.class);
			String url = myUrl.url();
			String httpMethod = myUrl.method();
			data.template().method(httpMethod);
			data.template().append(url);
		}
	}

	@Override
	protected boolean processAnnotationsOnParameter(MethodMetadata data,
			Annotation[] annotations, int paramIndex) {
		// TODO Auto-generated method stub
		return false;
	}

}

        (c)客户端调用

package org.crazyit.cloud.contract;

import org.crazyit.cloud.jaxrs.RsClient;

import feign.Feign;
import feign.jaxrs.JAXRSContract;

public class ContractMain {

	public static void main(String[] args) {
		ContractClient client = Feign.builder()
				.contract(new MyContract())
				.target(ContractClient.class,
				"http://localhost:8080");
		String result = client.hello();
		System.out.println(result);
	}

}

(3)请求拦截器

         用户拦截请求,处理一些公共信息。

         自定义请求拦截器:

        (a)实现一个请求拦截器

package org.crazyit.cloud.interceptor;

import feign.RequestInterceptor;
import feign.RequestTemplate;

public class MyInterceptor implements RequestInterceptor {

	public void apply(RequestTemplate template) {
		template.header("Content-Type", "application/json");
		System.out.println("这是自定义请求拦截器");
	}

}

        (b)客户端调用

package org.crazyit.cloud.interceptor;

import org.crazyit.cloud.HelloClient;

import feign.Feign;

public class InterceptorMain {

	public static void main(String[] args) {
		HelloClient client = Feign.builder()
				.requestInterceptor(new MyInterceptor())
				.target(HelloClient.class,
				"http://localhost:8080");
		String result = client.hello();
		System.out.println(result);
	}

}

(4)接口日志

         默认情况下接口是不会记录接口日志的,如果想要了解接口的调用情况,需要配置接口日志

       实现:         

package org.crazyit.cloud.log;

import org.crazyit.cloud.HelloClient;
import org.crazyit.cloud.interceptor.MyInterceptor;

import feign.Feign;
import feign.Logger;

public class LogMain {

	public static void main(String[] args) {
		HelloClient client = Feign.builder()
				.logLevel(Logger.Level.FULL)//日志级别说明 NONE:默认值,不记录日志;BASIC:记录请求方法、URL、响应状态代码和执行时间;HEADERS:除BASIC记录的日志外,还会记录请求头和响应头的信息;FULLL:在HEADERS的基础上,请求和响应的元数据,都会保存
				.logger(new Logger.JavaLogger().appendToFile("logs/http.log"))
				.target(HelloClient.class,
				"http://localhost:8080");
		String result = client.hello();
		System.out.println(result);
	}

}

3.Feign与Spring cloud整合

  (1)看Demo整理知识点

  (2)Feign的客户端中已经使用了Ribbon的Api,默认负载均衡配置是轮询,可以通过设置

           Ribbon的负载均衡策略来定义Feign客户端的负载均衡策略。

  (3)Feign的一些常用配置

           解码器(Decoder):bean名称为feignDecoder,ResponseEntityDecoder类。

           编码器(Encoder):bean名称为feignEecoder,SpringEncoder类。

          日志(Logger): bean名称为feignLogger,Slf4jLogger类。

          注解翻译器(Contract): bean名称为feignContract,SpringMvcContract类。

          Feign实例的创建者(Feign.Builder):bean名称为feignBuilder,HystrixFeign.Builder类。

                                                                        Hystrix框架将在后面章节中讲述。

          Feign客户端(Client):bean名称为feignClient,LoadBalancerFeignClient类。

          Logger.Level:接口日志的记录级别,相当于调用了Fiegn.Builder的logLevel方法,

                                  请见5.2.9章节。
          Retryer:重试处理器,相当于调用了Fiegn.Builder的retryer方法。
          ErrorDecoder:异常解码器,相当于调用了Fiegn.Builder的errorDecoder方法。
          Request.Options:设置请求的配置项,相当于调用了Fiegn.Builder的options方法。

          Collection<RequestInterceptor>:设置请求拦截器,相当于调用了Fiegn.Builder的

                                                                requestInterceptors方法。下面

                                                                是配置多个拦截器的样例,并不全,只是为了便于理解,

                                                                只是补充需要百度

 @Bean
    public RequestInterceptor getRequestInterceptorsA() {
    	return new RequestInterceptor() {
			public void apply(RequestTemplate template) {
				System.out.println("这是第一个请求拦截器");
			}    		
    	};
    }
    
    @Bean
    public RequestInterceptor getRequestInterceptorsB() {
    	return new RequestInterceptor() {
			public void apply(RequestTemplate template) {
				System.out.println("这是第二个请求拦截器");
			}    		
    	};
    }

          压缩配置:

                feign.compression.request.enabled:

                设置为true时,表示开启“请求”压缩。

                feign.compression.response.enabled:

                设置为true时,表示开启响应压缩。

                feign.compression.request.mime-types:

                数据类型列表,默认值为text/xml,application/xml,application/json。

                feign.compression.request.min-request-size:

                设置请求内容的最小阀值,默认值为2048。若大于2048则开启压缩。

 

Hystrix

1.Netflix提供的一个容错框架,它是为了解决雪崩效应而产生的。

  在一个分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,如何能够保

  证在一个依赖出问题的情况下,不会导致整体服务失败,这个就是Hystrix需要做的事情。

  Hystrix提供了熔断、隔离、Fallback、cache、监控等功能,能够在一个、或多个依赖同

  时出现问题时保证系统依然可用。

(1)雪崩效应:服务雪崩效应是一种因,服务提供者的不可用导致服务调用者的不可用,并

                           将不可用逐渐放大的过程。

         

        上图中, A为服务提供者,B为A的服务调用者,C和D是B的服务调用者,当A的不可

        用,引起B的不可用,并将不可用逐渐放大C和D时, 服务雪崩就形成了。

        雪崩效应的产生过程分为如下三个阶段:

             (a)服务提供者不可用

             (b)重试加大流量

             (c)服务调用者不可用

        服务雪崩的每个阶段都可能由不同的原因造成,比如造成服务不可用的原因有:"硬件

        故障”、“程序Bug”、“缓存击穿”、“用户大量请求”。硬件故障可能为硬件损坏造成的服

        务器主机宕机,网络硬件故障造成的服务提供者的不可访问。缓存击穿一般发生在缓

        存应用重启,所有缓存被清空时,以及短时间内大量缓存失效时。大量的缓存不命

        中,使请求直击后端,造成服务提供者超负荷运行,引起服务不可用。在秒杀和大促

        开始前,如果准备不充分,用户发起大量请求也会造成服务提供者的不可用。

               重试加大流量的原因有:“用户重试”、“代码逻辑重试”。在服务提供者不可用后,

        用户由于忍受不了界面上长时间的等待,而不断刷新页面甚至提交表单。服务调用端

        的会存在大量服务异常后的重试逻辑。这些重试都会进一步加大请求流量。在服务提

        供者不可用后,用户由于忍受不了界面上长时间的等待,而不断刷新页面甚至提交表

        单。服务调用端的会存在大量服务异常后的重试逻辑,这些重试都会进一步加大请求

        流量。

               服务调用者不可用 产生的主要原因是:“同步等待造成的资源耗尽”。当服务调用者

       使用同步调用时,会产生大量的等待线程占用系统资源。一旦线程资源被耗尽,服务调

       用者提供的服务也将处于不可用状态, 于是服务雪崩效应产生了。

              针对造成服务雪崩的不同原因, 可以使用不同的应对策略:“流量控制”,“改进缓存模

       式服务”、“自动扩容服务调用者降级服务”。

       针对造成服务雪崩的不同原因, 可以使用不同的应对策略:

            (a)流量控制

            (b)改进缓存模式

            (c)服务自动扩容

            (d)服务调用者降级服务

       “流量控制”的具体操作包括:

               网关限流

               用户交互限流

               关闭重试

       因为Nginx的高性能, 目前一线互联网公司大量采用Nginx+Lua的网关进行流量控制,由此

       而来的OpenResty也越来越热门,用户交互限流的具体措施有:

              采用加载动画,提高用户的忍耐等待时间。

              提交按钮添加强制等待时间机制。

      “改进缓存模式”的措施包括:

             缓存预加载

             同步改为异步刷新

     “服务自动扩容”的措施主要有:

             AWS的auto scaling

      “服务调用者降级服务”的措施包括:

            资源隔离

            对依赖服务进行分类

            不可用服务的调用快速失败

            资源隔离主要是对调用服务的线程池进行隔离。我们根据具体业务,将依赖服务分为:

     强依赖和弱依赖.。强依赖服务不可用会导致当前业务中止,而弱依赖服务的不可用不会导

     致当前业务的中止。不可用服务的调用快速失败一般通过“超时机制”、“熔断器”和熔断后的

     降级方法来实现。.

(2)Hystrix

       (a)Hystrix工作模式如下图:

                

       (b)Hystrix的设计

              (I)资源隔离

                货船为了进行防止漏水和火灾的扩散,会将货仓分隔为多个,这种资源隔离减少风险

                的方式被称为:Bulkheads(舱壁隔离模式)。Hystrix将同样的模式运用到了服务调

                用者上。在一个高度服务化的系统中,我们实现的一个业务逻辑通常会依赖多个服务,

                比如: 商品详情展示服务会依赖商品服务、价格服务,、商品评论服务.。如图所示:

                

               调用三个依赖服务会共享商品详情服务的线程池。如果其中的商品评论服务不可

               用,就会出现线程池里所有线程都因等待响应而被阻塞,从而造成服务雪崩。如

               图所示:

               

              Hystrix通过将每个依赖服务分配独立的线程池进行资源隔离,从而避免服务雪崩。

              如下图所示,当商品评论服务不可用时,即使商品服务独立分配的20个线程全部

              处于同步等待状态,也不会影响其他依赖服务的调用。

              

               Hystrix的隔离策略:

               两种隔离策略,线程池和信号量。

                     线程池隔离:对每个command创建一个自己的线程池,执行调用。通过线程

                                           池隔离来保证不同调用不会相互干扰和每一个调用的并发限制。

                     信号量隔离:对每个command创建一个自己的计数器,当并发量超过计数器

                                          指定值时,直接拒绝。信号量没有timeout机制。

                     二者比较:99%的情况使用的是线程池

 

 线程池隔离信号量隔离
线程

与调用线程非相同线程()

与调用线程相同(jetty线程)
开销排队、调度、上下文开销等无线程切换,开销低
异步支持不支持
并发支持支持(最大线程池大小)支持(最大信号量上限)
区别消息描述

当用户请求服务时,设请求的是服务A和服务

I,tomcat的线程会将请求的任务交给服务A或的内部线程池里面的线程来执行tomcat的线程就可以去干别的事情去了,当服务A和服务I自己线程池里面的线程执行完任务之后,就会将调用的结果返回给tomcat的线程,从而实现资源的隔离,当有大量并发的时候,服务内部的线程池的数量就决定了整个服务的并发度,例如服务A的线程池大小为10个,当同时有12请求时,只会允许10个任务在执行,其他的任务被放在线程池队列中,或者是直接走降级服务,此时,如果服务A挂了,就不会造成大量的tomcat线程被服务A拖死,服务I依然能够提供服务。整个系统不会受太大的影响。

信号量的资源隔离只是起到一个开关的作用,例

如,服务X的信号量大小为10,那么同时只允许

10个tomcat的线程(此处是tomcat的线程,而不

是服务X的独立线程池里面的线程)来访问服务

X,其他的请求就会被拒绝,从而达到限流保护的作用。

                    注意,表格中最后一项“区别消息描述”中的“线程池”和“信号量”的主要区别是:

                    “线程池”是使用服务提供者内部的线程池,“信号量”是使用的服务调用者的

                    Tomcat线程。

                            线程池和信号量配置:

                    execution.isolation.strategy :该属性用来设置执行的隔离策略,有如下二个选

                    项:

                    HREAD:通过线程池隔离的策略,在独立线程上执行,并且他的并发限制受线

                                    程池中线程数量的限制(默认)。

                    SEMAPHONE:通过信号量隔离的策略,在调用线程上执行,并且他的并发限

                                              制受信号量计数的限制。

                    线程池例子:

                    

package org.crazyit.cloud;
 
import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;
 
public class MyCommand extends HystrixCommand<String> {
	
	private int index;
 
	public MyCommand(int index) {
		super(Setter.withGroupKey(HystrixCommandGroupKey.Factory
				.asKey("TestGroupKey")));
		this.index = index;
	}
 
	@Override
	protected String run() throws Exception {
		Thread.sleep(500);
		System.out.println("执行方法,当前索引:" + index);
		return "";
	}
 
	@Override
	protected String getFallback() {
		System.out.println("执行回退,当前索引:" + index);
		return "";
	}
	
	
}

 


package org.crazyit.cloud;
 
import com.netflix.config.ConfigurationManager;
 
public class ThreadMain {
 
	public static void main(String[] args) throws Exception {
		ConfigurationManager.getConfigInstance().setProperty(
				"hystrix.threadpool.default.coreSize", 4);
		for(int i = 0; i < 6; i++) {
			MyCommand c = new MyCommand(i);
			c.queue();
		}
		Thread.sleep(5000);
	}
 
}

                   信号量(SEMAPHORE)例子:

                   

package org.crazyit.cloud;
 
import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;
 
public class MyCommand extends HystrixCommand<String> {
	
	private int index;
 
	public MyCommand(int index) {
		super(Setter.withGroupKey(HystrixCommandGroupKey.Factory
				.asKey("TestGroupKey")));
		this.index = index;
	}
 
	@Override
	protected String run() throws Exception {
		Thread.sleep(500);
		System.out.println("执行方法,当前索引:" + index);
		return "";
	}
 
	@Override
	protected String getFallback() {
		System.out.println("执行回退,当前索引:" + index);
		return "";
	}
	
	
}
        

 

 

package org.crazyit.cloud;
 
import com.netflix.config.ConfigurationManager;
import com.netflix.hystrix.HystrixCommandProperties.ExecutionIsolationStrategy;
 
public class SemaphoreMain {
 
	public static void main(String[] args) throws Exception {
		ConfigurationManager.getConfigInstance().setProperty(
				"hystrix.command.default.execution.isolation.strategy", ExecutionIsolationStrategy.SEMAPHORE);
		ConfigurationManager.getConfigInstance().setProperty(
				"hystrix.command.default.execution.isolation.semaphore.maxConcurrentRequests", 3);
		for(int i = 0; i < 6; i++) {
			final int index = i;
			Thread t = new Thread() {
				public void run() {
					MyCommand c = new MyCommand(index);
					c.execute();
				}
			};
			t.start();
		}
		Thread.sleep(5000);
	}
 
}


       (II)熔断器

                服务的健康状况 = 请求失败数 / 请求总数。

                当熔断器开关关闭时,请求被允许通过熔断器。如果当前健康状况高于设定阈

                值,开关继续保持关闭。如果当前健康状况低于设定阈值,,开关则切换为打开状

                态。当熔断器开关打开时,请求被禁止通过。当熔断器开关处于打开状态,经过

                一段时间后,熔断器会自动进入半开状态,这时熔断器只允许一个请求通过。当

                该请求调用成功时,熔断器恢复到关闭状态。若该请求失败,熔断器继续保持打

                开状态,接下来的请求被禁止通过。

       (III)命令模式(参考文章:https://blog.csdn.net/sjyttkl/article/details/73536297

                 首先,命令模式是一种设计模式。现在来具体讲解:举个例子,司令员下令让士

                 兵去干件事情,从整个事情的角度来考虑,司令员的作用是,发出口令,口令经

                 过传递,传到了士兵耳朵里,士兵去执行。这个过程好在,三者相互解耦,任何

                 一方都不用去依赖其他人,只需要做好自己的事儿就行,司令员要的是结果,不

                 会去关注到底士兵是怎么实现的。我们看看关系图:

                  

                 Invoker是调用者(司令员),Receiver是被调用者(士兵),MyCommand是

                 命令,实现了Command接口,持有接收对象,看实现代码:        

public interface Command
{
    public void exe();
}


public class Invoker
{
    private Command command;

    public Invoker(Command command)
    {
        this.command = command;
    }

    public void action()
    {
        command.exe();
    }
}


public class MyCommand implements Command
{
    private Receiver receiver;

    public MyCommand(Receiver receiver)
    {
        this.receiver = receiver;
    }

    @Override
    public void exe()
    {
        receiver.action();

    }

}



public class Receiver
{
    public void action()
    {
        System.out.println("command received!");
    }
}


public class Test
{
    public static void main(String[] args)
    {
        Receiver receiver = new Receiver();

        Command cmd = new MyCommand(receiver);

        Invoker invoker = new Invoker(cmd);
        invoker.action();

    }
}

          命令模式的目的就是达到命令的发出者和执行者之间解耦,实现请求和执行分开。

                 Hystrix使用命令模式(继承HystrixCommand类)来包裹具体的服务调用逻辑(run

          方法),并在命令模式中添加了服务调用失败后的降级逻辑(getFallback)。在使用了

          Command模式构建了服务对象之后,服务便拥有了熔断器和线程池的功能。

          Hystrix的内部处理:

          

           

             (i)构建Hystrix的Command对象, 调用执行方法。

             (ii)Hystrix检查当前服务的熔断器开关是否开启,若开启,则执行降级服务

                      getFallback方法。

             (iii)若熔断器开关关闭, 则Hystrix检查当前服务的线程池是否满载,若满载,

                       则执行降级服务getFallback方法;若线程池没有满载,Hystrix开始执行服

                       务调用具体逻辑run方法。

            (iv)若服务执行失败或超时,则执行降级服务getFallback方法,并将执行结果

                      上报Metrics(Hystrix的Metrics中保存了当前服务的健康状况,包括服务调

                      用总次数和服务调用失败次数等。根据Metrics的计数,熔断器从而能计算

                      出当前服务的调用失率,用来和设定的阈值比较从而决定熔断器的状态切换

                      逻辑。)更新服务健康状况;若服务执行成功,返回正常结果。.

             总结:

                      1.关于Hystrix执行回退,以下三种情况会执行回退:

                     (1)执行超时等失败情况

                     (2)断路器打开(在此处顺便将断路器的相关知识做个介绍)

                              断路器打开条件:

                           (a)默认情况下10秒产生20次请求

                           (b)在满足(a)的情况下,当请求错误百分比大于阈值,默认50%,

                                    则会打开断器。下例为断路器打开的例子,其中条件(b)不变,条

                                    件(a)为10秒中产生10次请求。

                             

package org.crazyit.cloud.open;
 
import com.netflix.config.ConfigurationManager;
import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;
import com.netflix.hystrix.HystrixCommandProperties;
import com.netflix.hystrix.HystrixCommand.Setter;
 
public class OpenMain {
 
	public static void main(String[] args) {
		// 10秒内大于10次请求,满足第一个条件
		ConfigurationManager
				.getConfigInstance()
				.setProperty(
						"hystrix.command.default.circuitBreaker.requestVolumeThreshold",
						10);
		for(int i = 0; i < 15; i++) {
			ErrorCommand c = new ErrorCommand();
			c.execute();
			// 输出断路器状态
			System.out.println(c.isCircuitBreakerOpen());
		}
	}
 
	static class ErrorCommand extends HystrixCommand<String> {
		public ErrorCommand() {
			super(Setter.withGroupKey(
					HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))
					.andCommandPropertiesDefaults(
							HystrixCommandProperties.Setter()
									.withExecutionTimeoutInMilliseconds(500)));
		}
 
		@Override
		protected String run() throws Exception {
			Thread.sleep(800);
			return "";
		}
 
		@Override
		protected String getFallback() {
			return "fallback";
		}
		
		
	}
}

                      2.断路器关闭:短路器打开后会在之后的一段之间内处于打开状态(默认是

                                               5秒),我们管这个叫休眠期,休眠期满后, 断路器会尝试

                                               执行一次命令,如果执行成功断路器关闭,否则断路器仍然

                                               处于打卡状态。下例中,是断路器打开后自动关闭的例子,

                                               知识满足断路器打开的第一个条件是“10秒有3次请求”,满足

                                               断路器打开的第二个条件不变。

package org.crazyit.cloud.open;
 
import com.netflix.config.ConfigurationManager;
import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;
import com.netflix.hystrix.HystrixCommandMetrics.HealthCounts;
import com.netflix.hystrix.HystrixCommandProperties;
 
public class CloseMain {
 
	public static void main(String[] args) throws Exception {
		// 10秒内大于3次请求,满足第一个条件
		ConfigurationManager
				.getConfigInstance()
				.setProperty(
						"hystrix.command.default.circuitBreaker.requestVolumeThreshold",
						3);
		boolean isTimeout = true;
		for(int i = 0; i < 10; i++) {
			TestCommand c = new TestCommand(isTimeout);
			c.execute();
			HealthCounts hc = c.getMetrics().getHealthCounts();
			System.out.println("断路器状态:" + c.isCircuitBreakerOpen() + ", 请求数量:" + hc.getTotalRequests());
			if(c.isCircuitBreakerOpen()) {
				isTimeout = false;
				System.out.println("============  断路器打开了,等待休眠期结束");
				Thread.sleep(6000);
			}
		}
	}
 
	static class TestCommand extends HystrixCommand<String> {
		
		private boolean isTimeout;
		
		public TestCommand(boolean isTimeout) {
			super(Setter.withGroupKey(
					HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))
					.andCommandPropertiesDefaults(
							HystrixCommandProperties.Setter()
									.withExecutionTimeoutInMilliseconds(500)));
			this.isTimeout = isTimeout;
		}
 
		@Override
		protected String run() throws Exception {
			if(isTimeout) {
				Thread.sleep(800);
			} else {
				Thread.sleep(200);
			}			
			return "";
		}
 
		@Override
		protected String getFallback() {
			return "fallback";
		}
	}
}

2.命令执行:

(1)共有四种命令执行方式:

         HystrixCommand实现了execute()和queure();HystrixObervableCommand实现了observe()

         和toObservable()。

        execute():同步执行,重依赖的服务返回一个单一的结果对象,或是发生错误时抛出异常。

                          其实通过queue()返回的异步对象Future<R>的get()方法来实现同步执行的。该方

                          法会等待任务执行结束,然后获得R类型的结果进行返回。

        queue():异步执行,直接返回一个Future对象,其中包含了服务执行结束时要返回的单一结

                        果对象。

        observer():返回Observable对象,它代表了操作的多个结果,它是一个Hot Obervable。

        toObservable():同样会返回Observable对象,代表了操作的多个结果,但是它返回的是一个

                                   Cold Obervable。

        Hot Obervable和Cold Obervable分别对应command.observe()和command.toObservable()的

        返回对象。

        Hot Obervable:它不论“事件源”是否有订阅者,都会在创建后对事件进行发布,所以对于

                                   HOt Observable的每一个“订阅者”都可能是从“事件源”的中途开始的,并可

                                   能只是看到了整个操作的局部过程。

        Cold Obervable:在没有“订阅者”的时候并不会发布事件,而是进行等待,直到有“订阅者”之

                                     后才发布事件,所以对于Cold Obervable的订阅者,它可以保证重一开始看

                                     到整个操作的全部过程

(2)RxJava的观察者——订阅者模式。

         Hystrix底层实现中大量的使用了RxJava,为了容易理解后续内容,在这里RxJava对观察者

         ——订阅者做一个简单介绍。.

        上面提到的Observable对象称为“事件源“或“被观察者”,与其对应的Subscriber对象可以理解为

        “订阅者”或者 “观察者”。

        (a)Observable用来向订阅者Subscriber对象发布事件,Subscriber则在接收到事件后对其进

                 行处理。

        (b)一个Observable可以发出多个事件,直到结束或者发生异常。

        (c)Observable对象每发出一个事件,就会调用对应观察者Subscriber对象的onNext()方法。

        (d)每一个Observable的执行,最后一定会调用Subscriber.onCompleted()或是

                 Subscriber.onError()来结束事件的操作流。

        

(1)HystrixCommand:用于返回单一响应。

(2)HystrixObservableCommand:用于返回多个可自定义的响应。

(1)execute():以同步堵塞方式执行run()。以demo为例,调用execute()后,hystrix先创建一个新

                            线程运行run(),接着调用程序要在execute()调用处一直堵塞着,直到run()运行完

                            成。

(2)queue():以异步非堵塞方式执行run()。以demo为例,一调用queue()就直接返回一个Future

                         对象,同时hystrix创建一个新线程运行run(),调用程序通过Future.get()拿到run()

                         的返回结果,而Future.get()是堵塞执行的。

(3)observe():以异步非堵塞方式执行run()。事件注册前执行run()/construct()。以demo为例,第

                            一步是事件注册前,先调用observe()自动触发执 行run()/construct()(如果继承的

                            是HystrixCommand,hystrix将创建新线程非堵塞执行run();如果继承的是

                            HystrixObservableCommand,将以调用程序线程堵塞执行construct()); 第二步

                            是从observe()返回后调用程序调 用subscribe()完成事件注册,如果run()/construct()

                            执行成功则触发onNext()和onCompleted(),如果执行异常则触发onError()。

(4)toObservable():以异步非堵塞方式执行run()。事件注册后执行run()/construct()。以demo为

                                     例,第一步是事件注册前,一调用toObservable()就直接返回一个

                                     Observable<String>对象,第二步调用subscribe()完成事件注册后自动触发

                                    执行run()/construct()(如果继承的是HystrixCommand,hystrix将创建新线程

                                    非堵塞执行run(),调用程序不必等待run();如果继承的是

                                    HystrixObservableCommand,将以调用程序线程堵塞执行construct(),调用

                                    程序等待construct()执行完才能继续往下走),如果run()/construct()执行成功

                                    则触发onNext()和onCompleted(),如果执行异常则触发onError()

3.GroupKey、

 

            

            断路器关闭:短路器打开后会在之后的一段之间内处于打开状态(默认是5秒),我们管这个

                                 叫休眠期,休眠期满后, 断路器会尝试执行一次命令,如果执行成功断路器关

                                 闭,否则断路器仍然处于打卡状态。下例中,是断路器打开后自动关闭的例子,

                                 知识满足断路器打开的第一个条件是“10秒有3次请求”,满足断路器打开的第二

                                个条件不变。

package org.crazyit.cloud.open;

import com.netflix.config.ConfigurationManager;
import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;
import com.netflix.hystrix.HystrixCommandMetrics.HealthCounts;
import com.netflix.hystrix.HystrixCommandProperties;

public class CloseMain {

	public static void main(String[] args) throws Exception {
		// 10秒内大于3次请求,满足第一个条件
		ConfigurationManager
				.getConfigInstance()
				.setProperty(
						"hystrix.command.default.circuitBreaker.requestVolumeThreshold",
						3);
		boolean isTimeout = true;
		for(int i = 0; i < 10; i++) {
			TestCommand c = new TestCommand(isTimeout);
			c.execute();
			HealthCounts hc = c.getMetrics().getHealthCounts();
			System.out.println("断路器状态:" + c.isCircuitBreakerOpen() + ", 请求数量:" + hc.getTotalRequests());
			if(c.isCircuitBreakerOpen()) {
				isTimeout = false;
				System.out.println("============  断路器打开了,等待休眠期结束");
				Thread.sleep(6000);
			}
		}
	}

	static class TestCommand extends HystrixCommand<String> {
		
		private boolean isTimeout;
		
		public TestCommand(boolean isTimeout) {
			super(Setter.withGroupKey(
					HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))
					.andCommandPropertiesDefaults(
							HystrixCommandProperties.Setter()
									.withExecutionTimeoutInMilliseconds(500)));
			this.isTimeout = isTimeout;
		}

		@Override
		protected String run() throws Exception {
			if(isTimeout) {
				Thread.sleep(800);
			} else {
				Thread.sleep(200);
			}			
			return "";
		}

		@Override
		protected String getFallback() {
			return "fallback";
		}
	}
}

 

   (3)线程池满载或信号量达到阈值

            Hystrix默认使用的是线程池

            (a)THREAD(线程池):由线程池决定命令的执行,如果线程池满载就不会执行命令,

                                                          从而执行回退逻辑,默认线程池大小为10。

            (b)SEMAPHORE(信号量):当一个请求并发数高于设定的阈值时就不会执行该命令,

                                                                  从而执行回退逻辑,默认最大并发数是10。

            (c)THREAD和SEMAPHORE比较:SEMAPHORE资源较THREAD资源消耗量小,但是

                                                                         不支持超时和异步。

     线程池示例:

package org.crazyit.cloud;

import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;

public class MyCommand extends HystrixCommand<String> {
	
	private int index;

	public MyCommand(int index) {
		super(Setter.withGroupKey(HystrixCommandGroupKey.Factory
				.asKey("TestGroupKey")));
		this.index = index;
	}

	@Override
	protected String run() throws Exception {
		Thread.sleep(500);
		System.out.println("执行方法,当前索引:" + index);
		return "";
	}

	@Override
	protected String getFallback() {
		System.out.println("执行回退,当前索引:" + index);
		return "";
	}
	
	
}

 

package org.crazyit.cloud;

import com.netflix.config.ConfigurationManager;

public class ThreadMain {

	public static void main(String[] args) throws Exception {
		ConfigurationManager.getConfigInstance().setProperty(
				"hystrix.threadpool.default.coreSize", 4);
		for(int i = 0; i < 6; i++) {
			MyCommand c = new MyCommand(i);
			c.queue();
		}
		Thread.sleep(5000);
	}

}

       SEMAPHORE示例

package org.crazyit.cloud;

import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;

public class MyCommand extends HystrixCommand<String> {
	
	private int index;

	public MyCommand(int index) {
		super(Setter.withGroupKey(HystrixCommandGroupKey.Factory
				.asKey("TestGroupKey")));
		this.index = index;
	}

	@Override
	protected String run() throws Exception {
		Thread.sleep(500);
		System.out.println("执行方法,当前索引:" + index);
		return "";
	}

	@Override
	protected String getFallback() {
		System.out.println("执行回退,当前索引:" + index);
		return "";
	}
	
	
}

        

package org.crazyit.cloud;

import com.netflix.config.ConfigurationManager;
import com.netflix.hystrix.HystrixCommandProperties.ExecutionIsolationStrategy;

public class SemaphoreMain {

	public static void main(String[] args) throws Exception {
		ConfigurationManager.getConfigInstance().setProperty(
				"hystrix.command.default.execution.isolation.strategy", ExecutionIsolationStrategy.SEMAPHORE);
		ConfigurationManager.getConfigInstance().setProperty(
				"hystrix.command.default.execution.isolation.semaphore.maxConcurrentRequests", 3);
		for(int i = 0; i < 6; i++) {
			final int index = i;
			Thread t = new Thread() {
				public void run() {
					MyCommand c = new MyCommand(index);
					c.execute();
				}
			};
			t.start();
		}
		Thread.sleep(5000);
	}

}

 

5.回退模式

 

如果CommandA和CommandB中都有回退,那么MainCommand 中就不会执行回退;如果

CommandA和CommandB中没有回退,那么MainCommand中的回退才会执行。

6.缓存

  感觉没什么用,生产环境使用Redis就可以了,不过还是简单记录一下,保持知识结构的完整性

  缓存在一次请求中才有效。如下例(c1、c2、c3演示的是缓存机制,c4演示的是清空缓存):

package org.crazyit.cloud.cache;

import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;
import com.netflix.hystrix.HystrixCommandKey;
import com.netflix.hystrix.HystrixRequestCache;
import com.netflix.hystrix.HystrixThreadPoolKey;
import com.netflix.hystrix.HystrixCommand.Setter;
import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy;
import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategyDefault;
import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext;

public class CacheMain {

	public static void main(String[] args) {
		HystrixRequestContext ctx = HystrixRequestContext.initializeContext();
		String key = "cache-key";
		CacheCommand c1 = new CacheCommand(key);
		CacheCommand c2 = new CacheCommand(key);
		CacheCommand c3 = new CacheCommand(key);
		c1.execute();
		c2.execute();
		c3.execute();
		
		System.out.println("命令c1,是否读取缓存:" + c1.isResponseFromCache());
		System.out.println("命令c2,是否读取缓存:" + c2.isResponseFromCache());
		System.out.println("命令c3,是否读取缓存:" + c3.isResponseFromCache());
		
		HystrixRequestCache cache = HystrixRequestCache.getInstance(HystrixCommandKey.Factory.asKey("MyCommandKey"), 
				HystrixConcurrencyStrategyDefault.getInstance());
		cache.clear(key);
		CacheCommand c4 = new CacheCommand(key);
		c4.execute();
		System.out.println("命令c4,是否读取缓存:" + c4.isResponseFromCache());
		
		ctx.shutdown();
	}

	static class CacheCommand extends HystrixCommand<String> {
		private String cacheKey;
		public CacheCommand(String cacheKey) {
			super(Setter.withGroupKey(
					HystrixCommandGroupKey.Factory.asKey("TestGroupKey"))
					.andCommandKey(
							HystrixCommandKey.Factory.asKey("MyCommandKey")));
			this.cacheKey = cacheKey;
		}
		@Override
		protected String run() throws Exception {
			System.out.println("执行方法");
			return "";
		}
		@Override
		protected String getFallback() {
			System.out.println("执行回退");
			return "";
		}
		@Override
		protected String getCacheKey() {
			return this.cacheKey;
		}
		
	}
}

8.Hystrix合并请求:

   在调用通一个服务(URL相同,参数不同)时,可以将多个请求合并为一个请求

    例子(该处例子是于spring cloud整合的):

    

package org.crazyit.cloud.collapser;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Future;

import org.crazyit.cloud.Member;
import org.springframework.stereotype.Service;

import com.netflix.hystrix.contrib.javanica.annotation.HystrixCollapser;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;

@Service
public class CollService {

	@HystrixCollapser(batchMethod = "getMembers", collapserProperties = {
			@HystrixProperty(name = "timerDelayInMilliseconds", value = "1000")
	})
	public Future<Member> getMember(Integer id) {
		System.out.println("执行单个查询的方法");
		return null;
	}
	
	@HystrixCommand
	public List<Member> getMembers(List<Integer> ids) {
		List<Member> mems = new ArrayList<Member>();
		for(Integer id : ids) {
			System.out.println(id);
			Member m = new Member();
			m.setId(id);
			m.setName("angus");
			mems.add(m);
		}
		return mems;
	}
}
package org.crazyit.cloud.collapser;

import java.util.concurrent.Future;

import org.crazyit.cloud.Member;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class CollController {
	
	@Autowired
	private CollService collService;

	@RequestMapping(value = "/coll", method = RequestMethod.GET, 
			produces = MediaType.APPLICATION_JSON_VALUE)
	public String testCollapse() throws Exception {
		// 连续执行3次请求
		Future<Member> f1 = collService.getMember(1);
		Future<Member> f2 = collService.getMember(2);
		Future<Member> f3 = collService.getMember(3);
		Member p1 = f1.get();
		Member p2 = f2.get();
		Member p3 = f3.get();	
		return "";
	}
}

 

9.spring cloud整合Hystrix

  (1)使用RestTemplate

          (a)Service

package org.crazyit.cloud;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;

@Service
//@DefaultProperties(defaultFallback = "getMemberFallback")
public class MemberService {

	@Autowired
	private RestTemplate restTpl;

	@HystrixCommand(fallbackMethod = "getMemberFallback", groupKey = "MemberGroup", commandKey = "MemberCommandKey", 
			commandProperties = {
			@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1000")
	}, threadPoolProperties = {
			@HystrixProperty(name = "coreSize", value = "2")
	})
	public Member getMember(Integer id) {
		try {
			Thread.sleep(2000);
		} catch (Exception e) {
			
		}
		
		Member member = restTpl.getForObject(
				"http://spring-hy-member/member/{id}", Member.class, id);
		return member;
	}

	public Member getMemberFallback(Integer id) {
		Member m = new Member();
		m.setId(1);
		m.setName("error member");
		return m;
	}
}

          (b)controller

package org.crazyit.cloud;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
public class TestController {

	@Autowired
	private MemberService memberService;

	@RequestMapping(value = "/router", method = RequestMethod.GET, 
			produces = MediaType.APPLICATION_JSON_VALUE)
	public Member router() {
		return memberService.getMember(1);
	}

}

10.Hystrix与Feign整合(重点)

    直接看例子:其中hello是测试回退的方法;toHello是测试断路器的方法

package org.crazyit.cloud.feign;

import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@FeignClient(name = "spring-hy-member", fallback = HelloClientFallback.class)
public interface HelloClient {

	@RequestMapping(method = RequestMethod.GET, value = "/hello")
	public String hello();
	
	@RequestMapping(method = RequestMethod.GET, value = "/toHello")
	public String toHello();
}

    

package org.crazyit.cloud.feign;

import org.springframework.stereotype.Component;

@Component
public class HelloClientFallback implements HelloClient {

	public String hello() {
		return "fallback hello";
	}

	public String toHello() {
		return "fallback timeout hello";
	}

}

配置断路器的超时时间、和打开需要的请求次数(标红的部分),hello方法不需要该配置,hello

关于断路器的配置是写在代码中的(标签)。

配置指定方法(toHello())超过500毫秒则执行回退方法:

server:
  port: 8081
spring:
  application:
    name: spring-hy-sale
feign:
  hystrix:
    enabled: true
hystrix:
  command:
    HelloClient#toHello():
      execution:
        isolation:
          thread: 
            timeoutInMilliseconds: 500
      circuitBreaker:
        requestVolumeThreshold: 3
eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/

配置所有方法如执行超过500毫秒就执行回退方法:

server:
  port: 8081
spring:
  application:
    name: spring-hy-sale
feign:
  hystrix:
    enabled: true
hystrix:
  command:
    default:
      execution:
        isolation:
          thread: 
            timeoutInMilliseconds: 500
      circuitBreaker:
        requestVolumeThreshold: 3
eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/

Zuul

1.功能和特性:

  (1)提供代理、过滤、路由等功能,不用暴露服务,提升安全性;

  (2)与Ribbon是整合的,先天支持负载均衡

  (3)Zuul可做负载均衡和反向代理

           关于负载均衡说明:下面的例子中若有若干个服务,服务的serviceId的名称相同,Zull就

           可以根据serviceId做负载均衡      

 

zuul:
  routes:
#Ribbon路由
    consumer:
      path: /consumer/**
      serviceId: consumer1

 

2.工作原理如下面三张图:

 

上图中三类过滤器:“pre”过滤器,"routing"过滤器、"post"过滤器,代表的都是一个阶段,其由

若干个过滤器组成(由若干个过滤器组成),其中“routing”过滤器是真正转发请求的,具有路由

功能。

       下图体现了过滤器的优先级,并代表@EnableZuulProxy。其中数字越小优先级越低,在

routing过滤阶段,只会选择一个过滤器去执行。

下图是@EnableZuulServer的执行流程,灰掉的是不执行的过滤器,所以@EnableZuulServer

 是一个低配版的@EnableZuulProxy

3.加入Zuul后的集群

4.更换Http客户端

默认使用HttpClient
可更换OkHttpClient

ribbon.okhttp.enabled=true

<groupId>com.squareup.okhttp3</groupId>

<artifactId>okhttp</artifactId>

OKHttpClient是一款http客户端,用来发送Http请求的。

5.路由配置

(1)路由的分类

  (a)简单路由:以Http或Https开头的是简单路由,例子如下

 zuul:
  routes:
        #简单路由第一种配置方式
        testSimpleRoute:
              path: /simpleRouteTest/163
                  url: http://www.163.com
        #简单路由第二种配置方式(省略path)
        simpleRouteTest163:

             url: http://www.163.com

    (b)跳转路由:以forward开头的,使用的是dispatcher的forward,响应请求的其实是

             forward去的地址。

        zuul:
          routes:
            #跳转路由
                testForward:
                  path: /foo/**
                  url: forward:/testForward

    (c)Ribbo路由

        zuul:
              routes:
                #Ribbon路由
                consumer:
                      path: /consumer/**

                      serviceId: consumer1

    (d)自定义路由:没学明白

(2).请求头配置,可以指定请求头信息不被转发,默认accept-language, cookie是不转发的

如下例:

zuul:

  sensitiveHeaders: accept-language, cookie

(3).路由端点:展示路由的映射关系

6.过滤器

(1)Zuu自带的过滤器及其优先级

其中数字越小优先级越高,routing阶段的过滤器只会选择一个去执行(通过外部的一个值来保证

过滤器执行的唯一性)、

(2)动态路由:没讲

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值