SpringCloud微服務之Nacos、Feign、GateWay詳解
- 1、Nacos配置管理
- 1.1、統一配置管理
- 1.1.1、在nacos中添加配置文件
- 1.1.2、從微服務拉取配置
- 1.2、配置熱更新
- 1.2.1、方式一
- 1.2.2、方式二
- 1.3、配置共享
- 1.3.1、配置共享的優先級
- 1.4、搭建nacos集群
- 1.4.1、初始化數據庫
- 1.4.2、下載nacos
- 1.4.3、配置nacos
- 1.4.4、啟動nacos集群
- 1.4.5、nginx反向代理
- 1.4.6、優化
- 2、Feign遠程調用
- 2.1、Feign遠程調用替代RestTemplate
- 2.2、Feign自定義配置
- 2.2.1、配置文件方式
- 2.2.2、Java代碼方式
- 2.3、Feign使用優化
- 2.4、最佳實踐
- 2.4.1、抽取Client模塊
- 3、Gateway服務網關
- 3.1、為什么需要網關
- 3.2、gateway快速入門
- 3.2.1、網關路由的流程圖
- 3.2.2、總結
- 3.3、斷言工廠
- 3.4、過濾器工廠
- 3.4.1、路由過濾器的種類
- 3.4.2、請求頭過濾器
- 3.4.3、默認過濾器
- 3.4.4、總結
- 3.5、全局過濾器
- 3.5.1、自定義全局過濾器
- 3.5.2、過濾器執行順序
- 3.6、跨域問題
- 3.6.1、什么是跨域問題
- 3.6.2、解決跨域問題
1、Nacos配置管理
Nacos除了可以做注冊中心,同樣可以做配置管理來使用。
1.1、統一配置管理
當微服務部署的實例越來越多,達到數十、數百時,逐個修改微服務配置就會讓人抓狂,而且很容易出錯。我們需要一種統一配置管理方案,可以集中管理所有實例的配置。
Nacos一方面可以將配置集中管理,另一方可以在配置變更時,及時通知微服務,實現配置的熱更新。
1.1.1、在nacos中添加配置文件
如何在nacos中管理配置呢?
注意:項目的核心配置,需要熱更新的配置才有放到nacos管理的必要。基本不會變更的一些配置還是保存在微服務本地比較好。
1.1.2、從微服務拉取配置
微服務要拉取nacos中管理的配置,第二步與本地的application.yml
配置合并,才能完成項目啟動。application.yml里面存放的是nacos的地址信息,第二步才讀取,可是第一步在拉取nacos中的配置又需要知道這些信息,那么第一步該如何得知nacos地址呢?
因此spring引入了一種新的配置文件:bootstrap.yaml
文件,會在application.yml之前被讀取,流程如下:
項目啟動后,先去讀取 bootstrap.yaml
文件,我們將nacos的信息都放在 bootstrap.yaml
,這樣就得知了 nacos 地址,第二步再去讀取本地配置文件 application.yaml
,二者合并,進行后續動作。
- 引入nacos-config依賴:首先,在user-service服務中,引入nacos-config的客戶端依賴:
<!--nacos配置管理依賴-->
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
- 添加bootstrap.yaml:然后,在user-service中添加一個bootstrap.yaml文件,內容如下,并且將
application.yml
里面相同的配置刪掉:
spring:application:name: userservice # 服務名稱profiles:active: dev #開發環境,這里是dev cloud:nacos:server-addr: localhost:8848 # Nacos地址config:file-extension: yaml # 文件后綴名
這里會根據spring.cloud.nacos.server-addr
獲取nacos地址,再根據
${spring.application.name}-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
作為文件id,來讀取配置。
本例中,就是去讀取userservice-dev.yaml
:
- 讀取nacos配置:在user-service中的UserController中添加業務邏輯,讀取
pattern.dateformat
配置:
- 訪問:
http://localhost:8081/user/now
1.2、配置熱更新
我們最終的目的,是修改nacos中的配置后,微服務中無需重啟即可讓配置生效,也就是配置熱更新。要實現配置熱更新,可以使用兩種方式:
- 方式一:在
@Value
注入的變量所在類上添加注解@RefreshScope
- 方式二:使用
@ConfigurationProperties
注解代替@Value
注解
1.2.1、方式一
方式一:在@Value
注入的變量所在類上添加注解@RefreshScope
1.2.2、方式二
方式二:使用@ConfigurationProperties
注解代替@Value
注解
- 在user-service服務中,添加一個類
cn/itcast/user/config/PatternProperties
,讀取patterrn.dateformat
屬性:
@Component
@Data
@ConfigurationProperties(prefix = "pattern")
public class PatternProperties {private String dateformat;
}
- 在UserController中使用這個類代替
@Value
,也就是通過對象的方法注入當前時間
- 測試,在nacos修改統一配置,配置內容更改為如下,然后發布
pattern:dateformat: yyyy年MM月dd日 HH:mm:ss
- 訪問:
http://localhost:8081/user/now
1.3、配置共享
其實微服務啟動時,會去nacos讀取多個配置文件,例如:
[spring.application.name]-[spring.profiles.active].yaml
- 例如:
userservice-dev.yaml
- 例如:
[spring.application.name].yaml
- 例如:
userservice.yaml
- 例如:
而[spring.application.name].yaml
不包含環境,因此可以被多個環境共享。下面我們通過案例來測試配置共享
- 我們在nacos中添加一個環境共享配置
userservice.yaml
文件:
- 在user-service中讀取共享配置:在
user-service
服務中,修改PatternProperties類,讀取新添加的屬性
在user-service服務中,修改UserController,添加一個方法:
- 運行兩個UserApplication,使用不同的profile,修改UserApplication2這個啟動項,改變其profile值:
這樣,UserApplication(8081)使用的profile是dev,UserApplication2(8082)使用的profile是test。
啟動UserApplication和UserApplication2:
- 訪問http://localhost:8081/user/prop,結果:
- 訪問http://localhost:8082/user/prop,結果:
可以看出來,不管是dev,還是test環境,都讀取到了envSharedValue這個屬性的值。
1.3.1、配置共享的優先級
當nacos、服務本地同時出現相同屬性時,優先級有高低之分:
1.4、搭建nacos集群
Nacos生產環境下一定要部署為集群狀態,官方給出的Nacos集群圖:
其中包含3個nacos節點,然后一個負載均衡器SLB代理3個Nacos。這里負載均衡器可以使用nginx。我們計劃的集群結構如下更清晰:
三個nacos節點的地址:
節點 | ip | port |
---|---|---|
nacos1 | 192.168.1.200 | 8845 |
nacos2 | 192.168.1.200 | 8846 |
nacos3 | 192.168.1.200 | 8847 |
搭建集群的基本步驟:
- 搭建數據庫,初始化數據庫表結構
- 下載nacos安裝包
- 配置nacos
- 啟動nacos集群
- nginx反向代理
1.4.1、初始化數據庫
官方推薦的最佳實踐是使用帶有主從的高可用數據庫集群,這里我們以單點的數據庫為例來講解。首先新建一個數據庫,命名為nacos,而后導入下面的SQL:
CREATE TABLE `config_info` (`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',`data_id` varchar(255) NOT NULL COMMENT 'data_id',`group_id` varchar(255) DEFAULT NULL,`content` longtext NOT NULL COMMENT 'content',`md5` varchar(32) DEFAULT NULL COMMENT 'md5',`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '創建時間',`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改時間',`src_user` text COMMENT 'source user',`src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip',`app_name` varchar(128) DEFAULT NULL,`tenant_id` varchar(128) DEFAULT '' COMMENT '租戶字段',`c_desc` varchar(256) DEFAULT NULL,`c_use` varchar(64) DEFAULT NULL,`effect` varchar(64) DEFAULT NULL,`type` varchar(64) DEFAULT NULL,`c_schema` text,PRIMARY KEY (`id`),UNIQUE KEY `uk_configinfo_datagrouptenant` (`data_id`,`group_id`,`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info';/******************************************/
/* 數據庫全名 = nacos_config */
/* 表名稱 = config_info_aggr */
/******************************************/
CREATE TABLE `config_info_aggr` (`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',`data_id` varchar(255) NOT NULL COMMENT 'data_id',`group_id` varchar(255) NOT NULL COMMENT 'group_id',`datum_id` varchar(255) NOT NULL COMMENT 'datum_id',`content` longtext NOT NULL COMMENT '內容',`gmt_modified` datetime NOT NULL COMMENT '修改時間',`app_name` varchar(128) DEFAULT NULL,`tenant_id` varchar(128) DEFAULT '' COMMENT '租戶字段',PRIMARY KEY (`id`),UNIQUE KEY `uk_configinfoaggr_datagrouptenantdatum` (`data_id`,`group_id`,`tenant_id`,`datum_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='增加租戶字段';/******************************************/
/* 數據庫全名 = nacos_config */
/* 表名稱 = config_info_beta */
/******************************************/
CREATE TABLE `config_info_beta` (`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',`data_id` varchar(255) NOT NULL COMMENT 'data_id',`group_id` varchar(128) NOT NULL COMMENT 'group_id',`app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',`content` longtext NOT NULL COMMENT 'content',`beta_ips` varchar(1024) DEFAULT NULL COMMENT 'betaIps',`md5` varchar(32) DEFAULT NULL COMMENT 'md5',`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '創建時間',`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改時間',`src_user` text COMMENT 'source user',`src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip',`tenant_id` varchar(128) DEFAULT '' COMMENT '租戶字段',PRIMARY KEY (`id`),UNIQUE KEY `uk_configinfobeta_datagrouptenant` (`data_id`,`group_id`,`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info_beta';/******************************************/
/* 數據庫全名 = nacos_config */
/* 表名稱 = config_info_tag */
/******************************************/
CREATE TABLE `config_info_tag` (`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',`data_id` varchar(255) NOT NULL COMMENT 'data_id',`group_id` varchar(128) NOT NULL COMMENT 'group_id',`tenant_id` varchar(128) DEFAULT '' COMMENT 'tenant_id',`tag_id` varchar(128) NOT NULL COMMENT 'tag_id',`app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',`content` longtext NOT NULL COMMENT 'content',`md5` varchar(32) DEFAULT NULL COMMENT 'md5',`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '創建時間',`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改時間',`src_user` text COMMENT 'source user',`src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip',PRIMARY KEY (`id`),UNIQUE KEY `uk_configinfotag_datagrouptenanttag` (`data_id`,`group_id`,`tenant_id`,`tag_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info_tag';/******************************************/
/* 數據庫全名 = nacos_config */
/* 表名稱 = config_tags_relation */
/******************************************/
CREATE TABLE `config_tags_relation` (`id` bigint(20) NOT NULL COMMENT 'id',`tag_name` varchar(128) NOT NULL COMMENT 'tag_name',`tag_type` varchar(64) DEFAULT NULL COMMENT 'tag_type',`data_id` varchar(255) NOT NULL COMMENT 'data_id',`group_id` varchar(128) NOT NULL COMMENT 'group_id',`tenant_id` varchar(128) DEFAULT '' COMMENT 'tenant_id',`nid` bigint(20) NOT NULL AUTO_INCREMENT,PRIMARY KEY (`nid`),UNIQUE KEY `uk_configtagrelation_configidtag` (`id`,`tag_name`,`tag_type`),KEY `idx_tenant_id` (`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_tag_relation';/******************************************/
/* 數據庫全名 = nacos_config */
/* 表名稱 = group_capacity */
/******************************************/
CREATE TABLE `group_capacity` (`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主鍵ID',`group_id` varchar(128) NOT NULL DEFAULT '' COMMENT 'Group ID,空字符表示整個集群',`quota` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '配額,0表示使用默認值',`usage` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '使用量',`max_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '單個配置大小上限,單位為字節,0表示使用默認值',`max_aggr_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '聚合子配置最大個數,,0表示使用默認值',`max_aggr_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '單個聚合數據的子配置大小上限,單位為字節,0表示使用默認值',`max_history_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '最大變更歷史數量',`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '創建時間',`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改時間',PRIMARY KEY (`id`),UNIQUE KEY `uk_group_id` (`group_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='集群、各Group容量信息表';/******************************************/
/* 數據庫全名 = nacos_config */
/* 表名稱 = his_config_info */
/******************************************/
CREATE TABLE `his_config_info` (`id` bigint(64) unsigned NOT NULL,`nid` bigint(20) unsigned NOT NULL AUTO_INCREMENT,`data_id` varchar(255) NOT NULL,`group_id` varchar(128) NOT NULL,`app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',`content` longtext NOT NULL,`md5` varchar(32) DEFAULT NULL,`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,`src_user` text,`src_ip` varchar(50) DEFAULT NULL,`op_type` char(10) DEFAULT NULL,`tenant_id` varchar(128) DEFAULT '' COMMENT '租戶字段',PRIMARY KEY (`nid`),KEY `idx_gmt_create` (`gmt_create`),KEY `idx_gmt_modified` (`gmt_modified`),KEY `idx_did` (`data_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='多租戶改造';/******************************************/
/* 數據庫全名 = nacos_config */
/* 表名稱 = tenant_capacity */
/******************************************/
CREATE TABLE `tenant_capacity` (`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主鍵ID',`tenant_id` varchar(128) NOT NULL DEFAULT '' COMMENT 'Tenant ID',`quota` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '配額,0表示使用默認值',`usage` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '使用量',`max_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '單個配置大小上限,單位為字節,0表示使用默認值',`max_aggr_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '聚合子配置最大個數',`max_aggr_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '單個聚合數據的子配置大小上限,單位為字節,0表示使用默認值',`max_history_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '最大變更歷史數量',`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '創建時間',`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改時間',PRIMARY KEY (`id`),UNIQUE KEY `uk_tenant_id` (`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='租戶容量信息表';CREATE TABLE `tenant_info` (`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',`kp` varchar(128) NOT NULL COMMENT 'kp',`tenant_id` varchar(128) default '' COMMENT 'tenant_id',`tenant_name` varchar(128) default '' COMMENT 'tenant_name',`tenant_desc` varchar(256) DEFAULT NULL COMMENT 'tenant_desc',`create_source` varchar(32) DEFAULT NULL COMMENT 'create_source',`gmt_create` bigint(20) NOT NULL COMMENT '創建時間',`gmt_modified` bigint(20) NOT NULL COMMENT '修改時間',PRIMARY KEY (`id`),UNIQUE KEY `uk_tenant_info_kptenantid` (`kp`,`tenant_id`),KEY `idx_tenant_id` (`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='tenant_info';CREATE TABLE `users` (`username` varchar(50) NOT NULL PRIMARY KEY,`password` varchar(500) NOT NULL,`enabled` boolean NOT NULL
);CREATE TABLE `roles` (`username` varchar(50) NOT NULL,`role` varchar(50) NOT NULL,UNIQUE INDEX `idx_user_role` (`username` ASC, `role` ASC) USING BTREE
);CREATE TABLE `permissions` (`role` varchar(50) NOT NULL,`resource` varchar(255) NOT NULL,`action` varchar(8) NOT NULL,UNIQUE INDEX `uk_role_permission` (`role`,`resource`,`action`) USING BTREE
);INSERT INTO users (username, password, enabled) VALUES ('nacos', '$2a$10$EuWPZHzz32dJN7jexM34MOeYirDdFAZm2kuWj7VEOJhhZkDrxfvUu', TRUE);INSERT INTO roles (username, role) VALUES ('nacos', 'ROLE_ADMIN');
1.4.2、下載nacos
GitHub地址:https://github.com/alibaba/nacos/tags
1.4.3、配置nacos
- 進入nacos的conf目錄,修改配置文件
cluster.conf.example
,重命名為cluster.conf
,然后添加內容
127.0.0.1:8845
127.0.0.1.8846
127.0.0.1.8847
- 然后修改
application.properties
文件,添加數據庫配置
spring.datasource.platform=mysqldb.num=1db.url.0=jdbc:mysql://127.0.0.1:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
db.user.0=root
db.password.0=123456
1.4.4、啟動nacos集群
- 將nacos文件夾復制三份,分別命名為:nacos1、nacos2、nacos3
- 然后分別修改三個文件夾中的
application.properties
nacos1:
server.port=8845
nacos2:
server.port=8846
nacos3:
server.port=8847
然后分別啟動三個nacos節點:
# 集群啟動
startup.cmd
1.4.5、nginx反向代理
- 修改nginx的
conf/nginx.conf
文件,配置如下:
# 配置 nacos-cluster 集群
upstream nacos-cluster {server 127.0.0.1:8845;server 127.0.0.1:8846;server 127.0.0.1:8847;
}# 反向代理
server {# 以后訪問nacos不用8848了,直接是80端口listen 80;server_name localhost;# 只要訪問了 /nacos 路徑,就代理到nacos集群location /nacos {proxy_pass http://nacos-cluster;}
}
- 代碼中application.yml文件配置如下:
spring:cloud:nacos:server-addr: localhost:80 # Nacos地址
- 而后在瀏覽器訪問:http://localhost/nacos 即可,這樣會在三個 nacos 作負載均衡
1.4.6、優化
-
實際部署時,需要給做反向代理的nginx服務器設置一個域名,這樣后續如果有服務器遷移nacos的客戶端也無需更改配置.
-
Nacos的各個節點應該部署到多個不同服務器,做好容災和隔離
2、Feign遠程調用
- Feign 讀音:
糞~
先來看我們以前利用RestTemplate發起遠程調用的代碼:
String url = "http://userservice/user/" + order.getUserId();
User user = restTemplate.getForObject(url,User.class);
存在下面的問題:
-
代碼可讀性差,編程體驗不統一
-
參數復雜URL難以維護
Feign是一個聲明式的http客戶端,官方地址:https://github.com/OpenFeign/feign
其作用就是幫助我們優雅的實現http請求的發送,解決上面提到的問題。
2.1、Feign遠程調用替代RestTemplate
- 引入依賴:我們在
order-service
服務的pom文件中引入feign的依賴:
<!--Feign-->
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
- 添加注解:在order-service的啟動類添加注解開啟Feign的功能:
- 編寫Feign的客戶端:在order-service中新建一個接口
roder/client/UserClient
,內容如下:
@FeignClient("userservice")
public interface UserClient {@GetMapping("/user/{id}")User findById(@PathVariable("id") Long id);
}
這個客戶端主要是基于SpringMVC的注解來聲明遠程調用的信息,比如:
- 服務名稱:userservice
- 請求方式:GET
- 請求路徑:/user/{id}
- 請求參數:Long id
- 返回值類型:User
這樣,Feign就可以幫助我們發送http請求,無需自己使用RestTemplate來發送了。
- 測試:修改order-service中的OrderService類中的queryOrderById方法,使用Feign客戶端代替RestTemplate
這樣看起來就優雅多了。
- 訪問:
http://localhost:8080/order/101
2.2、Feign自定義配置
Feign可以支持很多的自定義配置,如下表所示:
類型 | 作用 | 說明 |
---|---|---|
feign.Logger.Level | 修改日志級別 | 包含四種不同的級別:NONE、BASIC、HEADERS、FULL |
feign.codec.Decoder | 響應結果的解析器 | http遠程調用的結果做解析,例如解析json字符串為java對象 |
feign.codec.Encoder | 請求參數編碼 | 將請求參數編碼,便于通過http請求發送 |
feign. Contract | 支持的注解格式 | 默認是SpringMVC的注解 |
feign. Retryer | 失敗重試機制 | 請求失敗的重試機制,默認是沒有,不過會使用Ribbon的重試 |
一般情況下,默認值就能滿足我們使用,如果要自定義時,只需要創建自定義的@Bean覆蓋默認Bean即可。下面以日志為例來演示如何自定義配置。
- 方式一:配置文件方式
- 方式二:Java代碼方式
2.2.1、配置文件方式
基于配置文件修改feign的日志級別可以針對單個服務:
feign: client:config: userservice: # 針對某個微服務的配置loggerLevel: FULL # 日志級別
也可以針對所有服務:
feign: client:config: default: # 這里用default就是全局配置,如果是寫服務名稱,則是針對某個微服務的配置loggerLevel: BASIC # 日志級別
而日志的級別分為四種:
- NONE:不記錄任何日志信息,這是默認值。
- BASIC:僅記錄請求的方法,URL以及響應狀態碼和執行時間
- HEADERS:在BASIC的基礎上,額外記錄了請求和響應的頭信息
- FULL:記錄所有請求和響應的明細,包括頭信息、請求體、元數據。
2.2.2、Java代碼方式
也可以基于Java代碼來修改日志級別,先聲明一個類,然后聲明一個Logger.Level
的對象:
public class DefaultFeignConfiguration {@Beanpublic Logger.Level feignLogLevel(){return Logger.Level.BASIC; // 日志級別為BASIC}
}
- 如果要全局生效,將其放到啟動類的@EnableFeignClients這個注解中:
@EnableFeignClients(defaultConfiguration = DefaultFeignConfiguration .class)
- 如果是局部生效,則把它放到對應的@FeignClient這個注解中:
@FeignClient(value = "userservice", configuration = DefaultFeignConfiguration .class)
2.3、Feign使用優化
提高Feign的性能主要手段就是使用連接池
- 在order-service的pom文件引入依賴
<!--httpClient的依賴 -->
<dependency><groupId>io.github.openfeign</groupId><artifactId>feign-httpclient</artifactId>
</dependency>
- 配置連接池:在order-service的
application.yml
中添加配置:
feign:client:config:default: # default全局的配置loggerLevel: BASIC # 日志級別,BASIC就是基本的請求和響應信息httpclient:enabled: true # 開啟feign對HttpClient的支持max-connections: 200 # 最大的連接數max-connections-per-route: 50 # 每個路徑的最大連接數
總結:
- 日志級別盡量用basic
- 使用連接池
- 真實情況下連接池的參數需要經過壓測才能確定最合適的參數值
2.4、最佳實踐
Feign的客戶端與服務提供者的controller代碼非常相似,有沒有一種辦法簡化這種重復的代碼編寫呢?
2.4.1、抽取Client模塊
將Feign的Client抽取為獨立模塊,并且把接口有關的POJO、默認的Feign配置都放到這個模塊中,提供給所有消費者使用。
例如,將UserClient、User、Feign的默認配置都抽取到一個feign-api包中,所有微服務引用該依賴包,即可直接使用。
- 首先創建一個module,命名為feign-api:
-
然后,order-service中編寫的UserClient、User都復制到feign-api項目中
-
刪除order-service中的UserClient、User等類或接口
-
在order-service的pom文件中中引入feign-api的依賴:
<!--feign-api-->
<dependency><groupId>cn.itcast.demo</groupId><artifactId>feign-api</artifactId><version>1.0</version>
</dependency>
- 修改order-service中的所有與上述三個組件有關的導包部分,改成導入feign-api中的包
- 注意:order-service的
@EnableFeignClients
注解是在cn.itcast.order包下,而 UserClient 是在cn.itcast.feign
包下,所以需要解決掃描包問題
// 指定需要加載的Client接口
@EnableFeignClients(clients = {UserClient.class})
3、Gateway服務網關
Spring Cloud Gateway 是 Spring Cloud 的一個全新項目,該項目是基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等響應式編程和事件流技術開發的網關,它旨在為微服務架構提供一種簡單有效的統一的 API 路由管理方式。
3.1、為什么需要網關
Gateway網關是我們服務的守門神,所有微服務的統一入口。
網關的核心功能特性:
- 請求路由
- 權限控制
- 限流
架構圖:
我們有很多個微服務業務,每個微服務有自己的數據庫,每個微服務都可以去Nacos進行服務注冊和配置的管理,各個微服務之間若有相互調用關系,采用Feign遠程調用。
但是若外部想要訪問微服務,則需要先經過網關gateway,進行身份認證,認證成功后通過路由和負載均衡將其轉發到對應微服務服務。
- 權限控制:網關作為微服務入口,需要校驗用戶是是否有請求資格,如果沒有則進行攔截。
- 路由和負載均衡:一切請求都必須先經過gateway,但網關不處理業務,而是根據某種規則,把請求轉發到某個微服務,這個過程叫做路由。當然路由的目標服務有多個時,還需要做負載均衡。
- 限流:當請求流量過高時,在網關中按照下流的微服務能夠接受的速度來放行請求,避免服務壓力過大。
在SpringCloud中網關的實現包括兩種:
- gateway
- zuul
Zuul是基于Servlet的實現,屬于阻塞式編程。而SpringCloudGateway則是基于Spring5中提供的WebFlux,屬于響應式編程的實現,具備更好的性能。
3.2、gateway快速入門
下面,我們就演示下網關的基本路由功能。基本步驟如下:
- 創建SpringBoot工程gateway,引入網關依賴
- 編寫啟動類
- 編寫基礎配置和路由規則
- 啟動網關服務進行測試
我們來實現一下:
- 新建Maven模塊,繼承 cloud-demo 工程
- 引入依賴
<!--網關-->
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--nacos服務發現依賴-->
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
- 編寫啟動類:
src/main/java/cn/itcast/gateway/GatewayApplication.java
@SpringBootApplication
public class GatewayApplication {public static void main(String[] args) {SpringApplication.run(GatewayApplication.class, args);}
}
- 創建application.yml文件,編寫基礎配置和路由規則,內容如下:
server:port: 10010 # 網關端口
spring:application:name: gateway # 服務名稱cloud:nacos:server-addr: localhost:8848 # nacos地址gateway:routes: # 網關路由配置- id: user-service # 路由id,自定義,只要唯一即可# uri: http://127.0.0.1:8081 # 路由的目標地址 http就是固定地址(不推薦)uri: lb://userservice # 路由的目標地址 lb就是負載均衡,后面跟服務名稱predicates: # 路由斷言,也就是判斷請求是否符合路由規則的條件- Path=/user/** # 這個是按照路徑匹配,只要以/user/開頭就符合要求
-
我們將符合
Path
規則的一切請求,都代理到uri
參數指定的地址。 -
本例中,我們將
/user/**
開頭的請求,代理到lb://userservice
,lb是負載均衡,根據服務名拉取服務列表,實現負載均衡。
如下,我們也可以將/order/**
開頭的請求,代理到lb://orderservice
server:port: 10010 # 網關端口
spring:application:name: gateway # 服務名稱cloud:nacos:server-addr: localhost:8848 # nacos地址gateway:routes: # 網關路由配置- id: user-service # 路由id,自定義,只要唯一即可# uri: http://127.0.0.1:8081 # 路由的目標地址 http就是固定地址uri: lb://userservice # 路由的目標地址 lb就是負載均衡,后面跟服務名稱predicates: # 路由斷言,也就是判斷請求是否符合路由規則的條件- Path=/user/** # 這個是按照路徑匹配,只要以/user/開頭就符合要求- id: order-serviceuri: lb://orderservicepredicates:- Path=/order/**
- 重啟網關,訪問:
http://localhost:10010/user/1
,符合/user/**
規則,請求轉發到uri:http://userservice/user/1
,得到了結果:
3.2.1、網關路由的流程圖
用戶發起了http://127.0.0.1:10010/user/1
請求,網關端口也是10010,所以請求會先經過網關,網關基于路由規則判斷,把/user/**
請求的路徑代理到userservice
,把/order/**
請求的路徑代理到orderservice
,網關會去Nacos注冊中心拉取微服務列表,找到userservice
,負載均衡,發送請求
3.2.2、總結
網關搭建步驟:
-
創建項目,引入nacos服務發現和gateway依賴
-
配置application.yml,包括服務基本信息、nacos地址、路由
路由配置包括:
-
路由id:路由的唯一標示
-
路由目標(uri):路由的目標地址,http代表固定地址,lb代表根據服務名負載均衡
-
路由斷言(predicates):判斷路由的規則,
-
路由過濾器(filters):對請求或響應做處理
接下來,就重點來學習路由斷言和路由過濾器的詳細知識
3.3、斷言工廠
我們在配置文件中寫的斷言規則只是字符串,這些字符串會被Predicate Factory讀取并處理,轉變為路由判斷的條件。例如Path=/user/**是按照路徑匹配,這個規則是由 Path 斷言工廠來處理的,像這樣的斷言工廠在 SpringCloudGateway 還有十幾個:
我們只需要掌握Path這種路由工程就可以了。例如其他配置:
spring:application:name: gateway # 服務名稱cloud:nacos:server-addr: localhost:8848 # nacos地址gateway:routes: # 網關路由配置- id: user-service # 路由id,自定義,只要唯一即可# uri: http://127.0.0.1:8081 # 路由的目標地址 http就是固定地址uri: lb://userservice # 路由的目標地址 lb就是負載均衡,后面跟服務名稱predicates: # 路由斷言,也就是判斷請求是否符合路由規則的條件- Path=/user/** # 這個是按照路徑匹配,只要以/user/開頭就符合要求- After=2017-01-20T17:42:47.789-07:00[America/Denver] # 請求的時間必須是 JAN20,2017 年 17:42 山區時間(丹佛) 之后
3.4、過濾器工廠
GatewayFilter是網關中提供的一種過濾器,可以對進入網關的請求和微服務返回的響應做處理:
3.4.1、路由過濾器的種類
Spring提供了31種不同的路由過濾器工廠。例如:
名稱 | 說明 |
---|---|
AddRequestHeader | 給當前請求添加一個請求頭 |
RemoveRequestHeader | 移除請求中的一個請求頭 |
AddResponseHeader | 給響應結果中添加一個響應頭 |
RemoveResponseHeader | 從響應結果中移除有一個響應頭 |
RequestRateLimiter | 限制請求的流量 |
3.4.2、請求頭過濾器
下面我們以AddRequestHeader 為例來講解
需求:給所有進入userservice的請求添加一個請求頭:Truth=itcast is freaking awesome!
只需要修改gateway服務的application.yml
文件,添加路由過濾即可:
spring:application:name: gateway # 服務名稱cloud:nacos:server-addr: localhost:8848 # nacos地址gateway:routes: # 網關路由配置- id: user-service # 路由id,自定義,只要唯一即可# uri: http://127.0.0.1:8081 # 路由的目標地址 http就是固定地址uri: lb://userservice # 路由的目標地址 lb就是負載均衡,后面跟服務名稱predicates: # 路由斷言,也就是判斷請求是否符合路由規則的條件- Path=/user/** # 這個是按照路徑匹配,只要以/user/開頭就符合要求filters:- AddRequestHeader=name,Augenestern! # 添加請求頭
- 當前過濾器寫在userservice路由下,因此僅僅對訪問userservice的請求有效。
測試:改造 uservice 的controller下的方法來接收請求頭:
@GetMapping("/{id}")public User queryById(@PathVariable("id") Long id,@RequestHeader(value = "name",required = false) String name) {System.out.println("name: " + name);return userService.queryById(id);}
重啟服務,訪問:http://localhost:10010/user/1,在控制臺可以看到設置的請求頭
3.4.3、默認過濾器
如果要對所有的路由都生效,則可以將過濾器工廠寫到default下。格式如下:
spring:application:name: gateway # 服務名稱cloud:nacos:server-addr: localhost:8848 # nacos地址gateway:routes: # 網關路由配置- id: user-service # 路由id,自定義,只要唯一即可# uri: http://127.0.0.1:8081 # 路由的目標地址 http就是固定地址uri: lb://userservice # 路由的目標地址 lb就是負載均衡,后面跟服務名稱predicates: # 路由斷言,也就是判斷請求是否符合路由規則的條件- Path=/user/** # 這個是按照路徑匹配,只要以/user/開頭就符合要求
# filters: # 默認過濾項
# - AddRequestHeader=name,Augenestern! # 添加請求頭- id: order-serviceuri: lb://orderservicepredicates:- Path=/order/**default-filters: - AddRequestHeader=name,Augenestern! # 添加請求頭
3.4.4、總結
過濾器的作用是什么?
- 對路由的請求或響應做加工處理,比如添加請求頭
- 配置在路由下的過濾器只對當前路由的請求生效
defaultFilters的作用是什么?
- 對所有路由都生效的過濾器
3.5、全局過濾器
上一節學習的過濾器,網關提供了31種,但每一種過濾器的作用都是固定的。如果我們希望攔截請求,做自己的業務邏輯則沒辦法實現。全局過濾器的作用也是處理一切進入網關的請求和微服務響應,與GatewayFilter的作用一樣。區別如下:
- GatewayFilter通過配置定義,處理邏輯是固定的
- GlobalFilter的邏輯需要自己寫代碼實現。定義方式是實現GlobalFilter接口:
public interface GlobalFilter {/*** 處理當前請求,有必要的話通過{@link GatewayFilterChain}將請求交給下一個過濾器處理** @param exchange 請求上下文,里面可以獲取Request、Response等信息* @param chain 用來把請求委托給下一個過濾器 * @return {@code Mono<Void>} 返回標示當前過濾器業務結束*/Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);
}
3.5.1、自定義全局過濾器
需求:定義全局過濾器,攔截請求,判斷請求的參數是否滿足下面條件:
- 參數中是否有authorization,
- authorization參數值是否為admin
如果同時滿足則放行,否則攔截。
實現:
- 在gateway中定義一個過濾器,加上
@Order()
注解
// Order順序注解,過濾器的順序,越小優先級越高
@Order(-1)
@Component
public class AuthorizeFilter implements GlobalFilter {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {// 1.獲取請求參數MultiValueMap<String, String> params = exchange.getRequest().getQueryParams();// 2.獲取第一個匹配的authorization參數String auth = params.getFirst("authorization");// 3.校驗if ("admin".equals(auth)) {// 放行return chain.filter(exchange);}// 4.攔截// 4.1.禁止訪問,設置狀態碼exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);// 4.2.結束處理return exchange.getResponse().setComplete();}
}
測試:
- 重啟網關服務,訪問:
http://localhost:10010/user/1
- 訪問:
http://localhost:10010/user/1?authorization=admin
3.5.2、過濾器執行順序
請求進入網關會碰到三類過濾器:當前路由的過濾器、DefaultFilter默認過濾器、GlobalFilter全局過濾器。請求路由后,會將當前路由過濾器和DefaultFilter、GlobalFilter,合并到一個過濾器鏈(集合)中,排序后依次執行每個過濾器:
排序的規則是什么呢?
- 每一個過濾器都必須指定一個int類型的order值,order值越小,優先級越高,執行順序越靠前。
- GlobalFilter通過實現Ordered接口,或者添加
@Order
注解來指定order值,由我們自己指定 - 路由過濾器和defaultFilter的order由Spring指定,默認是按照聲明順序從1遞增。
- 當過濾器的order值一樣時,會按照 defaultFilter > 路由過濾器 > GlobalFilter的順序執行。
3.6、跨域問題
3.6.1、什么是跨域問題
跨域:域名不一致就是跨域,主要包括:
- 域名不同: www.taobao.com 和 www.taobao.org 和 www.jd.com
- 域名相同,端口不同:localhost:8080和localhost8081
跨域問題:瀏覽器禁止請求的發起者與服務端發生跨域ajax請求,請求被瀏覽器攔截的問題
解決方案:CORS,https://www.ruanyifeng.com/blog/2016/04/cors.html
3.6.2、解決跨域問題
網關處理跨域采用的同樣是CORS方案,只需簡單配置即可實現:
server:port: 10010 # 網關端口
spring:application:name: gateway # 服務名稱cloud:nacos:server-addr: localhost:8848 # nacos地址gateway:# 全局的跨域處理globalcors:add-to-simple-url-handler-mapping: true # 解決options請求被攔截問題corsConfigurations:'[/**]': # 攔截所有的請求allowedOrigins: # 允許哪些網站的跨域請求- "http://localhost:8090"allowedMethods: # 允許的跨域ajax的請求方式- "GET"- "POST"- "DELETE"- "PUT"- "OPTIONS"allowedHeaders: "*" # 允許在請求中攜帶的頭信息allowCredentials: true # 是否允許攜帶cookiemaxAge: 360000 # 這次跨域檢測的有效期