一.搭建空项目以及eureka服务
- New EmptyProject–>New Module–>Spring Cloud Discovery(选择Eureka Server)
- pom.xml中添加阿里云镜像
<repositories>
<repository>
<id>central</id>
<name>aliyun maven</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<layout>default</layout>
<!-- 是否开启发布版构件下载 -->
<releases>
<enabled>true</enabled>
</releases>
<!-- 是否开启快照版构件下载 -->
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
- 修改配置文件
server:
port: 8761
eureka:
instance:
hostname: localhost
client:
register-with-eureka: false
fetch-registry: false
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
- 访问:http://localhost:8761/
二.搭建商品服务
- New Module–>Web(选择Spring Web)、Spring Cloud Discovery(选择Eureka Discovery Client)
- 新建一个controller,代码如下
@RestController
@RequestMapping("/api/v1/product")
public class ProductController {
@Autowired
private ProductService productService;
@RequestMapping("list")
public Object list(){
return productService.listProduct();
}
@RequestMapping("find")
public Object list(@RequestParam("id") int id){
return productService.findByid(id);
}
}
- 新建service接口
public interface ProductService {
List<Product> listProduct();
Product findByid(int id);
}
- 新建service实现类
@Service
public class ProductServiceImpl implements ProductService {
private static Map<Integer,Product> daoMap = new HashMap<>();
static {
Product p1 = new Product(1, "iphone1", 111, 11);
Product p2 = new Product(2, "iphone2", 222, 12);
Product p3 = new Product(3, "iphone3", 333, 13);
Product p4 = new Product(4, "iphone4", 444, 14);
Product p5 = new Product(5, "iphone5", 555, 15);
Product p6 = new Product(6, "iphone6", 666, 16);
Product p7 = new Product(7, "iphone7", 777, 17);
daoMap.put(p1.getId(),p1);
daoMap.put(p2.getId(),p2);
daoMap.put(p3.getId(),p3);
daoMap.put(p4.getId(),p4);
daoMap.put(p5.getId(),p5);
daoMap.put(p6.getId(),p6);
daoMap.put(p7.getId(),p7);
}
@Override
public List<Product> listProduct() {
Collection<Product> collection = daoMap.values();
List<Product> list = new ArrayList<>(collection);
return list;
}
@Override
public Product findByid(int id) {
return daoMap.get(id);
}
}
- 修改配置
server:
port: 8771
spring:
application:
name: product-service
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
a. 注册中心报错:EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY'RE NOT. RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUST TO BE SAFE
b. 原因:这是eureka自我保护的一个警告
c. 解决:可以在配置文件中配置
server:
port: 8771
spring:
application:
name: product_service
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
#关闭自我保护
server:
enable-self-preservation: false
-
访问:
http://localhost:8771/api/v1/product/list
http://localhost:8771/api/v1/product/find?id=1 -
为什么只加配置文件,product服务就可以注册到eureka服务上,只要类路径下有spring-cloud-starter-netflix-client依赖,服务就会自动注册到eureka服务上
三.搭建订单服务
- 服务间调用方式
a. RPC:远程过程调用 ,像本地服务(方法)一样调用服务器的服务,客户端和服务器之间建立TCP连接,可以一次建立一个,也可以多个调用复用一次链接,数据包小
b. Rest(Http):发起http请求,数据包大。使用HttpClient、URLConnection - New Module–>Web(选择Spring Web)、Spring Cloud Discovery(选择Eureka Discovery Client)、Cloud Routing(选择Ribbon)
- Ribbon类似httpClient、URLConnection
- 创建controller
@RestController
@RequestMapping("api/v1/order")
public class OrderController {
@Autowired
private OrderService orderService;
@RequestMapping("save")
public Object save(@RequestParam("user_id")int userId, @RequestParam("product_id")int productId){
return orderService.save(userId, productId);
}
}
- 创建service接口
public interface OrderService {
ProductOrder save(int userId, int productId);
}
- 创建service实现类
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private RestTemplate restTemplate;
@Override
public ProductOrder save(int userId, int productId) {
Object obj = restTemplate.getForObject("http://PRODUCT-SERVICE/api/v1/product/find?id=" + productId, Object.class);
System.out.println(obj);
ProductOrder productOrder = new ProductOrder();
productOrder.setCreateTime(new Date());
productOrder.setUserId(userId);
productOrder.setTradeNo(UUID.randomUUID().toString());
return productOrder;
}
}
- 增加配置
server:
port: 8781
spring:
application:
name: order-service
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
- 启动类增加
@SpringBootApplication
public class OrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
- 请求订单服务:http://127.0.0.1:8781/api/v1/order/save?product_id=3&user_id=4
- ribbon实现原理:调用方向注册中心拉取可调用列表,然后ribbon根据策略选取调用服务
- 自定义负载均衡策略:
a. 通过设置配置文件:
server:
port: 8781
spring:
application:
name: order-service
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
#自定义负载均衡策略
product-service:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
- 访问:http://127.0.0.1:8781/api/v1/order/save?product_id=3&user_id=4
四.feigin改造订单服务
- 新增feign依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
- 在订单服务service中新加一个接口,接口上新加注解以及添加需要调用服务的方法,请求url和入参需要和🔐调用服务方法一致
@FeignClient(name = "product-service")
public interface ProductClient {
@RequestMapping("/api/v1/product/find")
String findById(@RequestParam("id") int id);
}
- 然后对这个接口进行代码调用
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private RestTemplate restTemplate;
@Autowired
private ProductClient productClient;
@Override
public ProductOrder save(int userId, int productId) {
//Object obj = restTemplate.getForObject("http://PRODUCT-SERVICE/api/v1/product/find?id=" + productId, Object.class);
String response = productClient.findById(productId);
JsonNode jsonNode = JsonUtils.str2JsonNode(response);
System.out.println(jsonNode);
ProductOrder productOrder = new ProductOrder();
productOrder.setCreateTime(new Date());
productOrder.setUserId(userId);
productOrder.setTradeNo(UUID.randomUUID().toString());
return productOrder;
}
}
- 访问:http://127.0.0.1:8781/api/v1/order/save?product_id=3&user_id=4
四.ribbon和feigin源码解读
- spring启动时候会对注解进行扫描,扫到@EnableFeignClients,开启一个feigin客户端,会扫描@FeignClient类,并对类进行实例化,放到IOC容器中,然后拦截请求,然后通过动态代理生成RestTemplate
五.服务降级熔断
概念相关
-
概念介绍
a. 熔断:防止整个系统故障,包含子系统和 下游服务 b. 降级:抛弃非核心 的接口和数据。熔断一般是下游 服务故障导致,服务降级一般是从整体系统负荷考虑,由调用方控制
-
参考文档:
a. https://github.com/Netflix/Hystrix
b. https://github.com/Netflix/Hystrix/wiki -
hystrix超时策略配置类:HystrixCommandProperties
-
配置参考文档:https://github.com/Netflix/Hystrix/wiki/Configuration
-
隔离策略:
a. THREAD(线程池隔离,默认)
b. SEMAPHORE(信号量) -
隔离策略修改代码,增加commandProperties参数
@RestController
@RequestMapping("api/v1/order")
public class OrderController {
@Autowired
private OrderService orderService;
@Autowired
private StringRedisTemplate redisTemplate;
@RequestMapping("save")
@HystrixCommand(fallbackMethod = "saveOrderFail",commandProperties = {@HystrixProperty(name = "execution.isolation.strategy", value = "SEMAPHORE")})
public Object save(@RequestParam("user_id")int userId, @RequestParam("product_id")int productId, HttpServletRequest request){
Map<String, Object> msg = new HashMap<>();
msg.put("code",0);
msg.put("data",orderService.save(userId, productId));
return msg;
}
public Object saveOrderFail(int userId, int productId, HttpServletRequest request){
new Thread(()->{
//监控报警
String saveOrderKey = "save-order";
String sendValue = redisTemplate.opsForValue().get(saveOrderKey);
String ip = request.getRemoteAddr();
if (StringUtils.isNullOrEmpty(sendValue)){
System.out.println("紧急短信下发,ip地址是:"+ip);
redisTemplate.opsForValue().set(saveOrderKey,"save-order-fail",20, TimeUnit.SECONDS);
}else {
System.out.println("已经发送过短信,20s内不重复发");
}
}).start();
Map<String, Object> msg = new HashMap<>();
msg.put("code",-1);
msg.put("msg","清稍后再试");
return msg;
}
}
- 其他配置:
c. execution.isolation.thread.timeoutInMilliseconds,默认1000毫秒
b. execution.timeout.enabled,是否开启超时限制
e. execution.isolation.semaphore.maxConcurrentRequests隔离策略为信号量的时候,如果达到最大并发数,后续请求会被拒绝,默认是10
Ribbon消费使用hystrix代码相关
- 添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
- 启动类增加注解:@EnableCircuitBreaker,如果注解太多可以用@SpringCloudApplication代替
- api方法上增加@HystrixCommand(fallbackMethod = “saveOrderFail”)
- 编写fallback方法实现,方法签名(saveOrderFail)和api方法签名一致
@RestController
@RequestMapping("api/v1/order")
public class OrderController {
@Autowired
private OrderService orderService;
@RequestMapping("save")
@HystrixCommand(fallbackMethod = "saveOrderFail")
public Object save(@RequestParam("user_id")int userId, @RequestParam("product_id")int productId){
Map<String, Object> msg = new HashMap<>();
msg.put("code",0);
msg.put("data",orderService.save(userId, productId));
return msg;
}
public Object saveOrderFail(int userId, int productId){
Map<String, Object> msg = new HashMap<>();
msg.put("code",-1);
msg.put("msg","清稍后再试");
return msg;
}
}
Feign结合Hystrix断路器
- 创建FeignClient接口类,实现feign接口,为fallback参数指定fallback方法
@FeignClient(name = "product-service", fallback = ProductClientFallback.class)
public interface ProductClient {
@RequestMapping("/api/v1/product/find")
String findById(@RequestParam("id") int id);
}
- 创建接口类,实现ProductClient接口,
@Component
public class ProductClientFallback implements ProductClient {
@Override
public String findById(int id) {
System.out.println();
return "feign中断路器已开启";
}
}
- 在配置文件中开启断路器
server:
port: 8781
spring:
application:
name: order-service
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
#自定义负载均衡策略
product-service:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
feign:
hystrix:
enabled: true
- 报错相关:
a. 报错:springboot使用Fegin,创建服务接口,在controller里面注入Feign接口对象,结果启动报错
b.原因:注解 @EnableFeignClients 与 @ComponentScan 有冲突,两种注解都会搜索注入指定目录中的 bean 。@EnableFeignClients 引入了 FeignClientsRegistrar 类,实现了 Spring 的bean 资源的加载。FeignClientsRegistrar中registerFeignClients方法获取了@EnableFeignClients注解中的basepackage 属性值,并进行注入。如果两种注解都使用时,其中@EnableFeignClients会覆盖 @ComponentScan 中指定的目录,从而恢复到默认目录。
c. 解决:在注解@EnableFeignClients指定clients,例如:@EnableFeignClients(clients = ProductClient.class)
Hystrix断路器整合redis预警
- 加入redis依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
- controller引入StringRedisTemplate
@RestController
@RequestMapping("api/v1/order")
public class OrderController {
@Autowired
private OrderService orderService;
@Autowired
private StringRedisTemplate redisTemplate;
@RequestMapping("save")
@HystrixCommand(fallbackMethod = "saveOrderFail")
public Object save(@RequestParam("user_id")int userId, @RequestParam("product_id")int productId, HttpServletRequest request){
Map<String, Object> msg = new HashMap<>();
msg.put("code",0);
msg.put("data",orderService.save(userId, productId));
return msg;
}
public Object saveOrderFail(int userId, int productId, HttpServletRequest request){
new Thread(()->{
//监控报警
String saveOrderKey = "save-order";
String sendValue = redisTemplate.opsForValue().get(saveOrderKey);
String ip = request.getRemoteAddr();
if (StringUtils.isNullOrEmpty(sendValue)){
System.out.println("紧急短信下发,ip地址是:"+ip);
redisTemplate.opsForValue().set(saveOrderKey,"save-order-fail",20, TimeUnit.SECONDS);
}else {
System.out.println("已经发送过短信,20s内不重复发");
}
}).start();
Map<String, Object> msg = new HashMap<>();
msg.put("code",-1);
msg.put("msg","清稍后再试");
return msg;
}
}
Hystrix仪表盘使用
参考博客:https://baijiahao.baidu.com/s?id=1623004854011062838&wfr=spider&for=pc
- 新建hystrix_dashboard模块
- 添加依赖
<!-- hystrix监控web依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
- 启动类增加注解@EnableHystrixDashboard
- 配置文件新增endpoint(目的是springboot2.0之后不会开启全部的监控元数据信息)
server.port=8791
spring.application.name=hystrix-dashboard
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
# actuator监控
management.endpoints.web.exposure.include=*
- 访问:http://localhost:8781/hystrix
- Hystrix Dashboard支持三种监控方式
a. 通过URL:turbine-hostname:port/actuator/turbine.stream开启,实现对默认集群的监控
b. 通过URL:turbine-hostname:port/actuator/turbine.stream?cluster=[clusterName]开启,实现对clusterName集群的监控
c. 通过URL/hystrix-app:port/actuator/hystrix.stream开启,实现对具体某个服务实例的监控 - Delay参数:控制服务器上轮询监控信息的延迟时间,默认2000毫秒
6.监控具体服务
- 对于Ribbon工程,在Dashboard输入: http://localhost:8781/actuator/hystrix.stream
如果显示:Unable to connect to Command Metric Stream
是因为:配置文件中没配置actuator,因为监控路径默认不开放 - 对于Feign工程,Feign自己集成了断路器,需要在启动类加@EnableCircuitBreaker注解
2.Hystrix仪表盘参数
参数 | 含义 |
---|---|
Host | 请求速率 |
Circuit | 阀值 |
- 仪表盘数据通过sse server-send-event推送到前端
7.Turbine项目聚合监控
- 原理:通过将将自己注册到注册中心,发现同一个注册中心上的hystrix服务,然后聚合数据。再通过暴露自己的端点,在仪表盘上进行展示