一.搭建空項目以及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服務,然後聚合數據。再通過暴露自己的端點,在儀表盤上進行展示