about 4 years ago

最後還是搬家了

後續不再更新了 請到 https://blog.samchu.dev/ 繼續支持

會陸續再整理可以用的文章過去新的 blog

感謝大家不嫌棄

謝謝

 
over 4 years ago

https://samzhu.github.io/SpringCloudStreamPresentation/

http://10.0.29.105:8080/login

http://10.0.29.105:8080/

 
over 4 years ago

功能:
希望外部訪問 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

 
almost 5 years ago

SpringBoot

目前最新的 SpringCloud 版本 Finchley.RELEASE 又再簡化一點 使用 Zipkin 這部分, 已經不用再自己建立一個 ZipkinServer

Read on →
 
almost 5 years ago

基本都是要用 SpringBoot2 了啊, 就不要想問 1.x 版了

Read on →
 
almost 5 years ago

查看時區的設定為何?

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 看看時間是否一致

 
almost 5 years ago

安裝桌機版的 CentOS 在筆電上, 當闔上螢幕時就會變休眠.

可以通過 /etc/systemd 來定義的電源管理

Read on →
 
almost 5 years ago

有天我重灌完 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.
Read on →
 
about 5 years ago

首先用 PuTTYgen Release 0.70 來產生公私鑰
有需要就從這邊下載 https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html

Read on →
 
about 5 years ago

這幾天 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

收工