管程(Monitor)概念及Java的實現原理_網頁設計公司

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

網站的第一印象網頁設計,決定了客戶是否繼續瀏覽的意願。台北網動廣告製作的RWD網頁設計,採用精簡與質感的CSS語法,提升企業的專業形象與簡約舒適的瀏覽體驗,讓瀏覽者第一眼就愛上它。

目錄

  • 互斥
  • 管程-Monitor
  • 當線程等待資源時
  • Hoare版本
  • Mesa版本
  • Brinch Hanson版本
  • 三種語義對比
  • Java版本的Monitor
  • Java monitor實現生產者/消費者

互斥

互斥訪問是併發編程要解決的核心問題之一。
有許多種方法可以滿足臨界區的互斥訪問。大體上可以分為三種,
一種是軟件方法,即由用戶程序承擔互斥訪問的責任,而不需要依賴編程語言或操作系統,譬如Dekker算法、Peterson算法等,通常這種方式會有一定的性能開銷和編程難度。
第二種是操作系統或編程語言對互斥的原生支持,譬如Linux中的mutex、Java語言的synchronized。
最後是硬件上的特殊指令,譬如著名的CAS。這種方式開銷最少,但是很難成為一種通用的解決方案,通常操作系統或編程語言的互斥是基於此建立起來的。

管程-Monitor

管程屬於編程語言級別的互斥解決方案,最早是Brinch Hanson和Hoare於1970s提出的概念,已在Pascal、Java、Python等語言中得到了實現。
“管程”一詞翻譯自英文Monitor Procedures,字面理解就是管理一個或多個執行過程。(但是個人感覺“管程”這個翻譯有點莫名其妙,看完更迷糊了,所以本文堅持用回原名Monitor。)
Monitor本質上是對通用同步工具的一種抽象,它就像一個線程安全的盒子,用戶程序把一個方法或過程(代碼塊)放進去,它就可以為他們提供一種保障:同一時刻只能有一個進程/線程執行該方法或過程,從而簡化了併發應用的開發難度
如果Monitor內沒有線程正在執行,則線程可以進入Monitor執行方法,否則該線程被放入入口隊列(entry queue)並使其掛起。當有線程從Monitor中退出時,會喚醒entry queue中的一個線程。
為了處理併發線程,Monitor還需要一個更基礎的同步工具,或者說需要一個機制,使得線程不僅被掛起,而且還能釋放Monitor,以便其他線程可以進入。
Monitor使用條件變量(Condition Variable)支持這種機制,這些條件變量(一個或多個)包含在Monitor中,並且只有在Monitor內才能被訪問 (類似Java對象的private變量)。
對外開放兩個方法以便用戶程序操作條件變量
cwait(c):調用該方法的線程在條件c上阻塞,monitor現在可以被其他線程使用。
csignal(c):恢復在條件c上被阻塞的線程。若有多個這樣的線程,選擇其中一個。
(通常,為了保證cwait/csignal對條件變量的變更是原子性的,還需要藉助CAS)

當線程等待資源時

當Monitor中正在執行的線程無法獲取所需資源時,情況會變得更加複雜。
如果發生這種情況,等待資源的線程可以先把自己掛起,並且釋放Monitor的使用權,使得其他線程得以進入Monitor。
那麼問題來了,當第二個線程在執行期間,第一個線程所需的資源可用了,會發生什麼?
立即喚醒第一個線程,還是第二個線程先執行完?
對此產生了多個對Monitor的定義。

Hoare版本

在Hoare的語義中,當資源可用時,ThreadA立即恢復執行,而ThreadB進入signal queue。

1.ThreadA 進入 monitor
2.ThreadA 等待資源 (進入wait queue)
3.ThreadB 進入monitor
4.ThreadB 資源可用 ,通知ThreadA恢復執行,並把自己轉移到signal queue。
5.ThreadA 重新進入 monitor
6.ThreadA 離開monitor
7.ThreadB 重新進入 monitor
8.ThreadB 離開monitor
9.其他在entry queue中的線程通過競爭進入monitor

Mesa版本

在Mesa Monitor的實現中,第二個線程會先執行完。
ThreadA的資源可用時,把它從wait queue轉移到entry queue。ThreadB繼續執行至結束。
ThreadA最終也會從entry queue中得以執行。

1.ThreadA 進入 monitor
2.ThreadA 等待資源 (進入wait queue,並釋放monitor)
3.ThreadB 進入monitor
4.ThreadB 資源可用,通知ThreadA。(ThreadA被轉移到entey queue)
5.ThreadB 繼續執行
6.ThreadB 離開monitor
7.ThreadA 獲得執行機會,從entry queue出隊列,恢復執行
8.ThreadA 離開monitor
9.其他在entry queue中的線程通過競爭進入monitor

由於ThreadA被轉移到了entry queue,當ThreadB退出monitor后,ThreadA與其他線程平等競爭monitor的進入條件,所以並不能保證立即執行。
更不幸的是,等到ThreadA重入monitor后,資源可能再次不可用,重複以上過程。

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

台中景泰電動車行只是一個單純的理由,將來台灣的環境,出門可以自由放心的深呼吸,讓空氣回歸自然的乾淨,減少污染,留給我們下一代有好品質無空污的優質環境

Brinch Hanson版本

Brinch Hanson Monitor(以下簡稱BH Monitor)只允許線程從monitor退出時發出信號,此時被通知的線程進入monitor恢復執行。

1.ThreadA 進入 monitor
2.ThreadA 等待資源a
3.ThreadB 進入monitor
4.ThreadB 離開Monitor,並給通知等待資源a的線程,資源可用
5.ThreadA 重新進入 monitor
6.ThreadA 離開monitor
7.其他線程從entry queue中競爭進入monitor

三種語義對比

Hoare Monitor中,資源可用時,ThreadB調用csignal()后被阻塞,以便ThreadA立即恢復執行。
這時ThreadB應該被放到哪裡?一種可能是轉移到entry queue,這樣它就必須與其他還未進入Montior的線程平等競爭獲取重入機會。
但是由於在調用csignal()之前,ThreadB已經執行了一部分,因此使它優先於其他線程是有意義的,
為此,Hoare Monitor增加了signal queue用於存放阻塞在csignal()上的線程。
Hoare Monitor的一個明顯缺點是,ThreadB在執行中途被中斷,需要額外的兩次線程切換才能恢復執行。
不同的是,Mesa Monitor和BH Monitor會保證ThreadB先執行完,因此不需要額外的signal queue。

Java版本的Monitor

Java在實現時對最初的Monitor定義做了一些合理的限制。首先,與以上三種都不一樣的是,Java Montior只允許一個條件變量,而不是多個。

不像BH monitor,signal可以出現在代碼的任何地方。

也不像Hoare monitor,資源可以時,被通知的線程不會立即執行,而是從BLOCK狀態變成RUNNABLE狀態,被CPU再次調度到時才恢復執行。

與cwait(c)和csignal(c)對應的是wait()和notify()方法。

Java monitor機制通過synchronized關鍵字暴露給用戶,syncronized可以修飾方法或代碼塊(兩者本質上都是一個過程)。

Java monitor實現生產者/消費者



//簡化版本,只允許一個生產者和一個消費者

class BoundedBuffer {    

   private int numSlots = 0;

   private double[] buffer = null;

   private int putIn = 0, takeOut = 0;

   private int count = 0;



   public BoundedBuffer(int numSlots) {

      if (numSlots <= 0) throw new IllegalArgumentException("numSlots<=0");

      this.numSlots = numSlots;

      buffer = new double[numSlots];

      System.out.println("BoundedBuffer alive, numSlots=" + numSlots);

   }



   public synchronized void deposit(double value) {

      while (count == numSlots)

         try {

            wait();

         } catch (InterruptedException e) {

            System.err.println("interrupted out of wait");

         }

      buffer[putIn] = value;

      putIn = (putIn + 1) % numSlots;

      count++;                  

      if (count == 1) notify();  //喚醒等待的consumer

   }



   public synchronized double fetch() {

      double value;

      while (count == 0)

         try {

            wait();

         } catch (InterruptedException e) {

            System.err.println("interrupted out of wait");

         }

      value = buffer[takeOut];

      takeOut = (takeOut + 1) % numSlots;

      count--;                           // wake up the producer

      if (count == numSlots-1) notify(); // 喚醒等待的producer

      return value;

   }

}

1.Monitors and Condition Variables:https://cseweb.ucsd.edu/classes/sp17/cse120-a/applications/ln/lecture8.html
2.《操作系統精髓與設計原理》第五章
3.https://en.m.wikipedia.org/wiki/Monitor_(synchronization)

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

以設計的實用美學觀點,規劃出舒適、美觀的視覺畫面,有效提昇使用者的心理期待,營造出輕鬆、愉悅的網站瀏覽體驗。

.NET編程5月小結 – Blazor, Unity, Dependency Injection_如何寫文案

※別再煩惱如何寫文案,掌握八大原則!

什麼是銷售文案服務?A就是幫你撰寫適合的廣告文案。當您需要販售商品、宣傳活動、建立個人品牌,撰寫廣告文案都是必須的工作。

本文是我在5月份看到的一些有趣的內容的集合。在這裏你可以找到許多有關Blazor、ASPNET Core的學習資源和示例項目,有關在Unity中使用Zenject進行單元測試的博客,有關Unity項目架構的討論,以及對依賴注入感興趣的人的必讀書籍。  

0x00 Blazor – app building workshop

  • Blazor是一個單頁面應用程序框架,使用.NET和WebAssembly來構建客戶端Web應用程序。在這個workshop中,我們將構建一個完整的Blazor應用程序,並逐步了解Blazor框架的各種功能。

 

0x01 Blazor – CarChecker

  • 一個BlazorWebAssembly應用程序的樣板工程,演示包括authenticationin-browser data storageoffline supportlocalizationresponsive layouts

    ※教你寫出一流的銷售文案?

    銷售文案是什麼?A文案是廣告用的文字。舉凡任何宣傳、行銷、販賣商品時所用到的文字都是文案。在網路時代,文案成為行銷中最重要的宣傳方式,好的文案可節省大量宣傳資源,達成行銷目的。

    等等功能。

 

0x02 Modern Web UI with Blazor WebAssembly

  • 上面的Github的樣板工程的視頻教學版本。

 

0x03 Awesome Blazor

  • 這個應該比較有名了,一個Github倉庫,收集了大量和Balzor有關的資源,這些資源包括示例項目,教程,視頻,文章,書籍,电子書等。

 

0x04 Practical ASP.NET Core

  • 該項目的目的是使.NET程序員能夠直接從代碼中學習新的ASP.NET Core技術棧。Readme文件包含所有項目的說明。

 

0x05 Game Programmer Resume Tips

  • 作者Dale Kim目前在Unity工作,在這個文檔中他回顧了過去使用過的實際簡歷。文檔中的技巧適用於具有編程背景並想要在遊戲行業找到第一份工作的學生。

 

0x06 Clean Architecture Solution Template for Angular 9 and .NET Core 3.1

  • 這是一個按照Clean Architecture原理使用Angular和ASP NET Core創建單頁應用程序(SPA)的解決方案模板。

 

0x07 Unit testing Unity and UniRX with Zenject and Moq

  • 這篇文章並沒有討論太多單元測試本身,而是提供了使用UniRX和Zenject等出色技術在Unity中創建單元測試的討論。

 

0x08 A better architecture for Unity projects

  • 來自GameDev Guru的一篇對Unity項目架構的思考和總結。

 

0x09 Dependency Injection Principles, Practices, and Patterns

  • Dependency Injection Principles, Practices, and Patterns(依賴注入原理,實踐和模式)是經典暢銷書《Dependency Injection in .NET》的修訂版和擴展版。它從頭開始教你DI,是對依賴注入感興趣的人的必讀書籍。

 

https://zhuanlan.zhihu.com/p/145260777

  本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

擁有後台管理系統的網站,將擁有強大的資料管理與更新功能,幫助您隨時新增網站的內容並節省網站開發的成本。

ASP.NET Core 依賴注入最佳實踐與技巧[譯]_網頁設計公司

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

當全世界的人們隨著網路時代而改變向上時您還停留在『網站美醜不重要』的舊有思維嗎?機會是留給努力改變現況的人們,別再浪費一分一秒可以接觸商機的寶貴時間!

ASP.NET Core 依賴注入最佳實踐與技巧

原文地址:https://medium.com/volosoft/asp-net-core-dependency-injection-best-practices-tips-tricks-c6e9c67f9d96 [正(ke)確(xue)上(shang)網(wang)]
posted by Halil İbrahim Kalkan Jul 12, 2018 · 7 min read

在這篇文章中,我將分享一下在ASP.NET Core應用程序中使用依賴注入的經驗與建議。
主要分享的目的,基於以下幾點原則:

  • 有效的設計服務及它們的依賴關係
  • 預防多線程問題
  • 預防內存移除
  • 預防潛在bugs

這篇文章的前提假設你已經對依賴注入和ASP.NET Core由基本的認識,如果還沒有,首先請閱讀ASP.NET Core Dependency Injection documentation。

基礎

構造函數注入

構造函數注入(Constructor injection)用於聲明和獲取服務對服務構造的依賴關係。

public class ProductService
{
    private readonly IProductRepository _productRepository;
    public ProductService(IProductRepository productRepository)
    {
        _productRepository = productRepository;
    }
    public void Delete(int id)
    {
        _productRepository.Delete(id);
    }
}

ProductService在構造函數中注入了它的依賴IProductRepository,然後使用了它的Delete方法。

良好實踐

  • 在服務構造函數中顯式定義所需的依賴項。這樣,服務缺失依賴關係就不能構造。
  • 將注入的依賴項賦值給一個只讀(read only)字段/屬性(防止在方法調用過程中無意的賦值了其他值)。

屬性注入

ASP.NET Core的標配的依賴注入容器並不支持屬性注入(property injection)。但是你可以使用其他的依賴注入容器支持屬性注入。。

using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
namespace MyApp
{
    public class ProductService
    {
        public ILogger<ProductService> Logger { get; set; }
        private readonly IProductRepository _productRepository;
        public ProductService(IProductRepository productRepository)
        {
            _productRepository = productRepository;
            Logger = NullLogger<ProductService>.Instance;
        }
        public void Delete(int id)
        {
            _productRepository.Delete(id);
            Logger.LogInformation(
                $"Deleted a product with id = {id}");
        }
    }
}

ProductService聲明了一個開放了Setter的日誌(Logger)屬性。依賴注入容器能賦值一個可用的值給這個日誌屬性(前提是已經在依賴注入容器內註冊過)。

良好實踐

  • 僅對可選依賴項使用屬性注入。這意味着你的服務可以在不提供這些依賴項的情況下正常工作。
  • 盡量使用空對象模式(如實例所示)。否則,在使用依賴項時始終做NULL檢查。

服務定位(Service Locator)

服務定位(Service Locator)模式是另一種獲取依賴項的方式。

public class ProductService
{
    private readonly IProductRepository _productRepository;
    private readonly ILogger<ProductService> _logger;
    public ProductService(IServiceProvider serviceProvider)
    {
        _productRepository = serviceProvider
          .GetRequiredService<IProductRepository>();
        _logger = serviceProvider
          .GetService<ILogger<ProductService>>() ??
            NullLogger<ProductService>.Instance;
    }
    public void Delete(int id)
    {
        _productRepository.Delete(id);
        _logger.LogInformation($"Deleted a product with id = {id}");
    }
}

ProductService注入了IServiceProvider,並使用它解析了ProdProductServiService的依賴關係。如果在使用之前注入容器的話,使用GetRequiredService方法會拋異常。另一邊,使用GetService則返回NULL。

當你在構造函數中解析(resolve)依賴服務時,他們隨着服務本身的釋放而釋放,所以你大可不必關係構造函數注入的依賴項的釋放(就像構造函數和屬性注入一樣)。

良好實踐

  • 盡可能不要使用服務定位(Service Locator)模式。因為這樣使得服務的依賴關係隱式化(譯註,++服務的依賴關係不是显示的注入,導致代碼層面的服務依賴關係不明確,從構造函數看,只有一個IServiceProvider的依賴++)。這意味着在創建服務實例時不能显示的看到服務的依賴項。而這對於單元測試尤其重要,因為你可能想要模擬服務的一些依賴項。
  • 盡可能使用構造函數解析服務依賴項。在服務方法中解析依賴項會讓應用程序變得更複雜,更容易出錯。接下來,我將介紹這些問題和解決方案。

服務生命周期

在ASP.NET Core依賴注入概念裏面,有三種服務的生命周期:

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

透過資料庫的網站架設建置,建立公司的形象或購物系統,並提供最人性化的使用介面,讓使用者能即時接收到相關的資訊

  1. Transient服務,在請求或注入服務的時候,每次都創建新實例。
  2. Scoped服務,在作用域內創建服務。在Web應用程序,每一個web請求都會創建一個新的獨立的服務作用域範圍。這意味着每個web請求通常都創建有作用域的服務
  3. Singleton服務,每個依賴注入容器會創建一次單例服務。在每個應用程序只會創建一次單例服務,在應用的整個生命周期都可用。

依賴注入容器會跟蹤所有解析出來的服務,在它們的生命周期結束後會釋放掉這些服務。

  • 如果服務有依賴項,這些依賴項也會自動釋放。
  • 如果服務已經實現了IDisposable接口,在服務被釋放的時候也會自動調用Dispose方法。

良好實踐

  • 盡可能的將你的服務註冊成Transient服務。設計一個Transient服務是相對簡單的,因為你通常不需要關心多線程和內存泄漏的問題,而且這些服務生命周期相對短。
  • 小心使用Scoped服務,因為當你創建子作用域或者在非web應用程序使用Scoped服務,會出現一些棘手的問題。
  • 小心使用Singleton服務,因為你需要正確處理多線程問題和潛在的內存泄露問題。
  • 不要在Singleton服務中依賴一個Transient服務或Scoped服務。因為這時Transient服務會變成Singleton服務,如果Transient服務不支持單例場景,當Singleton服務注入Transient服務時會產生異常問題。ASP.NET Core默認依賴注入容器在這種場景下會拋異常。

在方法內解析服務

在某些場景下,你可能需要在服務的方法中解析另外一個服務。這種情況下請確保在使用服務后及時釋放服務。這才是創建範圍作用域服務的最佳方式。

public class PriceCalculator
{
    private readonly IServiceProvider _serviceProvider;
    public PriceCalculator(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }
    public float Calculate(Product product, int count,
      Type taxStrategyServiceType)
    {
        using (var scope = _serviceProvider.CreateScope())
        {
            var taxStrategy = (ITaxStrategy)scope.ServiceProvider
              .GetRequiredService(taxStrategyServiceType);
            var price = product.Price * count;
            return price + taxStrategy.CalculateTax(price);
        }
    }
}

PriceCalculator在構造函數里注入了IServiceProvider並賦值給以只讀字段。然後PriceCalculatorCalculate的方法內創建了一個子範圍作用域。使用scope.ServiceProvider來解析服務依賴,而不是用_serviceProvider實例。這樣,在子範圍作用域內被解析的所有服務會在using的聲明結束后自動釋放。

良好實踐

  • 如果在方法內解析服務,請始終創建子範圍作用域,以確保已解析的服務被正確釋放。
  • 如果一個方法使用IServiceProvider作為參數,那麼可以直接使用它解析服務依賴,而不需要關心依賴服務是否釋放。創建/管理服務範圍作用域是調用方法代碼的職責。遵循這一原則可以使代碼更簡潔。
  • 不要保存對已解析服務的引用!否則,在使用對象引用時訪問已釋放的服務可能會導致內存泄漏(除非已解析的服務是單例的)。

單例服務 Singleton Services

單例服務通常為了保持應用程序狀態而設計。緩存是一個應用程序狀態的最好示例。

public class FileService
{
    private readonly ConcurrentDictionary<string, byte[]> _cache;
    public FileService()
    {
        _cache = new ConcurrentDictionary<string, byte[]>();
    }
    public byte[] GetFileContent(string filePath)
    {
        return _cache.GetOrAdd(filePath, _ =>
        {
            return File.ReadAllBytes(filePath);
        });
    }
}

FileService只是簡單的緩存了文件內容來減少磁盤讀取。像這樣的服務應該設計成單例服務。否則緩存將不能正常工作。

良好實踐

  • 如果一個服務持有某種狀態,應該以線程安全的方式訪問這個狀態。因為所有的請求將併發的訪問同一個實例,使用ConcurrentDictionary而不是Dictionary來確保線程安全。
  • 不要在單例服務內使用Scoped/Transient服務,因為Transient服務可能不是線程安全的設計。如果確實需要使用,請注意多線程(例如使用Lock)。
  • 引起內存泄漏的通常是由單例服務引起的。在應用程序結束之前,單例服務不會被釋放。它們實例化類(或注入實例)也不會提前被釋放,它們也會一直留在內存中,直到應用程序結束。確保在適當的時候釋放服務,請參閱在方法內解析服務。
  • 如果使用緩存數據(例如上述代碼示例中文件內容的緩存),應該創建一種機制當原始數據發生變更的時候去更新或淘汰已緩存的數據(示例中當磁盤的文件變更時應該更新緩存)。

範圍作用域服務Scoped Services

範圍作用域服務似乎是一個為每個web請求存儲數據的候選方式。因為ASP.NET Core為每一個Web請求都會創建一個服務範圍作用域。因此一個服務註冊成Scoped服務,在Web請求過程可以共享這個服務。

public class RequestItemsService
{
    private readonly Dictionary<string, object> _items;
    public RequestItemsService()
    {
        _items = new Dictionary<string, object>();
    }
    public void Set(string name, object value)
    {
        _items[name] = value;
    }
    public object Get(string name)
    {
        return _items[name];
    }
}

如果RequestItemsService註冊成範圍作用域的服務,並將RequestItemsService注入到兩個不同的服務中,這兩個服務可以訪問到另外一個服務添加的數據,因為這兩個服務在一個Web請求中是共享RequestItemsService實例的。

但是,現實情況可能不完全是這樣的。如果你創建了子範圍作用域並在子作用域範圍內解析RequestItemsService,你會得到一個全新的RequestItemsService,而這並非我們所期望的那樣。所有Scoped服務並非一個Web請求時共享一個服務實例。

你可能會認為你不會犯這樣明顯的錯誤(在子作用域內解析服務依賴)。但是這不是錯誤(一個常規用法而已)並且情況並沒有那麼簡單。假設在你的服務中有龐大的服務依賴關係,你可能不知道是否有人會這麼做(在子作用域內解析服務依賴)。

良好實踐

  • Scoped服務可以視作一種優化手段(在一個web請求中不想注入太多服務)。這樣在同一個Web請求中所有的服務使用同一個實例。
  • Scoped服務不需要設計線程安全。因為Scoped服務通常在一個線程或Web請求中使用,但是,這種場景下,不應該在不同線程之間共享Scoped服務。
  • 如果要設計一個作用域服務來在web請求中的其他服務之間共享數據,小心上述問題。你可以使用HttpContext(通過IHttpContextAccessor來訪問它)來存儲每一個Web請求需要存儲的數據,這是安全的處理方式。HttpContext生命周期並不是Scoped。實際上並沒有注入到依賴注入的容器內(這是為什麼使用IHttpContextAccessor訪問它而不是注入到容器內的原因)。++在一個Web請求中,HttpContextAccessor使用AsyncLocal來共享相同的HttpContext++。

結論

依賴注入在最初使用的時候好像是挺簡單的。如果不遵循嚴格的使用原則,依然會有潛在的多線程和內存泄漏問題。我在開發ASP.NET Boilerplate框架過程中,基於我的實踐體會分享了這些實踐原則。

總結

在使用ASP.NET Core 依賴注入時需要注意幾項:

  1. 在構造函數中显示的注入依賴關係。
  • 在依賴關係眾多時,職責單一原則,考慮拆分職責
  • 更有利於單元測試。
  1. 屬性注入,適用於可選依賴項,不影響服務正常運行,考慮空實現模式。
  • 通常我們在設計框架/基類時,可以適當引入屬性注入,這樣可以使得繼承類代碼更簡潔。
  • 必要時,屬性提供懶加載方式,提高服務啟動速度。
  1. 選擇合適的服務生命周期。順序依次Transient > Singleton > Scoped,不確定時使用Transient ,明確使用場景的時候考慮Singleton和Scoped。同需要需要考慮服務的構建成本。
  • Transient服務的生命周期短,可以有效的規避多線程和內存泄漏問題,同時也引起應用程序的內存使用量上升,帶了部分性能問題。
  • 在Singleton服務中,禁止依賴Transient/Scoped服務,一方面,Transient/Scoped服務也會變成單例服務。另一方面,Transient/Scoped服務沒有考慮多線程問題。
  • 在使用Singleton服務時,多注意潛在的線程安全和內存泄漏問題。
  • 在非Web應用場景和子作用服務場景,Scoped服務,並不能正確處理一個線程內共享實例。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

※想知道最厲害的網頁設計公司嚨底家"!

RWD(響應式網頁設計)是透過瀏覽器的解析度來判斷要給使用者看到的樣貌

MVVM 小雛形 knockout_網頁設計

※推薦評價好的iphone維修中心

擁有專業的維修技術團隊,同時聘請資深iphone手機維修專家,現場說明手機問題,快速修理,沒修好不收錢

前言

knockout學過的當工具腳本用,就像jquery一樣使用,學習成本15分鐘,沒學過的可學可不學。

knockout 是上古神器,話說在遠古開天闢地,前端到處是飛禽走獸,一片混亂。

這時候人類開始人類開始誕生了,因為惡劣的環境備受煎熬,在生存角逐中,人們通過智慧寫了各種js腳本,進入了石器時代,但是人類只有兩條腿和兩隻手切換工具的速度限制了人類的發展。

這時候John Resig 整理了各種工具,注入熔爐,一件草莽神器誕生了,jquery。人們開始利用jquery,在html上開墾大地,馬力十足,這是一個被jquery奴隸的時代,史稱奴隸時代。

後來人們就發現了一個問題,在html中這塊廣袤的大地上,js不同腳本是衝突的,部落與聯盟之間的戰爭一觸即發。隨着因為衝突,調試繁瑣,js的部落與部落之間在戰爭中,被require.js等模塊管理所統治,不同的部落得到分封,進入了封建時代。

但是幾乎在同一時間,mvvm思想開始萌芽,他們提出有一個假設,如果可以修改數據就能對html產生驅動變化,那麼是不是可以解放生產力?這個實現不斷得到驗證與實際,工業革命誕生了。這是一次沒有流血的革命,因為以前的技術誕生往往充滿着爭議,這個是真的解放人類的雙手,蒸汽時代開始來臨。

knockout 就是蒸汽時代的產物,它是mvvm模式在js實現的前驅,是現在電力時代3大框架的基石。好了,故事模式結束。

正文

首先說明一下什麼是mvvm,它是一種模式,還有一些其他模式比如說mvc,mvp等等。

他們其實是一個重的問題,偏向於哪一塊。

mvc的c很重,那麼它的重點功能在於控制器,可以說是c連接了視圖和model。

mvp的p很重,他們的視圖和model完全分離,中間p的其實相當於c,操作層,但是和mvc不同的是隔離了model層和視圖。比如說window form開發。

mvvm,偏向view,和mvp完全相反,他的視圖和數據層相當緊密,兩者不可分割。knockout就是一個例子,包括現在比較成熟的框架vue。

knockout它的作用就是一個重要功能在於監聽,監聽數據的變化,然後從新部分渲染。

台北網頁設計公司這麼多該如何選擇?

網動是一群專業、熱情、向前行的工作團隊,我們擁有靈活的組織與溝通的能力,能傾聽客戶聲音,激發創意的火花,呈現完美的作品

那麼開始實踐一下吧:

public class Product
{
	public int Id { get; set; }
	public string Name { get; set; }
	public string Category { get; set; }
	public decimal Price { get; set; }
}

一個model類,裏面存放這id,name,category,price,對應數據庫。

然後編輯控制器:

public class HomeController : Controller
{
	static List<Product> productsList = new List<Product>();
	public IActionResult Index()
	{
		return View();
	}

	[HttpGet]
	public IActionResult products() {

		productsList = new List<Product>() {
			new Product{Id=1,Category="哈哈",Name="張三",Price=10 },
			new Product{Id=2,Category="哈哈",Name="李小二",Price=10 }
		};
		return Json(productsList);
	}
}

這裏面只是提供一些數據:

前台:
html部分

<table id="products">
    <thead>
        <tr><th>ID</th><th>Name</th><th>Category</th><th>Price</th></tr>
    </thead>
    <tbody data-bind="foreach: products">
        <tr>
            <td data-bind="text: id"></td>
            <td data-bind="text: name"></td>
            <td data-bind="text: category"></td>
            <td data-bind="text: price"></td>
        </tr>
    </tbody>
</table>

js 部分:

function ViewModel() {
	var self = this;
	//創建綁定
	self.products = ko.observableArray(); // 創建數組綁定。
	self.product = ko.observable();//單個產品
	self.status = ko.observable();//單個錯誤提示
	// 得到全部的產品
	self.getAll = function () {
		self.products.removeAll();
		$.getJSON("/Home/products", function (products) {
			self.products(products);
		});
	}
	//更新
	self.update = function () {
		self.status("");
		var id = $('#productId').val();
		var product = {
			Name: $('#name').val(),
			Price: $('#price').val(),
			Category: $('#category').val()
		};
		$.ajax({
			url: '/Home/products/' + id,
			cache: false,
			type: 'PUT',
			contentType: 'application/json; charset=utf-8',
			data: JSON.stringify(product),
			success: self.getAll
		})
			.fail(
				function (xhr, textStatus, err) {
					self.status(err);
				});
	}
	//新增
	self.create = function () {
		self.status("");
		var product = {
			Name: $('#name2').val(),
			Price: $('#price2').val(),
			Category: $('#category2').val()
		};
		$.ajax({
			url: '/Home/products',
			cache: false,
			type: 'POST',
			contentType: 'application/json; charset=utf-8',
			data: JSON.stringify(product),
			statusCode: {
				201 /*Created*/: function (data) {
					//得到返回結果然後返回添加添加
					self.products.push(data);
				}
			}
		})
			.fail(
				function (xhr, textStatus, err) {
					self.status(err);
				});
	}
	//初始化
	self.getAll();
}

$(function () {
	var viewModel = new ViewModel();
	ko.applyBindings(viewModel);
})

在裏面配置增刪改查即可,裏面配置的方法可以在html這樣寫自動綁定:

<button data-bind="click: $root.create">添加</button>

就會觸發裏面的create 方法。

這裏只是作為一個綁定用例,其實在真正的編輯中是全部綁定的,不會出現這種$(‘#productName’).val();

而是使用:

<input data-bind="value: $root.Name" type="text" title="Name" />

在此就不多複述。

效果:

總結

感覺 knockout 也不是完全過時,小型的開發速率還是非常快的,綁定就完事,源碼也少,vs 編輯器也支持提示。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

網頁設計最專業,超強功能平台可客製化

窩窩以「數位行銷」「品牌經營」「網站與應用程式」「印刷品設計」等四大主軸,為每一位客戶客製建立行銷脈絡及洞燭市場先機。

怎樣實現登錄?| Cookie or JWT_貨運

※智慧手機時代的來臨,RWD網頁設計為架站首選

網動結合了許多網際網路業界的菁英共同研發簡單易操作的架站工具,及時性的更新,為客戶創造出更多的網路商機。

先問小夥伴們一個問題,登錄難嗎?“登錄有什麼難得?輸入用戶名和密碼,後台檢索出來,校驗一下不就行了。”凡是這樣回答的小夥伴,你明顯就是產品思維,登錄看似簡單,用戶名和密碼,後台校驗一下,完事了。但是,登錄這個過程涵蓋的知識點是非常多的,絕不是檢索數據,校驗一下這麼簡單的事。

那麼登錄都要哪些實現方式呢?i最傳統的就要是Cookie-Session這種方式了,最早的登錄方式都是這樣實現的。但是隨着手機端、H5端的興起,前後端分離的模式越來越流行,基於Cookie-Session這種登錄方式不是很方便,漸漸的JTW開始流行,現在大部分項目的登錄方式都是基於JWT的了。那麼Cookie和JWT都是怎樣實現登錄的呢?這兩種方式有什麼區別呢?我們在做登錄的x時候該怎麼選擇呢?咱們先看看這兩種方式的原理。

Cookie方式

因為Http協議是無狀態的,我們後台的服務(如Tomcat)在接收到前端發送過來的Http請求時,是區分不出哪個請求是誰發出的,這和我們的登錄功能是相違背的,登錄的功能就是要區分每一個請求是由哪個用戶發出的,這就變成了有狀態,那怎麼辦呢?Cookie應運而生,Cookie是存儲在瀏覽器端的,在Cookie中存儲的內容是鍵值對,也就是name-value。瀏覽器在向後台發送請求的時候,會把Cookie放在請求頭中,傳送給後台的服務,後台的服務會從請求頭中取到Cookie,再從Cookie中取出鍵值對中jsessionid對應的值。這個jsessionid的值就是你這次會話的id,對應着服務端的一個session。

好了,到這裏session這個概念出來了,session是什麼呢?session是存儲在服務端的,每一個會話對應服務中的一個session。咱們可以把session理解為一個Map,它的key存儲的session的id,value存儲的東西就隨便了,我們在寫程序時想存啥就存啥。它的key存儲的值就是Cookie中存儲的jsessionid的值,這樣,瀏覽器發送請求到後台服務,後台才能根據Cookie中的jsessionid取到對應的session,再從session中取到之前存儲的狀態,如存儲在session中的登錄狀態、用戶id等。Cookie-Session機制是通用的,所有的瀏覽器都支持Cookie,就連最低端的IE都支持,你說他普遍不普遍。Session是後端容器必須支持的,如Tomcat,還有像其他的如Resin、jetty等。這些對開發人員都是透明的,無需過多關注。

Cookie-Session的由來給大家說完了,我們看看基於Cookie這種方式的登錄流程,

  • 用戶在瀏覽器輸入用戶名、密碼,點擊登錄,發送請求到後台服務;
  • 後台服務校驗用戶名、密碼,將登錄狀態狀態和用戶id存儲在session中;
  • 將session的id存儲在Cookie中,通過響應頭返回到瀏覽器;
  • 當用戶點擊其他功能時,向後台發送的請求中會自動帶上Cookie;
  • 後台通過Cookie中的jsessionid找到對應的session,開發人員可從session中取出當前會話的登錄狀態和用戶id。

基於Cookie-Session機制的登錄實現方式的整體流程就是這個樣子。看上去很完美,但還是存在不少問題的,我們來看看這些問題。

分佈式會話

上面的示例,我們的後台服務只有一個,一個服務往往很難支撐服務,為了保障可靠性,最少都是部署兩個後台服務。但是當部署多個後台服務時,我們的session就會出現問題,看看下面的圖,

  • 假如用戶登錄的請求,分配到了後台服務1,後台服務1的session存了用戶的登錄狀態和用戶id。
  • 用戶在點擊其他功能時,請求分配到了後台服務2,可是後台服務2的session並沒有存儲登錄狀態和用戶id。

我們怎麼解決這個問題呢?其實也很簡單,第一,session集中管理,比如使用Redis;第二,所有的後台服務在獲取session時,統一從Redis中獲取。如下所示,

我們可以使用中間件Spring-Session和Redis就可以解決這個問題。

CORS

使用Cookie實現登錄的另外一個問題就是跨域,現在往往都採用前後端分離的方式進行開發,在開發的過程中,前端和後端通常不在一個域下,由於瀏覽器的同源策略,Cookie不能傳入到後端。至於同源策略,不明白的小夥伴可以問一下度娘,這裏不過多介紹了。要解決這個問題,在前端、後端都要進行設置,在我的另一篇文章《前後端分離|關於登錄狀態那些事》中有詳細的介紹。總體歸納為:

  • 後端設置CORS允許跨域的域名,並且withCredentials設置為true;
  • 前端在向後端發送請求時,也需要設置withCredentials = true;

這樣,我們的Cookie就可以實現跨域了。不進行這些設置,Cookie跨域是不可能的,同源策略保證了我們Cookie的安全。

※回頭車貨運收費標準

宇安交通關係企業,自成立迄今,即秉持著「以誠待人」、「以實處事」的企業信念

CSRF

CSRF,這個CORS是不一樣的,長的比較像,也比較容易混。CSRF往往和系統的安全扯上聯繫,也是等保測試中比較重要的測試內容,它也是和Cookie有關的,大體的流程是這樣的,

  • 用戶登錄了A網站,並沒有退出;
  • 此時,用戶又訪問了B網站;
  • 在B網站有個隱藏的請求,請求了A網站的一個重要的接口,比如:轉賬、支付等。
  • 在請求A網站的同時,帶上了A網站的Cookie,所以一些危險的操作就成功了。

關於CSRF的攻防,在我前面的文章《CSRF的原理與防禦 | 你想不想來一次CSRF攻擊?》中有詳細的介紹。總之,使用Cookie實現登錄是需要重點防範一下CSRF攻擊的。

JWT方式

近年來,由於手機端的興起,前後端分離開發方式的流行,JWT這種登錄的實現方式悄然興起,那麼什麼是JWT呢?JWT是英文JSON Web Token的縮寫,它由3部分組成,

  • header,一般情況下存儲兩個信息,1類型,一般都是JWT;2加密算法,比如:HMAC、RSA等;
  • payload,這裏就存儲登錄的相關信息了,比如:登錄狀態、用戶id、過期時間等。
  • signature,簽名,這個是將header、payload和密鑰的信息做一次加密,後台在接收到JWT的時候,一定要驗簽,謹防JWT的偽造。

下面咱們看看JWT的登錄實現,

我們看到整體的流程和Cookie的實現方式是一樣的,只不過是沒有用到Cookie、Session。那麼它與Cookie-Session的區別是什麼呢?

  • 登錄狀態、用戶id並沒有存儲到session,而是存在JWT的payload里,返回給了前端。
  • 在前端JWT不會自動存儲到Cookie中,前端開發人員要處理JWT的存儲問題,比如LocalStorage
  • 再次發起請求,JWT不會自動放到請求頭中,需前端同學手動設置
  • 後端從請求頭中取出JWT,驗簽通過後,拿到登錄狀態、用戶id,不是從session中取

相比Cookie的方式,JWT的方式需要更多的開發工作量。那麼其他的問題存在嗎?我們一個一個看。

分佈式會話

我們後台部署多個服務,會有分佈式會話的問題嗎?

無論請求被分配到哪一個後台服務中,登錄狀態和用戶id都是從JWT中取出來的,不會出現分佈式會話的問題。我們在後台部署集群的時候,根本不用care這個問題。

CORS

Cookie的跨域受到同源策略的保護,不經過特殊的設置,是不能夠跨域的。那麼JWT呢?JWT是前端同學手動在請求頭中設置的,如果向其他的域發送請求要注意,稍不注意,在請求的時候,調用了封裝的公共方法,就會把JWT發送給其他域的後台,前端的小夥伴要打起精神啊。

CSRF

Cookie的方式,B訪問A網站,會把A的Cookie帶上,從而造成了安全隱患。那麼JWT呢?JWT在前端存儲在A網站的域下,B在訪問A網站時,是拿不到A網站的JWT的,那麼也不可能在請求頭中設置JWT,A網站的後台拿不到JWT,也不會做其他操作。所以,JWT可以很好的防止CSRF攻擊。

總結

通過前面我們對Cookie和JWT的分析,可以總結成如下的表格,

Cookie-Session JWT
工作量 瀏覽器和容器天然支持 需要額外開發,有一定工作量
分佈式會話 需要藉助中間件 無需關心,登錄信息從JWT解出
CORS 不支持跨域、需特殊設置 開發人員設置請求頭,可以跨域
CSRF 需特殊防範 無需防範,第三方拿不到JWT

好了,Cookie和JWT的特點都總結出來了,大家在實現登錄的時候,就各取所需吧。結合自己的項目,選擇適合自己項目的實現方式吧。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

※評比南投搬家公司費用收費行情懶人包大公開

搬家價格與搬家費用透明合理,不亂收費。本公司提供下列三種搬家計費方案,由資深專業組長到府估價,替客戶量身規劃選擇最經濟節省的計費方式

上位機C#通過OPCUA和西門子PLC通信_網頁設計公司

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

透過選單樣式的調整、圖片的縮放比例、文字的放大及段落的排版對應來給使用者最佳的瀏覽體驗,所以不用擔心有手機版網站兩個後台的問題,而視覺效果也是透過我們前端設計師優秀的空間比例設計,不會因為畫面變大變小而影響到整體視覺的美感。

寫在前面:

很多人在學習OPCUA的時候,有個非常苦惱的問題,就是沒有OPCUA服務器的環境,這時候,有些人可能會想到通過類似於KepServer這樣的軟件來實現。那麼,有沒有一種方式,實現快速搭建OPCUA環境呢?答案是有的,今天繼續給大家分享S7-PLCSIM Advanced,S7-PLCSIM Advanced是SIEMENS推出的一款高功能仿真器,它的顯著特點是除了可以仿真一般的PLC邏輯控製程序外還可以仿真通信,功能是非常強大的,今天主要講述如何基於S7-PLCSIM Advanced搭建OPCUA通信仿真環境。

01.PLCSIM-Advanced安裝

對於該軟件的獲取,大家可以去西門子相關網站下載,也可以直接關注喜科堂上位機官方公眾號-dotNet工控上位機,然後發送關鍵詞PLCSIM-Advanced,即可下載使用。

圖表 1 PLCSIM Advanced軟件

 

PLCSIM-Advanced的安裝也比較簡單,基本上是一路NEXT即可,但是主要的是PLCSIM-Advanced會依賴WinPcap軟件,所以大家可以提前安裝好WinPcap,即使不提前安裝,安裝過程中也會提示你安裝的。

安裝完成后,電腦中會額外多出一個虛擬網卡,名稱為Siemens PLCSIM Virtual Ethernet Adapter,如下圖:

圖表 2網卡列表

 

 02.PLCSIM-Advanced使用

安裝完成后,桌面上會多出一個圖標,即S7-PLCSIM Advanced V3.0,如下圖所示:

  圖表 3 PLCSIM Advanced圖標

 

該軟件的使用可以按照下方的步驟執行和確定,一定要記住以下需要注意的地方,否則一個很小的問題,可能會讓你浪費半天的時間,這樣就得不償失了。

(1)軟件要以管理員權限運行:可以直接每次右擊,以管理員權限運行,如果想一勞永逸,可以點擊圖標,右擊屬性,在兼容性中,將以管理員身份運行此程序的選項勾選。

 圖表 4設置管理員權限運行

 

(2)將本地網卡及虛擬網卡的IP地址獲取設置為自動獲取。

(3)設置PG/PC接口:

通過控制面板,打開設置PG/PC接口界面,按照下圖所示設置應用程序訪問點:

 圖表 5設置PG/PC接口

 

(4)打開PLCSIM-Advanced,依次按照步驟進行設置:

圖表 6設置PLCSIM-Advanced

 

(5)使用博圖創建一個簡單項目,一定要選擇1500PLC,PLCSIM-Advanced只支持1500PLC,但是對學習通信來說,沒什麼影響。

(6)勾選允許Put/Get訪問:

圖表 7設置允許PUT GET訪問

 

(7)點擊項目,右擊屬性,將保護中的塊編譯時支持仿真選項勾選:

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

節能減碳愛地球是景泰電動車的理念,是創立景泰電動車行的初衷,滿意態度更是服務客戶的最高品質,我們的成長來自於你的推薦。

圖表 8設置塊編譯支持仿真

 

(8)下載PLC程序時,將PG/PC接口設置成Siemens PLCSIM Virtual Ethernet Adapter。

圖表 9選擇PG/PC接口

 

(9)選擇显示所有兼容的設備,點擊開始搜索,搜索到之後,點擊下載即可。

圖表 10搜索設備下載

 

 03.OPCUA配置

上述過程和搭建S7通信環境完全一樣,OPCUA其實就是在S7的基礎上,需要先增加一些DB存儲區,然後在DB塊中添加一些變量,這裏不需要去除優化訪問,如下圖所示:

圖表 11新建DB塊

 

       增加完成之後,雙擊CPU,在OPCUA選項中,將激活OPCUA服務器勾選,如下圖示所示:

圖表 12激活OPCUA服務器

 

激活OPCUA服務器之後,選擇運行系統許可證選項,選擇一個OPCUA許可證:

圖表 13選擇OPCUA許可證

 

以上操作完成之後,將PLC程序重新下載,下載步驟如上述一致。

 04.UAExpert通信測試

上述操作完成之後即完成了整個環境搭建的過程,下面先使用官方的UAExpert進行測試。

圖表 14 UAExpert通信測試

 

 05.通信測試平台測試

接着,我們使用新閣的通信測試平台軟件進行通信測試,設置服務器節點為opc.tcp://192.168.1.20:4840(根據實際情況修改),點擊連接,連接成功后,點擊變量管理,即可看到OPCUA服務器中的所有節點,通過選擇DataBlockGlobal下的KYJDB,找到了我們剛剛配置的變量,選擇想要讀取的變量,然後讀取即可。

圖表 15變量選擇

 

圖表 16新閣通信測試平台

 

寫在最後:

本文旨在結合SIEMENS推出的一款高功能仿真器PLCSIM-Advanced軟件實現快速搭建OPCUA通信仿真環境,對於從事上位機開發,想要學習OPCUA通信,手頭又沒有硬件的學員來說,無疑是一大利好,基於OPCUA與西門子PLC通信,可以通過變量名稱訪問,而不需要去除DB的優化訪問,這一點非常方便。

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

搬家費用:依消費者運送距離、搬運樓層、有無電梯、步行距離、特殊地形、超重物品等計價因素後,評估每車次單

OAuth + Security – 3 – JWT令牌_租車

※超省錢租車方案

商務出差、學生出遊、旅遊渡假、臨時用車!GO 神州租賃有限公司!合法經營、合法連鎖、合法租賃小客車!

PS:此文章為系列文章,建議從第一篇開始閱讀。

為什麼使用JWT令牌

在上面的資源服務器中,通過配置,我們了解到,當我們程序是前後端分離時,在拿着token去獲取資源時,程序會先去調用遠程認證服務器的端點去驗證解析token,這樣毫無疑問,當訪問量過大的時候,對認證服務器的壓力可想而知,所以為了解決上面的問題,我們採用JWT令牌格式,可以優化上面的問題。

令牌採用JWT格式即可解決上邊的問題,用戶認證通過會得到一個JWT令牌,JWT令牌中已經包括了用戶相關的信息,客戶端只需要攜帶JWT訪問資源服務,資源服務根據事先約定的算法自行完成令牌校驗,無需每次都請求認證服務完成授權。

改造認證服務器

  1. 修改TokenConfig類,如下:
@Configuration
public class TokenConfigure {

    private static final String SIGNING_KEY = "dimples";

    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(accessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        //對稱秘鑰,資源服務器使用該秘鑰來驗證
        converter.setSigningKey(SIGNING_KEY);
        return converter;
    }


}
  1. 修改認證服務器的配置
private TokenStore tokenStore;

private ClientDetailsService clientDetailsService;

private JwtAccessTokenConverter jwtAccessTokenConverter;
//通過構造方法注入
...


/**
 * 令牌管理服務
 *
 * @return TokenServices
 */
@Bean
public AuthorizationServerTokenServices tokenServices() {
    DefaultTokenServices services = new DefaultTokenServices();
    // 客戶端詳情服務
    services.setClientDetailsService(clientDetailsService);
    // 支持令牌刷新
    services.setSupportRefreshToken(true);
    // 令牌存儲策略
    services.setTokenStore(tokenStore);
    
    // 配置令牌增強 JWT
    TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
    tokenEnhancerChain.setTokenEnhancers(Collections.singletonList(jwtAccessTokenConverter));
    services.setTokenEnhancer(tokenEnhancerChain);
    
    // 令牌默認有效期2小時(如果客戶端設置了會覆蓋該值)
    services.setAccessTokenValiditySeconds(7200);
    // 刷新令牌默認有效期2天
    services.setRefreshTokenValiditySeconds(259200);
    return services;
}
  1. 最後別忘了在pom中添加JWT的依賴,否則項目將會報錯
<dependency>
	<groupId>org.springframework.security</groupId>
	<artifactId>spring-security-jwt</artifactId>
	<version>1.1.0.RELEASE</version>
</dependency>

測試結果如下:

可以使用OAuth的/oauth/check_token端點來解析驗證一下該token

但是我們需要明白一點的是,這種令牌還是存儲在內存中的,後期我們如何將其存儲到redis中是我們研究的方向。

改造資源服務器

當我們使用了JWT令牌以後,由於在JWT令牌中我們存儲了相應的用戶信息和權限,這時我們可以直接在資源服務器中直接去解析對應令牌,就不用每次都去請求認證服務器端點,加大認證服務器的壓力,下面我們開始改造資源服務器:

  1. 將上面認證服務器中寫的TokenConfigure類拷貝一份到資源服務器
  2. 在資源服務器中屏蔽調之前的資源服務器令牌解析服務( tokenService() )
  3. 注入TokenConfigure類,然後配置到ResourceServerSecurityConfigurer里

完整的配置如下:

@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class DimplesResourceServerConfigurerAdapter extends ResourceServerConfigurerAdapter {

    public static final String RESOURCE_ID = "dimples";

    private TokenStore tokenStore;
    

    @Autowired
    public DimplesResourceServerConfigurerAdapter(TokenStore tokenStore) {
        this.tokenStore = tokenStore;
    }

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) {
        resources.resourceId(RESOURCE_ID)
                .tokenServices(tokenService())
                .stateless(true);
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                // 配置客戶端權限scope
                .antMatchers("/**").access("#oauth2.hasScope('all')")
                .and().csrf().disable()
                // 關閉session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }

}

然後重啟服務,重新獲取令牌,然後訪問之前的測試接口:

待解決的問題

在此處的JWT的配置中,我們獲取的令牌信息還是存在內存中的,這樣不利於我們程序的擴展。那麼我們如何將生產的令牌去存儲到數據庫中或者存儲到redis中呢?請關注後續的文章。

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

日本、大陸,發現這些先進的國家已經早就讓電動車優先上路,而且先進國家空氣品質相當好,電動車節能減碳可以減少空污

使用非對稱加密

在上面的jwt加密中,我們的JWT簽名是寫死的字符串,可能我們的項目為了安全考慮,需要使用非對稱的加密,我們該怎麼配置呢?

首先獲取加密文件的私鑰和公鑰,需要先安裝安裝OpenSSL工具,參考鏈接【https://blog.csdn.net/qq_39081974/article/details/81059022】

  1. 生成JKS Java KeyStore文件

命令行執行:keytool -genkeypair -alias dimples -keyalg RSA -keypass dimples -keystore dimples.jks -storepass dimples

將生成一個名為medical.jks的文件,其中包含我們的密鑰 – 公鑰和私鑰。 還要確保keypass和storepass是一樣的

  1. 導出公鑰

keytool -list -rfc –keystore dimples.jks | openssl x509 -inform pem -pubkey

或 keytool -importkeystore -srckeystore dimples.jks -destkeystore dimples.jks -deststoretype pkcs12

將其複製到我們的資源服務器 src/main/resources/public.txt 中

  1. 配置認證服務器(TokenConfigure)
/**
 * 配置jwt生成token的轉換
 * 使用RSA Sign Key 進行加密
 *
 * @return JwtAccessTokenConverter
 */
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
    KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("medical.jks"), "medical".toCharArray());
    JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
    converter.setKeyPair(keyStoreKeyFactory.getKeyPair("medical"));
    return converter;
}
  1. 配置資源服務器(TokenConfigure)
/**
 * 配置jwt生成token的轉換
 *
 * @return JwtAccessTokenConverter
 */
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
    JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
    Resource resource = new ClassPathResource("public.txt");
    String publicKey;
    try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(resource.getInputStream()))) {
        publicKey = bufferedReader.lines().collect(Collectors.joining("\n"));
    } catch (final IOException e) {
        throw new RuntimeException(e);
    }
    converter.setSigningKey(publicKey);
    return converter;
}

擴展JWT的存儲信息

當我們使用如上的配置獲取Token后,將access_token中的內容複製到https://jwt.io/網站解析下:

可以看到在jwt中只是保存了我們的user_name,那麼我們怎麼去擴展呢?讓其可以保存我們需要的用戶詳細信息,這裡有兩種方案:

  • 實現TokenEnhancer(Token增強器)
public class JWTokenEnhancer implements TokenEnhancer {
    @Override
    public OAuth2AccessToken enhance(OAuth2AccessToken oAuth2AccessToken, OAuth2Authentication oAuth2Authentication) {
        Map<String, Object> info = new HashMap<>();
        info.put("other", "hello world");
        ((DefaultOAuth2AccessToken) oAuth2AccessToken).setAdditionalInformation(info);
        return oAuth2AccessToken;
    }
}

然後在TokenConfigure 中配置該Bean:

@Configuration
public class TokenConfigure {
    ......

    @Bean
    public TokenEnhancer tokenEnhancer() {
        return new JWTokenEnhancer();
    }
}

最後在認證服務器里配置該增強器:

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private TokenEnhancer tokenEnhancer;

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        .....
        
        TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
        List<TokenEnhancer> enhancers = new ArrayList<>();
        enhancers.add(tokenEnhancer);
        enhancers.add(jwtAccessTokenConverter);
        enhancerChain.setTokenEnhancers(enhancers);

        endpoints.tokenEnhancer(enhancerChain);
    }
    ......
}
  • 擴展username的內容

