Nacos注冊中心
服務端安裝
官網下載地址:https://nacos.io/zh-cn/docs/quick-start.html
解壓安裝包
進入bin目錄
單機模式啟動 sh startup.sh -m standalone
訪問 localhost:8848/nacos
默認賬號密碼 nacos/nacos

客戶端注冊與發現
1. 添加nacos的依賴 <!--添加nacos客戶端--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> 2. 在配置文件指定注冊中心地址 spring: cloud: nacos: discovery: server-addr: 127.0.0.1:8848 3. 在啟動類用注解開啟服務注冊 @EnableDiscoveryClient

統一配置中心
1. 添加代碼配置
1. 加入依賴 <!--統一配置中心--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> </dependency> 2. 配置bootstrap.yml (它的優先級比application.yml高) spring: application: name: order-service #在noas的名稱 cloud: nacos: config: server-addr: 192.168.200.100:8848 #Nacos配置中心地址 file-extension: yaml #文件拓展格式 group: risk #分組,一般用來區分不同的服務 namespace: test #命名空間,用來區分不同環境(默認是public) #profiles: # active: dev
2. 先新建命名空間,命名空間id和namespace保持一致;配置文件名稱和服務名稱一致;grop也保持一致。
3. 通過url查看配置是否生效 http://192.168.200.100:8848/nacos/v1/cs/configs?dataId=order-service.yaml&group=risk&tenant=test
4. 代碼里面通過 @RefreshScope 動態刷新Nacos配置

Feign遠程調用
1. 引入依賴包 <!--引入feign--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> 2. 在主函數開啟feign @EnableFeignClients 3. 編寫遠程調用接口(指定好其他服務的注冊名稱和路徑) @FeignClient(value = "video-service",path = "/video") public interface VideoFeign { @RequestMapping("find_by_id") JsonResult findById(@RequestParam("videoId") int videoId); } 4. 不設置也可以(feign默認1s超時,我們這里設置為5s) ribbon: ReadTimeout: 5000 #超時時間 ConnectTimeout: 5000 #連接時間 MaxAutoRetries: 0 #同一台實例最大重試次數,不包括首次調用 MaxAutoRetriesNextServer: 0 #重試負載均衡其他的實例最大重試次數,不包括首次調用 OkToRetryOnAllOperations: false #是否所有操作都重試

