6. Nacos配置管理
Nacos除了可以做注冊中心,同樣可以做配置管理來使用。
6.1 統(tǒng)一配置管理
當(dāng)微服務(wù)部署的實(shí)例越來越多,達(dá)到數(shù)十、數(shù)百時,逐個修改微服務(wù)配置就會讓人抓狂,而且很容易出錯。需要一種統(tǒng)一配置管理方案,可以集中管理所有實(shí)例的配置。
Nacos一方面可以將配置集中管理,另一方可以在配置變更時,及時通知微服務(wù),實(shí)現(xiàn)配置的熱更新。
6.1.1 在nacos中添加配置文件
如何在nacos中管理配置呢?
然后在彈出的表單中,填寫配置信息:
注意:項(xiàng)目的核心配置,需要熱更新的配置才有放到nacos管理的必要?;静粫兏囊恍┡渲眠€是保存在微服務(wù)本地比較好。
6.1.2 從微服務(wù)拉取配置
微服務(wù)要拉取nacos中管理的配置,并且與本地的application.yml配置合并,才能完成項(xiàng)目啟動。讀取bootstrap.yaml文件,拉取nacos線上的配置,會在application.yml之前被讀取,流程如下:
1)引入nacos-config依賴
首先,在user-service服務(wù)中,引入nacos-config的客戶端依賴:
<!--nacos配置管理依賴--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> </dependency>
2)添加bootstrap.yaml
然后,在user-service中添加一個bootstrap.yml文件,內(nèi)容如下:
spring: application: name: userservice # 服務(wù)名稱 profiles: active: dev #開發(fā)環(huán)境,這里是dev cloud: nacos: server-addr: localhost:8848 # Nacos地址 config: file-extension: yml # 文件后綴名
這里會根據(jù)spring.cloud.nacos.server-addr獲取nacos地址,再根據(jù)
${spring.application.name}-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
作為文件id,來讀取配置。本例中,就是去讀取
userservice-dev.yml
:3)讀取nacos配置
在user-service中的UserController中添加業(yè)務(wù)邏輯,讀取pattern.dateformat配置:
完整代碼:
package cn.yishooo.user.web; import cn.yishooo.user.pojo.User; import cn.yishooo.user.service.UserService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.*; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; @Slf4j @RestController @RequestMapping("/user") public class UserController { @Autowired private UserService userService; @Value("${pattern.dateformat}") private String dateformat; @GetMapping("now") public String now(){ return LocalDateTime.now().format(DateTimeFormatter.ofPattern(dateformat)); } // ...略 }
在頁面訪問,可以看到效果:
6.2 配置熱更新
最終的目的,是修改nacos中的配置后,微服務(wù)中無需重啟即可讓配置生效,也就是配置熱更新。
要實(shí)現(xiàn)配置熱更新,可以使用兩種方式:
方式一
在@Value注入的變量所在類上添加注解@RefreshScope:
方式二
使用@ConfigurationProperties注解代替@Value注解。
在user-service服務(wù)中,添加一個類,讀取patterrn.dateformat屬性:
package cn.yishooo.user.config; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; @Component @Data @ConfigurationProperties(prefix = "pattern") public class PatternProperties { private String dateformat; }
在UserController中使用這個類代替@Value:
完整代碼:
package cn.yishooo.user.web; import cn.yishooo.user.config.PatternProperties; import cn.yishooo.user.pojo.User; import cn.yishooo.user.service.UserService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; @Slf4j @RestController @RequestMapping("/user") public class UserController { @Autowired private UserService userService; @Autowired private PatternProperties patternProperties; @GetMapping("now") public String now(){ return LocalDateTime.now().format(DateTimeFormatter.ofPattern(patternProperties.getDateformat())); } // 略 }
6.3 配置共享
其實(shí)微服務(wù)啟動時,會去nacos讀取多個配置文件,例如:
[spring.application.name]-[spring.profiles.active].yml
,例如:userservice-dev.yml
[spring.application.name].yml
,例如:userservice.yml而
[spring.application.name].yml
不包含環(huán)境,因此可以被多個環(huán)境共享。下面通過案例來測試配置共享
1)添加一個環(huán)境共享配置
在nacos中添加一個userservice.yaml文件:
2)在user-service中讀取共享配置
在user-service服務(wù)中,修改PatternProperties類,讀取新添加的屬性:
在user-service服務(wù)中,修改UserController,添加一個方法:
3)運(yùn)行兩個UserApplication,使用不同的profile
修改UserApplication2這個啟動項(xiàng),改變其profile值:
這樣,UserApplication(8081)使用的profile是dev,UserApplication2(8082)使用的profile是test。
啟動UserApplication和UserApplication2
訪問http://localhost:8081/user/prop,結(jié)果:
訪問http://localhost:8082/user/prop,結(jié)果:
可以看出來,不管是dev,還是test環(huán)境,都讀取到了envSharedValue這個屬性的值。
4)配置共享的優(yōu)先級
當(dāng)nacos、服務(wù)本地同時出現(xiàn)相同屬性時,優(yōu)先級有高低之分:
6.4 Nacos集群搭建
6.4.1 集群結(jié)構(gòu)圖
官方給出的Nacos集群圖:
其中包含3個nacos節(jié)點(diǎn),然后一個負(fù)載均衡器代理3個Nacos。這里負(fù)載均衡器可以使用nginx。
三個nacos節(jié)點(diǎn)的地址:
節(jié)點(diǎn) ip port nacos1 127.0.0.1 8845 nacos2 127.0.0.1 8846 nacos3 127.0.0.1 8847 此處的IP是自己服務(wù)的IP
6.4.2 搭建集群
搭建集群的基本步驟:
- 搭建數(shù)據(jù)庫,初始化數(shù)據(jù)庫表結(jié)構(gòu)
- 下載nacos安裝包
- 配置nacos
- 啟動nacos集群
- nginx反向代理
初始化數(shù)據(jù)庫
Nacos默認(rèn)數(shù)據(jù)存儲在內(nèi)嵌數(shù)據(jù)庫Derby中,不屬于生產(chǎn)可用的數(shù)據(jù)庫。官方推薦的最佳實(shí)踐是使用帶有主從的高可用數(shù)據(jù)庫集群。
這里為簡化操作以單點(diǎn)的數(shù)據(jù)庫為例。
首先新建一個數(shù)據(jù)庫,命名為nacos,而后導(dǎo)入下面的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 '創(chuàng)建時間', `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'; /******************************************/ /* 數(shù)據(jù)庫全名 = 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 '內(nèi)容', `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='增加租戶字段'; /******************************************/ /* 數(shù)據(jù)庫全名 = 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 '創(chuàng)建時間', `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'; /******************************************/ /* 數(shù)據(jù)庫全名 = 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 '創(chuàng)建時間', `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'; /******************************************/ /* 數(shù)據(jù)庫全名 = 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'; /******************************************/ /* 數(shù)據(jù)庫全名 = 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表示使用默認(rèn)值', `usage` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '使用量', `max_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '單個配置大小上限,單位為字節(jié),0表示使用默認(rèn)值', `max_aggr_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '聚合子配置最大個數(shù),,0表示使用默認(rèn)值', `max_aggr_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '單個聚合數(shù)據(jù)的子配置大小上限,單位為字節(jié),0表示使用默認(rèn)值', `max_history_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '最大變更歷史數(shù)量', `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '創(chuàng)建時間', `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容量信息表'; /******************************************/ /* 數(shù)據(jù)庫全名 = 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='多租戶改造'; /******************************************/ /* 數(shù)據(jù)庫全名 = 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表示使用默認(rèn)值', `usage` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '使用量', `max_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '單個配置大小上限,單位為字節(jié),0表示使用默認(rèn)值', `max_aggr_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '聚合子配置最大個數(shù)', `max_aggr_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '單個聚合數(shù)據(jù)的子配置大小上限,單位為字節(jié),0表示使用默認(rèn)值', `max_history_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '最大變更歷史數(shù)量', `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '創(chuàng)建時間', `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 '創(chuàng)建時間', `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');
下載nacos
nacos在GitHub上有下載地址:https://github.com/alibaba/nacos/tags,可以選擇任意版本下載。
配置Nacos
將這個包解壓到任意非中文目錄下,如圖:
目錄說明:
- bin:啟動腳本
- conf:配置文件
進(jìn)入nacos的conf目錄,修改配置文件cluster.conf.example,重命名為cluster.conf:
然后添加內(nèi)容:(IP為服務(wù)的IP)
127.0.0.1:8845 127.0.0.1:8846 127.0.0.1:8847
在單節(jié)點(diǎn)運(yùn)行時,到nacos控制臺查看自己的節(jié)點(diǎn)IP。
然后修改application.properties文件,添加數(shù)據(jù)庫配置
spring.datasource.platform=mysql db.num=1 db.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=123
這里的數(shù)據(jù)庫地址、用戶名、密碼請根據(jù)實(shí)際情況配置。
啟動
將nacos文件夾復(fù)制三份,分別命名為:nacos1、nacos2、nacos3
然后分別修改三個文件夾中的application.properties,
nacos1:
server.port=8845
nacos2:
server.port=8846
nacos3:
server.port=8847
然后分別啟動三個nacos節(jié)點(diǎn):
startup.cmd
nginx反向代理
解壓并修改conf/nginx.conf文件,配置如下:
upstream nacos-cluster { server 127.0.0.1:8845; server 127.0.0.1:8846; server 127.0.0.1:8847; } server { listen 80; server_name localhost; location /nacos { proxy_pass http://nacos-cluster; } }
而后在瀏覽器訪問:http://localhost/nacos即可。
代碼中application.yml文件配置如下:
spring: cloud: nacos: server-addr: localhost:80 # Nacos地址
優(yōu)化
實(shí)際部署時,需要給做反向代理的nginx服務(wù)器設(shè)置一個域名,這樣后續(xù)如果有服務(wù)器遷移nacos的客戶端也無需更改配置.
Nacos的各個節(jié)點(diǎn)應(yīng)該部署到多個不同服務(wù)器,做好容災(zāi)和隔離
7. Feign遠(yuǎn)程調(diào)用
以前利用RestTemplate發(fā)起遠(yuǎn)程調(diào)用的代碼:
存在下面的問題:
?代碼可讀性差,編程體驗(yàn)不統(tǒng)一
?參數(shù)復(fù)雜URL難以維護(hù)
Feign是一個聲明式的http客戶端,官方地址:https://github.com/OpenFeign/feign
其作用就是幫助我們優(yōu)雅的實(shí)現(xiàn)http請求的發(fā)送,解決上面提到的問題。
7.1 Feign替代RestTemplate
Fegin的使用步驟如下:
1)引入依賴
我們在order-service服務(wù)的pom文件中引入feign的依賴:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
2)添加注解
在order-service的啟動類添加注解開啟Feign的功能:
3)編寫Feign的客戶端
在order-service中新建一個接口,內(nèi)容如下:
package cn.yishooo.order.client; import cn.yishooo.order.pojo.User; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @FeignClient("userservice") public interface UserClient { @GetMapping("/user/{id}") User findById(@PathVariable("id") Long id); }
這個客戶端主要是基于SpringMVC的注解來聲明遠(yuǎn)程調(diào)用的信息,比如:
- 服務(wù)名稱:userservice
- 請求方式:GET
- 請求路徑:/user/{id}
- 請求參數(shù):Long id
- 返回值類型:User
這樣,F(xiàn)eign就可以發(fā)送http請求,無需自己使用RestTemplate來發(fā)送了。
4)測試
修改order-service中的OrderService類中的queryOrderById方法,使用Feign客戶端代替RestTemplate:
5)總結(jié)
使用Feign的步驟:
① 引入依賴
② 添加@EnableFeignClients注解
③ 編寫FeignClient接口
④ 使用FeignClient中定義的方法代替RestTemplate
7.2 自定義配置(日志、解析器等)
Feign可以支持很多的自定義配置,如下表所示:
類型 作用 說明 feign.Logger.Level 修改日志級別 包含四種不同的級別:NONE、BASIC、HEADERS、FULL feign.codec.Decoder 響應(yīng)結(jié)果的解析器 http遠(yuǎn)程調(diào)用的結(jié)果做解析,例如解析json字符串為java對象 feign.codec.Encoder 請求參數(shù)編碼 將請求參數(shù)編碼,便于通過http請求發(fā)送 feign. Contract 支持的注解格式 默認(rèn)是SpringMVC的注解 feign. Retryer 失敗重試機(jī)制 請求失敗的重試機(jī)制,默認(rèn)是沒有,不過會使用Ribbon的重試 一般情況下,默認(rèn)值就能滿足我們使用,如果要自定義時,只需要創(chuàng)建自定義的@Bean覆蓋默認(rèn)Bean即可。
以日志為例來演示如何自定義配置。
配置文件方式
基于配置文件修改feign的日志級別可以針對單個服務(wù):
feign: client: config: userservice: # 針對某個微服務(wù)的配置 loggerLevel: FULL # 日志級別
也可以針對所有服務(wù):
feign: client: config: default: # 這里用default就是全局配置,如果是寫服務(wù)名稱,則是針對某個微服務(wù)的配置 loggerLevel: FULL # 日志級別
而日志的級別分為四種:
- NONE:不記錄任何日志信息,這是默認(rèn)值。
- BASIC:僅記錄請求的方法,URL以及響應(yīng)狀態(tài)碼和執(zhí)行時間
- HEADERS:在BASIC的基礎(chǔ)上,額外記錄了請求和響應(yīng)的頭信息
- FULL:記錄所有請求和響應(yīng)的明細(xì),包括頭信息、請求體、元數(shù)據(jù)。
Java代碼方式
也可以基于Java代碼來修改日志級別,先聲明一個類,然后聲明一個Logger.Level的對象:
public class DefaultFeignConfiguration { @Bean public Logger.Level feignLogLevel(){ return Logger.Level.BASIC; // 日志級別為BASIC } }
如果要全局生效,將其放到啟動類的@EnableFeignClients這個注解中:
@EnableFeignClients(defaultConfiguration = DefaultFeignConfiguration .class)
如果是局部生效,則把它放到對應(yīng)的@FeignClient這個注解中:
@FeignClient(value = "userservice", configuration = DefaultFeignConfiguration .class)
7.3 Feign使用優(yōu)化
Feign底層發(fā)起http請求,依賴于其它的框架。其底層客戶端實(shí)現(xiàn)包括:
?URLConnection:默認(rèn)實(shí)現(xiàn),不支持連接池
?Apache HttpClient :支持連接池
?OKHttp:支持連接池
因此提高Feign的性能主要手段就是使用連接池代替默認(rèn)的URLConnection。
這里用Apache的HttpClient來演示。
1)引入依賴
在order-service的pom文件中引入Apache的HttpClient依賴:
<!--httpClient的依賴 --> <dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-httpclient</artifactId> </dependency>
2)配置連接池
在order-service的application.yml中添加配置:
feign: client: config: default: # default全局的配置 loggerLevel: BASIC # 日志級別,BASIC就是基本的請求和響應(yīng)信息 httpclient: enabled: true # 開啟feign對HttpClient的支持 max-connections: 200 # 最大的連接數(shù) max-connections-per-route: 50 # 每個路徑的最大連接數(shù)
接下來,在FeignClientFactoryBean中的loadBalance方法中打斷點(diǎn):
Debug方式啟動order-service服務(wù),可以看到這里的client,底層就是Apache HttpClient:
總結(jié),F(xiàn)eign的優(yōu)化:
1.日志級別盡量用basic
2.使用HttpClient或OKHttp代替URLConnection
① 引入feign-httpClient依賴
② 配置文件開啟httpClient功能,設(shè)置連接池參數(shù)
7.4 最佳實(shí)踐
所謂最近實(shí)踐,就是使用過程中總結(jié)的經(jīng)驗(yàn),最好的一種使用方式。Feign的客戶端與服務(wù)提供者的controller代碼非常相似:
feign客戶端:
UserController:
7.4.1 繼承方式
一樣的代碼可以通過繼承來共享:
1)定義一個API接口,利用定義方法,并基于SpringMVC注解做聲明。
2)Feign客戶端和Controller都集成改接口
優(yōu)點(diǎn):
- 簡單
- 實(shí)現(xiàn)了代碼共享
缺點(diǎn):
- 服務(wù)提供方、服務(wù)消費(fèi)方緊耦合
- 參數(shù)列表中的注解映射并不會繼承,因此Controller中必須再次聲明方法、參數(shù)列表、注解
7.4.2 抽取方式
將Feign的Client抽取為獨(dú)立模塊,并且把接口有關(guān)的POJO、默認(rèn)的Feign配置都放到這個模塊中,提供給所有消費(fèi)者使用。
例如,將UserClient、User、Feign的默認(rèn)配置都抽取到一個feign-api包中,所有微服務(wù)引用該依賴包,即可直接使用。
7.4.3 實(shí)現(xiàn)基于抽取的最佳實(shí)踐
1)抽取
首先創(chuàng)建一個module,命名為feign-api:
項(xiàng)目結(jié)構(gòu):
在feign-api中然后引入feign的starter依賴
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
然后,order-service中編寫的UserClient、User、DefaultFeignConfiguration都復(fù)制到feign-api項(xiàng)目中
2)在order-service中使用feign-api
首先,刪除order-service中的UserClient、User、DefaultFeignConfiguration等類或接口。
在order-service的pom文件中中引入feign-api的依賴:
<dependency> <groupId>cn.yishooo.demo</groupId> <artifactId>feign-api</artifactId> <version>1.0</version> </dependency>
修改order-service中的所有與上述三個組件有關(guān)的導(dǎo)包部分,改成導(dǎo)入feign-api中的包
3)重啟測試
重啟后,發(fā)現(xiàn)服務(wù)報(bào)錯了:
這是因?yàn)閁serClient現(xiàn)在在cn.yishooo.feign.clients包下,
而order-service的@EnableFeignClients注解是在cn.yishooo.order包下,不在同一個包,無法掃描到UserClient。
4)解決掃描包問題
方式一:
指定Feign應(yīng)該掃描的包:
@EnableFeignClients(basePackages = "cn.yishooo.feign.clients")
方式二:
指定需要加載的Client接口:
@EnableFeignClients(clients = {UserClient.class})
8. Gateway服務(wù)網(wǎng)關(guān)
Spring Cloud Gateway 是 Spring Cloud 的一個全新項(xiàng)目,該項(xiàng)目是基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等響應(yīng)式編程和事件流技術(shù)開發(fā)的網(wǎng)關(guān),它旨在為微服務(wù)架構(gòu)提供一種簡單有效的統(tǒng)一的 API 路由管理方式。
8.1 為什么需要網(wǎng)關(guān)
Gateway網(wǎng)關(guān)是我們服務(wù)的守門神,所有微服務(wù)的統(tǒng)一入口。
網(wǎng)關(guān)的核心功能特性:
- 請求路由
- 權(quán)限控制
- 限流
架構(gòu)圖:
權(quán)限控制:網(wǎng)關(guān)作為微服務(wù)入口,需要校驗(yàn)用戶是是否有請求資格,如果沒有則進(jìn)行攔截。
路由和負(fù)載均衡:一切請求都必須先經(jīng)過gateway,但網(wǎng)關(guān)不處理業(yè)務(wù),而是根據(jù)某種規(guī)則,把請求轉(zhuǎn)發(fā)到某個微服務(wù),這個過程叫做路由。當(dāng)然路由的目標(biāo)服務(wù)有多個時,還需要做負(fù)載均衡。
限流:當(dāng)請求流量過高時,在網(wǎng)關(guān)中按照下流的微服務(wù)能夠接受的速度來放行請求,避免服務(wù)壓力過大。
在SpringCloud中網(wǎng)關(guān)的實(shí)現(xiàn)包括兩種:
- gateway
- zuul
Zuul是基于Servlet的實(shí)現(xiàn),屬于阻塞式編程。而SpringCloudGateway則是基于Spring5中提供的WebFlux,屬于響應(yīng)式編程的實(shí)現(xiàn),具備更好的性能。
8.2 gateway快速入門
下面,我們就演示下網(wǎng)關(guān)的基本路由功能?;静襟E如下:
- 創(chuàng)建SpringBoot工程gateway,引入網(wǎng)關(guān)依賴
- 編寫啟動類
- 編寫基礎(chǔ)配置和路由規(guī)則
- 啟動網(wǎng)關(guān)服務(wù)進(jìn)行測試
1)創(chuàng)建gateway服務(wù),引入依賴
創(chuàng)建服務(wù):
引入依賴:
<!--網(wǎng)關(guān)--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <!--nacos服務(wù)發(fā)現(xiàn)依賴--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency>
2)編寫啟動類
package cn.yishooo.gateway; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class GatewayApplication { public static void main(String[] args) { SpringApplication.run(GatewayApplication.class, args); } }
3)編寫基礎(chǔ)配置和路由規(guī)則
創(chuàng)建application.yml文件,內(nèi)容如下:
server: port: 10010 # 網(wǎng)關(guān)端口 spring: application: name: gateway # 服務(wù)名稱 cloud: nacos: server-addr: localhost:8848 # nacos地址 gateway: routes: # 網(wǎng)關(guān)路由配置 - id: user-service # 路由id,自定義,只要唯一即可 # uri: http://127.0.0.1:8081 # 路由的目標(biāo)地址 http就是固定地址 uri: lb://userservice # 路由的目標(biāo)地址 lb就是負(fù)載均衡,后面跟服務(wù)名稱 predicates: # 路由斷言,也就是判斷請求是否符合路由規(guī)則的條件 - Path=/user/** # 這個是按照路徑匹配,只要以/user/開頭就符合要求
將符合
Path
規(guī)則的一切請求,都代理到uri
參數(shù)指定的地址。本例中,將
/user/**
開頭的請求,代理到lb://userservice
,lb是負(fù)載均衡,根據(jù)服務(wù)名拉取服務(wù)列表,實(shí)現(xiàn)負(fù)載均衡。4)重啟測試
重啟網(wǎng)關(guān),訪問http://localhost:10010/user/1時,符合
/user/**
規(guī)則,請求轉(zhuǎn)發(fā)到uri:http://userservice/user/1,得到了結(jié)果:5)網(wǎng)關(guān)路由的流程圖
整個訪問的流程如下:
總結(jié):
網(wǎng)關(guān)搭建步驟:
創(chuàng)建項(xiàng)目,引入nacos服務(wù)發(fā)現(xiàn)和gateway依賴
配置application.yml,包括服務(wù)基本信息、nacos地址、路由
路由配置包括:
- 路由id:路由的唯一標(biāo)示
- 路由目標(biāo)(uri):路由的目標(biāo)地址,http代表固定地址,lb代表根據(jù)服務(wù)名負(fù)載均衡
- 路由斷言(predicates):判斷路由的規(guī)則,
- 路由過濾器(filters):對請求或響應(yīng)做處理
8.3 斷言工廠
在配置文件中寫的斷言規(guī)則只是字符串,這些字符串會被Predicate Factory讀取并處理,轉(zhuǎn)變?yōu)槁酚膳袛嗟臈l件
例如Path=/user/**是按照路徑匹配,這個規(guī)則是由
org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory
類來處理的,像這樣的斷言工廠在SpringCloudGateway還有十幾個:
名稱 說明 示例 After 是某個時間點(diǎn)后的請求 - After=2037-01-20T17:42:47.789-07:00[America/Denver] Before 是某個時間點(diǎn)之前的請求 - Before=2031-04-13T15:14:47.433+08:00[Asia/Shanghai] Between 是某兩個時間點(diǎn)之前的請求 - Between=2037-01-20T17:42:47.789-07:00[America/Denver], 2037-01-21T17:42:47.789-07:00[America/Denver] Cookie 請求必須包含某些cookie - Cookie=chocolate, ch.p Header 請求必須包含某些header - Header=X-Request-Id, \d+ Host 請求必須是訪問某個host(域名) - Host=.somehost.org,.anotherhost.org Method 請求方式必須是指定方式 - Method=GET,POST Path 請求路徑必須符合指定規(guī)則 - Path=/red/{segment},/blue/** Query 請求參數(shù)必須包含指定參數(shù) - Query=name, Jack或者- Query=name RemoteAddr 請求者的ip必須是指定范圍 - RemoteAddr=192.168.1.1/24 Weight 權(quán)重處理 只需要掌握Path這種路由工程就可以了。
Spring官網(wǎng)網(wǎng)關(guān)鏈接 可以通過首頁projects->spring cloud->spring cloud gateway->learn去查看相關(guān)參數(shù)的使用
8.4 過濾器工廠
GatewayFilter是網(wǎng)關(guān)中提供的一種過濾器,可以對進(jìn)入網(wǎng)關(guān)的請求和微服務(wù)返回的響應(yīng)做處理:
8.4.1 路由過濾器的種類
Spring提供了31種不同的路由過濾器工廠。例如:
名稱 說明 AddRequestHeader 給當(dāng)前請求添加一個請求頭 RemoveRequestHeader 移除請求中的一個請求頭 AddResponseHeader 給響應(yīng)結(jié)果中添加一個響應(yīng)頭 RemoveResponseHeader 從響應(yīng)結(jié)果中移除有一個響應(yīng)頭 RequestRateLimiter 限制請求的流量
8.4.2 請求頭過濾器
以AddRequestHeader 為例來講解。
需求:給所有進(jìn)入userservice的請求添加一個請求頭:Truth=Don not underestimate yourself !
只需要修改gateway服務(wù)的application.yml文件,添加路由過濾即可:
spring: cloud: gateway: routes: - id: user-service uri: lb://userservice predicates: - Path=/user/** filters: # 過濾器 - AddRequestHeader=Truth, Don not underestimate yourself! # 添加請求頭
當(dāng)前過濾器寫在userservice路由下,因此僅僅對訪問userservice的請求有效。
8.4.3 默認(rèn)過濾器
如果要對所有的路由都生效,則可以將過濾器工廠寫到default下。格式如下:
spring: cloud: gateway: routes: - id: user-service uri: lb://userservice predicates: - Path=/user/** default-filters: # 默認(rèn)過濾項(xiàng) - AddRequestHeader=Truth, Don not underestimate yourself!
總結(jié)
過濾器的作用是什么?
① 對路由的請求或響應(yīng)做加工處理,比如添加請求頭
② 配置在路由下的過濾器只對當(dāng)前路由的請求生效
defaultFilters的作用是什么?
① 對所有路由都生效的過濾器
8.5 全局過濾器
網(wǎng)關(guān)提供了37種,但每一種過濾器的作用都是固定的。如果要攔截請求,做自己的業(yè)務(wù)邏輯則沒辦法實(shí)現(xiàn)。
8.5.1 全局過濾器作用
全局過濾器的作用也是處理一切進(jìn)入網(wǎng)關(guān)的請求和微服務(wù)響應(yīng),與GatewayFilter的作用一樣。區(qū)別在于GatewayFilter通過配置定義,處理邏輯是固定的;而GlobalFilter的邏輯需要自己寫代碼實(shí)現(xiàn)。
定義方式是實(shí)現(xiàn)GlobalFilter接口。
public interface GlobalFilter { /** * 處理當(dāng)前請求,有必要的話通過{@link GatewayFilterChain}將請求交給下一個過濾器處理 * * @param exchange 請求上下文,里面可以獲取Request、Response等信息 * @param chain 用來把請求委托給下一個過濾器 * @return {@code Mono<Void>} 返回標(biāo)示當(dāng)前過濾器業(yè)務(wù)結(jié)束 */ Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain); }
在filter中編寫自定義邏輯,可以實(shí)現(xiàn)下列功能:
- 登錄狀態(tài)判斷
- 權(quán)限校驗(yàn)
- 請求限流等
8.5.2 自定義全局過濾器
需求:定義全局過濾器,攔截請求,判斷請求的參數(shù)是否滿足下面條件:
參數(shù)中是否有authorization,
authorization參數(shù)值是否為admin
如果同時滿足則放行,否則攔截
實(shí)現(xiàn):
在gateway中定義一個過濾器:
package cn.yishooo.gateway.filters; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.annotation.Order; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; @Order(-1) @Component public class AuthorizeFilter implements GlobalFilter { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { // 1.獲取請求參數(shù) MultiValueMap<String, String> params = exchange.getRequest().getQueryParams(); // 2.獲取authorization參數(shù) String auth = params.getFirst("authorization"); // 3.校驗(yàn) if ("admin".equals(auth)) { // 放行 return chain.filter(exchange); } // 4.攔截 // 4.1.禁止訪問,設(shè)置狀態(tài)碼 exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN); // 4.2.結(jié)束處理 return exchange.getResponse().setComplete(); } }
8.5.3 過濾器執(zhí)行順序
請求進(jìn)入網(wǎng)關(guān)會碰到三類過濾器:當(dāng)前路由的過濾器、DefaultFilter、GlobalFilter
請求路由后,會將當(dāng)前路由過濾器和DefaultFilter、GlobalFilter,合并到一個過濾器鏈(集合)中,排序后依次執(zhí)行每個過濾器:
排序的規(guī)則是什么呢?
- 每一個過濾器都必須指定一個int類型的order值,order值越小,優(yōu)先級越高,執(zhí)行順序越靠前。
- GlobalFilter通過實(shí)現(xiàn)Ordered接口,或者添加@Order注解來指定order值,由我們自己指定
- 路由過濾器和defaultFilter的order由Spring指定,默認(rèn)是按照聲明順序從1遞增。
- 當(dāng)過濾器的order值一樣時,會按照 defaultFilter > 路由過濾器 > GlobalFilter的順序執(zhí)行。
詳細(xì)內(nèi)容,可以查看源碼:
org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator#getFilters()
方法是先加載defaultFilters,然后再加載某個route的filters,然后合并。
org.springframework.cloud.gateway.handler.FilteringWebHandler#handle()
方法會加載全局過濾器,與前面的過濾器合并后根據(jù)order排序,組織過濾器鏈。
8.6 限流過濾器
限流:對應(yīng)用服務(wù)器的請求做限制,避免因過多請求而導(dǎo)致服務(wù)器過載甚至宕機(jī)。限流算法常見的包括兩種:
計(jì)數(shù)器算法,又包括窗口計(jì)數(shù)器算法、滑動窗口計(jì)數(shù)器算法
漏桶算法(Leaky Bucket)
令牌桶算法(Token Bucket)
|
8.7 跨域問題
8.7.1.什么是跨域問題
跨域:域名不一致就是跨域,主要包括:
域名不同: www.taobao.com 和 www.taobao.org 和 www.jd.com 和 miaosha.jd.com
域名相同,端口不同:localhost:8080和localhost8081
跨域問題:瀏覽器禁止請求的發(fā)起者與服務(wù)端發(fā)生跨域ajax請求,請求被瀏覽器攔截的問題。
解決方案:CORS
8.7.2.模擬跨域問題
將下面的index代碼放入tomcat或者nginx這樣的web服務(wù)器中,啟動并訪問。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> </head> <body> <pre> spring: cloud: gateway: globalcors: # 全局的跨域處理 add-to-simple-url-handler-mapping: true # 解決options請求被攔截問題 corsConfigurations: '[/**]': allowedOrigins: # 允許哪些網(wǎng)站的跨域請求 - "http://localhost:8090" - "http://www.leyou.com" allowedMethods: # 允許的跨域ajax的請求方式 - "GET" - "POST" - "DELETE" - "PUT" - "OPTIONS" allowedHeaders: "*" # 允許在請求中攜帶的頭信息 allowCredentials: true # 是否允許攜帶cookie maxAge: 360000 # 這次跨域檢測的有效期 </pre> </body> <script src="https://unpkg.com/axios/dist/axios.min.js"></script> <script> axios.get("http://localhost:10010/user/1?authorization=admin") .then(resp => console.log(resp.data)) .catch(err => console.log(err)) </script> </html>
可以在瀏覽器控制臺看到下面的錯誤:
從localhost:8090訪問localhost:10010,端口不同,顯然是跨域的請求。文章來源:http://www.zghlxwxcb.cn/news/detail-490517.html
8.7.3 解決跨域問題
在gateway服務(wù)的application.yml文件中,添加下面的配置:文章來源地址http://www.zghlxwxcb.cn/news/detail-490517.html
spring: cloud: gateway: # 。。。 globalcors: # 全局的跨域處理 add-to-simple-url-handler-mapping: true # 解決options請求被攔截問題 corsConfigurations: '[/**]': allowedOrigins: # 允許哪些網(wǎng)站的跨域請求 - "http://localhost:8090" allowedMethods: # 允許的跨域ajax的請求方式 - "GET" - "POST" - "DELETE" - "PUT" - "OPTIONS" allowedHeaders: "*" # 允許在請求中攜帶的頭信息 allowCredentials: true # 是否允許攜帶cookie maxAge: 360000 # 這次跨域檢測的有效期
到了這里,關(guān)于【學(xué)習(xí)日記2023.6.12】之nacos配置管理_Feign遠(yuǎn)程調(diào)用_Gateway服務(wù)網(wǎng)關(guān)的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!