比如存入json數據內容作為username的內容。相比較而言,方案二比較簡單還不用破壞UserDetails的結構

@Override 
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    //登錄賬號
    System.out.println("username="+username);
    //根據賬號去數據庫查詢...
    UserDto user = userDao.getUserByUsername(username);
    if(user == null){
        return null;
    }
    //查詢用戶權限
    List<String> permissions = userDao.findPermissionsByUserId(user.getId());
    String[] perarray = new String[permissions.size()];
    permissions.toArray(perarray);
    //創建userDetails
    //這裏將user轉為json,將整體user存入userDetails
    String principal = JSON.toJSONString(user);
    UserDetails userDetails = User.withUsername(principal).password(user.getPassword()).authorities(perarray).build();
    return userDetails;
}

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

有別於一般網頁架設公司,除了模組化的架站軟體,我們的營業主軸還包含:資料庫程式開發、網站建置、網頁設計、電子商務專案開發、系統整合、APP設計建置、專業網路行銷。

用python做時間序列預測一:初識概念_包裝設計

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

網動廣告出品的網頁設計,採用精簡與質感的CSS語法,提升企業的專業形象與簡約舒適的瀏覽體驗,讓瀏覽者第一眼就愛上她。

利用時間序列預測方法,我們可以基於歷史的情況來預測未來的情況。比如共享單車每日租車數,食堂每日就餐人數等等,都是基於各自歷史的情況來預測的。

什麼是時間序列?

  • 時間序列,是指同一個變量在連續且固定的時間間隔上的各個數據點的集合,比如每5分鐘記錄的收費口車流量,或者每年記錄的藥物銷量都是時間序列。

時間序列的類型

  • 根據時間間隔的不同,時間序列可以是按年度(Annual)、季度、月度、周、小時、分鐘、秒等頻率採集的序列。

時間序列的成分

  • 趨勢(Trend),比如長期上漲或長期下跌。
  • 季節性(Seasonal),比如羽絨服的銷量一般會在冬季更高,或者某家燒烤店的生意一般會在每周五和周六晚上更好。
  • 周期性(Cyclic),比如你時不時搞個大促,那麼銷量在那段時間就會比較好。
  • 誤差。

什麼是時間序列預測?

  • 就是用同一個變量的歷史值預測未來值,或者除了歷史值以外,還加入一些預測因子(又稱外生變量)來預測未來值。前者稱為單變量時間序列預測,後者稱為多變量時間序列預測。
  • 比如,我們要預測某海灘下個月的的遊客數量,除了用歷史遊客數量做預測外,還可以加入溫度這個因子。那麼只用歷史遊客數量做預測就是單變量時間預測,加入溫度這個因子就是多變量時間預測,當然還可以加入其它合理的預測因子,比如該海灘的每月廣告支出等。

一些簡單的預測方法

均值法

所有未來的預測值等於歷史數據的平均值。

樸素法

簡單的將最後一次觀測值作為未來的預測值。

季節性樸素法

相比樸素法,就是考慮了季節性,也就是說將同期的最後一次觀測值作為本期的預測值,比如預測本周的數值,那麼就將上周的周一觀測值作為本周的周一預測值,上周的周二觀測值作為本周的周二預測值,以此類推。

※產品缺大量曝光嗎?你需要的是一流包裝設計!

窩窩觸角包含自媒體、自有平台及其他國家營銷業務等,多角化經營並具有國際觀的永續理念。

漂移法(drift )

在起始觀測值和最後一次觀測值之間畫一條連接線,延伸到預測時間點,作為預測值,公式如下:

下面的2副圖展示了上面四種方法的預測效果:

常用的時間序列預測法

  • Exponential smoothing 指數平滑
    簡單說就是用過去的觀測值的加權平均值來作為預測值,權重隨着與當前時刻的距離變遠而呈指數衰減。
  • ARIMA
    簡單說就是用變量的自回歸(AR)與歷史預測誤差的自回歸(MA)構成的時間序列預測模型。
  • 基於深度學習的方法
    簡單說就是利用神經網絡強大的學習能力,從時間序列歷史數據中提取各種可能的特徵,從而對未來進行預測。這部分的模型比較多,比如LSTM,GRU,TCN等。

注意,上述的方法並不能說誰一定比誰好,不同的預測場景下每個方法都有可能做出更好的預測,所以通常需要相互比較,以便做出更合理的預測。

本篇介紹了時間序列的相關概念,下一篇將介紹時間序列的一般數據格式和基於python的可視化方法。

ok,本篇就這麼多內容啦~,感謝閱讀O(∩_∩)O。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

上新台中搬家公司提供您一套專業有效率且人性化的辦公室搬遷、公司行號搬家及工廠遷廠的搬家服務

kubeadm實現k8s高可用集群環境部署與配置_台中搬家

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

台中搬家公司推薦超過30年經驗,首選台中大展搬家

高可用架構

k8s集群的高可用實際是k8s各核心組件高可用,這裏使用主備模式,架構如下:

主備模式高可用架構說明:

核心組件 高可用模式 高可用實現方式
apiserver 主備 keepalived
controller-manager 主備 leader election
scheduler 主備 leader election
etcd 集群 kubeadm
  • apiserver 通過keepalived實現高可用,當某個節點故障時觸發keepalived vip 轉移;
  • controller-manager k8s內部通過選舉方式產生領導者(由–leader-elect 選型控制,默認為true),同一時刻集群內只有一個controller-manager組件運行;
  • scheduler k8s內部通過選舉方式產生領導者(由–leader-elect 選型控制,默認為true),同一時刻集群內只有一個scheduler組件運行;
  • etcd 通過運行kubeadm方式自動創建集群來實現高可用,部署的節點數為奇數,3節點方式最多容忍一台機器宕機。

部署環境

k8s版本

kubelet version kubeadm version kubectl version
v1.15.1 v1.15.1 v1.15.1

主機配置

Centos版本 系統內核 docker version flannel version Keepalived version
7.8.2003 4.4.223 19.03.9 v0.11.0 v1.3.5

主機列表

主機名 ip 主機配置 備註
master01 192.168.213.181 4U4G control plane
master02 192.168.213.182 4U4G control plane
master03 192.168.213.183 4U4G control plane
node01 192.168.213.192 2U2G node
node02 192.168.213.192 2U2G node
VIP 192.168.213.200 4U4G 在control plane上浮動

私有倉庫

主機名 ip 主機配置 備註
docker-registry 192.168.213.129 2U1G reg.zhao.com

其他準備

系統初始化,docker安裝,k8s(kubelet、kubeadm和kubectl)安裝省略

  • kubelet 運行在集群所有節點上,用於啟動Pod和容器
  • kubeadm 用於初始化集群,啟動集群
  • kubectl 用於和集群通信,部署和管理應用,查看各種資源,創建、刪除和更新各種組件

啟動kubelet並設置開機啟動 systemctl enable kubelet && systemctl start kubelet

keepalived安裝

在所有master節點上安裝

安裝keepalived

[root@master01 ~]# yum -y install keepalived

keepalived配置

master01

[root@master01 ~]# cat /etc/keepalived/keepalived.conf 
! Configuration File for keepalived
global_defs {
   router_id master01
}
vrrp_instance VI_1 {
    state MASTER 
    interface ens33
    virtual_router_id 50
    priority 150
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass 1111
    }
    virtual_ipaddress {
        192.168.213.200
    }
}

master02

[root@master02 ~]# cat /etc/keepalived/keepalived.conf 
! Configuration File for keepalived
global_defs {
   router_id master02
}
vrrp_instance VI_1 {
    state BACKUP 
    interface ens33
    virtual_router_id 50
    priority 100
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass 1111
    }
    virtual_ipaddress {
        192.168.213.200
    }
}

master03

[root@master03 ~]# cat /etc/keepalived/keepalived.conf 
! Configuration File for keepalived
global_defs {
   router_id master03
}
vrrp_instance VI_1 {
    state BACKUP 
    interface ens33
    virtual_router_id 50
    priority 100
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass 1111
    }
    virtual_ipaddress {
        192.168.213.200
    }
}

啟動keepalived並設置開機啟動

[root@master01 ~]# systemctl start keepalived
[root@master01 ~]# systemctl enable keepalived

VIP查看

配置master節點

初始化master01節點

master01初始化

#初始化的配置文件
[root@master01 ~]# cat kubeadm-config.yaml
apiVersion: kubeadm.k8s.io/v1beta2
kind: ClusterConfiguration
kubernetesVersion: v1.15.1
apiServer:
  certSANs:		##填寫所有kube-apiserver節點的hostname、IP、VIP
  - master01
  - master02
  - master03
  - node01
  - node02
  - 192.168.213.181
  - 192.168.213.182
  - 192.168.213.183
  - 192.168.213.191
  - 192.168.213.192
  - 192.168.213.200
controlPlaneEndpoint: "192.168.213.200:6443"
networking:
  podSubnet: "10.244.0.0/16"
[root@master01 ~]# kubeadm init --config=kubeadm-config.yaml|tee kubeadim-init.log

記錄kubeadm join的輸出,後面需要這個命令將備master節點和node節點加入集群中

Then you can join any number of worker nodes by running the following on each as root:

kubeadm join 192.168.213.200:6443 --token ebx4uz.9y3twsnoj9yoscoo \
    --discovery-token-ca-cert-hash sha256:1bc280548259dd8f1ac53d75e918a8ec99c234b13f4fe18a71435bbbe8cb26f3

加載環境變量

[root@master01 ~]# echo "export KUBECONFIG=/etc/kubernetes/admin.conf" >> ~/.bash_profile
[root@master01 ~]# source .bash_profile

安裝flannel網絡

[root@master01 ~]# kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/2140ac876ef134e0ed5af15c65e414cf26827915/Documentation/kube-flannel.yml

備master節點加入集群

配置免密登錄

配置master01到master02、master03免密登錄

#創建秘鑰
[root@master01 ~]# ssh-keygen -t rsa
#將秘鑰同步至master02,master03
[root@master01 ~]# ssh-copy-id -i /root/.ssh/id_rsa.pub root@192.168.213.182
[root@master01 ~]# ssh-copy-id -i /root/.ssh/id_rsa.pub root@192.168.213.183
#免密登陸測試
[root@master01 ~]# ssh master02
[root@master01 ~]# ssh 192.168.213.183

master01分發證書

在master01上運行腳本cert-main-master.sh,將證書分發至master02和master03

