2018年中總結(三)工作中遇到的問題(下)

這部分是另外一家,也就是我目前地公司筆記記錄,來這裏工作地三個月時間,主要工作是負責調研新技術,目前基本還沒接觸到業務層面。

最開始是學習spring cloud組件,爲公司的服務加上配置中心,這個我之前寫過文章,然後嘗試加熔斷,但是由於涉及代碼太多,且Hystrix和代碼的耦合度也蠻高的,所以就放棄了。

之後應領導要求,開始熟悉Kubernetes和Istio,應該是爲之後做技術儲備,但是說實話,公司的業務量很小,完全沒必要使用這兩種框架,不過有學習的機會,並且領導堅持,我還是非常開心的接下了這個任務。可以看我後續的博客更新記錄,基本全部和這兩個大方向有關。

這裏就簡單的記錄遇到的一些問題,做些歸納總結。

1.Spring Cloud資料彙總

spring cloud中文網(應該是機翻,但是還是挺感謝有人做貢獻的)

稍微說一下我之前接觸的部分服務組件:

  • 服務註冊發現——Eureka
  • 網絡和負載均衡——ribbon和feign
  • 熔斷——Hystrix
  • 路由——zuul
  • 配置中心——config
  • 總線——bus
  • 熔斷聚合監控——Turbine

當然上面的內容都是跟着demo寫了寫,大概知道是幹什麼用的,真正用到的也就是config和bus結合的配置中心了,其他的也沒特別的深入挖掘。

下面是當時的一些總結:

首先spring cloud是建立在spring boot的基礎上的。而貫穿微服務架構始終的是服務發現Eureka。
對於所有在架構內的爲服務,都需要註冊到eureka上,方便服務的管理。
然後ribbon和feign起到的作用就是基於restful調用的兩種服務調用方式,並且他們可以配置負載均衡。關鍵註解是@LoadBalanced
熔斷是微服務架構中比較重要的一環,在微服務中,服務之間是獨立的,每次請求可能會調用多個服務,此時如果出現某個服務掛掉宕機這種極端情況,整個請求可能會卡在這個掛掉的服務這裏,熔斷的作用就是快速反應,如果這個服務掛了,就果斷切除,返回一個fallback,保證請求能夠順利的執行下去。使用turbine可以對熔斷的dashboard進行聚合,在一個頁面中監控多個服務的熔斷信息。
路由zuul可以配置網關,認證,多重路由等等,主要功能是路由轉發和過濾器。
日誌收集sleuth只是簡單的和zipkin組合了一下,沒有嘗試使用複雜而又強大的elk,以後如果接觸到相關內容,可以花時間學習一下。
最後最關鍵的是config,因爲工作要求就是爲各個微服務配出config,而config和bus組合可以實現配置的熱部署。當然其中還需要使用GitHub的webhook功能。也即在GitHub上更新配置文件後,可以即時對所有相關config client進行更新。

2.整合config配置中心時遇到的一些問題。
開啓rabbitmq後,啓動config client項目報錯:

Caused by: com.rabbitmq.client.ShutdownSignalException: connection error; protocol method: #method<connection.close>(reply-code=400, ...

主要是因爲rabbitmq服務器沒有給用戶分配權限,分配權限命令如下:

rabbitmqctl set_permissions -p "/" username(默認的爲guest) "." "." ".*"

參考文檔

在使用rabbitmqctl命令的時候遇到的錯誤:

* Authentication failed (rejected by the remote node), please check the Erlang cookie

解決辦法:

C:\WINDOWS\System32\config\systemprofile.erlang.cookie和C:\Users{UserName}.erlang.cookie兩個文件內容一致即可。

這個當時浪費了我很長時間,因爲很多博客都有問題。參考文章

3.因爲公司業務全部依託阿里雲,所以熟悉了很多阿里的產品,比如雲效(以前聽都沒聽過),應用和流水線這類的東西。

4.Dockfile的一些指令選項

5.java啓動命令中加入參數:-Djava.security.egd=file:/dev/./urandom的含義。參考文章

6.配置熔斷的時候遇到報錯:

java.lang.ClassNotFoundException: com.netflix.hystrix.contrib.javanica.aop.aspectj.HystrixCommandAspect

可以看出缺少aop的相關類,需要加依賴。參考文章

7.Lombok插件
IDEA手動安裝插件

8.分佈式id生成算法SnowFlake

9.高頻詞彙 DDD —— 領域驅動設計。

