近期簡單了解一下SpringCloude微服務,本文主要就是我學習中所記錄的筆記,然后具體原理可能等以后再來深究,本文可能有些地方用詞不專業還望包容一下,感興趣可以參考官方文檔來深入學習一下微服務,然后我的下一步學習就是docker和linux了。
nacos:?Nacos 快速開始 | Nacos 官網
sentinel:?home | Sentinel
seata:?Seata Java Download | Apache Seata
單體架構
域名主要是綁定IP方便記名字,然后節點就是服務器的專業說法,當然一臺服務器也可以同時部署數據庫和應用(好處就是部署簡單)
一臺服務器性能有限,比如請求數量過多就不夠了
集群架構(大量公司常用)
多個服務干同一件事就是集群,干不同事就是分布式
對服務器(節點)做的復制品即為副本,而這些服務器構成的一個整體就叫做集群
域名不能單獨指向任何一個副本(否則和單體架構沒區別)必須通過網關(一般也需要部署在一個服務器上邊,比如Nginx),所以域名必須綁定網關的公網IP(網關需要完成功能:請求的路由,而且里邊我記得conf配置文件可以配置訪問的IP來實現負載均衡),數據庫也可能產生瓶頸,所以依然可以多復制幾個節點構成數據庫集群。然后比如雙十一流量大就可以擴容幾個副本,然后等流量過去在釋放服務器來釋放資源。
缺點: 1.維護和模塊化升級比較麻煩,牽一發而動全身,每次都要重新打包部署 2.如果涉及到多個語言交互就不能簡單打jar包部署服務器了
分布式架構
每個應用/數據庫可以按照業務功能模塊拆成一個個小應用(微服務)還實現了數據隔離,比如訂單找商品要,而不是直接連上商品的數據庫
當然為了增加并發量,仍然可以使用副本思想,比如商品肯定使用的最多,那么每臺服務器都部署一份,其余也可以看情況選擇部署幾份
注意:
1.一個服務器不能全部部署多個商品副本(微服務),因為如果這個服務器宕機其他服務器也不能正常工作了,即單點故障。
2.如果需要訪問的業務在別的服務器,那么就需要遠程調用(類似HttpClient吧)獲取返回的json數據即可(這是最常用的一種RPC方式)。
3.注冊中心組件包含服務中心和服務注冊功能: 通過注冊中心可以知道微服務在哪個服務器存在,并且調用時也可以從注冊中心知道業務在哪里(服務發現),而且這時候還可以用到網關(使用負載均衡,比如一種模式是按順序先后訪問,還有按權重等)。
4.每個微服務都可以保存在配置中心來統一管理。
5.高并發情況下,如果一個微服務卡頓導致整個調用鏈卡頓(有很多個請求,就執行了很多調用鏈),然后可能導致資源耗盡,即服務器雪崩。所以需要服務熔斷(可以設置規則,比如幾秒內有多少占比卡頓,那么以后的請求即快速釋放掉,防止占用資源)。
總結: 分布式就是一個大型應用被拆分成很多小應用分布部署在各個機器。一種架構(工作)方式
集群: 只是一種物理形態,只要部署在多個機器上就可以稱為集群。
網關: 這里的請求路由就可以配置規則,比如以order開頭就要轉給訂單所在的服務器(這時候需要問注冊中心)
分布式事務: 比如下完訂單和給用戶加積分倆個是一個整體,如果是單架構,則@Transactional基本可以解決,現在因為每個數據庫可能在不同服務器,所以就不能像以前那樣。
上述的各種方法即可解決各種問題。
Nacos
1.服務注冊
1.<packaging>pom</packaging>寫上就可以管理其他子項目,并且自身不用寫代碼了,一般最外層管理版本,再來一層來寫公共依賴,里邊再重新寫多個模塊即可。
所有服務創建后都應該經過注冊中心來注冊服務
在bin目錄下 startup.cmd -m standalone 以單機模式啟動(集群以后再了解吧)
然后輸入: localhost:8848/nacos 即可成功訪問
由于每個微服務都繼承了父類的springboot依賴,所以每個微服務也是springboot應用,然后也是定義application類,讓springboot應用跑起來。配置的yml文件也寫好基本就能成功了
<!--這里父項目指定了版本,所以不用寫version,而且所管理的子項目也會一起引入依賴--> <!--一般是外層的pom來引入方便統一管理,并且都需要這個依賴--> <!--服務發現--> <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>
可以配置端口號,防止占用 --server.port=8084
2.服務發現
第一步就是查看已經注冊的微服務
@SpringBootTest public class DiscoveryTest {@AutowiredDiscoveryClient discoveryClient;@AutowiredNacosServiceDiscovery nacosServiceDiscovery; ?@Testvoid discoveryClientTest(){for (String service : discoveryClient.getServices()) {System.out.println(service);// 獲取每個服務名下的相應的ip+端口discoveryClient.getInstances(service).forEach(instance -> {System.out.println("ip: " + instance.getHost() + " port: " + instance.getPort());});}} }
3.遠程調用(編碼式)
實體類: 除了可以pojo,也可以寫成bean
localhost:8081/order/create?userId=1&productId=6
? /*** 目前url其實也寫死了,所有后續負載均衡要改進一下* @param productId* @return*/private Product getProductFromRemote(Long productId) {// 1. 獲取到商品服務所在的所有機器ip+port// 目前我門已經知道serviceId了,所以為了方便演示就不獲取了List<ServiceInstance> instances = discoveryClient.getInstances("service-product");ServiceInstance serviceInstance = instances.get(0); // 隨便用一個// 后邊的即是定義的controller層地址String url = "http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort() + "/product/" + productId;log.info("遠程地址為: {}", url);// 2. 開始發送請求Product product = restTemplate.getForObject(url, Product.class);return product;}
負載均衡
<!--訂單要對訪問的商品做負載均衡--> <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-loadbalancer</artifactId> </dependency> 哪個業務需要就引入
默認是輪詢,如果想要制定其他規則,需要手寫。
方式一: 底層原理
private Product getProductFromRemoteWithLoadBalancer(Long productId) {// 1. 獲取到商品服務所在的所有機器ip+port// 目前我門已經知道serviceId了,所以為了方便演示就不獲取了ServiceInstance choose = loadBalancerClient.choose("service-product");// 后邊的即是定義的controller層地址String url = "http://" + choose.getHost() + ":" + choose.getPort() + "/product/" + productId;log.info("遠程地址為: {}", url);// 2. 開始發送請求Product product = restTemplate.getForObject(url, Product.class);return product; }
方式二: @LoadBalanced注解
在需要進行負載均衡訪問的配置類加上該注解就可以自動調用方法了
@Configuration public class OrderConfig { ?/*** 線程安全,這樣直接引入bean對象,這個方法就會自動調用哦* @return*/@Bean@LoadBalancedpublic RestTemplate restTemplate(){return new RestTemplate();} }
private Product getProductFromRemoteWithLoadBalancerAnnotation(Long productId) {// service-product會被動態替換成相應的ip和端口號String url = "http://service-product/product/" + productId;log.info("遠程地址為: {}", url);// 2. 開始發送請求Product product = restTemplate.getForObject(url, Product.class);return product; }
面試考點
總結就是如果第一次調用時,會先向注冊中心拿到ip和port并存儲到緩存中,之后如果注冊中心沒有更新,則會直接向緩存中拿ip和port,這樣不僅提高了效率,還保證了注冊中心宕機也會使用(當然前提你的ip和port都是能夠正常訪問的而且第一次不成功沒有存緩存,所以就失敗了)
4.配置中心
修改配置時只需在配置中心修改,這樣就能實時推送,并且不需要停機重新打包
配置中心基本使用:
yml格式的話,信息配置都要正確,也可以使用Properties。這里導入的nacos不寫地址就是默認的
方式一: @Value+@RefreshScope 獲取綁定值
@value就是獲取yml中自定義綁定的屬性值
@RefreshScope可以激活配置屬性的自動刷新功能,不用重啟就能拿到配置集的信息。
方式二: @ConfigurationProperties
專門抽取一個類來獲取綁定的屬性值,可以實現無感自動刷新。
方式三 編碼監聽配置變化
面試考點
后導入優先,外部優先。
1.如果配置中心和本身項目中定義的yml里的數據重復了,優先使用配置中心。
2.如果import: nacos:service-product.yml, classpath:application.yml,則后邊的會覆蓋前面的
5.數據隔離
nacos: Namespace(命名空間: 區分多種環境)=>Group(分組: 區分多套微服務)=>Data-id(數據集: 區分多種配置)
而在springboot中可以讓開發、測試、生成環境都綁定一套名稱空間,項目啟動時任意激活一個環境就可以按需加載使用。
注意其實命名都行,不過盡量規范就行,而且這個其實也不是真實的微服務名稱,只不過方便統一管理
小結
6.OpenFeign
遠程調用(聲明式)(重點)
補充小知識
@FeignClient("service-product") 可以指定微服務,如果微服務不存在的話 @FeignClient(value = "test-client", url = "http://xxx.com" ) 我們指定的url是協議加域名,后面的請求路徑則mvc注解來寫 這里:前面的即為協議-http,而后邊第一個/前面的即為域名xxx.com或者localhost:8080,最后的一部分即為請求路徑(比如@GetMapping(/ order)里邊指定的內容),而請求路徑后面也可能帶上參數來相互傳遞
小技巧
1.調用業務api: 如果是我們自己寫得客戶端想要用openfeign在一個業務調用其他業務的方法,可以直接將該controller層該方法和mvc注解復制一下即可,這時只要傳遞的參數正確一般都能正常訪問。
2.調用第三方api: 這時就需要參照第三方接口文檔來定義。
3.客戶端負載均衡: openFeign其實已經利用負載均衡算法選了一個地址調用。我們可以自己調整來解決高并發。
4.服務端負載均衡: 第三方一般只暴露了一個固定的地址,所以其內部自行進行負載均衡,我們就無需考慮。
進階配置
日志
首先需要配置一下yml:
超時控制
我們即使不設置,連接超時默認10s,讀取超時默認60s。
在yml中配置一下即可。
重試機制
如果遠程調用超時失敗后,還可以多次嘗試,仍然失敗則返回錯誤。
如上圖所示的默認配置: 初始定位100mm,那么每次失敗后的間隔時間就會100依次乘1.5,最大間隔定義為1s,然后嘗試次數為5次。
方式一: yml中配置
方式二: 手動配置
@Configuration // 表示可以有多個Bean public class OrderConfig { ?/*** 線程安全,這樣直接引入bean對象,這個方法就會自動調用哦* @return*/@Bean@LoadBalancedpublic RestTemplate restTemplate(){return new RestTemplate();} ?/*** 配置事務,yml中也可以配置* @return*/@BeanLogger.Level feignLoggerLevel(){return Logger.Level.FULL; // 固定寫法,注意包導入feign下的} ?@BeanRetryer retryer(){return new Retryer.Default(10, 1000, 4);} }
攔截器
@Component加上就可以全局攔截自動生效,如果想要局部攔截可以不加,直接在yml中配置。
包括請求攔截器(常用)和響應攔截器(用的較少)
@Component public class XTokenInterceptor implements RequestInterceptor {/*** 注意這個繼承的是feign里邊的攔截器方法* @param requestTemplate (詳細信息封裝在了這個模板中)*/@Overridepublic void apply(RequestTemplate requestTemplate) {System.out.println("攔截器啟動");requestTemplate.header("X-token", UUID.randomUUID().toString());} }
Fallback兜底
注意此時必須用到sentinel才可以
/*** 也是實現了自己寫得接口,只有失敗才會觸發* @FeignClient(value = "service-product", fallback = ProductFeignClientFallback.class)* 實現接口內的上述注解也不能少* 也要放在容器中*/ @Component public class ProductFeignClientFallback implements ProductFeignClient {@Overridepublic Product getByProductId(Long productId) {System.out.println("兜底回調...");Product product = new Product();product.setId(productId);product.setPrice(new BigDecimal("999"));product.setProductName("隱藏商品");product.setNum(0); ?return product;} }
7.Sentinel(可保護資源)
定義的規則可以存到Nacos、Zookeeper等配置中心來持久化使用
服務保護(限流||熔斷降級)
一般三步:1.定義資源 2.定義資源的保護規則 3.如何處理違反規則的異常
首先需要下載sentinel-dashboard-1.8.8.jar包然后cmd java - jar啟動即可(如果想修改端口也可以寫),然后默認用戶名和密碼都是sentinel
然后我們的項目微服務需要配置連接上sentinel控制臺即可,然后每個微服務都需要保護,所以依賴寫在最外邊。
/*** controller中是默認都為資源* 然后這個業務層我們也可以手動設置資源名讓它也被保護起來*/ @SentinelResource("createOrder") @Override public Order createOrder(Long productId, Long userId) {...}
當然在控制臺指定規則后所顯示的默認錯誤頁面不是json數據,傳遞給前端不友好,所以我們可以自定義異常處理器來解決
異常處理
如果不需要返回兜底數據,那么我們直接全局異常處理器即可顯示,而下方的各種異常如果使用了都可以覆蓋這個最外層的處理
/*** 是下邊這倆個異常的結合* @ControllerAdvice* @ResponseBody*/ @RestControllerAdvice public class GlobalExceptionHandler { ?@ExceptionHandler(Exception.class) // 捕捉所有異常public R handleException(Exception e) {return R.error("全局異常處理");} }
處理Web接口
注意: 此異常處理器只能處理默認的資源(比如controller下的各種mvc請求),而@SentinelResource自定義的資源不會處理,因為倆者底層不太一樣。
在exception包下新建一個處理類即可,不過注意由于sentinel在內存中,所以每次重啟項目都需要重新配置規則
@Component // 也是放進容器中即可 public class MyBlockExceptionHandler implements BlockExceptionHandler {private ObjectMapper objectMapper = new ObjectMapper();/*** resourceName就是我們控制臺定義的資源名稱*/@Overridepublic void handle(HttpServletRequest request, HttpServletResponse response,String resourceName, BlockException e) throws Exception {response.setContentType("application/json;charset=utf-8"); // 設置編碼格式,最開始就設置PrintWriter writer = response.getWriter();R error = R.error(resourceName + "被Sentinel限流了,請稍后再試,原因: " + e.getMessage()+ " 錯誤類: " + e.getClass());String json = objectMapper.writeValueAsString(error); // 可以用jackson來轉換成json字符串// 阿里的fastjson也行,不過jackson框架中常使用writer.write(json);writer.flush();writer.close();} }
處理@SentinelResource(一般標注在非controller層)
? /*** controller中是默認都為資源* 然后這個業務層我們也可以手動設置資源名讓它也被保護起來* blockHandler = "createOrderFallback"注意需要指定一下就會調用我們寫得兜底回調* 一般用這個blockHandler就行*/@SentinelResource(value = "createOrder", blockHandler = "createOrderFallback")@Overridepublic Order createOrder(Long productId, Long userId) {// 獲取商品 // ? ? ? Product product = getProductFromRemoteWithLoadBalancerAnnotation(productId);Product product = productFeignClient.getByProductId(productId);Order order = new Order();order.setId(1L);// 獲取總金額// 因為類型不同,所以轉換一下BigDecimal sum = product.getPrice().multiply(new BigDecimal(product.getNum()));order.setTotalAmount(sum);order.setUserId(userId);order.setNickName("清然");order.setAddress("萬達超市");// 遠程查詢商品列表,也是轉換一下order.setProductList(Arrays.asList(product)); ?return order;} ?/*** 參數寫法基本和我們的業務保持一致,然后加上BlockException可以了解出現了什么異常* 區分一下這是保護方法的兜底回調,以前的是OpenFeign超時配置的兜底回調*/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;}
處理OpenFeign
這個我們之前寫過,所以也會自動生效的
/*** 訂單中專門用Feign來調用商品微服務*/ @FeignClient(value = "service-product", fallback = ProductFeignClientFallback.class) // 訂單客戶端專門用來調用商品服務 public interface ProductFeignClient { ?// mvc注解此時標注在Feign上,所以是發送請求@GetMapping("/product/{id}") // 這個是路徑,不包含httpProduct getByProductId(@PathVariable("id") Long productId);// 如果用String則單純返回json字符串,而用Product則會自動封裝成該對象 }
/*** 也是實現了自己寫得接口,只有失敗才會觸發* @FeignClient(value = "service-product", fallback = ProductFeignClientFallback.class)* 實現接口內的上述注解也不能少* 也要放在容器中*/ @Component public class ProductFeignClientFallback implements ProductFeignClient {@Overridepublic Product getByProductId(Long productId) {System.out.println("兜底回調...");Product product = new Product();product.setId(productId);product.setPrice(new BigDecimal("999"));product.setProductName("隱藏商品");product.setNum(0); ?return product;} }
注意這個用到的是OpenFeign的注解,所以寫法是接口加繼承,如果用SentinelResource注解則可以在一個類下寫
Sphu
這個就是硬編碼的方式來處理,手動try-catch
try (Entry entry = SphU.entry("orderService")) {// 處理訂單邏輯System.out.println("訂單處理中..."); } catch ( BlockException ex) {// 觸發限流,返回友好提示System.out.println("當前請求過多,請稍后重試"); }
流控規則
閾值類型: 1.QPS(每秒的請求數,底層通過計數器來統計,輕量速度快) 2.并發線程數(配合線程池,控制線程池中的數量,可能涉及線程調度)
是否集群: 1.單機均攤(比如每個機器都1s內可以處理5個請求) 2.總體閾值(1s內一共可以處理5個請求)
流控模式(只有快速失敗才適用)
1.直接(默認模式,這樣只要資源訪問量大就做限流處理)
2.鏈路策略: 如果是倆個上下文,則必須先在yml中關閉,然后這樣每個不同請求路徑都是一個鏈路,否則所有請求默認都在一個下邊
spring:cloud:openfeign:client:config:default: # ? ? ? ? ? loggerLevel: full # 日志級別connectTimeout: 4000 #以mm為單位readTimeout: 2000 #2sservice-product: # 精確設置微服務名 # ? ? ? ? ? loggerLevel: full # 日志級別connectTimeout: 5000 #以mm為單位readTimeout: 2000 #2s # ? ? ? ? ? request-interceptors: # ? ? ? ? ? ? - com.daxz.order.interceptor.XTokenInterceptor # ? ? ? ? ? retryer: feign.Retryer.Defaultsentinel:transport:dashboard: localhost:8080 # 指定sentinel控制臺地址eager: true # 關閉懶加載(指只有真正發送請求時才開始建立連接,這樣可以適當減小開銷)為了方便,我們先關閉web-context-unify: false # 關閉,這樣可以開啟鏈路模式 ? feign:sentinel:enabled: true
3.當系統出現資源競爭時,可以采用關聯策略。(可以指定哪個資源要優先進行)
流控效果
1.快速失敗: 沒有超出閾值就交給業務處理,否則直接拋出異常
2.Warm Up: 有冷啟動時間
3.勻速排隊: 表示QPS的閾值在1s內均分,比如QPS=2,則500mm一個,不支持QPS>1000,因為這樣失效,而且會設置超時時間,沒排上隊就拋出異常即可。
熔斷規則
熔斷一般用于遠程調用(所以盡量自己編寫好兜底回調),并且熔斷配置在調用方
我們初始自定義的超時時間或者編寫的程序出現錯誤(比如1/0)都可以叫做異常,然后兜底返回。
錯誤糾正: 上邊的統計時長應該是熔斷策略開始統計的時間,超過最小請求數才會進行該過程。
最大RT就是預定時長。
熱點規則
實際上比流控規則就多了一條對指定參數的限制,更細致一點
// 測試了一個秒殺方法 @GetMapping("/seckill") // 此時又重新起個名字,因為熱點規則對Web不是自動適配的,還需要手寫 @SentinelResource(value = "seckill-order", fallback = "seckillFallback") public Order seckill(Long userId, Long productId) {Order order = orderService.createOrder(productId, Long.MAX_VALUE);return order; } ? // 上述的兜底回調方法 public Order seckillFallback(Long userId, Long productId, Throwable e) {Order order = new Order();order.setId(productId);order.setUserId(userId);order.setAddress("秒殺失敗,原因是: " + e.getMessage());return order; }
參數索引也是從0開始表示代碼中的第一個參數。
當然如果required = false,表示可以沒有參數,這樣沒有的話就不會觸發相應的規則,如果defaultValue = ... 仍然有默認參數會除法規則
注意: @SentinelResource中的 blockHandler可以專門處理BlockException流控異常,如果使用fallback需要寫為Throwable所有異常
因為fallback還能處理1/0等業務異常,所以范圍比BlockException更大。
授權規則和系統規則
這些基本沒啥用,因為基本都需要相互訪問,授權規則沒啥用,而系統規則則不太精確,以后k8s會更加精確,所以系統規則也不用管。
持久化
由于sentinel是在內存中,所以idea每次重啟都會丟失,這時候只需要將其配置存儲到nacos并且還可以連接數據庫,這樣就不會消失了。
8.Gateway
網關
屬于架構部分,所以可以直接創建在最外層即可,而且也能自動負載均衡
<dependencies><!--也需要nacos--><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>
路由
指routes下的id、uri、斷言規則、過濾器等每組整體稱作路由
配置好ym就可以簡單測試訪問網關。
server:port: 80 spring:profiles:include: routeapplication:name: gatewaycloud:nacos:server-addr: localhost:8848 #這樣可以通過網關來訪問 localhost/api/order/請求路徑即可訪問
spring:cloud:gateway:routes:- id: order-route # 我們起的名稱uri: lb://service-order # lb表示負載均衡的訪問我們項目的微服務名稱predicates: # 表示遵守的規則,也是各種集合,比如路徑規則- Path=/api/order/**# 還有更多規則,以后可以慢慢學- id: product-routeuri: lb://service-productpredicates:- Path=/api/product/**
原理
首先配置的id是全局唯一,然后滿足斷言規則(可以有多個),然后轉給uri目的地,這其中還可以有Filter過濾器。
注意: 如果不指定order排序(數字越小優先級越高),那么就自定向下匹配,如果滿足某id下的斷言后,其余就不會生效。
spring:cloud:gateway:routes:# 這就是個例子,當然很多網站可能都要登錄,所以匹配時可能也是錯誤頁面- id: test-routeuri: https://www.bilibili.com/ #https://cn.bing.compredicates:- Path=/**order: 10- id: order-route # 我們起的名稱uri: lb://service-order # lb表示負載均衡的訪問我們項目的微服務名稱predicates: # 表示遵守的規則,也是各種集合,比如路徑規則- Path=/api/order/**# 還有更多規則,以后可以慢慢學order: 0- id: product-routeuri: lb://service-productpredicates:- Path=/api/product/**order: 1 #如果不指定order,則順序匹配了規則,這樣即使localhost/api/order/config也不會正常訪問我們的地址
斷言
短寫法
-
Path=/api/order/** yml中各種名稱來自于斷言工廠xxxRoutePredicateFactory,然后里邊Config.class基本就是這些名稱來源了
長寫法
各種規則
上邊是一個Query例子。
自定義斷言工廠
這樣自定義配置后yml就可以用到了,斷言邏輯,還有我們參數的順序也是自己指定,然后長短寫法都ok
過濾器
大多就是修改請求和響應,比如請求頭、響應體。
如果想要微服務都生效可以使用默認filter
spring:cloud:gateway:routes:# 這就是個例子,當然很多網站可能都要登錄,所以匹配時可能也是錯誤頁面- id: test-routeuri: https://cn.bing.compredicates:- Path=/**order: 10- id: order-route # 我們起的名稱uri: lb://service-order # lb表示負載均衡的訪問我們項目的微服務名稱predicates: # 表示遵守的規則,也是各種集合,比如路徑規則- Path=/api/order/** # 匹配后也會按照這個請求發給微服務,如果不想全部修改微服務請求就可以重寫路徑filters: #RewritePath=/api/order/?(?<segment>.*), /${segment} 這種就是正則來重寫路徑 # ? ? ? ? ? - AddResponseHeader=X-Response-saber, Love 也可以自定義請求頭信息# 還有更多規則,以后可以慢慢學order: 0- id: product-routeuri: lb://service-productpredicates:- Path=/api/product/**order: 1default-filters: # 全局過濾器,所有請求都會經過- AddResponseHeader=X-Response-saber, Love # ? ? ? - AddRequestHeader=X-Request-role, violet 這個是提供給后端服務,所以瀏覽器不可見 #如果不指定order,則順序匹配了規則,這樣即使localhost/api/order/config也不會正常訪問我們的地址
也可以自定義一個全局filter:
package com.daxz.filter; ? import lombok.extern.slf4j.Slf4j; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.Ordered; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; ? /*** 用響應完成時間-請求開始時間就能知道響應時間(即請求耗費了多久)*/ @Component // 也要加注入容器 @Slf4j public class RtGlobalFilter implements GlobalFilter, Ordered {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {// filter中exchange包裝了請求和響應ServerHttpRequest request = exchange.getRequest();ServerHttpResponse response = exchange.getResponse();String uri = request.getURI().toString(); // 拿到請求地址long start = System.currentTimeMillis();log.info("請求地址[{}], 請求時間: {}", uri, start);// =================以上是前置邏輯,然后我們就可以用chain放行=================// 不過由于Mono是異步響應式,所以不能單純在下方寫后置邏輯,會瞬間執行// 流式處理加鏈式方法Mono<Void> filter = chain.filter(exchange).doFinally((result) -> {long end = System.currentTimeMillis();log.info("請求地址[{}], 響應時間: {}", uri, end - start);});// ================如果是用傳統的servlet則需要在下面寫后置邏輯================return filter;} ?// 可以指定順序@Overridepublic int getOrder() {return 0;} }
自定義過濾器工廠
和上邊的斷言工廠也一樣,就是yml中可以寫自定義的內容,具體的原理以后可以再深入學習
全局跨越
原始方案是給每個controller代碼都加上@CrossOrigin即可解決,不過有些麻煩,所以可以編寫跨域filter來解決跨域問題
spring:cloud:gateway:globalcors:cors-configurations:'[/**]':allowed-origin-patterns: '*'allowed-headers: '*'allowed-methods: '*'
9.Seata
以前是一條連接里邊的事務才可以一起提交,一起回滾。傳統事務是連接級別,所以微服務自治數據庫即使加上事務,有多個連接則沒有用
注意,目前是每個微服務都有獨立的數據庫,而不是一個數據庫里邊有微服務對應的多張表,那樣就是單純的單體/集群架構了。
當然如果是對一個數據庫中的各種表進行操作,引入事務注解還是可以操作的。
監聽8091,但也提供web頁面在7091,然后高版本好像有些問題,推薦2.1
必須引入TC才能管理事務
resource下配置file.conf即可連上seata,具體原理可以單獨學學
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(可以在service層標注,因為這里集中處理了事務)
@GlobalTransactional原理
二階提交協議
前鏡像: 在調用sql方法前來執行一次查詢操作
后鏡像: 更新后在執行一次查詢操作
還牽扯到全局鎖,可以多看幾遍了解原理
全部成功后才會刪除undo_log。
肯定效率不太高,以后深入學習再了解吧。
四種事務模式
AT模式
XA模式
AT和XA都是自動的,不過XA不會出現臟讀(可以搜搜),但效率低
TCC模式
這就是廣義事務(分三步),狹隘事務就是單純指對數據庫操作,當然TCC需要手寫
Saga模式
長事務,具體原理可以上網了解