什么是服务雪崩?
参考: <<重新定义spring cloud>>
代码:https://gitee.com/08081/hello-springcloud/tree/springcloud-fallback/
在微服务中,我们是服务于服务之间调用,当在微服务突然有大量的请求过来,一个服务瘫痪之后,后面的服务的请求积压,这就造成了服务雪崩!
一个服务瘫痪,另外调用这个服务的服务就会超时,导致整个服务群瘫痪.
造成雪崩的原因可以归结为以下三点:
1. 服务提供者不可用
2. 重试加大流量(用户重试,代码逻辑重试)
3. 服务调用者不可用(同步等待造成的资源耗尽)
最终的结果就是一个服务不可用,导致一系列的服务不可用,这种后果无法预料
如何解决在灾难性雪崩效应?
1.降级:
2.隔离(线程池隔离和信号量隔离): 限制调用分布式服务的资源使用,某一个调用的服务出现问题不会影响其他服务调用
3.熔断: 当失败率(如因网络故障/超时造成的失败率高)达到阈值自动触发降级.熔断器触发的快速失败会进行快速恢复
4.缓存:提供请求缓存
5.提供请求合并
解决方案之一服务降级
先说遇到的坑吧
1. 坑1 mapping 报错.feign调用服务端的时候说实例已经实例过了
当时报错的时候错误信息没有记录.
//@FeignClient(name = "book-service",fallback = BookServiceFallback.class,path = "/")
2.坑2 在写好fallback方法之后,一直调用fallback方法,不调用原来的方法.不知为什么hystrix的默认时间一直1秒钟
尝试配置了很多次.最终结合官网解决:
feign:
hystrix:
enabled: true
#局部配置 这个必须加
client:
config:
default:
connectTimeout: 5000
readTimeout: 5000
loggerLevel: basic
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 2000
3.坑3: 使用fallbackFactory报错
要是因为我返回空的list还去添加
代码
如何下载代码运行案例?
1.到码云下下载项目
2.运行ch3-eureka-ribbon 下的eureka-server
3.运行ch4-fegin下的ch4-book-service
4.运行ch6-hystrix下的book-consumer-hystrix
我创建了一个子项目book-consumer-hystrix
pom文件如下:
spring:
application:
name: book-hystrix
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:8888/eureka/
instance:
prefer-ip-address: true
server:
port: 8002
feign:
hystrix:
enabled: true
#局部配置
client:
config:
default:
connectTimeout: 5000
readTimeout: 5000
loggerLevel: basic
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 5000
View Code
启动类:
/**
* Created by xiaodao
* date: 2019/7/18
*/
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
@EnableHystrix
public class BookConsumerHystrixApplication {
public static void main(String[] args) {
SpringApplication.run(BookConsumerHystrixApplication.class,args);
}
}
View Code
feignClient调用类:
注意坑2.的解决方案:必须加path
/**
* Created by xiaodao
* date: 2019/7/17
*/
@Component
//@FeignClient(name = "book-service",fallback = BookServiceFallback.class,path = "/")
@FeignClient(name = "book-service",fallbackFactory = WebFeignFallbackFactory.class,path = "/")
public interface BookService extends BookApi {
}
controller 调用类:
/**
* Created by xiaodao
* date: 2019/7/17
*/
@RestController
@RequestMapping("/book")
@AllArgsConstructor
public class BookController {
private BookService bookService;
@GetMapping("/findList")
public List<Book> findList(){
return bookService.findList();
}
}
fallback的俩个类
一个是正常的fallback
另一个是打印错误日志的fallbackFactory
/**
* Created by xiaodao
* date: 2019/7/18
*/
@Component
public class BookServiceFallback implements BookService {
public List<Book> findList() {
return Collections.emptyList();
}
}
/**
* Created by xiaodao
* date: 2019/7/18
* 这个fallback工厂类为了查看回退的原因的异常信息
*/
@Component
public class WebFeignFallbackFactory implements FallbackFactory<BookService> {
private static final Logger LOGGER = LoggerFactory.getLogger(WebFeignFallbackFactory.class);
@Override
public BookService create(Throwable throwable) {
return new BookService(){
// 日志最好放在各个fallback方法中,而不要直接放在create方法中
@Override
public List<Book> findList() {
WebFeignFallbackFactory.LOGGER.info("fallback; reason was:", throwable);
Book book = Book.builder().id(1000).name("异常回退!").build();
List<Book> list = new ArrayList<>();
list.add(book);
return list;
};
};
}
}
以下4种情况触发fallback调用
1.方法抛出非HystrixBadRequestException异常
2.方法调用超时
3.熔断器开启拦截调用
4.线程池/信号量/队列是否跑满
解决方案之一服务请求缓存
hystrix 有俩个缺点:
1.不支持集群
2.不支持第三方缓存
所以我们做集成第三方缓存,并且可以支持集群.所以我们选用redis, 在书上,和网上的案例很多都是基于hystrixCommand 来实现的,我们来来模仿真实案例来实现.
ch6下创建俩个项目
1.book-consumer-hystrix-cache
2.book-service-cache
启动eurekaserver服务
代码
book-consumer-hystrix-cache服务
controller 添加了俩个方法
@RestController
@RequestMapping("/book-consumer")
@AllArgsConstructor
public class BookController {
private BookService bookService;
@GetMapping("/findList")
public List<Book> findList(){
return bookService.findList();
}
@GetMapping("get/{id}")
public Book get(@PathVariable(value = "id") Integer id ){
return bookService.get(id);
}
@GetMapping("del/{id}")
public Book del(@PathVariable(value = "id") Integer id ){
return bookService.del(id);
}
}
View Code
service:
@FeignClient(name = "book-service-cache",path = "/")
public interface BookService extends BookApi {
}
book-service-cache 服务
pom文件 依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
bootstrap.yml配置了redis的连接池
spring:
application:
name: book-service-cache
#redis的索引默认是0
redis:
database: 1
host: localhost
port: 6379
password:
lettuce:
#负数表示没有限制
pool:
max-active: 100
#最大空闲连接
max-idle: 10
#连接池最大阻塞等待时间(使用负数表示没有限制)
max-wait: -1ms
#连接池最小空闲连接
min-idle: 0
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:8888/eureka/
instance:
prefer-ip-address: true
server:
port: 8000
View Code
bookServiceImpl
/**
* Created by xiaodao
* date: 2019/7/17
*/
@RestController
@RequestMapping
@CacheConfig(cacheNames = {"xiaodao.book"})
public class BookServiceImpl implements BookApi {
// @GetMapping(value = "list")
@Override
public List<Book> findList() {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
Book book =Book.builder().id(1).name("第一本书").build();
System.out.println(book);
List<Book> list = new CopyOnWriteArrayList<>();
list.add(book);
return list;
}
@Cacheable(key = "'get-' + #id")
@Override
public Book get(Integer id) {
System.out.println("-----查询------"+id+"-------");
return Book.builder().id(id).name("使用缓存").build();
}
@CacheEvict(key = "'get-'+ #id")
@Override
public Book del(Integer id) {
System.out.println("------删除-----"+id+"-------");
return Book.builder().id(id).name("使用缓存").build();
}
}
View Code
还有book-api也需要改造一下.
就是加了俩个接口.
public interface BookApi {
@GetMapping(value = "/book/list")
List<Book> findList();
@GetMapping(value = "/book/get/{id}")
Book get(@PathVariable(value = "id") Integer id );
@GetMapping(value = "/book/del/{id}")
Book del(@PathVariable(value = "id") Integer id );
}
View Code
上面基本就是缓存方案的所有代码的所有代码
贴一张注册中心的图:
http://localhost:8002/book-consumer/get/100
{"id":100,"name":"使用缓存"}
在后台看到无论请求多少遍.只有第一遍,会加载后去就会到缓存中查找
------删除-----100-------
------删除-----100-------
------删除-----100-------
-----查询------100-------
删除的时候回调用多次,这样我们就实现了请求缓存