目錄
一.創建微服務項目
(一)環境準備
(二)項目結構圖
(三)流程
二. Nacos
(一)注冊中心
1.服務注冊
2.服務發現
3.編寫微服務API
4.遠程調用基本實現
5.負載均衡
6.@LoadBalanced注解式注解均衡
7.注冊中心宕機,遠程調用還能成功嗎
(二)配置中心
1.基本用法
2. 動態刷新
3. 配置監聽
4.Nacos中的數據集和application.properties有相同的配置項,哪個生效?
5. 數據隔離
三.?OpenFeign
(一)遠程調用
1.聲明式實現(業務API)
2. 第三方API
3.小技巧與面試題
(二)進階配置
1. 日志
2. 超時控制
3.重試機制
4. 攔截器
5.Fallback
四.Sentinel
(一)基礎
(二)整合使用
(三)異常處理
1.Web接口自定義處理異常
2.?@SentinelResource
3. OpenFeign
(四)流控規則
1. 流量模式-直接
2. 流量模式-鏈路
3. 流量模式-關聯
4. 流量效果-直接
5.流量效果-Warm Up
6. 流量效果-勻速排隊
(五)熔斷規則
1. 斷路器工作原理
2. 熔斷策略-慢調用比例
3.熔斷策略-異常比例
4.熔斷策略-異常數
(六)熱點規則
五.Gateway
(一)網關功能
(二)創建網關
(三)路由
1. 規則配置
2.原理
3. 斷言
(四)過濾器?編輯
1.路徑重寫
2.默認fliter
3.全局過濾器
4. 全局跨域
六. Seata-分布式事務
(一)架構原理?
(二) 二階提交協議
(三)XA模式
(四)AT模式
一.創建微服務項目
(一)環境準備
? 創建微服務架構項目
? 引入 SpringClould、Spring Cloud Alibaba 相關依賴
? 注意版本適配
https://github.com/alibaba/spring-cloud-alibaba/wiki/版本說明
版本選擇:
(二)項目結構圖
?
(三)流程
把這些刪掉:
在pom.xml里加上這一行表示為父項目:
<packaging>pom</packaging>
把這些依賴刪掉:
加上:
<properties><maven.compiler.source>17</maven.compiler.source><maven.compiler.target>17</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><spring-cloud.version>2023.0.3</spring-cloud.version><spring-cloud-alibaba.version>2023.0.3.2</spring-cloud-alibaba.version></properties><dependencyManagement><dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>${spring-cloud.version}</version><type>pom</type><scope>import</scope></dependency><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-alibaba-dependencies</artifactId><version>${spring-cloud-alibaba.version}</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement>
把springboot版本換成3.3.4:
<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.3.4</version><relativePath/> </parent>
創建子模塊:
在子項目pom.xml中寫入并刷新并將src刪了:
<packaging>pom</packaging>
在services中創建幾個微服務
在services的pom.xml中寫入:
<dependencies><!--服務發現--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency> </dependencies>
二. Nacos
注冊中心:
安裝:
Nacos /nɑ:k??s/ 是 Dynamic Naming and Configuration Service的首 字母簡稱,一個更易于構建云原生應用的動態服務發現、配置管 理和服務管理平臺。
官網:https://nacos.io/zh-cn/docs/v2/quickstart/quick-start.html
啟動命令: startup.cmd -m standalone
下載后解壓進入bin文件夾,打開cmd輸入startup.cmd -m standalone
打開瀏覽器訪問localhost:8848/nacos/可訪問
(一)注冊中心
1.服務注冊
在services-order中的pom.xml里寫:
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency> </dependencies>
在services-order中寫啟動類并在resources中新建文件application.properties并寫入,運行啟動類后nacos的服務列表中會自動創建服務
@SpringBootApplication public class OrderMainApplication {public static void main(String[] args) {SpringApplication.run(OrderMainApplication.class,args);} }
spring.application.name=service-order server.port=8000spring.cloud.nacos.server-addr=127.0.0.1:8848
同理services-products中也這么做
idea配置微服務:
點擊運行配置類型選擇springboot
復制服務:
鼠標右鍵點擊復制配置:
點擊修改選項:
點擊程序實參:
寫端口號并點擊應用:
2.服務發現
在啟動類上加注解@EnableDiscoveryClient
@EnableDiscoveryClient // 開啟服務發現功能 @SpringBootApplication public class ProductsMainApplication {public static void main(String[] args) {SpringApplication.run(ProductsMainApplication.class,args);} }
導入測試依賴:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope> </dependency>
新建測試類:
@SpringBootTest public class DiscoveryTest {@AutowiredDiscoveryClient discoveryClient;@AutowiredNacosServiceDiscovery nacosServiceDiscovery;@Testvoid nacosServiceDiscoveryTest() throws NacosException {for (String service : nacosServiceDiscovery.getServices()){System.out.println("service="+service);//獲取ip+portList<ServiceInstance> instances=nacosServiceDiscovery.getInstances(service);for (ServiceInstance instance : instances){System.out.println("ip:"+instance.getHost()+";"+" post="+instance.getPort());}}}@Testvoid discoveryClientTesr(){for (String service : discoveryClient.getServices()){System.out.println("service="+service);//獲取ip+portList<ServiceInstance> instances=discoveryClient.getInstances(service);for (ServiceInstance instance : instances){System.out.println("ip:"+instance.getHost()+";"+" post="+instance.getPort());}}} }兩個方法運行都能打印:service=service-orderip:192.168.100.1; post=8000service=service-productsip:192.168.100.1; post=9000
3.編寫微服務API
遠程調用 - 基本流程
遠程調用 - 下單場景:
在services中引入lombook
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId>scope>annotationProcessor</scope></dependency>
新建bean,constroller,service
services-product中:
@Data public class Product {private Long id;private BigDecimal price;private String productName;private int num; }@RestController public class ProductController {@AutowiredProductService productService;//查詢商品@GetMapping("/product/{id}")public Product getProduct(@PathVariable("id") Long productId){Product product=productService.getProductById(productId);return product;} }public interface ProductService {Product getProductById(Long productId); }@Service public class ProductServiceimpl implements ProductService {@Overridepublic Product getProductById(Long productId) {Product product=new Product();product.setId(productId);product.setPrice(new BigDecimal("99"));product.setProductName("蘋果"+productId);product.setNum(2);return product;} }
運行啟動類后:
services-order中:
@Data public class Order {private Long id;private BigDecimal totalAmount;private Long userId;private String nickName;private String address;private List<Object> productList; }@RestController public class OrderController {@AutowiredOrderService orderService;//創建訂單@GetMapping("/create")public Order createOrder(@RequestParam("userId") Long userId,@RequestParam("productId")Long productId){Order order = orderService.createOrder(productId,userId);return order;} }public interface OrderService {Order createOrder(Long productId,Long userId); }@Service public class OrderServiceImpl implements OrderService {@Overridepublic Order createOrder(Long productId, Long userId) {Order order = new Order();order.setId(1L);order.setTotalAmount(new BigDecimal("0"));order.setUserId(userId);order.setNickName("zhangsan");order.setAddress("尚硅谷");order.setProductList(null);return order;} }
運行啟動類后:
4.遠程調用基本實現
訂單和商品都啟動多個實例:
要在訪問訂單時也能訪問到商品的數據
因為在services-order中訪問不到Product類,所有在cloud-demo下一級新建一個model模塊,把訂單模塊和商品模塊的bean都復制到model中并把訂單和商品模塊中的bean刪掉,將model模塊引入lombook,將services中引入model模塊這樣在訂單和商品模塊就能用model中的bean了
在訂單模塊創建一個配置類:
@Configuration public class OrderConfig {@BeanRestTemplate restTemplate(){return new RestTemplate(); //restTemplate用于給遠程發送請求} }
OrderServiceImpl:
@Service @Slf4j public class OrderServiceImpl implements OrderService {@AutowiredDiscoveryClient discoveryClient;@AutowiredRestTemplate restTemplate;@Overridepublic Order createOrder(Long productId, Long userId) {Product product=getProductFromRemote(productId);Order order = new Order();order.setId(1L);//總金額order.setTotalAmount(product.getPrice().multiply(new BigDecimal(product.getNum())));order.setUserId(userId);order.setNickName("zhangsan");order.setAddress("尚硅谷");//遠程查詢商品列表order.setProductList(Arrays.asList( product));return order;}private Product getProductFromRemote(Long productId){//1.獲取到商品服務所在的所有機器ip+portList<ServiceInstance> instances=discoveryClient.getInstances("service-products");ServiceInstance instance=instances.get(0);//遠程URLString url="http://"+instance.getHost()+":"+instance.getPort()+"/product/"+productId;log.info("遠程請求:{}",url);//2.給遠程發送請求Product product=restTemplate.getForObject(url,Product.class);return product;} }
將Order類中的集合類型改為Product:
@Data public class Order {private Long id;private BigDecimal totalAmount;private Long userId;private String nickName;private String address;private List<Product> productList; }
將所有服務全部重啟:
瀏覽器向訂單發起請求:
如果把9001,9002,,9003關掉一個或兩個那也能照常運行,比如關掉9001就會使用9002或9003。
缺點:每次都會給固定位置發請求,比如第一次給9001,那以后都是9001,除非9001被關掉
5.負載均衡
在services-order中引入spring-cloud-starter-loadbalancer以及測試類
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-loadbalancer</artifactId> </dependency> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId> </dependency>
新增測試類:
@SpringBootTest public class LoadBalancerTest {@AutowiredLoadBalancerClient loadBalancerClient;@Testvoid test(){ServiceInstance choose=loadBalancerClient.choose("service-products");System.out.println("choose =" +choose.getHost()+": "+choose.getPort());ServiceInstance choose2=loadBalancerClient.choose("service-products");System.out.println("choose =" +choose2.getHost()+": "+choose2.getPort());ServiceInstance choose3=loadBalancerClient.choose("service-products");System.out.println("choose =" +choose3.getHost()+": "+choose3.getPort());ServiceInstance choose4=loadBalancerClient.choose("service-products");System.out.println("choose =" +choose4.getHost()+": "+choose4.getPort());} }
運行顯示:
OrderServiceImpl:
@Service @Slf4j public class OrderServiceImpl implements OrderService {@AutowiredRestTemplate restTemplate;@Autowired //一定導入spring-cloud-starter-loadbalancerLoadBalancerClient loadBalancerClient;@Overridepublic Order createOrder(Long productId, Long userId) {Product product=getProductFromRemoteWithLoadBalance(productId);Order order = new Order();order.setId(1L);//總金額order.setTotalAmount(product.getPrice().multiply(new BigDecimal(product.getNum())));order.setUserId(userId);order.setNickName("zhangsan");order.setAddress("尚硅谷");//遠程查詢商品列表order.setProductList(Arrays.asList( product));return order;}//完成負載均衡發送請求private Product getProductFromRemoteWithLoadBalance(Long productId){//1.獲取到商品服務所在的所有機器ip+portServiceInstance choose = loadBalancerClient.choose("service-products");//遠程URLString url="http://"+choose.getHost()+":"+choose.getPort()+"/product/"+productId;log.info("遠程請求:{}",url);//2.給遠程發送請求Product product=restTemplate.getForObject(url,Product.class);return product;} }
多次向訂單發起請求后端口一直在循環:
6.@LoadBalanced注解式注解均衡
在配置類中加上注解:
@Configuration public class OrderConfig {@LoadBalanced //注解式負載均衡@BeanRestTemplate restTemplate(){return new RestTemplate();} }
OrderServiceImpl:
@Service @Slf4j public class OrderServiceImpl implements OrderService {@AutowiredRestTemplate restTemplate;@Overridepublic Order createOrder(Long productId, Long userId) {Product product=getProductFromRemoteWithLoadBalanceAnnotation(productId);Order order = new Order();order.setId(1L);//總金額order.setTotalAmount(product.getPrice().multiply(new BigDecimal(product.getNum())));order.setUserId(userId);order.setNickName("zhangsan");order.setAddress("尚硅谷");//遠程查詢商品列表order.setProductList(Arrays.asList( product));return order;}//基于注解的負載均衡private Product getProductFromRemoteWithLoadBalanceAnnotation(Long productId){String url="http://service-products/product/"+productId;//2.給遠程發送請求 service-products(微服務名字)會被動態替換Product product=restTemplate.getForObject(url,Product.class);return product;} }
在ProductController中添加輸出語句:
@RestController public class ProductController {@AutowiredProductService productService;//查詢商品@GetMapping("/product/{id}")public Product getProduct(@PathVariable("id") Long productId){System.out.println("hello");Product product=productService.getProductById(productId);return product;} }
運行后多次請求會發現循環在9001,9002,9003三個控制臺輸入hello
7.注冊中心宕機,遠程調用還能成功嗎
沒調?過如果宕機,調?會失敗
調?過如果宕機,因為會緩存名單,遠程調用不在依賴注冊中心,所以調?會成功
(二)配置中心
1.基本用法
配置中心 - 基本使用:
在services下引入nacos作為配置中心的依賴:
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> </dependency>
在service-order中的application.properties中寫入:
spring.cloud.nacos.server-addr=127.0.0.1:8848 spring.config.import=nacos:service-order.properties
點擊創建配置:
輸入Date ID 配置格式和配置內容并點擊發布:
在OrderController中獲取配置文件中的配置值,
@RefreshScope //自動刷新 @RestController public class OrderController {@AutowiredOrderService orderService;@Value("${order.timeout}")String orderTimeout;@Value("${order.auto-confirm}")String orderAutoConfirm;@GetMapping("/config")public String config(){return "order.timeout="+orderTimeout+": order.auto-confirm="+orderAutoConfirm;}//創建訂單@GetMapping("/create")public Order createOrder(@RequestParam("userId") Long userId,@RequestParam("productId")Long productId){Order order = orderService.createOrder(productId,userId);return order;} }
運行啟動類并在瀏覽器輸入http://localhost:8000/config則可以看到,@RefreshScope注解表示激活配置屬性的自動刷新功能,就是如果在nacos中修改了配置,刷新瀏覽器就可以自動修改值,如果不加這個注解值就不會變
注意:如果引入了配置中心但項目啟動沒有導入任何配置就會報錯,這時候需要在application.properties中寫:
#禁用導入檢查 spring.cloud.nacos.config.import-check.enabled=false
2. 動態刷新
使用步驟
- @Value(“${xx}”) 獲取配置 + @RefreshScope 實現自動刷新
- @ConfigurationProperties 無感自動刷新
- NacosConfigManager 監聽配置變化
將經常用的配置可以抽取到一個Properties類中,新建OrderProperties類
@Component @ConfigurationProperties(prefix = "order") //prefix寫配置項中.前面的 //配置批量綁定在nacos中,可以無需@RefreshScope就能實現自動刷新 @Data public class OrderProperties {String timeout; //屬性名寫配置項中.后面的。String autoConfirm; //如果是短橫線寫法就寫成駝峰命名方式 }
OrderController中:
@RestController public class OrderController {@AutowiredOrderService orderService;@AutowiredOrderProperties orderProperties;@GetMapping("/config")public String config(){return "order.timeout="+orderProperties.getTimeout()+": order.auto-confirm="+orderProperties.getAutoConfirm();}//創建訂單@GetMapping("/create")public Order createOrder(@RequestParam("userId") Long userId,@RequestParam("productId")Long productId){Order order = orderService.createOrder(productId,userId);return order;} }
3. 配置監聽
OrderMainApplication中:
@EnableDiscoveryClient @SpringBootApplication public class OrderMainApplication {public static void main(String[] args) {SpringApplication.run(OrderMainApplication.class,args);}//1. 項目啟動就監聽配置文件變化//2. 發生變化后拿到變化值//3. 發送郵件@BeanApplicationRunner applicationRunner(NacosConfigManager nacosConfigManager){return args-> {ConfigService configService= nacosConfigManager.getConfigService();configService.addListener("service-order.properties","DEFAULT_GROUP", new Listener() {@Overridepublic Executor getExecutor() {return Executors.newFixedThreadPool(4);}@Overridepublic void receiveConfigInfo(String configInfo) {System.out.println("變化的配置信息:"+configInfo);System.out.println("郵件通知...");}});System.out.println("啟動");};} }
啟動服務后修改nocos中配置的值后控制臺打印:
4.Nacos中的數據集和application.properties有相同的配置項,哪個生效?
spring.cloud.nacos.server-addr=127.0.0.1:8848 #導入多個配置 spring.config.import=nacos:service-order.properties,nacos:common.properties
后導入優先,外部優先
5. 數據隔離
需求描述:
- ? 項目有多套環境:dev,test,prod
- ? 每個微服務,同一種配置,在每套環境的值都不一樣。? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?如:database.properties? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?如:common.properties
- ??項目可以通過切換環境,加載本環境的配置
難點 :
- ? ?區分多套環境
- ? ?區分多種微服務
- ? ?區分多種配置
- ? ?按需加載配置
把application.properties去掉換成application.yml,注意yml里不能出現注釋,有注釋會報錯,復制下面的時候要把注釋刪了
server:port: 8000 spring:profiles:active: prod #激活哪個環境application:name: service-ordercloud:nacos:server-addr: 127.0.0.1:8848config:namespace: ${spring.profiles.active:public} #動態取值,:表示默認public--- #---是多文檔模式 spring:config:import:- nacos:common.properties?group=order #?后面表示導入哪個組命名空間下的配置文件- nacos:database.properties?group=orderactivate: #在哪種環境下生效on-profile: dev --- spring:config:import:- nacos:common.properties?group=order- nacos:database.properties?group=order- nacos:haha.properties?group=orderactivate:on-profile: test --- spring:config:import:- nacos:common.properties?group=order- nacos:database.properties?group=order- nacos:haha.properties?group=orderactivate:on-profile: prod
三.?OpenFeign
(一)遠程調用
1.聲明式實現(業務API)
Declarative REST Client
聲明式 REST 客戶端 vs 編程式 REST 客戶端(RestTemplate)
注解驅動 :?
- 指定遠程地址:@FeignClient?
- 指定請求方式:@GetMapping、@PostMapping、@DeleteMapping
- 指定攜帶數據:@RequestHeader、@RequestParam、@RequestBody?
- 指定結果返回:響應模型
在services中引入依賴:
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
遠程調用 - 業務API:
在訂單的啟動類中加上注解@EnableFeignClients
@EnableFeignClients //開啟Feign的遠程調用功能 @EnableDiscoveryClient @SpringBootApplication public class OrderMainApplication {public static void main(String[] args) {SpringApplication.run(OrderMainApplication.class,args);}//1. 項目啟動就監聽配置文件變化//2. 發生變化后拿到變化值//3. 發送郵件@BeanApplicationRunner applicationRunner(NacosConfigManager nacosConfigManager){return args-> {ConfigService configService= nacosConfigManager.getConfigService();configService.addListener("service-order.properties","DEFAULT_GROUP", new Listener() {@Overridepublic Executor getExecutor() {return Executors.newFixedThreadPool(4);}@Overridepublic void receiveConfigInfo(String configInfo) {System.out.println("變化的配置信息:"+configInfo);System.out.println("郵件通知...");}});System.out.println("啟動");};} }
編寫一個遠程調用的客戶端:
@FeignClient(value = "service-products") //feign客戶端 public interface ProductFeignClient {//mvc注解的兩套使用邏輯//1. 標注在Controller上,是接受這樣的請求//2. 標注在FeignClient上,是發送這樣的請求@GetMapping("/product/{id}")Product getProductById(@PathVariable("id")Long id); }
修改OrderServiceImpl中創建訂單的邏輯:
@Service @Slf4j public class OrderServiceImpl implements OrderService {@AutowiredProductFeignClient productFeignClient;@Overridepublic Order createOrder(Long productId, Long userId) {//使用Feign完成遠程調用Product product=productFeignClient.getProductById(productId);Order order = new Order();order.setId(1L);//總金額order.setTotalAmount(product.getPrice().multiply(new BigDecimal(product.getNum())));order.setUserId(userId);order.setNickName("zhangsan");order.setAddress("尚硅谷");//遠程查詢商品列表order.setProductList(Arrays.asList( product));return order;} }
運行后瀏覽器訪問:
OpenFeign能自動實現負載均衡
2. 第三方API
編寫一個遠程調用的客戶端:
//http://aliv18.data.moji.com/whapi/json/alicityweather/condition @FeignClient(value = "weather-client",url ="http://aliv18.data.moji.com" ) //因為是第三方api,沒有注冊中心的名字,所以value隨便寫 public interface WeatherFeignCilent {@PostMapping("/whapi/json/alicityweather/condition")String getWeather(@RequestHeader("Authorization") String auth,@RequestParam("token") String Token,@RequestParam("cityId") String cityId); }
新增測試類:
@SpringBootTest public class WeatherTest {@AutowiredWeatherFeignCilent weatherFeignCilent;@Testvoid test01(){String weather=weatherFeignCilent.getWeather("APPCODE 93b7e19861a24c519a7548b17dc16d75","50b53ff8dd7d9fa320d3d3ca32cf8ed1","2182");System.out.println("weather = "+weather);} }
3.小技巧與面試題
如何編寫好OpenFeign聲明式的遠程調用接口:
? ? ? 業務API:直接復制對方Controller簽名即可
? ? ? 第三方API:根據接口文檔確定請求如何發
客戶端負載均衡與服務端負載均衡區別:
(二)進階配置
1. 日志
在yml文件添加:
logging:level:com.itheima.feign: debug
注意:這里的com.itheima.feign是自己定義的feign客戶端的包名
包名這樣取:
在配置類中添加:
@Bean Logger.Level feignLoggerLevel() {return Logger.Level.FULL; }
2. 超時控制
在ProductServiceimpl中添加睡眠代碼:
@Service public class ProductServiceimpl implements ProductService {@Overridepublic Product getProductById(Long productId) {Product product=new Product();product.setId(productId);product.setPrice(new BigDecimal("99"));product.setProductName("蘋果"+productId);product.setNum(2);try {TimeUnit.SECONDS.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}return product;} }
打開瀏覽器訪問會等待60秒,然后顯示:
控制臺報錯:
超時配置:
services-order的application.yml中增加:
server:port: 8000 spring:profiles:active: prod#include是包含的意思,包含feign,feign就是下面新建的文件application-feign的-后面部分include: feign application:name: service-ordercloud:nacos:server-addr: 127.0.0.1:8848config:namespace: ${spring.profiles.active:pubilc}logging:level:com.itheima.feign: debug--- spring:config:import:- nacos:common.properties?group=order- nacos:database.properties?group=orderactivate:on-profile: dev --- spring:config:import:- nacos:common.properties?group=order- nacos:database.properties?group=order- nacos:haha.properties?group=orderactivate:on-profile: test --- spring:config:import:- nacos:common.properties?group=order- nacos:database.properties?group=order- nacos:haha.properties?group=orderactivate:on-profile: prod
新建application-feign.yml文件:
spring:cloud:openfeign:client:config:default: #默認設置logger-level: fullconnect-timeout: 1000 #連接超時read-timeout: 2000 #讀取超時service-products: #精確設置logger-level: fullconnect-timeout: 3000read-timeout: 5000
3.重試機制
遠程調用超時失敗后,還可以進行多次嘗試,如果某次成功返回ok,如果多次依然失敗則結束調用,返回錯誤
第一次請求失敗后間隔100毫秒重新發送,第二次請求失敗后間隔(100*1.5)毫秒重新發送,第三次請求失敗后間隔(100*1.5*1.5)毫秒后重新發送......最大不能超過1秒,超過按1秒算。
在配置類中加上:
@BeanRetryer retryer(){return new Retryer.Default();}//不傳參數就是間隔100毫秒,最大間隔1秒,最多嘗試5次
4. 攔截器
創建一個攔截器:
@Component public class XTokenRequestInterceptor implements RequestInterceptor {/*** 請求攔截器* @param requestTemplate */@Overridepublic void apply(RequestTemplate requestTemplate) {System.out.println("請求攔截器啟動");requestTemplate.header("X-Token","123456");} }
在ProductController中可以取出X-token:
@RestController public class ProductController {@AutowiredProductService productService;//查詢商品@GetMapping("/product/{id}")public Product getProduct(@PathVariable("id") Long productId,HttpServletRequest request){System.out.println(request.getHeader("X-Token"));System.out.println("hello");Product product=productService.getProductById(productId);return product;} }
重新運行并訪問就能打印值
5.Fallback
引? sentinel:
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-sentinel</artifactId></dependency>
在yml文件開啟熔斷:
feign:sentinel:enabled: true
兜底回調:
@Component public class ProductFeignClientFallback implements ProductFeignClient {@Overridepublic Product getProductById(Long id){System.out.println("兜底回調");Product product=new Product();product.setId(id);product.setPrice(new BigDecimal("0"));product.setProductName("未知商品");product.setNum(0);return product;} }
修改ProductFeignClient接口:
@FeignClient(value = "service-products",fallback = ProductFeignClientFallback.class) //feign客戶端 fallback表示該客戶端調用失敗了用哪個做兜底 public interface ProductFeignClient {//mvc注解的兩套使用邏輯//1. 標注在Controller上,是接受這樣的請求//2. 標注在FeignClient上,是發送這樣的請求@GetMapping("/product/{id}")Product getProductById(@PathVariable("id")Long id); }
把服務關了并請求:
四.Sentinel
(一)基礎
功能介紹:隨著微服務的流行,服務和服務之間的穩定性變得越來越重要。Spring Cloud Alibaba Sentinel 以流量為切入點,從流量控制、流量路由、熔斷降級、系統自適應過載保護、熱點流量防護等多個維度保護服務的穩定性
架構原理:
資源&規則:
定義資源:
- 主流框架自動適配(Web Servlet、Dubbo、Spring Cloud、gRPC、Spring WebFlux、Reactor); 所有Web接口均為資源
- ?編程式:SphU API
- 聲明式:@SentinelResource
定義規則:
- 流量控制(FlowRule)
- 熔斷降級(DegradeRule)
- 系統保護(SystemRule)
- 來源訪問控制(AuthorityRule) ?
- 熱點參數(ParamFlowRule)
工作原理:
(二)整合使用
下載控制臺:https://security.feishu.cn/link/safety?target=https%3A%2F%2Fgithub.com%2Falibaba%2FSentinel%2Freleases&scene=ccm&logParams=%7B%22location%22%3A%22ccm_docs%22%7D&lang=zh-CN
在下載后存放jar的文件夾輸入cmd,在cmd輸入java -jar sentinel-dashboard-1.8.8.jar
訪問8080就是sentinel控制臺,默認賬號密碼就是sentinel
在services中引入sentinel依賴
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency>
在兩個微服務模塊的application-feign.yml和application.properties中寫入:
spring:cloud:openfeign:client:config:default:logger-level : fullconnect-timeout: 1000read-timeout: 2000service-products:logger-level: fullconnect-timeout: 3000read-timeout: 5000sentinel:transport:dashboard: localhost:8080eager: true feign:sentinel:enabled: true
spring.application.name=service-products server.port=9001spring.cloud.nacos.server-addr=127.0.0.1:8848spring.cloud.nacos.config.import-check.enabled=falsespring.cloud.sentinel.transport.dashboard=localhost:8080 spring.cloud.sentinel.eager=true
運行訂單和商品模塊瀏覽器控制臺就會顯示:
在OrderServiceImpl中給createOrder添加注解?@SentinelResource(value = "createOrder"):
@Service @Slf4j public class OrderServiceImpl implements OrderService {@AutowiredProductFeignClient productFeignClient;@SentinelResource(value = "createOrder")@Overridepublic Order createOrder(Long productId, Long userId) {//使用Feign完成遠程調用Product product=productFeignClient.getProductById(productId);Order order = new Order();order.setId(1L);//總金額order.setTotalAmount(product.getPrice().multiply(new BigDecimal(product.getNum())));order.setUserId(userId);order.setNickName("zhangsan");order.setAddress("尚硅谷");//遠程查詢商品列表order.setProductList(Arrays.asList( product));return order;} }
重新運行訂單模塊并在瀏覽器請求
sentinel控制臺的簇點鏈路會出現:
點擊create右邊的流控,并把單機閾值設為1(QPS表示每秒請求數量,單機閾值為1表示每秒最多請求1次)并點擊新增:
流控規則中會顯示:
用瀏覽器頻繁刷新(每秒超過一個請求)會顯示:
(三)異常處理
1.Web接口自定義處理異常
在service-order中?定義異常處理類BlockExceptionHandler:
@Component public class MyBlockExceptionHandler implements BlockExceptionHandler {private ObjectMapper objectMapper=new ObjectMapper();@Overridepublic void handle(HttpServletRequest request,HttpServletResponse response,String resourseName, BlockException e) throws Exception {response.setContentType("application/json;charset=utf-8");PrintWriter writer = response.getWriter();R error=R.error(500,resourseName+"被Sentinel限制了,原因:"+e.getClass());String json=objectMapper.writeValueAsString(error); //將對象轉成字符串writer.write(json);writer.flush();writer.close();} }
在model模塊新建R對象:
@Data public class R {private Integer code;private String msg;private Object data;public static R ok() {R r = new R();r.setCode(200);return r;}public static R ok(String msg, Object data) {R r = new R();r.setCode(200);r.setMsg(msg);r.setData(data);return r;}public static R error() {R r = new R();r.setCode(500);return r;}public static R error(Integer code, String msg) {R r = new R();r.setCode(code);r.setMsg(msg);return r;} }
一秒內頻繁刷新瀏覽器會就會顯示:
2.?@SentinelResource
給createOrder添加流控規則:
為了方便測試把之前/create的流程規則刪了
如果在一秒內頻繁刷新瀏覽器會顯示一個默認的錯誤頁:
修改OrderServiceImpl(如果被@SentinelResource標注的資源沒用違反規則則調用真實業務邏輯并返回真實數據,如果違反了規則被Sentinel限制了,則調用blockHandler指定的方法返回兜底數據):
@Service @Slf4j public class OrderServiceImpl implements OrderService {@AutowiredProductFeignClient productFeignClient;@SentinelResource(value = "createOrder",blockHandler = "createOrderFallback")//blockHandler指定兜底回調@Overridepublic Order createOrder(Long productId, Long userId) {//使用Feign完成遠程調用Product product=productFeignClient.getProductById(productId);Order order = new Order();order.setId(1L);//總金額order.setTotalAmount(product.getPrice().multiply(new BigDecimal(product.getNum())));order.setUserId(userId);order.setNickName("zhangsan");order.setAddress("尚硅谷");//遠程查詢商品列表order.setProductList(Arrays.asList( product));return order;}//執行兜底回調public Order createOrderFallBack(Long productId, Long userId, BlockException e) {Order order = new Order();order.setId(0L);order.setTotalAmount(new BigDecimal(0));order.setUserId(userId);order.setNickName("未知用戶");order.setAddress("異常信息"+e.getClass());return order;} }
重新啟動訂單服務,并在一秒內頻繁刷新頁面顯示:
3. OpenFeign
為遠程調用添加流控:
因為之前編寫OpenFeign客戶端的時候指定了一個Fallback回調,所以只要失敗了就會調用兜底回調
@FeignClient(value = "service-products",fallback = ProductFeignClientFallback.class) //feign客戶端 public interface ProductFeignClient {//mvc注解的兩套使用邏輯//1. 標注在Controller上,是接受這樣的請求//2. 標注在FeignClient上,是發送這樣的請求@GetMapping("/product/{id}")Product getProductById(@PathVariable("id")Long id); }
@Component public class ProductFeignClientFallback implements ProductFeignClient {@Overridepublic Product getProductById(Long id){System.out.println("兜底回調");Product product=new Product();product.setId(id);product.setPrice(new BigDecimal("0"));product.setProductName("未知商品");product.setNum(0);return product;} }
一秒內多次刷新就會顯示:
(四)流控規則
1. 流量模式-直接
限制多余請求,從而保護系統資源不被耗盡
QPS: 統計每秒請求數
并發線程數:?統計并發線程數
2. 流量模式-鏈路
調用關系包括調用方、被調用方;一個方法又可能會調用其它方法,形成一個調用鏈路的層次關 系;有了調用鏈路的統計信息,我們可以衍生出多種流量控制手段
在OrderController中新增方法sekill表示秒殺創建訂單:
@RestController public class OrderController {@AutowiredOrderService orderService;@AutowiredOrderProperties orderProperties;@GetMapping("/config")public String config(){return "order.timeout="+orderProperties.getTimeout()+": order.auto-confirm="+orderProperties.getAutoConfirm()+": "+"order.db-url="+orderProperties.getDbUrl();}//創建訂單@GetMapping("/create")public Order createOrder(@RequestParam("userId") Long userId,@RequestParam("productId")Long productId){Order order = orderService.createOrder(productId,userId);return order;}//秒殺創建訂單@GetMapping("/sekill")public Order sekill(@RequestParam("userId") Long userId,@RequestParam("productId")Long productId){Order order = orderService.createOrder(productId,userId);order.setId(Long.MAX_VALUE);return order;} }
在yml文件新增配置web-context-unify: false表示不統一web上下文:
spring:cloud:openfeign:client:config:default:logger-level : fullconnect-timeout: 1000read-timeout: 2000service-products:logger-level: fullconnect-timeout: 3000read-timeout: 5000sentinel:transport:dashboard: localhost:8080eager: trueweb-context-unify: falsefeign:sentinel:enabled: true
重新啟動Order模塊后Sentinel 控制臺有兩個鏈路:
對createOrder進行限流
這時候如果訪問/create沒有流量限制,但請求/sekill就有限制
一秒內訪問多次/sekill:
3. 流量模式-關聯
在OrderController中新增兩個方法:
@GetMapping("writeDb") public String writeDb(){return "writeDb"; } @GetMapping("readDb") public String readDb(){return "readDb"; }
給readDb添加流控:
當/writeDb訪問量極大時再訪問/readDb就會:
4. 流量效果-直接
注意:只有快速失敗支持流控模式(直接、 關聯、鏈路)的設置
給/readDb新增流控:
利用apipost進行壓測:
5.流量效果-Warm Up
QPS表示每秒通過幾個請求,Period表示預熱時常是幾秒,比如QPS=3,Period=3,那么如果遇到超高峰流量到達,請求數量會從第一秒1個第二秒2個第三秒3個遞增,一直到設置的峰值
重新設置/readDb的流控:
用apipost測壓(設成每秒最多并發10個,持續5秒):
查看idea控制臺:
第一秒打印3個第一秒還沒開始統計,第二秒打印3個,第三秒打印5個,第四秒打印9個,第五秒10個,第六秒10個,idea控制臺有誤差,但整體趨勢是對的
6. 流量效果-勻速排隊
每秒兩個請求,多余的排隊,timeout表示超時時間,超過這個時間就會被丟棄
重新設置/readDb的流控:
使用apipost進行壓力測試:
idea控制臺:
(五)熔斷規則
1. 斷路器工作原理
熔斷降級(DegradeRule):
2. 熔斷策略-慢調用比例
設置熔斷:
表示5秒內如果有80%的請求響應時間大于1秒就是慢調用(最少有5個請求),一旦發生慢調用就認為對方是不可靠的就開啟熔斷30秒,這30秒內的所有請求都不會發給遠程服務。
修改ProductController(誰調用商品模塊都會休眠2秒):
@RestController public class ProductController {@AutowiredProductService productService;//查詢商品@GetMapping("/product/{id}")public Product getProduct(@PathVariable("id") Long productId,HttpServletRequest request){System.out.println(request.getHeader("X-Token"));System.out.println("hello");Product product=productService.getProductById(productId);try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {throw new RuntimeException(e);}return product;} }
重新啟動商品模塊,瀏覽器請求http://localhost:8888/create?userId=1&productId=777,5秒內多次刷新,會出現兜底數據,然后30秒內一直是這個數據并且不會像商品模塊發送請求。
3.熔斷策略-異常比例
修改ProductController故意制造錯誤:
@RestController public class ProductController {@AutowiredProductService productService;//查詢商品@GetMapping("/product/{id}")public Product getProduct(@PathVariable("id") Long productId,HttpServletRequest request){System.out.println(request.getHeader("X-Token"));System.out.println("hello");Product product=productService.getProductById(productId);int i=10/0;return product;} }
給GET:http://service-products/product/{id}設置新熔斷:
5秒內如果異常比例占80%就熔斷30秒,30秒內不給商品模塊發請求
重啟商品模塊并在瀏覽器請求,前五秒頻繁刷新會觸發熔斷,導致30不對商品模塊發請求,30秒后會嘗試發請求,這時候再不斷刷新就又會觸發熔斷
4.熔斷策略-異常數
5秒內異常數超過10個就觸發熔斷30秒
(六)熱點規則
熱點參數限流
熱點參數:
需求1:
修改OrderController中的sekill方法(添加@SentinelResource注解):
@GetMapping("/sekill") @SentinelResource(value = "sekill-order",fallback = "sekillFallBack") // required表示不必填,defaultValue表示默認值 public Order sekill(@RequestParam(value = "userId",defaultValue = "888")Long userId,@RequestParam(value = "productId",required = false)Long productId){Order order = orderService.createOrder(productId,userId);order.setId(Long.MAX_VALUE);return order; } public Order sekillFallback(Long userId, Long productId,Throwable exception){System.out.println("sekillFallback...");Order order=new Order();order.setId(productId);order.setUserId(userId);order.setAddress("異常信息:"+exception.getClass());return order; }fallback與blockHandler兜底回調區別: 使用fallback是有blockHandler優先使用blockHandler,沒有blockHandler再使用fallback fallback可以處理業務異常,如果使用fallback那么兜底回調的異常要寫成Throwable 如果使用blockHandler那兜底異常要使用BlockException 否則兜底回調不生效,限流會返回一個默認錯誤頁
添加熱點規則:
表示限制第一個參數每秒只能一個,瀏覽器訪問并頻繁刷新會:
如果去掉userId參數就沒有熱點參數限流
需求2:
修改熱點規則:
這樣6號用戶就不被限流了
需求3:
新增熱點:
編輯:
五.Gateway
(一)網關功能
Spring Cloud Gateway:
需求:
? ?1. 客戶端發送 /api/order/** 轉到 service-order
? ?2. 客戶端發送 /api/product/** 轉到 service-product
? ?3. 以上轉發有負載均衡效果
(二)創建網關
在cloud-demo下創建gateway模塊
引入依賴:
<dependencies><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-loadbalancer</artifactId></dependency> </dependencies>
新建application.yml文件:
spring:application:name: gatewaycloud:nacos:server-addr: 127.0.0.1:8848server:port: 80
新建GatewayMainApplication啟動類:
@EnableDiscoveryClient //服務發現 @SpringBootApplication public class GatewayMainApplication {public static void main(String[] args) {SpringApplication.run(GatewayMainApplication.class,args);} }
運行并打開nocos:
(三)路由
1. 規則配置
新建路由規則文件application-route.yml:
spring:cloud:gateway:routes:- id: order-route #路由規則id,自定義,唯一uri: lb://service-order #路由目標微服務,lb代表負載均衡predicates: #路由斷言,判斷請求是否符合規則,符合則路由到目標- Path=/api/order/** #以請求路徑做判斷- id: products-routeuri: lb://service-productspredicates:- Path=/api/products/**#id表示路由的名字 #uri表示把請求轉到哪 #predicates表示路由的匹配規則 #-Path表示路徑為api/order/開頭的都轉給uri
讓application.yml包含該文件:
spring:profiles:include: routeapplication:name: gatewaycloud:nacos:server-addr: 127.0.0.1:8848server:port: 80
在OrderController類上加上@RequestMapping("/api/order")
在ProductController類上加上@RequestMapping("/api/products")
在ProductFeignClient類的getProductById的注解改為@GetMapping("/api/products/product/{id}")
瀏覽器訪問http://localhost/api/order/writeDb:
2.原理
3. 斷言
長短寫法:
spring:cloud:gateway:routes:- id: order-routeuri: lb://service-orderpredicates: #長斷言- name: Pathargs:patterns: /api/order/**match-trailing-slash: true- id: products-route uri: lb://service-productspredicates: #短斷言- Path=/api/products/**
(四)過濾器
1.路徑重寫
這樣就不用在控制層寫@RequestMapping()了
spring:cloud:gateway:routes:- id: order-routeuri: lb://service-orderpredicates:- name: Pathargs:patterns: /api/order/**match-trailing-slash: truefilters:- RewritePath=/api/order/(?<segment>.*), /$\{segment}- id: products-routeuri: lb://service-productspredicates:- Path=/api/products/**filters:- RewritePath=/api/products/(?<segment>.*), /$\{segment}
2.默認fliter
spring:cloud:gateway:default-filters: #默認filters,如果其他沒有設置就使用這個- AddResponseHeader=X-Response-Abc,123
3.全局過濾器
新建RtGlobalFilter類做全局過濾器
@Component @Slf4j public class RtGlobalFilter implements GlobalFilter, Ordered {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {ServerHttpRequest request = exchange.getRequest();ServerHttpResponse response = exchange.getResponse();String uri = request.getURI().toString();long start=System.currentTimeMillis();log.info("請求:"+uri+" 開始時間:"+start);//---------以上是前置邏輯--------------//Mono<Void> filter=chain.filter(exchange).doFinally((result)->{//----------以下是后置邏輯-------//long end=System.currentTimeMillis();log.info("請求"+uri+" 結束時間:"+end+" 耗時"+(end-start));});//放行return filter;}@Overridepublic int getOrder() {return 0;} }
這樣控制臺就可以輸出每個請求的路徑和耗時時間
4. 全局跨域
spring:cloud:gateway:globalcors:cors-configurations:'[/**]':allowed-origins: "*"allowed-headers: "*"allowed-methods: "*"
六. Seata-分布式事務
(一)架構原理?
下載Seata:Seata Java Download | Apache Seata
2.1.0版本下載后解壓并進入bin目錄,使用cmd輸入seata-server.bat
訪問http://localhost:7091/#/login進入頁面,賬號密碼都是seata
在所有微服務模塊引入:
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-seata</artifactId> </dependency>
每個微服務創建 file.conf ?件:
service {#transaction service group mappingvgroupMapping.default_tx_group = "default"#only support when registry.type=file, please don't set multiple addressesdefault.grouplist = "127.0.0.1:8091"#degrade, current not supportenableDegrade = false#disable seatadisableGlobalTransaction = false }
在最大的方法入口標注注解@GlobalTransactional就會實現報錯所有數據回滾
(二) 二階提交協議
(三)XA模式
設置XA模式:
(四)AT模式
設置AT模式: