功能:
希望外部訪問 http://domain/acc/v1/endpoint 時, 可以轉發到 springcloud 中 ACC-V1 的實例,
並且將路由資訊儲存至 Redis 下次重啟 or 增加新的 gateway 不用重新配置路由資訊.
配置部分
application.yml
spring:
application:
name: gateway
cloud:
gateway:
discovery:
locator:
enabled: false
redis:
host: 127.0.0.1
port: 6379
password:
server:
port: ${port:9000}
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka
instance:
prefer-ip-address: true
management:
endpoints:
web:
exposure:
include: '*'
關閉由服務發現來建立路由
spring.cloud.gateway.discovery.locator.enabled = false
開放 gateway 的 endpoint
management.endpoints.web.exposure.include = "*"
程式部分
準備兩支
Redis 配置
RedisConfig.java
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
GenericJackson2JsonRedisSerializer jackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
redisTemplate.setDefaultSerializer(jackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
需要配置使用 Json 儲存到 Redis, 因為 org.springframework.cloud.gateway.route.RouteDefinition 並沒有 implements Serializable,
所以不轉成 Json 會出錯.
實作 RouteDefinitionRepository
RedisRouteDefinitionRepository.java
@Slf4j
@Component
public class RedisRouteDefinitionRepository implements RouteDefinitionRepository {
public static final String GATEWAY_ROUTES = "geteway_routes";
@Autowired
private RedisTemplate redisTemplate;
@Override
public Flux<RouteDefinition> getRouteDefinitions() {
List<RouteDefinition> routeDefinitions = new ArrayList<>();
redisTemplate.opsForHash().values(GATEWAY_ROUTES).stream().forEach(routeDefinition -> {
try {
routeDefinitions.add((RouteDefinition) routeDefinition);
} catch (Exception e) {
e.printStackTrace();
}
});
return Flux.fromIterable(routeDefinitions);
}
@Override
public Mono<Void> save(Mono<RouteDefinition> route) {
return route
.flatMap(routeDefinition -> {
redisTemplate.opsForHash().put(GATEWAY_ROUTES, routeDefinition.getId(),
routeDefinition);
return Mono.empty();
});
}
@Override
public Mono<Void> delete(Mono<String> routeId) {
return routeId.flatMap(id -> {
if (redisTemplate.opsForHash().hasKey(GATEWAY_ROUTES, id)) {
redisTemplate.opsForHash().delete(GATEWAY_ROUTES, id);
return Mono.empty();
}
return Mono.defer(() -> Mono.error(new NotFoundException("RouteDefinition not found: " + routeId)));
});
}
}
gateway 預設是將路由放在記憶體中管理, 但當發現有額外提供 RouteDefinitionRepository 時則會採用你提供的,
這邊實作則是將路由寫到 Redis 做存放.
看一下 Eureka 上有哪些服務
跟 gateway 要路由表資訊看看
curl -X GET http://localhost:9000/actuator/gateway/routes
ResponseBody
[]
可以看到路由表是空的
新增路由
curl -X POST \
http://localhost:9000/actuator/gateway/routes/acc_v1 \
-H 'Content-Type: application/json' \
-d '{
"predicates": [
{
"name": "Path",
"args": {
"pattern": "/ACC/V1/**"
}
}
],
"filters": [
{
"name": "RewritePath",
"args": {
"regexp": "/ACC/V1/(?<remaining>.*)",
"replacement": "/${remaining}"
}
}
],
"uri": "lb://ACC-V1",
"order": 0
}'
再次做查詢
curl -X GET http://localhost:9000/actuator/gateway/routes
ResponseBody
[
{
"route_id": "acc_v1",
"route_definition": {
"id": "acc_v1",
"predicates": [
{
"name": "Path",
"args": {
"pattern": "/ACC/V1/**"
}
}
],
"filters": [
{
"name": "RewritePath",
"args": {
"regexp": "/ACC/V1/(?<remaining>.*)",
"replacement": "/${remaining}"
}
}
],
"uri": "lb://ACC-V1",
"order": 0
},
"order": 0
}
]
此時可以發現已經有路由表了, 也去 Redis 查一下
接下來去請求 ACC-V1 的資料
curl -X GET http://localhost:9000/ACC/V1/version
ResponseBody
{
"appName": "acc-v1"
}
可以正確拿到v1的資料
此時啟動新的服務版本是 v2
這時候去拿 v2 資料是拿不到
curl -X GET http://localhost:9000/ACC/V2/version
ResponseBody
{
"timestamp": "2018-11-07T09:11:10.323+0000",
"path": "/ACC/V2/version",
"status": 404,
"error": "Not Found",
"message": null
}
增加新服務路由 v2
curl -X POST \
http://localhost:9000/actuator/gateway/routes/acc_v2 \
-H 'Content-Type: application/json'
-d '{
"predicates": [
{
"name": "Path",
"args": {
"pattern": "/ACC/V2/**"
}
}
],
"filters": [
{
"name": "RewritePath",
"args": {
"regexp": "/ACC/V2/(?<remaining>.*)",
"replacement": "/${remaining}"
}
}
],
"uri": "lb://ACC-V2",
"order": 0
}'
這時候去拿 v2 的資料就可以正確取得
curl -X GET http://localhost:9000/ACC/V2/version
ResponseBody
{
"appName": "acc-v2"
}
但服務 acc-v1 還是可以訪問到, v1 v2 服務同時提供服務, 並且透過 url 來指定就完成了
curl -X GET http://localhost:9000/ACC/V1/version
ResponseBody
{
"appName": "acc-v1"
}
當 v1 要廢棄的時候, 可以先透過 management.endpoints 來刪除
curl -X DELETE \
http://localhost:9000/actuator/gateway/routes/acc_v1
刪除之後確定沒問題就可以真正的把服務關掉囉
curl -X GET \
http://localhost:9000/actuator/gateway/routes
ResponseBody
[
{
"route_id": "acc_v2",
"route_definition": {
"id": "acc_v2",
"predicates": [
{
"name": "Path",
"args": {
"pattern": "/ACC/V2/**"
}
}
],
"filters": [
{
"name": "RewritePath",
"args": {
"regexp": "/ACC/V2/(?<remaining>.*)",
"replacement": "/${remaining}"
}
}
],
"uri": "lb://ACC-V2",
"order": 0
},
"order": 0
}
]
當 gateway 重啟或是增加新的時候, 都會去 Redis 取得路由資訊, 這樣就不用一個一個去配置啦
如果手動增加或刪除 Redis 內的路由資訊, 其實也是可以同步到所有 Gateway 上, 這樣也不一定需要開 management.endpoints gateway.
參考資料
Spring Cloud Gateway的动态路由实现 | 许进沉思录-专注于互联网与中间件基础架构技术研究
Spring-Cloud-Gateway 源码解析 —— 路由(1.3)之 RouteDefinitionRepository 存储器 | 芋道源码 —— 纯源码解析博客
Spring Cloud Gateway运行时动态配置网关 - 学习记录 - 开源中国
原碼的話可以看看這幾支
GatewayAutoConfiguration.jav
GatewayControllerEndpoint.java
RouteDefinition.java
InMemoryRouteDefinitionRepository.java
SpringBoot
目前最新的 SpringCloud 版本 Finchley.RELEASE 又再簡化一點 使用 Zipkin 這部分, 已經不用再自己建立一個 ZipkinServer
Read on →查看時區的設定為何?
timedatectl
Local time: 一 2018-07-09 10:47:34 CST
Universal time: 一 2018-07-09 02:47:34 UTC
RTC time: 日 2018-07-08 22:47:34
Time zone: America/New_York (CST, +0800)
NTP enabled: yes
NTP synchronized: yes
RTC in local TZ: yes
DST active: n/a
Warning: The system is configured to read the RTC time in the local time zone.
This mode can not be fully supported. It will create various problems
with time zone changes and daylight saving time adjustments. The RTC
time is never updated, it relies on external facilities to maintain it.
If at all possible, use RTC in UTC by calling
'timedatectl set-local-rtc 0'.
查看所有時區:
timedatectl list-timezones
設定時區區域:
timedatectl set-timezone UTC
要設定成台灣的話
timedatectl set-timezone Asia/Taipei
與NTP server同步:
timedatectl set-ntp yes
啟用UTC時間:
timedatectl set-local-rtc no
關閉UTC時間,使用本地時間:
timedatectl set-local-rtc yes
https://centminmod.com/datetimezones.html
ls /usr/share/zoneinfo
copy /usr/share/zoneinfo/* 內的確切時區資料檔案為 /etc/localtime
cp /usr/share/zoneinfo/Asia/Taipei /etc/localtime
cp /usr/share/zoneinfo/UTC /etc/localtime
執行 date -u
輸入 date 看看是否已經是正確時區
調整系統時間
透過 ntpdate -u time.stdtime.gov.tw 來校正目前時間
透過 date –set=“2003-02-19 17:44” 方式直接手動設定
ntpdate -u time.stdtime.gov.tw
輸入 hwclock 看看目前 BIOS 的時間是否正確
sudo hwclock
西元2018年07月09日 (週一) 10時54分41秒 -0.860622 秒
執行 clock –systohc 將目前系統時間寫入 BIOS 時間
sudo clock --systohc
輸入 date 與 hwclock 看看時間是否一致
有天我重灌完 NB 後要用 SSH 連線出現下面訊息
zhushanglide-MacBook-Air:~ sam$ ssh 192.168.31.114
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
Someone could be eavesdropping on you right now (man-in-the-middle attack)!
It is also possible that a host key has just been changed.
The fingerprint for the ECDSA key sent by the remote host is
SHA256:fO7ZM2C2+EULHR+G+HOfQWMjcT7Fmf0AvP8BKD3I1kc.
Please contact your system administrator.
Add correct host key in /Users/sam/.ssh/known_hosts to get rid of this message.
Offending ECDSA key in /Users/sam/.ssh/known_hosts:20
ECDSA host key for 192.168.31.114 has changed and you have requested strict checking.
Host key verification failed.
首先用 PuTTYgen Release 0.70 來產生公私鑰
有需要就從這邊下載 https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html
這幾天 MySQL 出 8 的版本,
沒想到密碼儲存方式變更了, 頓時一堆人慘叫XD
因為 之前 MySQL 預設儲存密碼的方式是 mysql_native_password, 但 8.0 之後 預設的儲存方式為 caching_sha2_password, 說是又快又安全啦, 但讓人措手不及, 一堆 SQL 工具 (Navicat) 可能還來不及更新支援 就...
登入失敗!!
Authentication plugin 'caching_sha2_password' cannot be loaded: dlopen(/usr/local/mysql/lib/plugin/caching_sha2_password.so, 2): image not found
方法1 : 就升級上去吧, 變更 JDBC 連結方式
1.首先更新你的 JDBC 驅動 runtime 'mysql:mysql-connector-java:8.0.11'
2.修改你的 driver-class-name , 因為 Loading class `com.mysql.jdbc.Driver'. This is deprecated. , 請你換成新版的 com.mysql.cj.jdbc.Driver
3.JDBC URL 增加參數 allowPublicKeyRetrieval=true
完成, 這樣你的 JDBC 就可以搭配使用上 caching_sha2_password 的機制了
方法2 : 我要最新版但是不要用新的密碼機制
我是用 Docker 啦, 多個參數就可以正常使用了 --default-authentication-plugin=mysql_native_password
docker run --name askask-mysql -e MYSQL_DATABASE=askask -e MYSQL_ROOT_PASSWORD=1qaz2wsx -p 3306:3306 -d mysql --default-authentication-plugin=mysql_native_password
收工