熔斷降級
1. 導入依賴 <!--引入sentinel--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> 2. 開啟Feign對Sentinel的支持 feign: sentinel: enabled: true 3. 配置feign容錯類 @FeignClient(value = "video-service",path = "/video", fallback = VideoServiceFallback.class) public interface VideoFeign { 4. 創建容錯類, 實現容錯邏輯, 記得加注解 @Service @Service public class VideoServiceFallback implements VideoFeign { @Override public JsonResult findById(int videoId) { return JsonResult.error("feign的url錯誤,走了fallback"); } }

Sentinel流量哨兵
中文文檔 https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8D
-Dserver.port=8080 控制台端口,sentinel控制台是一個spring boot程序,指定啟動端口。
-Dcsp.sentinel.dashboard.server=localhost:8080 指把控制台后當作一個客戶端,然后自動向該地址發送心跳包。
-Dproject.name=sentinel-dashboard 指定Sentinel控制台程序的名稱
啟動
nohup java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.0.jar &
訪問 localhost:8080
默認賬號密碼 sentinel/sentinel

客戶端接入
1. 導入依賴 <!--引入sentinel--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> 2. 添加注冊地址 spring: cloud: sentinel: transport: port: 9999 #跟控制台交流的端口,隨意指定一個未使用的端口即可,不同的服務用不同的端口 dashboard: localhost:8080 # 指定控制台服務的地址
默認是懶加載的,啟動后,我們調用先服務接口,然后刷新 sentinel 面板就能看到了
流控規則
在簇點鏈路里面根據url配置,我們這是事根據qps來限流,然后快速刷新瀏覽器就會出現失敗提示了。

熔斷降級
慢調用比例(響應時間): 選擇以慢調用比例作為閾值,需要設置允許的慢調用 RT(即最大的響應時間),請求的響應時間大於該值則統計為慢調用
比例閾值:修改后不生效(這是一個bug,期待官方后續修復)
熔斷時長:超過時間后會嘗試恢復
最小請求數:熔斷觸發的最小請求數,請求數小於該值時即使異常比率超出閾值也不會熔斷
異常比例:當單位統計時長內請求數目大於設置的最小請求數目,並且異常的比例大於閾值,則接下來的熔斷時長內請求會自動被熔斷
比例閾值
熔斷時長:超過時間后會嘗試恢復
最小請求數:熔斷觸發的最小請求數,請求數小於該值時,即使異常比率超出閾值也不會熔斷
異常數:當單位統計時長內的異常數目超過閾值之后會自動進行熔斷
異常數:
熔斷時長:超過時間后會嘗試恢復
最小請求數:熔斷觸發的最小請求數,請求數小於該值時即使異常比率超出閾值也不會熔斷

自定義異常降級
現在失敗后,默認都是返回 Blocked by Sentinel (flow limiting) 字符串,比較不容易排查。我們可以自定義項目數據交互格式。AlibabCloud版本升級,自從v2.1.0到v2.2.0后就出現了不兼容問題。
- 【舊版】實現UrlBlockHandler並且重寫blocked方法
@Component public class BaseUrlBlockHandler implements UrlBlockHandler { @Override public void blocked(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws IOException { //降級業務處理 } }
- 【新版】實現BlockExceptionHandler並且重寫handle方法
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.slots.block.authority.AuthorityException; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException; import com.alibaba.csp.sentinel.slots.block.flow.FlowException; import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException; import com.alibaba.csp.sentinel.slots.system.SystemBlockException; import com.alibaba.fastjson.JSON; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.HashMap; import java.util.Map; @Component public class BaseBlockExceptionHandler implements BlockExceptionHandler { @Override public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws Exception { Map<String,Object> info = new HashMap<>(); if(e instanceof FlowException){ info.put("code",-1); info.put("msg","限流異常"); } else if(e instanceof DegradeException){ info.put("code",-2); info.put("msg","降級異常"); } else if(e instanceof ParamFlowException){ info.put("code",-3); info.put("msg","熱點參數異常"); } else if(e instanceof SystemBlockException){ info.put("code",-4); info.put("msg","系統異常"); } else if(e instanceof AuthorityException){ info.put("code",-5); info.put("msg","授權異常"); } //設置json返回 httpServletResponse.setStatus(200); httpServletResponse.setHeader("content-type","application/json;charset=UTF-8"); httpServletResponse.getWriter().write(JSON.toJSONString(info)); } }

gateway網關
動態路由
1. 添加依賴 <!--添加gateway--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> 2. 配置路由規則 server: port: 7100 spring: application: name: api-gateway cloud: gateway: discovery: locator: enabled: true #開啟網關拉取nacos的服務 routes: #數組形式 - id: order-service #路由唯一標識 uri: lb://order-service #從nocas進行轉發到指定服務 #order: 1 #優先級,數字越小優先級越高 predicates: #斷言 配置路由規則 - Path=/order/** filters: #過濾器,請求在傳遞過程中通過過濾器修改 - StripPrefix=1 #轉發到具體服務時候,自動去掉第一層前綴(predicates的第一層地址) - id: video-service uri: lb://video-service predicates: - Path=/video/** filters: - StripPrefix=1 nacos: discovery: server-addr: 192.168.200.100:8848 # nacos的地址 zipkin: base-url: http://192.168.200.100:7200/ #zipkin地址 discovery-client-enabled: false #不用開啟服務發現 sleuth: sampler: probability: 1.0 #采樣百分比

過濾器
import org.apache.commons.lang3.StringUtils; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.Ordered; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; /** * 網關不要加太多業務邏輯,否則會影響性能 */ @Component public class TestFilter implements GlobalFilter, Ordered { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { System.out.println("TestFilter。。。。。。"); //寫業務邏輯 String token = exchange.getRequest().getHeaders().getFirst("token"); if(StringUtils.isBlank(token)){ //停止請求 exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); return exchange.getResponse().setComplete(); } //繼續往下執行 return chain.filter(exchange); } //數字越小,優先級越高 @Override public int getOrder() { return 0; } }
鏈路追蹤
Sleuth鏈路追蹤
1. 各個微服務添加依賴 <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-sleuth</artifactId> </dependency>
參數解析:
[order-service,6f3487a81c89682e,7db08a9ad1ea24d6,false]
第一個值,spring.application.name的值
第二個值,6f3487a81c89682e,sleuth生成的一個ID,叫Trace ID,用來標識一條請求鏈路,一條請求鏈路中包含一個Trace ID,多個Span ID
第三個值,7db08a9ad1ea24d6、spanid 基本的工作單元,獲取元數據,如發送一個http
第四個值:false,是否要將該信息輸出到zipkin服務中來收集和展示。
然后進行鏈路調用的時候,就會出現如下日志了:

zipkin儀表盤
安裝與持久化
1. 官網以及服務端下載 https://zipkin.io/pages/quickstart.html
2. 啟動服務端 默認端口是9411 可以通過 http://127.0.0.1:9411/zipkin/ 進行訪問
java -jar zipkin-server-2.12.9-exec.jar
3. 持久化
日志數據默認是存在內存中的,zipkin重啟后數據就沒了。我們可以持久化到mysql、es 中。
--STORAGE_TYPE 指定外部存儲源 mysql/es
--MYSQL_HOST mysql地址
--MYSQL_TCP_PORT 數據庫端口
--MYSQL_DB 數據庫名稱
--MYSQL_USER 賬號
--MYSQL_PASS 密碼
nohup java -jar zipkin-server-2.12.9-exec.jar --STORAGE_TYPE=mysql --MYSQL_HOST=127.0.0.1 --MYSQL_TCP_PORT=3306 --MYSQL_DB=zipkin_log --MYSQL_USER=root --MYSQL_PASS=root &
CREATE TABLE IF NOT EXISTS zipkin_spans ( `trace_id_high` BIGINT NOT NULL DEFAULT 0 COMMENT 'If non zero, this means the trace uses 128 bit traceIds instead of 64 bit', `trace_id` BIGINT NOT NULL, `id` BIGINT NOT NULL, `name` VARCHAR(255) NOT NULL, `remote_service_name` VARCHAR(255), `parent_id` BIGINT, `debug` BIT(1), `start_ts` BIGINT COMMENT 'Span.timestamp(): epoch micros used for endTs query and to implement TTL', `duration` BIGINT COMMENT 'Span.duration(): micros used for minDuration and maxDuration query', PRIMARY KEY (`trace_id_high`, `trace_id`, `id`) ) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8 COLLATE utf8_general_ci; ALTER TABLE zipkin_spans ADD INDEX(`trace_id_high`, `trace_id`) COMMENT 'for getTracesByIds'; ALTER TABLE zipkin_spans ADD INDEX(`name`) COMMENT 'for getTraces and getSpanNames'; ALTER TABLE zipkin_spans ADD INDEX(`remote_service_name`) COMMENT 'for getTraces and getRemoteServiceNames'; ALTER TABLE zipkin_spans ADD INDEX(`start_ts`) COMMENT 'for getTraces ordering and range'; CREATE TABLE IF NOT EXISTS zipkin_annotations ( `trace_id_high` BIGINT NOT NULL DEFAULT 0 COMMENT 'If non zero, this means the trace uses 128 bit traceIds instead of 64 bit', `trace_id` BIGINT NOT NULL COMMENT 'coincides with zipkin_spans.trace_id', `span_id` BIGINT NOT NULL COMMENT 'coincides with zipkin_spans.id', `a_key` VARCHAR(255) NOT NULL COMMENT 'BinaryAnnotation.key or Annotation.value if type == -1', `a_value` BLOB COMMENT 'BinaryAnnotation.value(), which must be smaller than 64KB', `a_type` INT NOT NULL COMMENT 'BinaryAnnotation.type() or -1 if Annotation', `a_timestamp` BIGINT COMMENT 'Used to implement TTL; Annotation.timestamp or zipkin_spans.timestamp', `endpoint_ipv4` INT COMMENT 'Null when Binary/Annotation.endpoint is null', `endpoint_ipv6` BINARY(16) COMMENT 'Null when Binary/Annotation.endpoint is null, or no IPv6 address', `endpoint_port` SMALLINT COMMENT 'Null when Binary/Annotation.endpoint is null', `endpoint_service_name` VARCHAR(255) COMMENT 'Null when Binary/Annotation.endpoint is null' ) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8 COLLATE utf8_general_ci; ALTER TABLE zipkin_annotations ADD UNIQUE KEY(`trace_id_high`, `trace_id`, `span_id`, `a_key`, `a_timestamp`) COMMENT 'Ignore insert on duplicate'; ALTER TABLE zipkin_annotations ADD INDEX(`trace_id_high`, `trace_id`, `span_id`) COMMENT 'for joining with zipkin_spans'; ALTER TABLE zipkin_annotations ADD INDEX(`trace_id_high`, `trace_id`) COMMENT 'for getTraces/ByIds'; ALTER TABLE zipkin_annotations ADD INDEX(`endpoint_service_name`) COMMENT 'for getTraces and getServiceNames'; ALTER TABLE zipkin_annotations ADD INDEX(`a_type`) COMMENT 'for getTraces and autocomplete values'; ALTER TABLE zipkin_annotations ADD INDEX(`a_key`) COMMENT 'for getTraces and autocomplete values'; ALTER TABLE zipkin_annotations ADD INDEX(`trace_id`, `span_id`, `a_key`) COMMENT 'for dependencies job'; CREATE TABLE IF NOT EXISTS zipkin_dependencies ( `day` DATE NOT NULL, `parent` VARCHAR(255) NOT NULL, `child` VARCHAR(255) NOT NULL, `call_count` BIGINT, `error_count` BIGINT, PRIMARY KEY (`day`, `parent`, `child`) ) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8 COLLATE utf8_general_ci;
啟動客戶端
1. 加入依賴 <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zipkin</artifactId> </dependency>
2. 配置地址和采樣百分比配置
spring:
application:
name: api-gateway
zipkin:
base-url: http://127.0.0.1:9411/ #zipkin地址
discovery-client-enabled: false #不用開啟服務發現
sleuth:
sampler:
probability: 1.0 #采樣百分比
默認為0.1,即10%,這里配置1,是記錄全部的sleuth信息,是為了收集到更多的數據(僅供測試用)。在分布式系統中,過於頻繁的采樣會影響系統性能,所以這里配置需要采用一個合適的值。
請求過后,直接可以在面板就能看到 鏈路詳細信息 了。

Seata事務踩坑
版本一定要對應,不然會出現各種奇怪的錯誤
服務端版本: seata-server-1.1.0.tar.gz 客戶端版本: <dependencies> <!--SpringBoot--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>2.3.3.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> <!--SpringCloud--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Hoxton.SR8</version> <type>pom</type> <scope>import</scope> </dependency> <!--AlibabaCloud--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>2.2.1.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> <!--seata--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-seata</artifactId> <version>2.2.1.RELEASE</version> </dependency> </dependencies>
服務端
1. 解壓seata
tar -zxvf seata-server-1.1.0.tar.gz
2. 創建日志文件夾
cd seata && mkdir logs
3. 務必后台啟動seata,否則關掉啟動日志后,客戶端會連接失敗,出現 “can not register RM,err:register error,role:RMROLE,err:cost 30001 ms”
nohup ./bin/seata-server.sh >log.out 2>1 &
客戶端
1. 全局異常處理(務必加入異常判斷,否則在方法加上本地事務注解時,接口報錯會返回數據庫異常信息)

import com.wulei.common.pojo.Result; import com.wulei.common.pojo.StatusCode; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.transaction.TransactionSystemException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestControllerAdvice; import java.util.Arrays; @RestControllerAdvice public class BaseExceptionHandler { public final Logger log = LoggerFactory.getLogger(this.getClass()); /*** * 異常處理 * @param e * @return */ @ExceptionHandler(value = Exception.class) @ResponseBody public Result error(Exception e) { log.error("func [exceptionHandle] Exception [{} - {}] stackTrace[{}]",e.getCause(), e.getMessage(), Arrays.deepToString(e.getStackTrace())); // 務必加上這一截異常判斷 if(e instanceof TransactionSystemException) { return new Result(false, StatusCode.REMOTEERROR, "調用異常"); } return new Result(false, StatusCode.ERROR, e.getMessage()); } /** * 自定義異常(處理業務邏輯異常) * @param e * @return */ @ExceptionHandler(RiskException.class) @ResponseBody public Result riskException(RiskException e) { log.error("func [RiskException] Exception [{} - {}] stackTrace[{}]",e.getCause(), e.getMessage(), Arrays.deepToString(e.getStackTrace())); return new Result(false, StatusCode.ERROR, e.getMessage()); } }
2. 配置切面攔截
import io.seata.core.context.RootContext; import io.seata.core.exception.TransactionException; import io.seata.spring.annotation.GlobalTransactional; import io.seata.tm.api.GlobalTransaction; import io.seata.tm.api.GlobalTransactionContext; import org.apache.commons.lang.StringUtils; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.reflect.MethodSignature; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import java.lang.reflect.Method; @Aspect @Component public class SeataTransactionalAspect { private final static Logger logger= LoggerFactory.getLogger(SeataTransactionalAspect.class); @Before("execution(* com.wulei.*.service.*.*(..))") public void before(JoinPoint joinPoint) throws TransactionException { MethodSignature signature = (MethodSignature)joinPoint.getSignature(); Method method = signature.getMethod(); if(method.isAnnotationPresent(GlobalTransactional.class)) { logger.info("攔截到需要分布式事務的方法," + method.getName()); // 此處可用redis或者定時任務來獲取一個key判斷是否需要關閉分布式事務 GlobalTransaction tx = GlobalTransactionContext.getCurrentOrCreate(); tx.begin(); logger.info("創建分布式事務完畢" + tx.getXid()); } } @AfterThrowing(throwing = "e", pointcut = "execution(* com.wulei.*.service.*.*(..))") public void doRecoveryActions(Throwable e) throws TransactionException { logger.info("方法執行異常:{}", e.getMessage()); if (!StringUtils.isBlank(RootContext.getXID())) { GlobalTransactionContext.reload(RootContext.getXID()).rollback(); } } }
3. 配置注冊信息
# seata
seata:
enabled: true
application-id: ${spring.application.name}
tx-service-group: my_test_tx_group
enable-auto-data-source-proxy: true
service:
vgroup-mapping:
my_test_tx_group: default
grouplist:
default: 192.168.200.100:8091
。