10.12-factor

11.k8s集羣拉取私有鏡像倉庫失敗,需要進行相關配置纔可使用(配置 pull secret)。參考

忘記在配置文件中加imagePullSecrets: 標籤,導致deployment創建失敗。

12.使用命令kubectl get pods報錯:

The connection to the server localhost:8080 was refused - did you specify the right host or port?

原因是認證問題,當前node節點沒有對應的admin.conf文件。將master節點的admin.conf文件copy到node節點/etc/kubernetes/admin.conf文件上。然後使用命令:

export KUBECONFIG=/etc/kubernetes/admin.conf

13.關於網段子網掩碼的計算,例如網段192.168.17.0/24

其子網掩碼的前24位爲1,所以對應的子網掩碼錶示爲255.255.255.0;此時能夠使用的主機共254臺,因爲0和255不允許作爲主機地址。(網絡廣播地址192.168.17.255 和網絡標示192.168.17.0)

14.在改項目的時候發現,如果存在spring cloud的部分組件,restTemplate就不能按照localhost/ip+port訪問服務,主要就是和ribbon的@LoadBalance註解有關,去掉這個註解就可以正常工作。

15.沒有volatile關鍵字的double check不安全。因爲:使用volatile變量禁止指令重排序相關文章

其他內容沒什麼乾貨,或者就是和工作內容直接相關的小問題,沒有寫的價值。

不過我下面需要重點介紹兩個問題。

Spring Security

這塊是我在修改服務代碼的時候偶然碰上的問題,爲此,我花了點時間瞭解了一下Spring Security,當然,依然還是處於簡單瞭解的層面,依然沒有進行應用,也沒有進行深耕,希望以後有機會可以深度發掘。

一切的起源來自一個報錯:

java.lang.ClassCastException: org.springframework.security.authentication.AnonymousAuthenticationToken cannot be cast to org.springframework.security.oauth2.provider.OAuth2Authentication

可以看出這是一個類型轉換的問題,但是爲什麼會這樣?

定位到了問題代碼:

(OAuth2Authentication) SecurityContextHolder.getContext().getAuthentication()

在這裏出現了問題,但是爲什麼會出現這個問題?

從這裏開始,就拉開了我一點點了解Spring Security的序幕。

當然首先就是不明所以的讀博客去了解了,因爲我的時間有限,所以沒有選擇閱讀官方文檔式的深耕,而是選擇了最快接受消息,解決問題的讀博客方式(弊端就是一知半解,爲了解決問題,會導致很快遺忘)。這裏推薦一個系列,寫的很好,比較清晰。(能找到好的博客也是需要能力,能寫好的博客是能力出衆的體現!我還是需要繼續努力,增加自己的硬實力,寫出好的博客造福各位,而不是當毒瘤!)

看過這個系列文章後,大致總結了一下:

Spring Security通過繼承WebSecurityConfigurerAdapter 並重寫其中的configure()方法來進行相應的配置(主要是HttpSecurityAuthenticationManagerBuilder )。自定義的配置類WebSecurityConfig加上了@EnableWebSecurity註解,同時繼承了WebSecurityConfigurerAdapter

使用configure(AuthenticationManagerBuilder auth)暴露一個AuthenticationManager(最核心的身份認證管理器)的建造器:AuthenticationManagerBuilder

HttpSecurity的典型配置,其中http作爲根開始配置,每一個and()對應了一個模塊的配置(等同於xml配置中的結束標籤),並且and()返回了HttpSecurity本身,於是可以連續進行配置。通過authorizeRequests()配置路徑攔截,表明路徑訪問所對應的權限,角色,認證信息。通過formLogin()對應表單認證相關的配置。通過logout()對應了註銷相關的配置。通過httpBasic()可以配置basic登錄。以及antMatchers()進行角色權限和路徑的匹配。

上面只是簡述了一下在spring boot中對於spring security的簡單配置,下面再簡單總結一下spring security的核心過濾器鏈路調用(詳情可參閱這篇文章):只介紹幾個重要的過濾器

SecurityContextPersistenceFilter :它有兩個主要職責;請求來臨時,創建SecurityContext安全上下文信息,請求結束時清空SecurityContextHolder

UsernamePasswordAuthenticationFilter :主要是表單驗證的時候非常重要的一個過濾器,表單提交username和password,被封裝成token進行一系列的認證,主要通過這個過濾器完成。
偷一張時序圖:
這裏寫圖片描述