[root@master01 ~]# cat cert-main-master.sh
USER=root # customizable
CONTROL_PLANE_IPS="192.168.213.182 192.168.213.183"
for host in ${CONTROL_PLANE_IPS}; do
    scp /etc/kubernetes/pki/ca.crt "${USER}"@$host:
    scp /etc/kubernetes/pki/ca.key "${USER}"@$host:
    scp /etc/kubernetes/pki/sa.key "${USER}"@$host:
    scp /etc/kubernetes/pki/sa.pub "${USER}"@$host:
    scp /etc/kubernetes/pki/front-proxy-ca.crt "${USER}"@$host:
    scp /etc/kubernetes/pki/front-proxy-ca.key "${USER}"@$host:
    scp /etc/kubernetes/pki/etcd/ca.crt "${USER}"@$host:etcd-ca.crt
    # Quote this line if you are using external etcd
    scp /etc/kubernetes/pki/etcd/ca.key "${USER}"@$host:etcd-ca.key
done
[root@master01 ~]# ./cert-main-master.sh

備master節點移動證書至指定目錄

在master02,master03上運行腳本cert-other-master.sh,將證書移至指定目錄

[root@master02 ~]# cat cert-other-master.sh
USER=root # customizable
mkdir -p /etc/kubernetes/pki/etcd
mv /${USER}/ca.crt /etc/kubernetes/pki/
mv /${USER}/ca.key /etc/kubernetes/pki/
mv /${USER}/sa.pub /etc/kubernetes/pki/
mv /${USER}/sa.key /etc/kubernetes/pki/
mv /${USER}/front-proxy-ca.crt /etc/kubernetes/pki/
mv /${USER}/front-proxy-ca.key /etc/kubernetes/pki/
mv /${USER}/etcd-ca.crt /etc/kubernetes/pki/etcd/ca.crt
# Quote this line if you are using external etcd
mv /${USER}/etcd-ca.key /etc/kubernetes/pki/etcd/ca.key
[root@master02 ~]# ./cert-other-master.sh 

備master節點加入集群

在master02和master03節點上運行加入集群的命令

kubeadm join 192.168.213.200:6443 --token ebx4uz.9y3twsnoj9yoscoo \
    --discovery-token-ca-cert-hash sha256:1bc280548259dd8f1ac53d75e918a8ec99c234b13f4fe18a71435bbbe8cb26f3

備master節點加載環境變量

此步驟是為了在備master節點上也能執行kubectl命令

scp master01:/etc/kubernetes/admin.conf /etc/kubernetes/
echo "export KUBECONFIG=/etc/kubernetes/admin.conf" >> ~/.bash_profile
source .bash_profile

node節點加入集群

加入集群

在node節點運行初始化master生成的加入集群的命令

kubeadm join 192.168.213.200:6443 --token ebx4uz.9y3twsnoj9yoscoo \
    --discovery-token-ca-cert-hash sha256:1bc280548259dd8f1ac53d75e918a8ec99c234b13f4fe18a71435bbbe8cb26f3

集群節點查看

[root@master01 ~]# kubectl get nodes
[root@master01 ~]# kubectl get pod -o wide -n kube-system 

所有control plane節點處於ready狀態,所有的系統組件也正常

對接私有倉庫

私有倉庫配置省略,在所有節點上執行以下步驟

修改daemon.json

[root@master01 ~]# cat /etc/hosts
127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4
::1         localhost localhost.localdomain localhost6 localhost6.localdomain6
192.168.213.181 master01
192.168.213.182 master02
192.168.213.183 master03
192.168.213.191 node01
192.168.213.192 node02
192.168.213.129 reg.zhao.com
[root@master01 ~]# cat /etc/docker/daemon.json
{
    "registry-mirrors": ["https://sopn42m9.mirror.aliyuncs.com"],
    "exec-opts": ["native.cgroupdriver=systemd"],
    "log-driver": "json-file",
        "log-opts": {
            "max-size": "100m"
        },
    "insecure-registries": ["https://reg.zhao.com"]
}
[root@master01 ~]# systemctl daemon-reload
[root@master01 ~]# systemctl restart docker

創建認證secret

使用Kuberctl創建docker register認證secret

[root@master01 ~]# kubectl create secret docker-registry myregistrykey --docker-server=https://reg.zhao.com --docker-username=admin --docker-password=Harbor12345 --docker-email=""
secret/myregistrykey created
[root@master02 ~]# kubectl get secrets
NAME                  TYPE                                  DATA   AGE
default-token-6mrjd   kubernetes.io/service-account-token   3      18h
myregistrykey         kubernetes.io/dockerconfigjson        1      19s

在創建Pod的時通過imagePullSecret引用myregistrykey

imagePullSecrets:
  - name: myregistrykey

集群功能測試

測試私有倉庫

[root@master02 ~]# cat test_sc.yaml
apiVersion: v1
kind: Pod
metadata:
  name: foo
spec:
  containers:
    - name: foo
      image: reg.zhao.com/zhao/myapp:v1.0
#  imagePullSecrets:
#    - name: myregistrykey

打開註釋,應用密鑰,可以拉取到鏡像

台中搬家公司費用怎麼算?

擁有20年純熟搬遷經驗,提供免費估價且流程透明更是5星評價的搬家公司

測試集群高可用

測試master節點高可用

通過ip查看apiserver所在節點,通過leader-elect查看scheduler和controller-manager所在節點

[root@master01 ~]# ip a|grep ens33
[root@master01 ~]# kubectl get endpoints kube-scheduler -n kube-system -o yaml |grep holderIdentity
[root@master01 ~]# kubectl get endpoints kube-controller-manager -n kube-system -o yaml |grep holderIdentity

組件名 所在節點
apiserver master01
controller-manager master01
scheduler master01

關閉master01,模擬宕機,master01狀態為NotReady

[root@master01 ~]# init 0

VIP飄到了master02,controller-manager和scheduler也發生了遷移

組件名 所在節點
apiserver master02
controller-manager master03
scheduler master02

測試node節點高可用

K8S 的pod-eviction在某些場景下如節點 NotReady,資源不足時,會把 pod 驅逐至其它節點

Kube-controller-manager 周期性檢查節點狀態,每當節點狀態為 NotReady,並且超出 pod-eviction-timeout 時間后,就把該節點上的 pod 全部驅逐到其它節點,其中具體驅逐速度還受驅逐速度參數,集群大小等的影響。最常用的 2 個參數如下:
pod-eviction-timeout:NotReady 狀態節點超過該時間后,執行驅逐,默認 5 min
node-eviction-rate:驅逐速度,默認為 0.1 pod/秒

創建pod,維持副本數3

[root@master02 ~]# cat myapp_deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-deploy
  namespace: default
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp
      release: stabel
  template:
    metadata:
      labels:
        app: myapp
        release: stabel
        env: test
    spec:
      containers:
      - name: myapp
        image: library/nginx
        imagePullPolicy: IfNotPresent
        ports:
        - name: http
          containerPort: 80

可以看到pod分佈在node01和node02節點上
關閉node02,模擬宕機,node02狀態為NotReady
可以看到 NotReady 狀態節點超過指定時間后,pod被驅逐到 Ready 的節點上,deployment維持運行3個副本

問題

初始化master節點失敗

如果初始化失敗,可執行kubeadm reset后重新初始化

[root@master01 ~]# kubeadm reset
#非root用戶還須執行rm -rf $HOME/.kube/config

flanne文件下載失敗

方法一:可以直接下載kube-flannel.yml文件,然後再執行apply
方法二:配置域名解析
在https://site.ip138.com查詢服務器IP
echo "151.101.76.133 raw.Githubusercontent.com" >>/etc/hosts

節點狀態NotReady

在節點機器上執行journalctl -f -u kubelet查看kubelet的輸出日誌信息如下:

Container runtime network not ready: NetworkReady=false reason:NetworkPluginNotReady message:docker: network plugin is not ready: cni config uninitialized

出現這個錯誤的原因是網絡插件沒有準備好,在節點上執行命令 docker images|grep flannel 查看flannel鏡像是否已經成功拉取,這個花費的時間可能會很長

如果很長時間仍然沒有拉下來flannel鏡像,可以使用如下方法解決

docker save把主節點上的flannel鏡像保存為壓縮文件(或在官方倉庫https://github.com/coreos/flannel/releases下載鏡像傳到主機上,要注意版本對應),在節點機器上執行docker load加載鏡像

[root@master02 ~]# docker save -o my_flannel.tar quay.io/coreos/flannel:v0.11.0-amd64
[root@master02 ~]# scp my_flannel.tar node01:/root
[root@node01 ~]# docker load < my_flannel.tar

unexpected token `$’do\r”

shell,運行出錯:syntax error near unexpected token `$’do\r”

原因:Linux和windows下的回車換行符不兼容

解決方法:將windows下面的CR LF,轉換為Linux下面的LF
用notepad++打開文件,編輯->檔案格式轉換->轉換為UNIX格式->保存即可

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

台中搬家公司費用怎麼算?

擁有20年純熟搬遷經驗,提供免費估價且流程透明更是5星評價的搬家公司

使用VUE開發用戶後台時的動態路由問題、按鈕權限問題以及其他頁面處理問題_台中搬家公司

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

還在煩惱搬家費用要多少哪?台中大展搬家線上試算搬家費用,從此不再擔心「物品怎麼計費」、「多少車才能裝完」

如今前後端分離是大勢所趨,筆者雖然是做後台的,但也不得不學學前端的流行框架VUE -_-||| 。

為了學習VUE,筆者搭建了一個簡單的用戶後台,以此來了解VUE的開發思路(注:本項目不用於實際開發,只是為了學習,本文重點在於vue的動態路由添加,動態權限以及頁面處理的一些小問題)。

一、項目組成

  VUE 2.6.11 + axios +VueX + ElementUI 2.13.2 

二、整體思路

  1.  用戶登錄后,獲取菜單數據,用以显示菜單。

  2.  用戶登錄后,後台獲取Vue路由數據,用以動態添加到VueRouter中。

  3.  用戶登錄后,從後台獲取用戶的權限,用以控制用戶是否對某一功能具有可操作權限。

三、具體實現

·   1.  登錄。由於本人學習重點是使用VUE動態添加路由、菜單显示和權限控制,所以登錄頁面沒有做具體功能,點擊登錄按鈕就表示登錄成功了。

      由於登錄頁是用戶的第一界面,不存在任何權限問題,所以筆者就直接將登錄頁的路由直接寫在了VueRouter實例中。如下:

   

import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)

const routes = [{
  path: '/Login',
  name: 'Login',
  component: () => import('../views/Login.vue')
}]

export function initRouter() {
  return new VueRouter({
    routes
  })
}

export default initRouter()

  用戶通過 http://localhost:8080/#/login 可訪問到登陸頁面:

 

 

       點擊登錄按鈕表示登錄成功!

  登錄成功后的處理:

  (1)向後台發請求拿到路由數據並存入VueX的state中,並通過addRoutes(routerObjArr)動態添加到路由實例中注:後台返回的數據結構需跟route相一致,如圖:

  前端所需數據結構:

  

  後台返回的數據結構:

  

 

 

 

 

 

   細節處理:由於後台返回的component字段是個組件地址字符串,這就需要將後台返回的route數據 一 一 做處理,通過import() 方法動態的加載組件,然後將返回的compoent對象重新賦值到component字段上。如圖:

  

 

  代碼:

  

