共计 4881 个字符,预计需要花费 13 分钟才能阅读完成。
在之前的 18 次文章中,我们实现了广告系统的 广告投放
, 广告检索
业务功能,中间使用到了 服务发现 Eureka
,服务调用 Feign
,网关路由 Zuul
以及 错误熔断 Hystrix
等 Spring Cloud
组件。
简单调用关系:
但是系统往往都会报错,我们之前定义了一些容错类和方法,但是只是在控制台可以看到错误信息,我们想要统计一些数据,怎么才能更直观的看到我们的服务调用情况呢,接下来,和大家讨论一个新的熔断监控组件Hystrix Dashboard
, 顾名思义,从名字上我们就能看出来,它是监控的图形化界面。
Hystrix 在服务中的使用
结合 openfeign 使用
在我们实际的项目当中,使用的最多的就是结合 FeignClient#fallback
和Hystrix
一起来实现熔断,我们看一下我们在 mscx-ad-feign-sdk
中的实现。
@FeignClient(value = "mscx-ad-sponsor", fallback = SponsorClientHystrix.class)
public interface ISponsorFeignClient {@RequestMapping(value = "/ad-sponsor/plan/get", method = RequestMethod.POST)
CommonResponse<List<AdPlanVO>> getAdPlansUseFeign(@RequestBody AdPlanGetRequestVO requestVO);
@RequestMapping(value = "/ad-sponsor/user/get", method = RequestMethod.GET)
/**
* Feign 埋坑之 如果是 Get 请求,必须在所有参数前添加{@link RequestParam}, 不能使用{@link Param}
* 会被自动转发为 POST 请求。*/
CommonResponse getUsers(@RequestParam(value = "username") String username);
}
在上述代码中,我们自定义了一个 feignclient, 并且给了这个 client 一个 fallback 的实现类:
@Component
public class SponsorClientHystrix implements ISponsorFeignClient {
@Override
public CommonResponse<List<AdPlanVO>> getAdPlansUseFeign(AdPlanGetRequestVO requestVO) {return new CommonResponse<>(-1, "mscx-ad-sponsor feign & hystrix get plan error.");
}
@Override
public CommonResponse getUsers(String username) {return new CommonResponse<>(-1, "mscx-ad-sponsor feign & hystrix get user error.");
}
}
这个 fallback 类实现了我们自定义的 ISponsorFeignClient
,那是因为 fallback 的方法必须和原始执行类的方法签名保持一致,这样在执行失败的时候,可以通过反射映射到响应的降级方法 / 容错方法。
在mscx-ad-search
服务中,我们通过注入 ISponsorFeignClient
来调用我们的 mscz-ad-sponsor
服务。
@RestController
@Slf4j
@RequestMapping(path = "/search-feign")
public class SearchFeignController {
/**
* 注入我们自定义的 FeignClient
*/
private final ISponsorFeignClient sponsorFeignClient;
@Autowired
public SearchFeignController(ISponsorFeignClient sponsorFeignClient) {this.sponsorFeignClient = sponsorFeignClient;}
@GetMapping(path = "/user/get")
public CommonResponse getUsers(@Param(value = "username") String username) {log.info("ad-search::getUsersFeign -> {}", JSON.toJSONString(username));
CommonResponse commonResponse = sponsorFeignClient.getUsers(username);
return commonResponse;
}
}
使用HystrixCommand
其实 Hystrix 本身提供了一种直接在方法中应用的方式,就是使用@ com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand
, 我们看一下这个类的源码:
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface HystrixCommand {
...
/**
* Specifies a method to process fallback logic.
* A fallback method should be defined in the same class where is HystrixCommand.
* Also a fallback method should have same signature to a method which was invoked as hystrix command.
* for example:
* <code>
* @HystrixCommand(fallbackMethod = "getByIdFallback")
* public String getById(String id) {...}
*
* private String getByIdFallback(String id) {...}
* </code>
* Also a fallback method can be annotated with {@link HystrixCommand}
* <p/>
* default => see {@link com.netflix.hystrix.contrib.javanica.command.GenericCommand#getFallback()}
*
* @return method name
*/
String fallbackMethod() default "";
...
}
我们主要关注 2 个点:
-
@Target({ElementType.METHOD})
表明当前的注解只能应用在方法上面。 - 可直接定义
fallbackMethod
来保证容错。这个方法有一个缺陷,就是必须和执行方法在同一个类文件中,这就会造成我们的方法在实现的时候,显得特别的冗余和不够优雅。
以我们的 mscx-ad-search
中的广告查询为例:
@Service
@Slf4j
public class SearchImpl implements ISearch {
/**
* 查询广告容错方法
*
* @param e 第二个参数可以不指定,如果需要跟踪错误,就指定上
* @return 返回一个空 map 对象
*/
public SearchResponse fetchAdsFallback(SearchRequest request, Throwable e) {System.out.println("查询广告失败,进入容错降级 : %s" + e.getMessage());
return new SearchResponse().builder().adSlotRelationAds(Collections.emptyMap()).build();}
@HystrixCommand(fallbackMethod = "fetchAdsFallback")
@Override
public SearchResponse fetchAds(SearchRequest request) {...}
}
在我们请求出错的时候,会转到我们的 fallback 方法,这个实现是通过在应用启动的时候,我们开始了 @EnableCircuitBreaker
注解,这个注解会通过 AOP 拦截所有的 HystrixCommand
方法,将 HystrixCommand
整合到 springboot 的容器中,并且将注解标注的方法放入 hystrix 的线程中,一旦失败,通过反射调用 fallback 方法来实现。
创建 dashboard project
上述代码我们看了 Hystrix 实现熔断的 2 种方式,接下来我们来实现请求监控的图形化界面,创建 mscx-ad-dashboard
,Let’s code.
依然遵从我们 springboot 项目的三部曲:
-
加依赖
<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-hystrix</artifactId> <version>1.2.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-hystrix-dashboard</artifactId> <version>1.2.7.RELEASE</version> </dependency> <!--eureka client--> <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-actuator</artifactId> </dependency> </dependencies>
-
加注解
/**
*
* @author <a href="mailto:magicianisaac@gmail.com">Isaac.Zhang | 若初 </a>
* @since 2019/8/15
*/
@SpringBootApplication
@EnableDiscoveryClient
@EnableHystrixDashboard
public class AdDashboardApplication {public static void main(String[] args) {SpringApplication.run(AdDashboardApplication.class, args);
}
}
```
-
改配置
server: port: 1234 spring: application: name: mscx-ad-dashboard eureka: client: service-url: defaultZone: http://server1:7777/eureka/,http://server2:8888/eureka/,http://server3:9999/eureka/ management: endpoints: web: exposure: include: "*"`
直接启动, 可以看到如下页面:
添加要监控的服务地址: