SpringCloud详细搭建(下篇)【Finchley版本】
github源码下载:https://github.com/LuckyShawn/simple-spring-cloud
1.断路器Hystrix
Hystrix简述:
-
Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。
-
“断路器”本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。
作用:服务降级,服务熔断,服务限流,接近实时的监控 等
官网:https://github.com/Netflix/Hystrix/wiki/How-To-Use
1.1 ribbon-hystrix版断路器
- 创建ribbon-hystrix服务
pom.xml增加:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
2.创建HelloService
package com.shawn.serverhystrix.service;
@Service
public class HelloService {
@Autowired
private RestTemplate restTemplate;
//一旦调用服务方法失败并抛出了错误信息后,
//会自动调用@HystrixCommand标注好的fallbackMethod调用类中的指定方法
@HystrixCommand(fallbackMethod = "helloError")
public String hello(String name){
return restTemplate.getForObject("http://eureka-client/hello?name="+name,String.class);
}
public String helloError(String name){
return "hello!"+name+",An Error Has Occurred";
}
}
3.创建HelloController,ConfigBeans
package com.shawn.serverhystrix.controller;
@RestController
public class HelloController {
@Autowired
private HelloService helloService;
@GetMapping("/hello")
public String hello(String name){
return helloService.hello(name);
}
}
package com.shawn.serverhystrix.config;
@Configuration
public class ConfigBeans {
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
- 修改主启动类
@SpringBootApplication
@EnableHystrix //开启断路器功能
@EnableEurekaClient
public class ServerHystrixApplication {
public static void main(String[] args) {
SpringApplication.run(ServerHystrixApplication.class, args);
}
}
//交替出现
Hello!!shawn,I am from:8763
Hello!!shawn,I am from:8762
停止一个eureka-client8763
重复访问:http://localhost:8766/hello?name=shawn
//在一定访问次数交替出现
Hello!!shawn,I am from:8762
hello!shawn,An Error Has Occurred
在多访问几次,不再出现‘hello!shawn,An Error Has Occurred’,说明不在访问8763端口。避免了整体服务失败和雪崩效应。
2.feign版断路器
- 创建feign-hystrix服务
- 新建并编写application.yml
server:
port: 8767
spring:
application:
name: feign-hystrix
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
- 创建FeignService接口
package com.shawn.feignhystrix.service;
//fallback的指定类,发生熔断执行的类
@FeignClient(value = "eureka-client",fallback = FeignServiceHystrix.class)
public interface FeignService {
@RequestMapping(value = "/hello",method = RequestMethod.GET)
String sayHello(@RequestParam(value = "name")String name);
}
- 创建熔断类FeignServiceHystrix 实现FeignService接口
package com.shawn.feignhystrix.hystrix;
@Component
public class FeignServiceHystrix implements FeignService {
@Override
public String sayHello(String name) {
return "Sorry!"+name+",An Error Has Occurred!";
}
}
- 创建FeignController
package com.shawn.feignhystrix.controller;
@RestController
public class FeignController {
@Autowired
private FeignService feignService;
@GetMapping("/hello")
public String sayHello(String name){
return feignService.sayHello(name);
}
}
- 修改主启动类
@SpringBootApplication
@EnableFeignClients
@EnableEurekaClient
public class FeignHystrixApplication {
public static void main(String[] args) {
SpringApplication.run(FeignHystrixApplication.class, args);
}
}
7.测试方法和结果与ribbon-hystrix相同
补充:其实feign是自带断路器的,可以试着把hystrix的依赖注释掉,也一样可以测试成功。
2.路由网关Zuul
- 概述:
-
Zuul包含了对请求的路由和过滤两个最主要的功能:
其中路由功能负责将外部请求转发到具体的微服务实例上,是实现外部访问统一入口的基础而过滤器功能则负责对请求的处理过程进行干预,是实现请求校验、服务聚合等功能的基础. -
Zuul和Eureka进行整合,将Zuul自身注册为Eureka服务治理下的应用,同时从Eureka中获得其他微服务的消息,也即以后的访问微服务都是通过Zuul跳转后获得。
注意:Zuul服务最终还是会注册进Eureka
-
代理+路由+过滤三大功能
- 创建工程zuul-gateway
pom.xml新增:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
- 新增并编写application.yml
server:
port: 8768
spring:
application:
name: server-zuul
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
# serviceId必须与相应的服务名字对应!
zuul:
prefix: /shawn #设置统一域名前缀
routes:
zuul-a:
path: /zuul-a/**
serviceId: consumer-ribbon
zuul-b:
path: /zuul-b/**
serviceId: consumer-feign
zuul-c:
path: /zuul-c/**
serviceId: ribbon-hystrix
zuul-d:
path: /zuul-d/**
serviceId: feign-hystrix
- 修改主启动类
@SpringBootApplication
@EnableZuulProxy //开启zuul路由功能
@EnableEurekaClient
public class ServerZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ServerZuulApplication.class, args);
}
}
- 启动测试
现在的结构是:
重复访问http://localhost:8768/shawn/zuul-a/hello?name=shawn
//交替出现
Hello!!shawn,I am from:8762
Hello!!shawn,I am from:8763
依次访问zuul-b,zuul-c,zuul-d,都是交替出现,说明路由成功且负载均衡功能正常。
- 测试断路器功能
需要修改zuul项目的application.yml
server:
port: 8768
spring:
application:
name: server-zuul
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
# serviceId必须与相应的服务名字对应!
zuul:
prefix: /shawn #设置统一域名前缀
routes:
zuul-a:
path: /zuul-a/**
serviceId: consumer-ribbon
zuul-b:
path: /zuul-b/**
serviceId: consumer-feign
zuul-c:
path: /zuul-c/**
serviceId: ribbon-hystrix
zuul-d:
path: /zuul-d/**
serviceId: feign-hystrix
host:
socket-timeout-millis: 60000
connect-timeout-millis: 60000
ribbon:
ReadTimeout: 6000000
ConnectTimeout: 6000000
hystrix:
command:
c4i-store:
execution:
timeout:
enabled: true
isolation:
thread:
timeoutInMilliseconds: 6000000
ribbon:
ReadTimeout: 600000
ConnectTimeout: 600000
#如果未配置以上的超时时间 就会com.netflix.zuul.exception.ZuulException: Forwarding error报错
重复访问http://localhost:8768/shawn/zuul-d/hello?name=shawn
8763,8762交替
关闭Eureka-client 8762端口
重复访问http://localhost:8768/shawn/zuul-d/hello?name=shawn
说明断路器功能正常,负载均衡功能也正常,重复访问多次后,不再访问8762端口。
3.Zuul拦截功能
添加一个Myfilter类集成ZuulFilter
package com.shawn.serverzuul.filter;
@Component
public class Myfilter extends ZuulFilter {
private static Logger logger = LoggerFactory.getLogger(Myfilter.class);
// filterType:返回一个字符串代表过滤器的类型,在zuul中定义了四种不同生命周期的过滤器类型,具体如下:
// pre:路由之前
// routing:路由之时
// post: 路由之后
// error:发送错误调用
@Override
public String filterType() {
return "pre";
}
//filterOrder:过滤的顺序
@Override
public int filterOrder() {
return 0;
}
//shouldFilter:这里可以写逻辑判断,是否要过滤,本文true,永远过滤。
@Override
public boolean shouldFilter() {
return true;
}
//run:过滤器的具体逻辑。可用很复杂,包括查sql,nosql去判断该请求到底有没有权限访问。
@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
Object password = request.getParameter("password");
if(null == password){
logger.warn("密码为空!");
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(500);
try {
ctx.getResponse().getWriter().write("登录失败!");
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
if("123456".equals(password)){
logger.info("登录成功!");
return null;
}else{
logger.info("密码错误!");
ctx.setSendZuulResponse(true);
ctx.setResponseStatusCode(500);
try {
ctx.getResponse().getWriter().write("登录失败!");
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
}
重启server-zuul,访问:http://localhost:8768/shawn/zuul-a/hello?name=shawn&password=123
访问:http://localhost:8768/shawn/zuul-a/hello?name=shawn&password=123456