const _import = require('@/router/_import_' + process.env.NODE_ENV) //獲取組件的方法
/**將router的json字符串中的component轉換為組件對象 */
export function filterAsyncRouter(asyncRouterMap) {
  if (!asyncRouterMap) return [];

  function _iter(before) {
    const after = Object.assign({}, before)
    if (after.component) {
      after.component = _import(after.component);
    }
    if (after.children && after.children.length) {
      after.children = filterAsyncRouter(after.children);
    }
    return after
  }

  return asyncRouterMap.map(_iter)

}

 

 

   圖中所用的import方法,根據生產環境不同,引用不同的文件,如圖:

  

 

 

   各自的代碼如下:

  _import_development.js:

module.exports = file => require('@/views/' + file + '.vue').default // vue-loader at least v13.0.0+

  _import_production.js

module.exports = file => () => import('@/views/' + file + '.vue')

  將後台的返回的路由數據處理完成后,最後就可以使用addRoutes方法動態加入VueRouter中了。此時,用戶便可以按照路由訪問頁面了。代碼如下:

//動態生成路由
      axios
        .get("https://localhost:5001/AdminApi/Home/GetMenuJson?userid=" + 1)
        .then(res => {
          //取出路由列表 (isRoute=1)
          res.data.obj.router[0].children = res.data.obj.router[0].children.filter(
            a => a.meta.isRoute == 1 //isRoute=1的
          );

          //存入緩存
          this.$store.commit("setRoutes", res.data.obj.router);

          //轉換組件對象
          var getRouter = filterAsyncRouter(res.data.obj.router);

          //打印當前路由列表
          console.log("當前路由列表:", res.data.obj.router[0].children);

          //清空之前的路由信息
          this.$router.matcher = initRouter().matcher;

          //重新添加路由信息
          this.$router.addRoutes(getRouter);

          //跳轉到 Layout 組件
          this.$router.push("/");
        });

 

  (2)向後台發請求拿到權限數據,並存入VueX的state中。代碼如下:

※推薦台中搬家公司優質服務,可到府估價

台中搬鋼琴,台中金庫搬運,中部廢棄物處理,南投縣搬家公司,好幫手搬家,西屯區搬家

     axios
        .get(
          "https://localhost:5001/AdminApi/Access/ActionPermission?userid=" + 1
        )
        .then(res => {
          //存入權限
          console.log("權限列表:", res.data.obj);
          this.$store.commit("setAccess", res.data.obj);
        });

  (3)向後台請求數據並存入VueX中的state之前,需要清空上一次存入的數據(包括路由數據和權限數據),否則會造成數據混亂,如圖:   

        

  (4)addRoutes之前,不僅要做component字段的字符串轉對象的處理,還要清掉上一個用戶登錄后存入router中的路由數據,否則會造成數據混亂或者vue警告重複的路由名稱。

  

 

 

   Login.vue組件中的全部代碼如下:

<template>
  <div class="about">
    <button @click="login">登錄</button>
  </div>
</template>

<script> import { filterAsyncRouter } from "../common/promission"; import axios from "axios"; import { initRouter } from "@/router"; export default { created() { this.$store.commit("logout"); }, methods: { login() { //動態生成路由
 axios .get("https://localhost:5001/AdminApi/Home/GetMenuJson?userid=" + 1) .then(res => { //取出路由列表 (isRoute=1)
 res.data.obj.router[0].children = res.data.obj.router[0].children.filter( a => a.meta.isRoute == 1 //isRoute=1的
 ); //存入緩存
          this.$store.commit("setRoutes", res.data.obj.router); //轉換組件對象
          var getRouter = filterAsyncRouter(res.data.obj.router); //打印當前路由列表
 console.log("當前路由列表:", res.data.obj.router[0].children); //清空之前的路由信息
          this.$router.matcher = initRouter().matcher; //重新添加路由信息
          this.$router.addRoutes(getRouter); //跳轉到 Layout 組件
          this.$router.push("/"); }); axios .get( "https://localhost:5001/AdminApi/Access/ActionPermission?userid=" + 1 ) .then(res => { //存入權限
 console.log("權限列表:", res.data.obj); this.$store.commit("setAccess", res.data.obj); }); } } }; </script>

  promiss.js代碼如下:

const _import = require('@/router/_import_' + process.env.NODE_ENV) //獲取組件的方法
/**將router的json字符串中的component轉換為組件對象 */
export function filterAsyncRouter(asyncRouterMap) {
  if (!asyncRouterMap) return [];

  function _iter(before) {
    const after = Object.assign({}, before)
    if (after.component) {
      after.component = _import(after.component);
    }
    if (after.children && after.children.length) {
      after.children = filterAsyncRouter(after.children);
    }
    return after
  }

  return asyncRouterMap.map(_iter)

}

  store.js代碼如下:

import Vue from 'vue'
import Vuex from 'vuex'
import VuexPersistence from 'vuex-persist'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    routes: [],
  },
  mutations: {
    setRoutes: (state, routes) => {
      state.routes = routes
    },
    setAccess: (state, access) => {
      state.access = access;
    },
    logout: (state) => {
      state.routes = [];
      state.access = []
    }
  },
  actions: {},
  modules: {},
  plugins: [new VuexPersistence().plugin]
})

 

  2. 菜單。將Layout組件用作菜显示組件,將ele中的菜單組件複製到該組件中,並通過向後台請求數據,拿到菜單和菜單對應的分組數據 。拿到菜單和菜單分組數據后,循環遍歷,將菜單按照對應的分組全部显示(後台判斷當前用戶可显示的菜單,沒有權限的菜單直接不返給前台)。vue代碼以及後台數據如下:

  

<template>
  <el-container>
    <el-header>
      <el-dropdown>
        <i class="el-icon-setting"></i>
        <el-dropdown-menu slot="dropdown">
          <el-dropdown-item>修改密碼</el-dropdown-item>
          <el-dropdown-item>退出</el-dropdown-item>
        </el-dropdown-menu>
      </el-dropdown>
      <span>王小虎</span>
    </el-header>
    <el-container>
      <el-aside width="250px">
        <el-menu @select="handleSelect">
          <el-submenu :index="group.name" v-for="group in groupList" :key="group.id">
            <template slot="title">
              <i class="el-icon-message"></i> {{group.title}} </template>
            <template v-for="router in routerList">
              <el-menu-item :index="router.path" :key="router.meta.id" v-if="router.meta.groupId == group.id"
              >{{router.meta.title}}</el-menu-item>
            </template>
          </el-submenu>
        </el-menu>
      </el-aside>
      <el-main>
        <router-view />
      </el-main>
    </el-container>
  </el-container>
</template>

<script> import axios from "axios"; export default { data() { return { activeIndex: "/home/Index", groupList: [], routerList: [] }; }, mounted() { this.getGroupList(); this.getRouterList(); }, methods: { //菜單點擊事件
 handleSelect(key) { this.$router.push(key); }, //獲取菜單分組數據
 getGroupList() { var that = this; axios .get("https://localhost:5001/AdminApi/Home/GetGroupJson") .then(res => { that.groupList = res.data.obj; }); }, //獲取菜單數據
 getRouterList() { var that = this; axios .get("https://localhost:5001/AdminApi/Home/GetMenuJson") .then(res => { that.routerList = res.data.obj.router[0].children.filter( a => a.meta.display == 1 //取display=1的
 ); console.log("當前菜單列表"); console.log(that.routerList); }); } } }; </script>

<style> @import "../styles/layout.css"; /*引入公共樣式*/
</style>

後台分組數據:

{
"id": 14,
"name": "Customer",
"title": "客戶中心",
"target": "mainRigh",
"url": "#",
"icoCss": "layui-icon-username",
"delFlag": 0,
"sortNo": 0,
"createMan": 1,
"createTime": "2019-05-05T11:30:06"
},
{
"id": 9,
"name": "System",
"title": "系統設置",
"target": "123",
"url": "#",
"icoCss": "fa-gears",
"delFlag": 0,
"sortNo": 1,
"createMan": 1,
"createTime": "2019-05-05T11:29:56"
}

後台菜單數據:

 

 

效果圖:

  3.  功能頁面的處理。

    (1)組件的動態加載規則由於該vue項目中的組件是動態加載,那麼後台返回的路由數據中的component字段中的路徑自然也要按照某一種規則來返給前端。否則會造成import()組件的時候,由於地址不對解析加載不到組件而報錯。

   例如筆者是按照這種規則:

       後台數據

 

 

 斜杠”/“前邊表示文件夾名稱,後邊表示組件名稱,這樣就可以按照這種規則動態加載到組件了。

 

 

 

 (2).頁面刷新變成空白頁?(路由丟失)

  遇到這個問題的話,在main.js中加入一段代碼,每次刷新頁面都把存入VueX state中的數據拿出來,判斷一下路由裡邊還存不存在當前刷新頁面的路由,如果沒有,則對VueRouters重新賦值

  main.js 代碼如下:

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import store from './common/store'
import {
  filterAsyncRouter
} from "./common/promission";

Vue.config.productionTip = false
Vue.use(ElementUI);

new Vue({
  router,
  store,
  render: h => h(App),
  mounted() {
    // 緩存的路由信息
    const routes = this.$store.state.routes
    // 判斷當前路由是否被清除
    const hasRoute = this.$router.options.routes.some(r => r.name == 'index')
    // 如果 緩存中有路由信息 並且 當前路由被清除
    if (routes.length && !hasRoute) {
      //獲取路由Json字符串
      var getRouter = filterAsyncRouter(routes);
      // 再次添加路由信息
      this.$router.addRoutes(getRouter);
      // 然後強制更新當前組件
      this.$forceUpdate()
    }
  },
}).$mount('#app')

  (3)  頁面按鈕的控制

  將前面存入vuex state中的權限數據,在每個組件中都拿出來一下存入一個變量中,在按鈕上使用v-if、array.indexOf(‘權限名稱’)來控制显示/隱藏。

  原理是如果用戶存在該權限,則v-if=”true“,按鈕則显示,否則按鈕隱藏。

  代碼如下:

<el-button @click="edit(scope.row)" type="text" size="small" v-if="accessList.indexOf('SysRole/AddEdit')>-1"
        >編輯</el-button>

  

 

 效果圖:

 

好了,筆者就介紹到這裏。當然,如果要做一個完整的後台,肯定還有很多要做的東西,比如用戶角色啊、角色授權啊等等;但筆者這次學習的重點是VUE的動態路由、動態權限,以及頁面處理中一些比較常見的坑,所以別的就不多介紹了。

如有需要,朋友們可以聯繫我,大家多多交流。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

還在煩惱搬家費用要多少哪?台中大展搬家線上試算搬家費用,從此不再擔心「物品怎麼計費」、「多少車才能裝完」