AnonymousAuthenticationFilter:匿名身份過濾器,需要將它與UsernamePasswordAuthenticationFilter 放在一起比較理解,spring security爲了兼容未登錄的訪問,也走了一套認證流程,只不過是一個匿名的身份。

剩下不一一介紹,不過看到這,應該就能明白我之前遇到的報錯是什麼問題了,因爲匿名過濾器是載表單驗證過濾器後的,所以說明我的報錯是因爲表單驗證出現了問題,然後走了匿名流程,最後導致的類型轉換錯誤。

之後又簡單的瞭解了一下OAuth2.0。

看了阮一峯的關於OAuth2.0的文章,總結了一下:

OAuth就是爲了第三方認證場景纔出現的。它在“客戶端”和“服務提供商”之間設置了一個授權層。“客戶端”不能直接登陸“服務提供商”,只能登陸授權層,以此將用戶與客戶端區分開來。“客戶端”登陸授權層所用的令牌(token),與與用戶的密碼不同。用戶可以在登陸的時候指定授權層令牌的權限範圍和有效期。
“客戶端”登陸授權層以後,“服務提供商”根據令牌的權限範圍和有效期,向“客戶端”開放用戶存儲的資料。

然後偷一張圖。。。
這裏寫圖片描述

(A)用戶打開客戶端以後,客戶端要求用戶給予授權。
(B)用戶同意給予客戶端授權。
(C)客戶端使用上一步獲得的授權,向認證服務器申請令牌。
(D)認證服務器對客戶端進行認證以後,確認無誤,同意發放令牌。
(E)客戶端使用令牌,向資源服務器申請獲取資源。
(F)資源服務器確認令牌無誤,同意向客戶端開放資源。

而 OAuth 2 授權服務器必須的端點:

AuthorizationEndpoint 是用於授權服務請求的。默認的URL是:/oauth/authrize
TokenEndpoint 是用於獲取訪問令牌(Access Tokens)的請求。默認的URL是:/oauth/token

這兩個url在spring cloud中整合spring security及OAuth 2.0的時候,經常能看到的配置。

到這裏,我的問題解決了,學習也就暫時告一段落,寫的很亂,因爲當時的思路比這個更亂,所以這塊還是不太好歸納。

發送post請求遇到的問題

下一個問題是我在測試的時候,遇到的spring boot中,使用restTemplate發送post請求遇到的問題。

因爲之前一直使用的spring mvc,所以在我寫這個測試項目的時候,思想還是停留在那個階段的。而且spring boot有很多之前沒用過,見過的註解,所以感覺自己落伍了。。。

首先是第一次發送請求的時候,接收端無法獲取傳遞參數。當時的想法是從request中獲取參數,寫出了下面不倫不類的代碼:

@RequestMapping("/login")
public String getLogin(User user){
    logger.info("get in ============> RouteController.getLogin()");
    System.out.println(user);
    HttpHeaders headers = new HttpHeaders();
    MediaType type = MediaType.parseMediaType("application/json; charset=UTF-8");
    headers.setContentType(type);
    headers.add("Accept", MediaType.APPLICATION_JSON.toString());

    Gson gson = new Gson();
    String entity = gson.toJson(user);
    HttpEntity<String> formEntity = new HttpEntity<>(entity,headers);
    String ss = gson.toJson(formEntity);
    System.out.println(ss);

    String result = this.restTemplate.postForObject(remoteURL + "/login",formEntity,String.class);
    System.out.println(result);
    return result;
}

// 然後我覺得應該從request中取值,所以接收端是這樣的
@RequestMapping(value = "/login")
public String login(HttpServletRequest request, User user){
    logger.info("get in ============> LoginController.login()");

    String username;
    String password;

    if (user.getUsername() == null && user.getPassword() == null){
        String[] usernames = request.getParameterValues("username");
        String[] passwords = request.getParameterValues("password");
        System.out.println(usernames + " " + passwords);
        username = usernames[0];
        password = passwords[0];
    }else {
        username = user.getUsername();
        password = user.getPassword();
    }

    if (username.equals("german") && password.equals("123456")){
        return "go";
    }

    return "fail";
}

當時的想法很簡單,我發送了請求,request中肯定是有參數的對吧,但是就是無法從request中取到值,所以應該是哪個地方出現了問題,當然爲什麼request中沒有取到值,我現在也沒想通,網絡學的比較差,唉。。。

之後就去網上搜了一下,然後照貓畫虎,寫了第二個示例:

@PostMapping("/login")
    public String getLogin(User user){
        logger.info("get in ============> RouteController.getLogin()");
        System.out.println(user);
        HttpHeaders headers = new HttpHeaders();
        MediaType type = MediaType.parseMediaType("application/json; charset=UTF-8");
        headers.setContentType(type);
        headers.add("Accept", MediaType.APPLICATION_JSON.toString());

        Gson gson = new Gson();
//        String entity = gson.toJson(user);
        MultiValueMap<String, Object> params = new LinkedMultiValueMap<>();
        params.add("username", user.getUsername());
        params.add("password", user.getPassword());
        HttpEntity httpEntity  = new HttpEntity(params,headers);
        String ss = gson.toJson(httpEntity );
        System.out.println(ss);

        ResponseEntity<String> request = this.restTemplate.postForEntity(remoteURL + "/login",httpEntity ,String.class);
        System.out.println(request.getBody());
        return request.getBody();
    }

接受端一度改爲這種狀態:
@RequestMapping(value = "/login")
public String login(@RequestBody String username, @RequestBody String password){
    logger.info("get in ============> LoginController.login()");
    System.out.println(usernames + " " + passwords);

    if (username.equals("german") && password.equals("123456")){
        return "go";
    }

    return "fail";
}

這個時候發送端一直報錯400,是傳遞的數據格式問題。

然後又修改了一版,得到最終版本,也發現了上面的出現400的原因。

發送端:
@PostMapping("login")
public String getLogin(@RequestParam String username, String password){
    logger.info("get in ============> RouteController.getLogin()");
    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.MULTIPART_FORM_DATA);
    headers.add("Accept", MediaType.APPLICATION_JSON.toString());

    MultiValueMap<String, Object> params = new LinkedMultiValueMap<>();
    params.add("username", username);
    params.add("password", password);
    HttpEntity httpEntity = new HttpEntity(params,headers);
    ResponseEntity<String> request = this.restTemplate.exchange(remoteURL + "/login", HttpMethod.POST, httpEntity,String.class);
    System.out.println(request.getBody());
    return request.getBody();
}

接收端:
@PostMapping(value = "login", produces = MediaType.APPLICATION_JSON_VALUE)
public String login(@RequestParam String username, String password){
    logger.info("get in ============> LoginController.login()");

    if (username.equals("german") && password.equals("123456")){
        return "go";
    }

    return "fail";
}

問題的根源在於接收端的接受參數註解!使用@RequestParam和使用@RequestBody完全是兩個概念!就是這裏導致的400問題。

然後就搜了一下兩者的區別

簡單來說,我的失誤在於:

因爲我在請求頭中設置的ContentType類型爲multipart/form-data,此類型多用來上傳文件類型—即使用@RequestBody不能處理這種格式的數據,@RequestParam這個卻是可以處理的。
而使用其他格式包括application/json, application/xml等。這些格式的數據,必須使用@RequestBody來處理。(親測,確實如此,不過這樣的話,傳遞的json全部在一個變量中,需要再次處理,和之前使用@RequestParam不同。)

也是比較初級的錯誤,很久沒有遇到,有些遺忘了。

最後再附RestTemplate的幾種請求調用

到此爲止,我的年中總結也就結束了。可以看到,學習的東西也沒有太多,遇到的問題可能也都不是太難,甚至還有一些很低級的錯誤。究其原因就是,覆盤太少,導致低級的錯誤,時間長遺忘,重複犯錯;學習新東西的時候,還是有些急躁,所以會導致學完不久就忘,掌握的既不紮實,也沒有內化到自己的知識體系中,純粹是爲了解決問題而短暫學習,是無法得到長足的技術進步的。以上都是前半年的不足之處,需要改正。

不過學到的東西就是:感覺上比較困難的問題,在多次拆解後,也是一個一個比較小的問題,將問題逐漸細化,最後總能找到解決方案

最後看到雷軍的一段話,送給自己,有則改之無則加勉。

“如果浪費了半小時時間,我就覺得很慚愧。後來我看到很多人不珍惜時間的時候, 我就覺得這樣的人真沒出息。時間是自己的,你到一個公司打工的時候,偷懶,老闆沒有看見,就覺得自己又蒙了一下,玩貓和老鼠的遊戲,真是沒有必要。公司所付的那麼一點錢, 就買下了你一個月的青春?學會的東西首先是自己的,其次纔是公司的。沒有多少人真正計算過自己一個小時值多少錢。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章