標籤: 銷售文案

  • Java Jar 包加密 — XJar

    Java Jar 包加密 — XJar

    Java Jar 包加密

    一、緣由

      Java的 Jar包中的.class文件可以通過反彙編得到源碼。這樣一款應用的安全性就很難得到保證,別人只要得到你的應用,不需花費什麼力氣,就可以得到源碼。

      這時候就需要對jar進行加密處理。

    二、技術&工具

      XJar

      GitHub:https://github.com/core-lib/xjar

      碼雲:https://gitee.com/core-lib/xjar?_from=gitee_search

      maven集成 XJar

     

      GitHub:https://github.com/core-lib/xjar-maven-plugin

      碼雲:https://gitee.com/core-lib/xjar-maven-plugin?_from=gitee_search

      xjar-agent-hibernate

      GitHub:https://github.com/core-lib/xjar-agent-hibernate

      碼雲:https://gitee.com/core-lib/xjar-agent-hibernate?_from=gitee_search

      go語言、maven、eclipse

      文檔可以到github、碼雲上去了解,這裏只描述使用過程,親測可用!

    三、實現過程

      這裏使用的maven版本是:apache-maven-3.6.3,低版本的沒測試過

      1、XJar

       1-1.在github或碼雲上下載該項目,導入eclipse,然後新建一個main類,填入參數,直接運行得到一個xjar.go 和 加密后的jar包【xx-encrypted.jar】。

        注意:這種直接在項目中跑mian,不提倡,會導致jar包中包含這段代碼,導緻密碼泄露,所以要通過命令行的方式來執行這段代碼。

        

        1-2.這時候可以用反編譯軟件 jb-gui 打開jar看看加密的效果,這時候反編譯軟件已經看不到.class文件的源碼了

     

     

         1-3.加密后的jar包,不能直接用原來的java 命令來執行,需要用到同時生成的xjar.go文件,執行命令 go build xjar.go

          這裏要等待一小會,等待編譯出目標文件xjar.exe

          

     

     

           

          1-4. 最後執行命令,xjar java -jar /path/to/encrypted.jar,即可運行加密后的jar包

           注意:Spring Boot + JPA(Hibernate) 啟動會報錯

                       1-5:沒有採用 Spring Boot + JPA(Hibernate) 技術的可以略過以下步驟。

          a、到碼雲、GitHub上下載  xjar-agent-hibernate  項目

          b、導入eclipse 打包出jar包

          c、然後執行命令  xjar java -javaagent:xjar-agent-hibernate-v1.0.0.jar -jar path\wx-encrypted.jar,即可正常運行

          

       2、maven集成 XJar

        第二種方式就比較簡單了,直接在項目中引入xjar-maven-plugin,然後打包就可以了,其他操作方式和第一種類似

        注意:密碼最好採用命令行方式

    <pluginRepositories>
      <pluginRepository>
        <id>jitpack.io</id>
        <url>https://jitpack.io</url>
      </pluginRepository>
    </pluginRepositories>

           

     <plugin> <groupId>com.github.core-lib</groupId> <artifactId>xjar-maven-plugin</artifactId> <version>4.0.0</version> <executions> <execution> <goals> <goal>build</goal> </goals> <phase>install</phase> <configuration> <password>1233445</password> <includes>
                       <!----> <include>/com/xxx/xxx/**/*.class</include> </includes> <!-- 無需加密的資源路徑表達式 --> <excludes> <exclude>static/**</exclude> <exclude>META-INF/resources/**</exclude> </excludes> <!-- 源jar所在目錄 --> <sourceDir>path\</sourceDir> <!-- 源jar名稱 --> <sourceJar>xxx.jar</sourceJar> <!-- 目標jar存放目錄 --> <targetDir>path\test2</targetDir> <!-- 目標jar名稱 --> <targetJar>xxx-encrypted.jar</targetJar> </configuration> </execution> </executions> </plugin>

    四、後記

      其實所有軟件,都可以被破解,只是破解過程是簡單還是複雜、以及破解成本的高低。

      最關鍵的還是自己軟件要更新迭代的快,這樣才能把模仿者遠遠甩在身後。

      轉發請註明出處!!!

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

    【其他文章推薦】

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

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

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

    ※超省錢租車方案

    FB行銷專家,教你從零開始的技巧

  • 程序員必須掌握的Java 框架,小白學會之後15k不是問題

    程序員必須掌握的Java 框架,小白學會之後15k不是問題

    Spring 的核心特性是什麼?Spring 優點?

    Spring 的核心是控制反轉(IoC)和面向切面(AOP)

    Spring 優點:

    程序員必須掌握的Java 框架,學會之後50k不是問題
    (1)方便解耦,簡化開發 (高內聚低耦合)

    Spring 就是一個大工廠(容器),可以將所有對象創建和依賴關係維護,交給 Spring管理

    spring 工廠是用於生成 bean

    (2)AOP 編程的支持

    Spring 提供面向切面編程,可以方便的實現對程序進行權限攔截、運行監控等功能

    (3) 聲明式事務的支持

    只需要通過配置就可以完成對事務的管理,而無需手動編程

    (4) 方便程序的測試

    Spring 對 Junit4 支持,可以通過註解方便的測試 Spring 程序

    (5)方便集成各種優秀框架

    Spring 不排斥各種優秀的開源框架,其內部提供了對各種優秀框架(如:Struts、Hibernate、MyBatis、Quartz 等)的直接支持

    (6) 降低 JavaEE API 的使用難度

    Spring 對 JavaEE 開發中非常難用的一些 API(JDBC、JavaMail、遠程調用等),都提供了封裝,使這些 API 應用難度大大降低

    spring 框架中需要引用哪些 jar 包,以及這些 jar 包的用途

    4 + 1 : 4 個 核 心 ( beans 、 core 、 context 、 expression ) + 1 個 依 賴(commons-loggins…jar)

    理解 AOP、IoC 的基本原理;

    IOC:控制反轉(IoC)與依賴注入(DI)是同一個概念,

    控制反轉的思想:

    傳統的 java 開發模式中,當需要一個對象時,我們會自己使用 new 或者getInstance 等直接或者間接調用構造方法創建一個對象。而在 spring 開發模式中,spring 容器使用了工廠模式為我們創建了所需要的對象,不需要我們自己創建了,直接調用 spring 提供的對象就可以了

    引入 IOC 的目的:

    (1)脫開、降低類之間的耦合;(2)倡導面向接口編程、實施依賴倒換原則;

    (3)提高系統可插入、可測試、可修改等特性

    AOP:面向切面編程(AOP)面向切面編程思想:

    在面向對象編程(oop)思想中,我們將事物縱向抽成一個個的對象。而在面向切面編程中,我們將一個個的對象某些類似的方面橫向抽成一個切面,對這個切面進行一些如權限控制、事物管理,記錄日誌等公用操作處理的過程。

    切面:簡單說就是那些與業務無關,卻為業務模塊所共同調用的邏輯或責任封裝起來,便於減少系統的重複代碼,降低模塊之間的耦合度,並有利於未來的可操作性和可維護性。

    AOP 底層:動態代理。

    如果是接口採用 JDK 動態代理,如果是類採用 CGLIB 方式實現動態代理。

    AOP 的一些場景應用;

    AOP 用來封裝橫切關注點,具體可以在下面的場景中使用:

    Authentication 權限

    Caching 緩存

    Context passing 內容傳遞

    Error handling 錯誤處理

    Lazy loading 懶加載

    Debugging調試

    logging, tracing, profiling and monitoring 記錄跟蹤 優化 校準

    Performance optimization 性能優化

    Persistence持久化

    Resource pooling 資源池

    Synchronization 同步

    Transactions 事務

    spring 注入的幾種方式
    (1)構造方法注入

    (2)setter 注入

    (3)基於註解

    Spring 中 Bean 的作用域有哪些

    作用域:用於確定 spring 創建 bean 實例個數

    取值(常用的兩個):

    singleton 單例,默認值。prototype 多例,一個 bean 的定義可以有多個實例。每執行一次 getBean 將獲得一個實例。

    請介紹一下 bean 的生命周期

    (1)bean 定義:在配置文件裏面用來進行定義。

    (2)bean 初始化:有兩種方式初始化:在配置文件中通過指定 init-method 屬性來完成實現
    org.springframwork.beans.factory.InitializingBean 接口

    (3)bean 調用:有三種方式可以得到 bean 實例,並進行調用

    (4)bean 銷毀:銷毀有兩種方式使用配置文件指定的 destroy-method 屬性實現
    org.springframwork.bean.factory.DisposeableBean 接口

    Spring 中自動裝配的方式有哪些

    no:不進行自動裝配,手動設置 Bean 的依賴關係。

    byName:根據 Bean 的名字進行自動裝配。

    byType:根據 Bean 的類型進行自動裝配。

    constructor:類似於 byType,不過是應用於構造器的參數,如果正好有一個 Bean與構造器的參數類型相同則可以自動裝配,否則會導致錯誤。autodetect:如果有默認的構造器,則通過 constructor 的方式進行自動裝配,否則使用 byType 的方式進行自動裝配。

    (自動裝配沒有自定義裝配方式那麼精確,而且不能自動裝配簡單屬性(基本類型、字符串等),在使用時應注意。)

    @Resource 和 @Autowired 區別?分別用在什麼場景?

    (1)共同點:兩者都可以寫在字段和 setter 方法上。兩者如果都寫在字段上,那麼就不需要再寫 setter 方法。

    (2)不同點:

    @Autowired

    @Autowired為Spring提供的註解,需要導入包
    org.springframework.beans.factory.annotation.Autowired;只按照 byType 注入。@Autowired 註解是按照類型(byType)裝配依賴對象,默認情況下它要求依賴對象必須存在,如果允許 null 值,可以設置它的 required 屬性為 false。如果我們想使用按照名稱(byName)來裝配,可以結合@Qualifier 註解一起使用。

    @Resource

    @Resource 默 認 按 照 ByName 自 動 注 入 , 由 J2EE 提 供 , 需 要 導 入 包javax.annotation.Resource。@Resource 有兩個重要的屬性:name 和 type,而 Spring將@Resource 註解的 name 屬性解析為 bean 的名字,而 type 屬性則解析為 bean的類型。所以,如果使用 name 屬性,則使用 byName 的自動注入策略,而使用type 屬性時則使用 byType 自動注入策略。如果既不制定 name 也不制定 type 屬性,這時將通過反射機制使用 byName 自動注入策略。

    Hibernate 和 mybatis 的區別?

    (1)兩者最大的區別

    針對簡單邏輯,Hibernate 與 MyBatis 都有相應的代碼生成工具,可以生成簡單基本的 DAO 層方法。

    針對高級查詢,MyBatis 需要手動編寫 SQL 語句,以及 ResultMap,而 Hibernate有良好的映射機制,開發者無需關心 SQL 的生成與結果映射,可以更專註於流程。

    (2)開發難度對比

    Hibernate 的開發難度大於 MyBatis,主要由於 Hibernate 比較複雜,龐大,學習周期比較長。

    MyBatis 則相對簡單,並且 MyBatis 主要依賴於生氣了的書寫,讓開發者剛進更熟悉。

    (3)sql 書寫比較

    Hibernate 也可以自己寫 sql 來指定需要查詢的字段,但這樣就破壞了Hibernate 開發的簡潔性,不過 Hibernate 具有自己的日誌統計。

    MyBatis 的 sql 是手動編寫的,所以可以按照要求指定查詢的字段,不過沒有自己的日誌統計,所以要藉助 Log4j 來記錄日誌。

    (4)數據庫擴展性計較Hibernate 與數據庫具體的關聯在 XML 中,所以 HQL 對具體是用什麼數據庫
    並不是很關心MyBatis 由於所有 sql 都是依賴數據庫書寫的,所以擴展性、遷移性比較差。

    (5)緩存機制比較

    Hibernate 的二級緩存配置在 SessionFactory 生成配置文件中進行詳細配置,然後再在具體的表對象映射中配置那種緩存。

    MyBatis 的二級緩存配置都是在每個具體的表對象映射中進行詳細配置,這樣針對不同的表可以自定義不同的緩衝機制,並且 MyBatis 可以在命名空間中共享相同的緩存配置和實例,通過 Cache-ref 來實現。

    兩者比較,因為 Hibernate 對查詢對象有着良好的管理機制,用戶無需關心 SQL,所以在使用二級緩存時如果出現臟數據,系統會報出錯誤提示。 而 MyBatis 在這一方面使用二級緩存時需要特別小心,如果不能完全去頂數據更新操作的波及範圍,避免 cache 的盲目使用,否則,臟數據的出現會給系統的正常運行帶來很大的隱患。

    mybatis 是如何工作的?

    一、Mybatis 工作原理圖

    mybatis 原理圖如下所示:

    二、工作原理解析

    mybatis 應用程序通過 SqlSessionFactoryBuilder 從 mybatis-config.xml 配置文件(也可以用 Java 文件配置的方式,需要添加@Configuration)來構建 SqlSessionFactory(SqlSessionFactory 是線程安全的);

    然後,SqlSessionFactory 的實例直接開啟一個 SqlSession,再通過 SqlSession 實例獲得 Mapper 對象並運行 Mapper 映射的 SQL 語句,完成對數據庫的 CRUD 和事務提交,之後關閉 SqlSession。說明:SqlSession 是單線程對象,因為它是非線程安全的,是持久化操作的獨享對象,類似 jdbc 中的 Connection,底層就封裝了 jdbc 連接。

    詳細流程如下:

    (1)、加載 mybatis 全局配置文件(數據源、mapper 映射文件等),解析配置文件,MyBatis 基於 XML 配置文件生成 Configuration,和一個個 MappedStatement(包括了參數映射配置、動態 SQL 語句、結果映射配置),其對應着標籤項。

    (2)、SqlSessionFactoryBuilder 通過 Configuration 對象生成 SqlSessionFactory,用來開啟 SqlSession。

    (3)、SqlSession 對象完成和數據庫的交互:

    a、用戶程序調用 mybatis 接口層 api(即 Mapper 接口中的方法)

    b、SqlSession 通過調用 api 的 Statement ID 找到對應的 MappedStatement 對象

    c、通過 Executor(負責動態 SQL 的生成和查詢緩存的維護)將 MappedStatement對象進行解析,sql 參數轉化、動態 sql 拼接,生成 jdbc Statement 對象

    d、JDBC 執行 sql。

    e、藉助 MappedStatement 中的結果映射關係,將返回結果轉化成 HashMap、JavaBean 等存儲結構並返回。

    Hibernate 對象有幾個狀態值?

    Transient 瞬時 :對象剛 new 出來,還沒設 id,設了其他值。

    Persistent 持久:調用了 save()、saveOrUpdate(),就變成 Persistent,有 id

    Detached 脫管 : 當 session close()完之後,變成 Detached。

    簡述 Springmvc 的流程;

    spring 工作的流程

    流程如下:(1)用戶發起請求到前端控制器(DispatcherServlet),該控制器會過濾出哪些請求可以訪問 Servlet、哪些不能訪問。就是 url-pattern 的作用,並且會加載springmvc.xml 配置文件。

    (2)前端控制器會找到處理器映射器(HandlerMapping),通過 HandlerMapping完成 url 到 controller 映射的組件,簡單來說,就是將在 springmvc.xml 中配置的或者註解的 url 與對應的處理類找到並進行存儲,用 map<url,handler>這樣的方式來存儲。

    (3)HandlerMapping 有了映射關係,並且找到 url 對應的處理器,HandlerMapping就會將其處理器(Handler)返回,在返回前,會加上很多攔截器。

    (4)DispatcherServlet 拿到 Handler 后,找到 HandlerAdapter(處理器適配器),通過它來訪問處理器,並執行處理器。

    (5)執行處理器

    (6)處理器會返回一個 ModelAndView 對象給 HandlerAdapter

    (7) 通 過 HandlerAdapter 將 ModelAndView 對 象 返 回 給 前 端 控 制 器(DispatcherServlet)

    (8)前端控制器請求視圖解析器(ViewResolver)去進行視圖解析,根據邏輯視圖名解析成真正的視圖(jsp),其實就是將 ModelAndView 對象中存放視圖的名稱進行查找,找到對應的頁面形成視圖對象

    (9)返回視圖對象到前端控制器。

    (10)視圖渲染,就是將 ModelAndView 對象中的數據放到 request 域中,用來讓頁面加載數據的。

    (11)通過第 8 步,通過名稱找到了對應的頁面,通過第 10 步,request 域中有了所需要的數據,那麼就能夠進行視圖渲染了。最後將其返回即可。

    Springmvc 和 Springboot 有什麼區別?

    Spring MVC 是基於 Spring 的一個 MVC 框架 ;

    Spring Boot 是基於 Spring4 的條件註冊的一套快速開發整合包。

    Springboot 為什麼配置簡單?(即它自動做了什麼操作才能簡化程序員的操作)
    主要是使用了 spring3 之後提供的註解,來代替 xml 文件的配置,最核心的是以下兩個註解

    @Configuration,標註在類上,相當於定義一個配置類,一份 spring 的配置文件

    @Bean,類似於 spring 配置文件中的通過這兩個註解就可以用 java 代碼的方式來完成相關 spring 配置

    持久層設計要考慮的問題有哪些?請談一下你用過的持久層框架都有哪些?

    “持久”就是將數據保存到可掉電式存儲設備中以便今後使用,簡單的說,就是將內存中的數據保存到關係型數據庫、文件系統、消息隊列等提供持久化支持的設備中。持久層就是系統中專註於實現數據持久化的相對獨立的層面。

    持久層設計的目標包括:

    數據存儲邏輯的分離,提供抽象化的數據訪問接口。

    數據訪問底層實現的分離,可以在不修改代碼的情況下切換底層實現。

    資源管理和調度的分離,在數據訪問層實現統一的資源調度(如緩存機制)。

    數據抽象,提供更面向對象的數據操作。

    持久層框架有: Hibernate、MyBatis、TopLink、 Guzz、 jOOQ、 Spring Data、ActiveJDBC

    需要更多技術文檔可以在後台私信【學習】

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

    【其他文章推薦】

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

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

    ※超省錢租車方案

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

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

  • 005.OpenShift訪問控制-權限-角色

    005.OpenShift訪問控制-權限-角色

    一 Kubetcl namespace

    1.1 namespace描述


    Kubernetes namespace提供了將一組相關資源組合在一起的機制。在Red Hat OpenShift容器平台中,project是一個帶有附加註釋的Kubernetes namespace。

    namespace提供以下特性:

    1. 命名資源,以避免基本的命名衝突;
    2. 將管理權限授予受信任的用戶;
    3. 限制用戶資源消耗的能力;
    4. 用戶和用戶組隔離。

    1.2 project


    project提供了一種機制,通過這種機制可以管理普通用戶對資源的訪問。project允許一組用戶獨立於其他組組織和管理其內容,必須允許用戶訪問項目。如果允許創建項目,用戶將自動訪問自己的項目。

    項目可以有單獨的name、display name和description。

    name是項目的唯一標識符,在使用CLI工具或API時都是基於name,name的最大長度為63個字符。

    display name是項目在web控制台中显示的方式(默認為name)。

    description是項目的更詳細描述,並且在web控制台中也可見。

    以下組件適用於項目:

    • Object:pod、service、rc等;
    • Policies:決定用戶可以或不能對對象執行哪些操作的規則;
    • Constraints:可以限制的每種對象的配額。

    1.3 cluster管理


    集群管理員可以創建項目並將項目的管理權限委託給任何用戶。在OpenShift容器平台中,項目用於對相關對象進行分組和隔離。

    管理員可以讓用戶訪問某些項目,允許他們創建自己的項目,並在單個項目中賦予他們管理權限。

    管理員可以將角色應用於允許或限制其創建項目能力的用戶和組,同時可以在用戶初始登錄之前分配角色。

    限制項目創建:從通過身份驗證的用戶和組中刪除self-provisioning集群角色,將拒絕任何新項目的權限。

    [root@master ~]$ oc adm policy remove-cluster-role-from-group \

    self-provisioner \

    system:authenticated \

    system:authenticated:oauth

    授予項目創建:項目創建授予具有self-供應者角色和self-provisione集群角色綁定的用戶。默認情況下,所有經過身份驗證的用戶都可以使用這些角色。

    [root@master ~]$ oc adm policy add-cluster-role-to-group \

    self-provisioner \

    system:authenticated \

    system:authenticated:oauth

    1.4 創建project


    如果項目創建權限被授予用戶,則可以使用oc命令創建project。

    [root@master ~]$ oc new-project demoproject \

    –description=”Demonstrate project creation” \

    –display-name=”demo_project”

    二 OpenShift角色

    2.1 角色概述


    role具有不同級別的訪問和策略,包括集群和本地策略。user和group可以同時與多個role關聯。運行oc description命令查看角色及其綁定的詳細信息。

    在集群策略中具有cluster-admin缺省角色的用戶可以查看集群策略和所有本地策略。在給定的本地策略中具有admin缺省角色的用戶可以基於per-project查看策略。

    可通過以下命令查看當前的集群綁定集,其中显示綁定到不同角色的用戶和組。

    [root@demo ~]# oc describe clusterPolicyBindings :default

    2.2 查看本地policy


    儘管本地角色列表及其關聯的規則集在本地策略中是不可查看的,但是所有缺省角色仍然適用,並且可以添加到用戶或組中,cluster-admin缺省角色除外。但是,本地綁定是可見的。

    可通過以下命令查看當前的本地綁定,其中显示綁定到不同角色的用戶和組。

    [root@demo ~]# oc describe policyBindings :default

    提示:默認情況下,在本地策略中,只會列出admin角色的綁定。但是,如果將其他默認角色添加到本地策略中的用戶和組,也會列出它們。

    2.3 管理role綁定


    向用戶或組添加或綁定角色,從而實現向用戶或組提供角色授予的相關訪問權限。可以使用oc adm policy命令在用戶和組之間添加和刪除角色。

    當使用以下操作管理本地策略的用戶和組角色時,可以使用-n選項指定項目。如果沒有指定,則使用當前項目。

    常見管理本地策略操作:





























    命令 描述
    oc adm policy who-can verb resource 設置哪些用戶可以對資源執行操作
    oc adm policy add-role-to-user role username 將指定角色綁定到指定用戶
    oc adm policy remove-role-from-user role username 從指定用戶中移除給定角色
    oc adm policy remove-user username 刪除指定的用戶及其所有角色
    oc adm policy add-role-to-group role groupname 將指定的角色綁定到指定的組
    oc adm policy remove-role-fromgroup role groupname 從指定組中移除給定角色
    oc adm policy remove-group groupname 刪除指定的組及其所有角色


    還可以使用如下所示的的操作管理cluster policy的role binding,這類命令不需要-n選項,因為cluster policy不在namespace級別上操作。

    常見管理cluster policy操作:




















    命令 描述
    oc adm policy add-cluster-role-to-user role username 將集群中所有項目的指定角色綁定到指定用戶
    oc adm policy remove-cluster-role-from-user role username 為集群中的所有項目從指定用戶中刪除指定角色
    oc adm policy add-cluster-role-togroup role groupname 為集群中的所有項目將指定的角色綁定到指定的組
    oc adm policy remove-cluster-role-from-group role groupname 從集群中所有項目的指定組中移除給定角色


    提示:oc policy命令應用於當前項目,而oc adm policy命令應用於集群範圍的操作。

    示例:在example項目中為developer用戶提供admin角色。

    [root@demo ~]# oc adm policy add-role-to-user admin developer -n example

    [root@demo ~]# oc describe policybindings :default -n example #檢查綁定

    三 安全上下文約束(SCCS)

    3.1 SCCS概述


    OpenShift提供安全上下文約束(SCCS),它控制pod可以執行的操作和它可以訪問的資源。默認情況下,任何容器的執行都只授予受限制的SCC定義的功能。

    SCCS相關命令:

      1 [user@demo ~]$ oc get scc			                        #列出可用的SCC
      2 [user@demo ~]$ oc describe scc scc_name		                #現實特定SCC詳細信息
      3 [user@demo ~]$ oc adm policy add-scc-to-user scc_name user_name
      4 [user@demo ~]$ oc adm policy add-scc-to-group scc_name group_name	#要授予用戶或組特定的SCC
      5 [user@demo ~]$ oc adm policy remove-scc-from-user scc_name user_name
      6 [user@demo ~]$ oc adm policy remove-scc-from-group scc_name group_name	#從特定的SCC中刪除用戶或組


    四 服務賬戶

    4.1 服務賬戶


    service account提供了一種靈活的方法來控制API訪問,而無需共享常規用戶的憑據。如果應用程序需要訪問受限制的SCC未授予的功能,可創建一個新的、特定的service account並將其添加到適當的SCC中。

    例如,在缺省情況下,OpenShift不支持部署需要提升特權的應用程序。若有此需求,可創建一個service account,修改dc,然後添加service account至SCC。

    示例:將anyuid配置為在容器中作為root用戶運行。

    [user@demo ~]$ oc create serviceaccount useroot #創建一個名為useroot的新服務帳戶

    [user@demo ~]$ oc patch dc/demo-app \

    –patch ‘{“spec”:{“template”:{“spec”:{“serviceAccountName”: “useroot”}}}}’ #修改應用程序的DC

    [user@demo ~]$ oc adm policy add-scc-to-user anyuid -z useroot #將useroot服務帳戶添加到anyuid SCC中,作為容器中的根用戶運行

    4.2 Web管理user成員


    OCP平台的默認配置是,在用戶首次登錄成功時,自動創建該用戶對象。

    要管理允許訪問項目的用戶,請以項目管理員或集群管理員的身份登錄到web控制台,並選擇要管理的項目。在左側窗格中,單擊Resources——>membership進入項目member頁面。

    在Users列中,在突出显示的文本框中輸入用戶名。在“添加另一個角色”列中,從用戶所在行的列表中選擇一個角色,然後單擊“添加”。

    4.3 Cli管理user成員


    CLI中如果自動創建對象功能被關閉,集群管理員可通過如下方式創建新用戶:

    [root@master ~]$ oc create user demo-user

    同時還需要在身份認證軟件中創建用戶,如為HTPasswdIdentityProvider才用戶命令如下:

    [root@master ~]$ htpasswd /etc/origin/openshift-passwd demo-user

    要向用戶添加項目角色,首先使用oc project命令輸入項目,然後使用oc policy add-role-to-user命令:

    [root@master ~]$ oc policy add-role-to-user edit demo-user

    要從用戶中刪除項目角色,使用oc policy remove-role-from-user命令:

    [root@master ~]$ oc policy remove-role-from-user edit demo-user

    並不是所有OpenShift角色都由項目限定範圍。要分配這些規則,請使用oc adm policy command命令。

    [root@master ~]$ oc adm policy add-cluster-role-to-user cluster-admin admin

    4.4 身份驗證和授權


    身份驗證層標識與對OpenShift容器平台API的請求相關聯的用戶,然後授權層使用關於請求用戶的身份信息來確定是否應該允許該請求。

    • user和group


    OCP容器平台中的用戶是一個可以向OpenShift API發出請求的實體。通常,這表示與OpenShift交互的develop或administrator的帳戶。

    可以將用戶分配給一個或多個組,每個組表示一組特定的角色(或權限)。當需要通過管理授權策略給多個客戶授權時候,group會比較合適。例如允許訪問項目中的對象,而不是單獨授予用戶。

    • Authentication Tokens


    API調用必須使用訪問令牌或X.509證書進行身份驗證,會話token表示用戶,並且是短期的,默認情況下在24小時內到期。

    可以通過運行oc whoami命令來驗證經過身份驗證的用戶。

    [root@master ~]$ oc login -u demo-user

    [root@master ~]$ oc whoami

    demo-user

    4.5 身份驗證類型


    本環境中,身份驗證由HTPasswdIdentityProvider模塊提供,該模塊根據使用htpasswd命令生成的文件驗證用戶名和密碼。

    OpenShift容器平台支持的其他認證類型包括:

    • Basic Authentication (Remote)


    一種通用的後端集成機制,允許用戶使用針對遠程標識提供者驗證的憑據登錄到OpenShift容器平台。用戶將他們的用戶名和密碼發送到OpenShift容器平台,OpenShift平台通過到服務器的請求驗證這些憑據,並將憑據作為基本的Auth頭傳遞。這要求用戶在登錄過程中向OpenShift容器平台輸入他們的憑據。

    • Request Header Authentication


    用戶使用請求頭值(如X-RemoteUser)登錄到OpenShift容器平台。它通常與身份驗證代理結合使用,身份驗證代理對用戶進行身份驗證,然後通過請求頭值為OpenShift容器平台提供用戶標識。

    • Keystone Authentication


    Keystone是一個OpenStack項目,提供標識、令牌、目錄和策略服務。OpenShift容器平台與Keystone集成,通過配置OpenStack Keystone v3服務器將用戶存儲在內部數據庫中,從而支持共享身份驗證。這種配置允許用戶使用Keystone憑證登錄OpenShift容器平台。

    • LDAP Authentication


    用戶使用他們的LDAP憑證登錄到OpenShift容器平台。在身份驗證期間,LDAP目錄將搜索與提供的用戶名匹配的條目。如果找到匹配項,則嘗試使用條目的專有名稱(DN)和提供的密碼進行簡單綁定。

    • GitHub Authentication


    GitHub使用OAuth,它允許與OpenShift容器平台集成使用OAuth身份驗證來促進令牌交換流。這允許用戶使用他們的GitHub憑證登錄到OpenShift容器平台。為了防止使用GitHub用戶id的未授權用戶登錄到OpenShift容器平台集群,可以將訪問權限限制在特定的GitHub組織中。

    五 管理項目及賬戶

    5.1 前置準備


    準備完整的OpenShift集群,參考《003.OpenShift網絡》2.1。

    5.2 本練習準備


    [student@workstation ~]$ lab secure-resources setup

    5.3 創建htpasswd賬戶

      1 [kiosk@foundation0 ~]$ ssh root@master
      2 [root@master ~]# htpasswd -b /etc/origin/master/htpasswd user1 redhat
      3 [root@master ~]# htpasswd -b /etc/origin/master/htpasswd user2 redhat
      4 #添加基於htpasswd形式的user1和user2,密碼都為redhat。


    5.4 設置策略

      1 [student@workstation ~]$ oc login -u admin -p redhat https://master.lab.example.com	#使用管理員登錄
      2 [student@workstation ~]$ oc adm policy remove-cluster-role-from-group \
      3 self-provisioner system:authenticated:oauth
      4 #刪除所有賦予普通創建項目的功能,該命令可參考本環境如下目錄中的命令。
      5 [student@workstation ~]$ cat /home/student/DO280/labs/secure-resources/configure-policy.sh
      6 #!/bin/bash
      7 oc adm policy remove-cluster-role-from-group \
      8     self-provisioner system:authenticated system:authenticated:oauth


    5.5 驗證策略

      1 [student@workstation ~]$ oc login -u user1 -p redhat https://master.lab.example.com	#使用普通用戶user1登錄
      2 [student@workstation ~]$ oc new-project test					#測試創建project
      3 Error from server (Forbidden): You may not request a new project via this API.


    5.6 創建項目

      1 [student@workstation ~]$ oc login -u admin -p redhat https://master.lab.example.com	#使用集群管理員登錄
      2 [student@workstation ~]$ oc new-project project-user1				#創建兩個項目
      3 [student@workstation ~]$ oc new-project project-user2


    5.7 將項目與user關聯

      1 #選擇項目1
      2 Now using project "project-user1" on server "https://master.lab.example.com:443".
      3 [student@workstation ~]$ oc policy add-role-to-user admin user1		#將user1添加為項目1的管理員
      4 role "admin" added: "user1"
      5 [student@workstation ~]$ oc policy add-role-to-user edit user2		#將user2添加為項目1的開發員
      6 role "edit" added: "user2"
      7 
      8 [student@workstation ~]$ oc project project-user2			        #選擇項目2
      9 Now using project "project-user2" on server "https://master.lab.example.com:443".
     10 [student@workstation ~]$ oc policy add-role-to-user edit user2		#將user2添加為項目2的開發員
     11 role "edit" added: "user2"


    5.8 驗證訪問

      1 [student@workstation ~]$ oc login -u user1 -p redhat https://master.lab.example.com	#使用user1登錄
      2 [student@workstation ~]$ oc project project-user1					#驗證項目1的訪問
      3 Already on project "project-user1" on server "https://master.lab.example.com:443".
      4 [student@workstation ~]$ oc project project-user2					#驗證項目2的訪問
      5 error: You are not a member of project "project-user2".
      6 You have one project on this server: project-user1
      7 
      8 [student@workstation ~]$ oc login -u user2 -p redhat https://master.lab.example.com	#使用user2登錄
      9 [student@workstation ~]$ oc project project-user1
     10 Already on project "project-user1" on server "https://master.lab.example.com:443".	#驗證項目1的訪問
     11 [student@workstation ~]$ oc project project-user2
     12 Now using project "project-user2" on server "https://master.lab.example.com:443".	#驗證項目2的訪問


    5.9 部署特權應用

      1 [student@workstation ~]$ oc login -u user2 -p redhat https://master.lab.example.com
      2 [student@workstation ~]$ oc project project-user1
      3 Now using project "project-user1" on server "https://master.lab.example.com:443".
      4 [student@workstation ~]$ oc new-app --name=nginx --docker-image=registry.lab.example.com/nginx:latest
      5 #使用在項目1上不具備admin權限的用戶user2登錄,並部署應用,會出現如下提示:





    5.10 驗證部署

      1 [student@workstation ~]$ oc get pods




    結論:由上可知,部署失敗是因為容器映像需要root用戶,pod以CrashLoopBackOff或錯誤狀態結束。

    5.11 故障排除


    若要解決此故障需要減少特定項目的安全限制。

    要使用特權訪問運行容器,可創建一個允許pod使用操作系統普通用戶運行的service account。

    如下部分需要具有項目管理員特權的用戶執行,而另一些操作需要具有集群管理員特權的用戶執行。

    本環境中,相關操作命令可以從/home/student/DO280/labs/secure-resources文件夾中的configure-sc.sh腳本運行或複製。

      1 [student@workstation ~]$ oc login -u user1 -p redhat https://master.lab.example.com	#使用項目1的admin賬戶登錄 
      2 [student@workstation ~]$ oc create serviceaccount useroot		  #創建服務賬戶
      3 serviceaccount "useroot" created
      4 [student@workstation ~]$ oc login -u admin -p redhat https://master.lab.example.com	#使用集群管理員登錄
      5 [student@workstation ~]$ oc project project-user1			  #選擇項目1
      6 Already on project "project-user1" on server "https://master.lab.example.com:443".
      7 [student@workstation ~]$ oc adm policy add-scc-to-user anyuid -z useroot  #設置SCC策略
      8 scc "anyuid" added to: ["system:serviceaccount:project-user1:useroot"]    #將服務帳戶與anyuid安全上下文關聯,此操作需要集群管理員用戶。
      9 [student@workstation ~]$ oc login -u user2 -p redhat https://master.lab.example.com	#切換user2用戶
     10 [student@workstation ~]$ oc project project-user1
     11 Already on project "project-user1" on server "https://master.lab.example.com:443".
     12 [student@workstation ~]$ oc patch dc nginx --patch='{"spec":{"template":{"spec":{"serviceAccountName": "useroot"}}}}'



    #更新負責管理nginx的dc資源,任何開發人員用戶都可以執行此操作。本環境中,相關操作命令可以從/home/student/DO280/labs/secure-resources文件夾中的configure-sc.sh腳本運行或複製。

    5.12 驗證確認

      1 [student@workstation ~]$ oc get pods
      2 NAME            READY     STATUS    RESTARTS   AGE
      3 nginx-2-98k8f   1/1       Running   0          3m
      4 

    5.13 暴露服務

      1 [student@workstation ~]$ oc expose svc nginx
      2 route "nginx" exposed
      3 [student@workstation ~]$ oc get svc
      4 NAME      TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
      5 nginx     ClusterIP   172.30.118.63   <none>        80/TCP    13m
      6 [student@workstation ~]$ oc get route
      7 NAME      HOST/PORT                                  PATH      SERVICES   PORT      TERMINATION   WILDCARD
      8 nginx     nginx-project-user1.apps.lab.example.com             nginx      80-tcp                  None


    5.14 測試訪問

      1 [student@workstation ~]$ curl -s http://nginx-project-user1.apps.lab.example.com

    5.15 策略刪除演示

      1 [student@workstation ~]$ oc login -u admin -p redhat
      2 [student@workstation ~]$ oc adm policy add-cluster-role-to-group self-provisioner system:authenticated system:authenticated:oauth
      3 cluster role "self-provisioner" added: ["system:authenticated" "system:authenticated:oauth"]
      4 [student@workstation ~]$ oc delete project project-user1
      5 project "project-user1" deleted
      6 [student@workstation ~]$ oc delete project project-user2
      7 [root@master ~]# htpasswd -D /etc/origin/master/htpasswd user1 
      8 [root@master ~]# htpasswd -D /etc/origin/master/htpasswd user2


    #為所有常規用戶重新啟用項目創建,即重置為初始狀態。本環境中,相關操作命令可以從/home/student/DO280/labs/secure-resources文件夾中的restore-policy.sh腳本運行或複製。

    六 管理加密信息

    6.1 secret特性


    Secret對象類型提供了一種機制來保存敏感信息,如密碼、OCP客戶端配置文件、Docker配置文件和私有倉庫憑據。Secrets將敏感內容與Pod解耦。可以使用Volume插件將Secrets掛載到容器上,或者系統可以使用Secrets代表pod執行操作。

    Secrets的主要特徵包括:

    • Secrets data可以獨立於其定義引用。
    • Secrets data Volume由臨時文件存儲支持。
    • 可以在名稱空間中共享Secrets data。

    6.2 創建Secrets


    在依賴於該Secrets的pod之前創建一個Secrets。

      1 [user@demo ~]$ oc create secret generic secret_name \
      2 --from-literal=key1=secret1 \
      3 --from-literal=key2=secret2	#用secret data創建secret對象
      4 [user@demo ~]$ oc secrets add --for=mount serviceaccount/serviceaccount-name \
      5 secret/secret_name		#更新pod的服務帳戶,允許引用該secrets。

    例如,允許一個運行在指定服務帳戶下的pod掛載一個secrets

    創建一個pod,該pod使用環境變量或數據卷作為文件的方式使用該secret,通常使用模板完成。

    6.3 使用secret暴露Pod


    secrets可以作為數據卷掛載,也可以作為環境變量以便供pod中的容器使用。

    例如,要向pod公開一個secrets,首先創建一個secrets並將username和password以k/v形式配置,然後將鍵名分配給pod的YAML文件env定義。

    示例:創建名為demo-secret的secrets,它定義用戶名和密碼為username/demo-user。

    [user@demo ~]$ oc create secret generic demo-secret \

    –from-literal=username=demo-user

    要使用前面的secret作為MySQL數據庫pod的數據庫管理員密碼,請定義環境變量,並引用secret名稱和密碼。

      1 env:
      2   - name: MYSQL_ROOT_PASSWORD
      3     valueFrom:
      4       secretKeyRef:
      5        key: username
      6        name: demo-secret


    6.4 web端管理secret


    從web控制台管理secret:

    1. 以授權用戶身份登錄到web控制台。

    2. 創建或選擇一個項目來承載secret。

    3. 導航到resource——>secrets。

    6.5 Secret使用場景


    • password和user names


    敏感信息(如password和user name)可以存儲在一個secret中,該secret被掛載為容器中的數據卷。數據显示為位於容器的數據卷目錄中的文件中的內容。然後,應用程序(如數據庫)可以使用這些secret對用戶進行身份驗證。

    • 傳輸層安全性(TLS)和密鑰對


    通過讓集群將簽名證書和密鑰對生成到項目名稱空間中的secret中,可以實現對服務的通信的保護。證書和密鑰對使用PEM格式存儲以類似tls.crt和tls.key的格式存儲在secret的pod中。

    七 ConfigMap對象

    7.1 ConfigMap概述


    ConfigMaps對象類似於secret,但其設計目的是支持處理不包含敏感信息的字符串。ConfigMap對象持有配置數據的鍵值對,這些配置數據可以在pods中使用,或者用於存儲系統組件(如控制器)的配置數據。

    ConfigMap對象提供了將配置數據注入容器的機制。ConfigMap存儲精細的粒度信息,比如單個屬性,或者詳細信息,比如整個配置文件或JSON blob。

    7.2 CLI創建ConfigMap


    可以使用–from-literal選項從CLI創建ConfigMap對象。

    示例:創建一個ConfigMap對象,該對象將IP地址172.20.30.40分配給名為serverAddress的ConfigMap密鑰。

      1 [user@demo ~]$ oc create configmap special-config \
      2 --from-literal=serverAddress=172.20.30.40
      3 [user@demo ~]$ oc get configmaps special-config -o yaml		#查看configMap
      4 apiVersion: v1
      5 data:
      6   key1: serverAddress=172.20.30.40
      7 kind: ConfigMap
      8 metadata:
      9   creationTimestamp: 2017-07-10T17:13:31Z
     10   name: special-config
     11 ……
     12 在配置映射的pod定義中填充環境變量APISERVER。
     13 env:
     14   - name: APISERVER
     15       valueFrom:
     16         configMapKeyRef:
     17           name: special-config
     18           key: serverAddress


    7.3 web管理ConfigMap


    從web控制台管理ConfigMap對象:

    1. 以授權用戶身份登錄到web控制台。

    2. 創建或選擇一個項目來承載ConfigMap。

    3. 導航到資源→配置映射。

    八 加密練習

    8.1 前置準備


    準備完整的OpenShift集群,參考《003.OpenShift網絡》2.1。

    8.2 本練習準備


    [student@workstation ~]$ lab secure-secrets setup

    8.3 創建項目

      1 [student@workstation ~]$ oc login -u developer -p redhat
      2 [student@workstation ~]$ cd /home/student/DO280/labs/secure-secrets/
      3 [student@workstation secure-secrets]$ less mysql-ephemeral.yml		#導入本環境MySQL模板







    模板解讀:

    該mysql-ephemeral.yml模板文件,包含openshift項目中的mysql臨時模板,pod所需的其他環境變量由模板參數初始化,並具有默認值。

    但沒有secret定義,後續操作將手動創建模板所需的secret。

    根據模板的要求,創建一個包含MySQL容器image使用的憑證的secret,將這個secret命名為mysql。

    • 應用程序訪問的數據庫用戶名由database-user定義。
    • 數據庫用戶的密碼由database-password定義。
    • 數據庫管理員密碼由database-root-password定義

    8.4 創建新secret

      1 [student@workstation secure-secrets]$ oc create secret generic mysql \
      2 --from-literal='database-user'='mysql' \
      3 --from-literal='database-password'='redhat' \
      4 --from-literal='database-root-password'='do280-admin'
      5 [student@workstation secure-secrets]$ oc get secret mysql -o yaml	#確認secret




    8.5 創建應用

      1 [student@workstation secure-secrets]$ oc new-app --file=mysql-ephemeral.yml
      2 [student@workstation secure-secrets]$ oc get pods		#確認應用
      3 NAME            READY     STATUS    RESTARTS   AGE
      4 mysql-1-j4fnz   1/1       Running   0          1m


    8.6 端口轉發

      1 [student@workstation secure-secrets]$ cd
      2 [student@workstation ~]$ oc port-forward mysql-1-j4fnz 3306:3306



    提示:驗證完成之前forward不要關閉。

    8.7 確認驗證

      1 [student@workstation ~]$ mysql -uroot -pdo280-admin -h127.0.0.1	#新開終端測試MySQL



    九 管理security policy

    9.1 OCP authorization授權


    OCP定義了用戶可以執行的兩組主要操作:

    與項目相關的操作(也稱為本地策略):project-related

    與管理相關的操作(也稱為集群策略):administration-related

    由於這兩種策略都有大量可用的操作,所以將一些操作分組並定義為角色。














    默認角色 描述
    cluster-admin 此角色中的所有用戶都可以管理OpenShift集群。
    cluster-status 此角色中的所有用戶都提供對集群信息的只讀訪問。


    為管理本地政策,OCP提供以下角色:




















    默認角色 描述
    edit 角色中的用戶可以從項目中創建、更改和刪除公共應用程序資源,比如service和dc。 但是不能對限制範圍和配額等管理資源採取行動,也不能管理對項目的訪問權限。
    basic-user 角色中的用戶具有對項目的讀訪問權。
    self-provisioner 角色中的用戶可以創建新項目。這是一個集群角色,而不是項目角色。
    admin 角色中的用戶可以管理項目中的所有資源,包括授予對項目的其他用戶的訪問權


    除了能夠創建新應用程序之外,admin角色還允許用戶訪問項目資源,比如配額和限制範圍。

    edit角色允許用戶在項目中充當開發人員,但要在項目管理員配置的約束下工作。

    9.2 相關命令

      1 向集群用戶添加角色
      2 $ oc adm policy add-cluster-role-to-user cluster-role username
      3 示例:將普通用戶更改為集群管理員。
      4 $ oc adm policy add-cluster-role-to-user cluster-role username
      5 從用戶中刪除集群角色
      6 $ oc adm policy remove-cluster-role-from-user cluster-role username
      7 示例:將集群管理員更改為普通用戶。
      8 $ oc adm policy remove-cluster-role-from-user cluster-admin username
      9 將指定的用戶綁定到項目中的角色
     10 $ oc adm policy add-role-to-user role-name username -n project
     11 示例:在WordPress項目中dev用戶綁定basic-user角色。
     12 $ oc adm policy add-role-to-user basic-user dev -n wordpress


    9.3 權限及規則


    OpenShift將一組規則集合成一個角色,規則由謂詞和資源定義。如create user是OpenShift中的一條規則,它是一個名為cluster-admin的角色的所擁有的權限的一部分。

      1 $ oc adm policy who-can delete user

    9.4 user類型


    與OCP的交互基於用戶,OCP的user對象表示可以通過向該用戶或用戶組添加角色來從而實現相應權限的授予。

    Regular users:通常以這種用戶類型與OCP交互,常規用戶用User對象表。例如,user1,user2。

    System users:通常在安裝OCP中定義基礎設施時自動創建的,主要目的是使基礎設施能夠安全地與API交互。包括集群管理員(可以訪問所有內容)、每個節點的用戶、路由器和內部倉庫使用的用戶,以及各種其他用戶。還存在一個匿名系統用戶,默認情況下,該用戶用於未經身份驗證的請求。system user主要包括:system:admin、system:openshift-registry和system:node:node1.example.com。

    Service accounts:這些是與項目關聯的特殊系統用戶。有些是在第一次創建項目時自動創建的,項目管理員可以創建多個,以便定義對每個項目內容的訪問。Service accounts由ServiceAccount對象表示。Service accounts主要包括:system:serviceaccount:default:deployer和system:serviceaccount:foo:builder。

    每個用戶在訪問OpenShift容器平台之前必須進行身份驗證。沒有身份驗證或身份驗證無效的API請求將使用匿名系統用戶身份驗證來請求服務。身份驗證成功后,策略確定用戶被授權做什麼。

    9.5 安全上下文約束(SCCS)


    OpenShift提供了一種名為安全上下文約束的安全機制,它限制對資源的訪問,但不限制OpenShift中的操作。

    SCC限制從OpenShift中運行的pod到主機環境的訪問:

    • 運行特權容器
    • 請求容器的額外功能
    • 使用主機目錄作為卷
    • 更改容器的SELinux上下文
    • 更改用戶ID


    社區開發的一些容器可能需要放鬆安全上下文約束,因為它們可能需要訪問默認禁止的資源,例如文件系統、套接字或訪問SELinux上下文。

    OpenShift定義的安全上下文約束(SCCs)可以使用以下命令作為集群管理員列出。

    $ oc get scc

    SCC通常有以下7中SCCS:

    • anyuid
    • hostaccess
    • hostmount-anyuid
    • nonroot
    • privileged
    • restricted(默認)


    $ oc describe scc anyuid #查看某一種SCC詳情

    OpenShift創建的所有容器都使用restricted類型的SCC,它提供了對OpenShift外部資源的有限訪問。

    對於anyuid安全上下文,run as user策略被定義為RunAsAny,表示pod可以作為容器中可用的任何用戶ID運行。這允許需要特定用戶使用特定用戶ID運行命令的容器。

    要將容器更改為使用不同的SCC運行,需要創建綁定到pod的服務帳戶。

    $ oc create serviceaccount service-account-name #首先創建服務賬戶

    $ oc adm policy add-scc-to-user SCC -z service-account #將服務帳戶與SCC關聯

    要確定哪個帳戶可以創建需要更高安全性要求的pod,可以使用scc-subject-review子命令。

    $ oc export pod pod-name > output.yaml

    $ oc adm policy scc-subject-review -f output.yaml

    9.6 OpenShift與SELinux


    OpenShift要求在每個主機上啟用SELinux,以便使用強制訪問控制提供對資源的安全訪問。同樣,由OpenShift管理的Docker容器需要管理SELinux上下文,以避免兼容性問題。

    為了最小化在不支持SELinux的情況下運行容器的風險,可以創建SELinux上下文策略。

    為了更新SELinux上下文,可以使用現有的SCC作為起點生成一個新的SCC。

    $ oc export scc restricted > custom_selinux.yml #導出默認的SCC

    編輯導出的YAML文件以更改SCC名稱和SELinux上下文。

    $ oc create -f yaml_file #使用修改后的ymal重新創建一個SCC

    9.7 特權容器


    有些容器可能需要訪問主機的運行時環境。S2I構建器容器需要訪問宿主docker守護進程來構建和運行容器。

    例如,S2I構建器容器是一類特權容器,它要求訪問超出其自身容器的限制。這些容器可能會帶來安全風險,因為它們可以使用OpenShift節點上的任何資源。通過創建具有特權訪問權的服務帳戶,可以使用SCCs啟用特權容器的訪問。

    十 資源訪問控制綜合實驗

    10.1 前置準備


    準備完整的OpenShift集群,參考《003.OpenShift網絡》2.1。

    10.2 本練習準備

      1 [student@workstation ~]$ lab secure-review setup

    10.3 創建用戶

      1 [root@master ~]# htpasswd /etc/origin/master/htpasswd user-review
      2 New password: 【redhat】
      3 Re-type new password: 【redhat】


    10.4 修改策略

      1 [student@workstation ~]$ oc login -u admin -p redhat
      2 [student@workstation ~]$ oc adm policy remove-cluster-role-from-group \
      3 self-provisioner system:authenticated system:authenticated:oauth
      4 禁用所有常規用戶的項目創建功能


    10.5 確認驗證

      1 [student@workstation ~]$ oc login -u user-review -p redhat
      2 [student@workstation ~]$ oc new-project test			#普通用戶無法創建項目
      3 Error from server (Forbidden): You may not request a new project via this API.


    10.6 創建項目

      1 [student@workstation ~]$ oc login -u admin -p redhat
      2 [student@workstation ~]$ oc new-project secure-review		#使用管理員創建項目


    10.7 授權用戶

      1 [student@workstation ~]$ oc project secure-review
      2 Already on project "secure-review" on server "https://master.lab.example.com:443".
      3 [student@workstation ~]$ oc policy add-role-to-user edit user-review	#將edit的role和user-review進行關聯


    10.8 測試訪問

      1 [student@workstation ~]$ oc login -u user-review -p redhat
      2 [student@workstation ~]$ oc project secure-review		#測試訪問
      3 Already on project "secure-review" on server "https://master.lab.example.com:443".


    10.9 檢查模板

      1 [student@workstation ~]$ cd /home/student/DO280/labs/secure-review/
      2 [student@workstation secure-review]$ less mysql-ephemeral.yml





    模板解讀:

    該mysql-ephemeral.yml模板文件,包含openshift項目中的mysql臨時模板,pod所需的其他環境變量由模板參數初始化,並具有默認值。

    但沒有secret定義,後續操作將手動創建模板所需的secret。

    根據模板的要求,創建一個包含MySQL容器image使用的憑證的secret,將這個secret命名為mysql。

    • 應用程序訪問的數據庫用戶名由database-user定義。
    • 數據庫用戶的密碼由database-password定義。
    • 數據庫管理員密碼由database-root-password定義


    使用user-review developer用戶創建一個名為mysql的secret。這個secret應該存儲用戶名mysql、密碼redhat和數據庫管理員密碼do280-admin。

    數據庫用戶名由database-user定義。此用戶的密碼由mysql secret密鑰定義。

    數據庫管理員密碼由database-root-password定義。

    10.10 創建secret

      1 [student@workstation secure-review]$ oc create secret generic mysql \
      2 --from-literal='database-user'='mysql' \
      3 --from-literal='database-password'='redhat' \
      4 --from-literal='database-root-password'='do280-admin'
      5 [student@workstation secure-review]$ oc get secret mysql -o yaml	#確認驗證secret


    10.11 部署應用

      1 [student@workstation secure-review]$ oc new-app --file=mysql-ephemeral.yml
      2 [student@workstation secure-review]$ oc get pods
      3 NAME            READY     STATUS    RESTARTS   AGE
      4 mysql-1-2lr7t   1/1       Running   0          31s


    10.12 轉發端口

      1 [student@workstation ~]$ oc port-forward mysql-1-2lr7t 3306:3306

    10.13 測試訪問

      1 [student@workstation ~]$ mysql -umysql -predhat -h127.0.0.1

    10.14 部署phpmyadmin應用


    使用內部倉庫registry.lab.example.com的image部署phpmyadmin:4.7容器。phpmyadmin:4.7容器需要名為PMA_HOST的環境變量來提供MySQL服務器的IP地址。

    使用模板創建一個基於FQND的MySQL pod的service。

    為使用模板創建的MySQL服務器pod使用服務FQDN,該模板是mysql.secure-review.svc.cluster.local。

      1 [student@workstation ~]$ oc new-app --name=phpmyadmin \
      2 --docker-image=registry.lab.example.com/phpmyadmin/phpmyadmin:4.7 \
      3 -e PMA_HOST=mysql.secure-review.svc.cluster.local






    結論:該命令會發出警告,提示需要root特權。默認情況下,OpenShift不支持使用操作系統的root用戶運行容器。

    10.15 查看pod

      1 [student@workstation ~]$ oc get pods
      2 NAME                 READY     STATUS    RESTARTS   AGE
      3 mysql-1-2lr7t        1/1       Running   0          8m
      4 phpmyadmin-1-v7tl7   0/1       Error     2          1m
      5 因為沒有root權限,因此部署失敗,需要提權。


    10.16 授予權限

      1 [student@workstation ~]$ oc login -u admin -p redhat			#使用管理員登錄
      2 [student@workstation ~]$ oc create serviceaccount phpmyadmin-account	#首先創建服務賬戶
      3 [student@workstation ~]$ oc adm policy add-scc-to-user anyuid -z phpmyadmin-account
      4 scc "anyuid" added to: ["system:serviceaccount:secure-review:phpmyadmin-account"]	#將服務帳戶與anyuid安全上下文關聯


    10.17 更新應用

      1 [student@workstation ~]$ oc patch dc phpmyadmin --patch='{"spec":{"template":{"spec":{"serviceAccountName": "phpmyadmin-account"}}}}'


    #更新負責管理phpmyadmin的dc資源,任何開發人員用戶都可以執行此操作。

    本環境中,相關操作命令可以從/home/student/DO280/labs/secure-review文件夾中的patch-dc.sh腳本運行或複製。

    10.18 確認驗證

      1 [student@workstation ~]$ oc login -u user-review -p redhat
      2 [student@workstation ~]$ oc get pods			#確認pod是否正常
      3 NAME                 READY     STATUS    RESTARTS   AGE
      4 mysql-1-2lr7t        1/1       Running   0          13m
      5 phpmyadmin-2-bdjvq   1/1       Running   0          1m


    10.19 暴露服務

      1 [student@workstation ~]$ oc expose svc phpmyadmin --hostname=phpmyadmin.apps.lab.example.com

    10.20 訪問測試

      1 [student@workstation ~]$ curl -s http://phpmyadmin.apps.lab.example.com

    10.21 確認及刪除

      1 [student@workstation ~]$ lab secure-review grade		#環境腳本判斷
      2 [student@workstation ~]$ oc login -u admin -p redhat
      3 [student@workstation ~]$ oc adm policy add-cluster-role-to-group \
      4 self-provisioner system:authenticated system:authenticated:oauth
      5 [student@workstation ~]$ oc delete project secure-review
      6 [student@workstation ~]$ ssh root@master htpasswd -D \
      7 /etc/origin/master/htpasswd user-review			#刪除用戶
      8 [student@workstation ~]$ oc delete user user-review		#刪除項目

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

    【其他文章推薦】

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

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

    ※回頭車貨運收費標準

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

    ※超省錢租車方案

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

  • Linux 進程間通信(IPC)總結

    Linux 進程間通信(IPC)總結

    概述

    一個大型的應用系統,往往需要眾多進程協作,進程(Linux進程概念見附1)間通信的重要性顯而易見。本系列文章闡述了 Linux 環境下的幾種主要進程間通信手段。

    進程隔離

    進程隔離是為保護操作系統中進程互不干擾而設計的一組不同硬件和軟件的技術。這個技術是為了避免進程A寫入進程B的情況發生。 進程的隔離實現,使用了虛擬地址空間。進程A的虛擬地址和進程B的虛擬地址不同,這樣就防止進程A將數據信息寫入進程B。

    虛擬地址空間

    當創建一個進程時,操作系統會為該進程分配一個 4GB 大小的虛擬進程地址空間。之所以是 4GB ,是因為在 32 位的操作系統中,一個指針長度是 4 字節,而 4 字節指針的尋址能力是從 0x00000000~0xFFFFFFFF ,最大值 0xFFFFFFFF 表示的即為 4GB 大小的容量。與虛擬地址空間相對的,還有一個物理地址空間,這個地址空間對應的是真實的物理內存。要注意的是這個 4GB 的地址空間是“虛擬”的,並不是真實存在的,而且每個進程只能訪問自己虛擬地址空間中的數據,無法訪問別的進程中的數據,通過這種方法實現了進程間的地址隔離。

    針對 Linux 操作系統,將最高的1G字節(從虛擬地址 0xC0000000 到 0xFFFFFFFF )供內核使用,稱為內核空間,而較低的 3G 字節(從虛擬地址 0x00000000 到0xBFFFFFFF),供各個進程使用,稱為用戶空間。每個進程都可以通過系統調用進入到內核。其中在 Linux 系統中,進程的用戶空間是獨立的,而內核空間是共有的,進程切換時,用戶空間切換,內核空間不變

    創建虛擬地址空間目的是為了解決進程地址空間隔離的問題。但程序要想執行,必須運行在真實的內存上,所以,必須在虛擬地址與物理地址間建立一種映射關係。這樣,通過映射機制,當程序訪問虛擬地址空間上的某個地址值時,就相當於訪問了物理地址空間中的另一個值。人們想到了一種分段、分頁的方法,它的思想是在虛擬地址空間和物理地址空間之間做一一映射。這種思想理解起來並不難,操作系統保證不同進程的地址空間被映射到物理地址空間中不同的區域上,這樣每個進程最終訪問到的物理地址空間都是彼此分開的。通過這種方式,就實現了進程間的地址隔離。

     

    系統調用/內核態/用戶態

    雖然從邏輯上抽離出用戶空間和內核空間;但是不可避免的的是,總有那麼一些用戶空間需要訪問內核的資源;比如應用程序訪問文件,網絡是很常見的事情,怎麼辦呢?

    用戶空間訪問內核空間的唯一方式就是系統調用;通過這個統一入口接口,所有的資源訪問都是在內核的控制下執行,以免導致對用戶程序對系統資源的越權訪問,從而保障了系統的安全和穩定。用戶軟件良莠不齊,要是它們亂搞把系統玩壞了怎麼辦?因此對於某些特權操作必須交給安全可靠的內核來執行。

    當一個任務(進程)執行系統調用而陷入內核代碼中執行時,我們就稱進程處於內核運行態(或簡稱為內核態)此時處理器處於特權級最高的(0級)內核代碼中執行。當進程在執行用戶自己的代碼時,則稱其處於用戶運行態(用戶態)。即此時處理器在特權級最低的(3級)用戶代碼中運行。處理器在特權等級高的時候才能執行那些特權CPU指令。

    IPC 通信原理

    理解了上面的幾個概念,我們再來看看進程之間是如何實現通信的。

    通常的做法是消息發送方將要發送的數據存放在內存緩存區中,通過系統調用進入內核態。然後內核程序在內核空間分配內存,開闢一塊內核緩存區,調用 copy_from_user() 函數將數據從用戶空間的內存緩存區拷貝到內核空間的內核緩存區中。同樣的,接收方進程在接收數據時在自己的用戶空間開闢一塊內存緩存區,然後內核程序調用 copy_to_user() 函數將數據從內核緩存區拷貝到接收進程的內存緩存區。這樣數據發送方進程和數據接收方進程就完成了一次數據傳輸,我們稱完成了一次進程間通信。如下圖:

    進程間通信方式

    Linux 進程間基本的通信方式主要有:管道(pipe) (包括匿名管道和命名管道)、信號(signal)、消息隊列(queue)、共享內存、信號量和套接字。

    管道

    管道的實質是一個內核緩衝區(調用 pipe 函數來開闢),管道的作用正如其名,需要通信的兩個進程在管道的兩端,進程利用管道傳遞信息。管道對於管道兩端的進程而言,就是一個文件,但是這個文件比較特殊,它不屬於文件系統並且只存在於內存中。 Linux一切皆文件,操作系統為管道提供操作的方法:文件操作,用 fork 來共享管道原理。

    管道依據是否有名字分為匿名管道和命名管道(有名管道),這兩種管道有一定的區別。

    匿名管道有幾個重要的限制:

    1. 管道是半雙工的,數據只能在一個方向上流動,A進程傳給B進程,不能反向傳遞
    2. 管道只能用於父子進程或兄弟進程之間的通信,即具有親緣關係的進程。

    命名管道允許沒有親緣關係的進程進行通信。命名管道不同於匿名管道之處在於它提供了一個路徑名與之關聯,這樣一個進程即使與創建有名管道的進程不存在親緣關係,只要可以訪問該路徑,就能通過有名管道互相通信。

    pipe 函數接受一個參數,是包含兩個整數的數組,如果調用成功,會通過 pipefd[2] 傳出給用戶程序兩個文件描述符,需要注意 pipefd[0] 指向管道的讀端, pipefd[1] 指向管道的寫端,那麼此時這個管道對於用戶程序就是一個文件,可以通過 read(pipefd [0]);或者 write(pipefd [1]) 進行操作。pipe 函數調用成功返回 0,否則返回 -1.

    那麼再來看看通過管道進行通信的步驟:

    • 父進程創建管道,得到兩個文件描述符指向管道的兩端
    • 利用fork函數創建齣子進程,則子進程也得到兩個文件描述符指向同一管道
    • 父進程關閉讀端(pipe[0]),子進程關閉寫端pipe[1],則此時父進程可以往管道中進行寫操作,子進程可以從管道中讀,從而實現了通過管道的進程間通信。

         

    管道的特點:

    • 只能單向通信

    兩個文件描述符,用一個,另一個不用,不用的文件描述符就要 close

    • 只能血緣關係的進程進行通信

    • 依賴於文件系統

    • 生命周期隨進程

    • 面向字節流的服務

    面向字節流:數據無規則,沒有明顯邊界,收發數據比較靈活:對於用戶態,可以一次性發送也可以分次發送,當然接受數據也如此;而面向數據報:數據有明顯邊界,數據只能整條接受 

    • 管道內部提供了同步機制

    臨界資源: 大家都能訪問到的共享資源

    臨界區: 對臨界資源進行操作的代碼

    同步: 臨界資源訪問的可控時序性(一個操作完另一個才可以操作)

    互斥: 對臨界資源同一時間的唯一訪問性(保護臨界資源安全)

    說明:因為管道通信是單向的,在上面的例子中我們是通過子進程寫父進程來讀,如果想要同時父進程寫而子進程來讀,就需要再打開另外的管道;

    管道的讀寫端通過打開的文件描述符來傳遞,因此要通信的兩個進程必須從它們的公共祖先那裡繼承管道的件描述符。 上面的例子是父進程把文件描述符傳給子進程之後父子進程之 間通信,也可以父進程fork兩次,把文件描述符傳給兩個子進程,然後兩個子進程之間通信, 總之 需要通過fork傳遞文件描述符使兩個進程都能訪問同一管道,它們才能通信。

    四個特殊情況:

    1. 如果所有指向管道寫端的文件描述符都關閉了,而仍然有進程從管道的讀端讀數據,那麼管道中剩餘的數據都被讀取后,再次read會返回0,就像讀到文件末尾一樣

    2. 如果有指向管道寫端的文件描述符沒關閉,而持有管道寫端的進程也沒有向管道中寫數據,這時有進程從管道讀端讀數據,那麼管道中剩餘的數據都被讀取后,再次read會阻塞,直到管道中有數據可讀了才讀取數據並返回。

    3. 如果所有指向管道讀端的文件描述符都關閉了,這時有進程指向管道的寫端write,那麼該進程會收到信號SIGPIPE,通常會導致進程異常終止。

    4. 如果有指向管道讀端的文件描述符沒關閉,而持有管道寫端的進程也沒有從管道中讀數據,這時有進程向管道寫端寫數據,那麼在管道被寫滿時再write會阻塞,直到管道中有空位置了才寫入數據並返回。

    命名管道FIFO

    在管道中,只有具有血緣關係的進程才能進行通信,對於後來的命名管道,就解決了這個問題。FIFO 不同於管道之處在於它提供一個路徑名與之關聯,以 FIFO 的文件形式存儲於文件系統中。命名管道是一個設備文件,因此,即使進程與創建FIFO的進程不存在親緣關係,只要可以訪問該路徑,就能夠通過 FIFO 相互通信。值得注意的是, FIFO (first input first output) 總是按照先進先出的原則工作,第一個被寫入的數據將首先從管道中讀出。

    命名管道的創建

    創建命名管道的系統函數有兩個: mknod 和 mkfifo。兩個函數均定義在頭文件 sys/stat.h,
    函數原型如下:

    #include <sys/types.h>
    #include <sys/stat.h>
    int mknod(const char *path,mode_t mod,dev_t dev);
    int mkfifo(const char *path,mode_t mode);

    函數 mknod 參數中 path 為創建的命名管道的全路徑名: mod 為創建的命名管道的模指明其存取權限; dev 為設備值,該值取決於文件創建的種類,它只在創建設備文件時才會用到。這兩個函數調用成功都返回 0,失敗都返回 -1。

    命名管道打開特性:

    1. 如果用只讀打開命名管道,open 函數將阻塞等待直至有其他進程以寫的方式打開這個命名管道,如果沒有進程以寫的方式發開這個命名管道,程序將停在此處

    2. 如果用只寫打開命名管道,open 函數將阻塞等到直至有其他進程以讀的方式打開這個命名管道,如果沒有進程以讀的方式發開這個命名管道,程序將停在此處;

    3. 如果用讀寫打開命名管道,則不會阻塞(但是管道是單向)

    System V IPC   

    IPC(Inter-Process Communication)是指多個進程之間相互通信,交換信息的方法,System V 是 Unix 操作系統最早的商業發行版,由 AT&T(American Telephone & Telegraph)開發。System V IPC 是指 Linux 引入自 System V 的進程通信機制,一共有三種:

    • 信號量,用來管理對共享資源的訪問;

    • 共享內存,用來高效地實現進程間的數據共享;

    • 消息隊列,用來實現進程間數據的傳遞。

    這三種統稱 IPC 資源,每個 IPC 資源都是請求時動態創建的,都是永駐內存,除非被進程显示釋放,都是可以被任一進程使用。每個 IPC 資源都使用一個 32 位的 IPC 關鍵字和 32 位的 IPC 標識符,前者類似文件系統中的路徑名,由程序自由定製,後者類似打開文件的文件描述符,由內核統一分配,在系統內部是唯一的,當多個進程使用同一個IPC資源通信時需要該資源的 IPC 標識符。     

    創建新的 IPC 資源時需要指定 IPC 關鍵字,如果沒有與之關聯的 IPC 資源,則創建一個新的 IPC 資源;如果已經存在,則判斷當前進程是否具有訪問權限,是否超過資源使用限制等,如果符合條件則返回該資源的 IPC 標識符。為了避免兩個不同的 IPC 資源使用相同的 IPC 關鍵字,創建時可以指定IPC關鍵字為 IPC_PRIVATE,由內核負責生成一個唯一的關鍵字。   

    創建新的 IPC 資源時最後一個參數可以包括三個標誌,PC_CREAT 說明如果IPC資源不存在則必須創建它,IPC_EXCL 說明如果資源已經存在且設置了 PC_CREAT 標誌則創建失敗,IPC_NOWAIT 說明訪問 IPC 資源時進程從不阻塞。 

    信號量

    信號量(semaphore)是一種用於提供不同進程之間或者一個給定的不同線程間同步手段的原語。信號量多用於進程間的同步與互斥,簡單的說一下同步和互斥的意思:

    同步:處理競爭就是同步,安排進程執行的先後順序就是同步,每個進程都有一定的先後執行順序。

    互斥:互斥訪問不可共享的臨界資源,同時會引發兩個新的控制問題(互斥可以說是特殊的同步)。

    競爭:當併發進程競爭使用同一個資源的時候,我們就稱為競爭進程。

    共享資源通常分為兩類:一類是互斥共享資源,即任一時刻只允許一個進程訪問該資源;另一類是同步共享資源,即同一時刻允許多個進程訪問該資源;信號量是解決互斥共享資源的同步問題而引入的機制。

    下面說一下信號量的工作機制,可以直接理解成計數器(當然其實加鎖的時候肯定不能這麼簡單,不只只是信號量了),信號量會有初值(>0),每當有進程申請使用信號量,通過一個 P 操作來對信號量進行-1操作,當計數器減到 0 的時候就說明沒有資源了,其他進程要想訪問就必須等待(具體怎麼等還有說法,比如忙等待或者睡眠),當該進程執行完這段工作(我們稱之為臨界區)之後,就會執行 V 操作來對信號量進行 +1 操作。

    • 臨界區:臨界區指的是一個訪問共用資源(例如:共用設備或是共用存儲器)的程序片段,而這些共用資源又無法同時被多個線程訪問的特性。

    • 臨界資源:只能被一個進程同時使用(不可以多個進程共享),要用到互斥。

    我們可以說信號量也是進程間通信的一種方式,比如互斥鎖的簡單實現就是信號量,一個進程使用互斥鎖,並通知(通信)其他想要該互斥鎖的進程,阻止他們的訪問和使用。

    當有進程要求使用共享資源時,需要執行以下操作:

    1. 系統首先要檢測該資源的信號量;

    2. 若該資源的信號量值大於 0,則進程可以使用該資源,此時,進程將該資源的信號量值減1;

    3. 若該資源的信號量值為 0,則進程進入休眠狀態,直到信號量值大於 0 時進程被喚醒,訪問該資源;

           當進程不再使用由一個信號量控制的共享資源時,該信號量值增加 1,如果此時有進程處於休眠狀態等待此信號量,則該進程會被喚醒

    每個信號量集都有一個與其相對應的結構,該結構定義如下:

    /* Data structure describing a set of semaphores.  */  
    struct semid_ds  
    {  
        struct ipc_perm sem_perm;   /* operation permission struct */  
        struct sem *sem_base;       /* ptr to array of semaphores in set */  
        unsigned short sem_nsems;   /* # of semaphores in set */  
        time_t sem_otime;           /* last-semop() time */  
        time_t sem_ctime;           /* last-change time */  
    };  
      
    /* Data structure describing each of semaphores.  */  
    struct sem  
    {  
        unsigned short semval;  /* semaphore value, always >= 0 */  
        pid_t          sempid;  /* pid for last successful semop(), SETVAL, SETALL */  
        unsigned short semncnt; /* # processes awaiting semval > curval */  
        unsigned short semzcnt; /* # processes awaiting semval == 0 */  
    };  

    信號量集的結構圖如下所示:

    消息隊列

    消息隊列,是消息的鏈接表,存放在內核中。一個消息隊列由一個標識符(即隊列 ID)來標識。其具有以下特點:

    1. 消息隊列是面向記錄的,其中的消息具有特定的格式以及特定的優先級。

    2. 消息隊列獨立於發送與接收進程。進程終止時,消息隊列及其內容並不會被刪除。

    3. 消息隊列可以實現消息的隨機查詢,消息不一定要以先進先出的次序讀取,也可以按消息的類型讀取。

    原型

    1 #include <sys/msg.h>
    2 // 創建或打開消息隊列:成功返回隊列ID,失敗返回-1
    3 int msgget(key_t key, int flag);
    4 // 添加消息:成功返回0,失敗返回-1
    5 int msgsnd(int msqid, const void *ptr, size_t size, int flag);
    6 // 讀取消息:成功返回消息數據的長度,失敗返回-1
    7 int msgrcv(int msqid, void *ptr, size_t size, long type,int flag);
    8 // 控制消息隊列:成功返回0,失敗返回-1
    9 int msgctl(int msqid, int cmd, struct msqid_ds *buf);

    在以下兩種情況下,msgget 將創建一個新的消息隊列:

    • 如果沒有與鍵值key相對應的消息隊列,並且flag中包含了IPC_CREAT標誌位。

    • key參數為IPC_PRIVATE

    函數msgrcv在讀取消息隊列時,type參數有下面幾種情況:

    • type == 0,返回隊列中的第一個消息;

    • type > 0,返回隊列中消息類型為 type 的第一個消息;

    • type < 0,返回隊列中消息類型值小於或等於 type 絕對值的消息,如果有多個,則取類型值最小的消息。

    可以看出,type 值非 0 時用於以非先進先出次序讀消息。也可以把 type 看做優先級的權值。

    共享內存

    共享內存是 System V 版本的最後一個進程間通信方式。共享內存,顧名思義就是允許兩個不相關的進程訪問同一個邏輯內存,共享內存是兩個正在運行的進程之間共享和傳遞數據的一種非常有效的方式。不同進程之間共享的內存通常為同一段物理內存。進程可以將同一段物理內存連接到他們自己的地址空間中,所有的進程都可以訪問共享內存中的地址。如果某個進程向共享內存寫入數據,所做的改動將立即影響到可以訪問同一段共享內存的任何其他進程。

    特別提醒:共享內存並未提供同步機制,也就是說,在第一個進程結束對共享內存的寫操作之前,並無自動機制可以阻止第二個進程開始對它進行讀取,所以我們通常需要用其他的機制來同步對共享內存的訪問,例如信號量。

    共享內存的通信原理
    在 Linux 中,每個進程都有屬於自己的進程控制塊(PCB)和地址空間(Addr Space),並且都有一個與之對應的頁表,負責將進程的虛擬地址與物理地址進行映射,通過內存管理單元(MMU)進行管理。兩個不同的虛擬地址通過頁表映射到物理空間的同一區域,它們所指向的這塊區域即共享內存。

    共享內存的通信原理示意圖:

     

    對於上圖我的理解是:當兩個進程通過頁表將虛擬地址映射到物理地址時,在物理地址中有一塊共同的內存區,即共享內存,這塊內存可以被兩個進程同時看到。這樣當一個進程進行寫操作,另一個進程讀操作就可以實現進程間通信。但是,我們要確保一個進程在寫的時候不能被讀,因此我們使用信號量來實現同步與互斥。

    對於一個共享內存,實現採用的是引用計數的原理,當進程脫離共享存儲區后,計數器減一,掛架成功時,計數器加一,只有當計數器變為零時,才能被刪除。當進程終止時,它所附加的共享存儲區都會自動脫離。

    為什麼共享內存速度最快?

    藉助上圖說明:Proc A 進程給內存中寫數據, Proc B 進程從內存中讀取數據,在此期間一共發生了兩次複製

    (1)Proc A 到共享內存       (2)共享內存到 Proc B

    因為直接在內存上操作,所以共享內存的速度也就提高了。

    共享內存的接口函數以及指令

    查看系統中的共享存儲段

    ipcs -m

    刪除系統中的共享存儲段

    ipcrm -m [shmid]

    shmget ( ):創建共享內存

    int shmget(key_t key, size_t size, int shmflg);

    [參數key]:由ftok生成的key標識,標識系統的唯一IPC資源。

    [參數size]:需要申請共享內存的大小。在操作系統中,申請內存的最小單位為頁,一頁是4k字節,為了避免內存碎片,我們一般申請的內存大小為頁的整數倍。

    [參數shmflg]:如果要創建新的共享內存,需要使用IPC_CREAT,IPC_EXCL,如果是已經存在的,可以使用IPC_CREAT或直接傳0。

    [返回值]:成功時返回一個新建或已經存在的的共享內存標識符,取決於shmflg的參數。失敗返回-1並設置錯誤碼。

    shmat ( ):掛接共享內存

    void *shmat(int shmid, const void *shmaddr, int shmflg);

    [參數shmid]:共享存儲段的標識符。

    [參數*shmaddr]:shmaddr = 0,則存儲段連接到由內核選擇的第一個可以地址上(推薦使用)。

    [參數shmflg]:若指定了SHM_RDONLY位,則以只讀方式連接此段,否則以讀寫方式連接此段。

    [返回值]:成功返回共享存儲段的指針(虛擬地址),並且內核將使其與該共享存儲段相關的shmid_ds結構中的shm_nattch計數器加1(類似於引用計數);出錯返回-1。

    shmdt ( ):去關聯共享內存:當一個進程不需要共享內存的時候,就需要去關聯。該函數並不刪除所指定的共享內存區,而是將之前用shmat函數連接好的共享內存區脫離目前的進程。

    int shmdt(const void *shmaddr);

    [參數*shmaddr]:連接以後返回的地址。

    [返回值]:成功返回0,並將shmid_ds結構體中的 shm_nattch計數器減1;出錯返回-1。

    shmctl ( ):銷毀共享內存

    int shmctl(int shmid, int cmd, struct shmid_ds *buf);

    [參數shmid]:共享存儲段標識符。

    [參數cmd]:指定的執行操作,設置為IPC_RMID時表示可以刪除共享內存。

    [參數*buf]:設置為NULL即可。

    [返回值]:成功返回0,失敗返回-1。

    POSIX 消息隊列   

    POSIX 消息隊列是 POSIX 標準在 2001 年定義的一種 IPC 機制,與 System V 中的消息隊列相比有如下差異:

    • 更簡單的基於文件的應用接口,Linux 通過 mqueue 的特殊文件系統來實現消息隊列,隊列名跟文件名類似,必須以”/”開頭,每個消息隊列在文件系統內都有一個對應的索引節點,返回的隊列描述符實際是一個文件描述符

    • 完全支持消息優先級,消息在隊列中是按照優先級倒序排列的(即0表示優先級最低)。當一條消息被添加到隊列中時,它會被放置在隊列中具有相同優先級的所有消息之後。如果一個應用程序無需使用消息優先級,那麼只需要將msg_prio指定為0即可。

    • 完全支持消息到達的異步通知,當新消息到達且當前隊列為空時會通知之前註冊過表示接受通知的進程。在任何一個時刻都只有一個進程能夠向一個特定的消息隊列註冊接收通知。如果一個消息隊列上已經存在註冊進程了,那麼後續在該隊列上的註冊請求將會失敗。可以給進程發送信號或者另起一個線程調用通知函數完成通知。當通知完成時,註冊即被撤銷,進程需要繼續接受通知則必須重新註冊。

    • 用於阻塞發送與接收操作的超時機制,可以指定阻塞的最長時間,超時自動返回 

    套接字:

    套接字是更為基礎的進程間通信機制,與其他方式不同的是,套接字可用於不同機器之間的進程間通信。

    有兩種類型的套接字:基於文件的和面向網絡的。

    • Unix 套接字是基於文件的,並且擁有一個“家族名字”–AF_UNIX,它代表地址家族 (address family):UNIX。

    • 第二類型的套接字是基於網絡的,它也有自己的家族名字–AF_INET,代表地址家族 (address family):INTERNET

    不管採用哪種地址家族,都有兩種不同的套接字連接:面向連接的和無連接的。

    • 面向連接的套接字 (SOCK_STREAM):進行通信前必須建立一個連接,面向連接的通信提供序列化的、可靠地和不重複的數據交付,而沒有記錄邊界。

    這意味着每條信息可以被拆分成多個片段,並且每個片段都能確保到達目的地,然後在目的地將信息拼接起來。

    實現這種連接類型的主要協議是傳輸控制協議 (TCP)。

    • 無連接的套接字 (SOCK_DGRAM):在通信開始之前並不需要建立連接,在數據傳輸過程中並無法保證它的順序性、可靠性或重複性。

    然而,數據報確實保存了記錄邊界,這就意味着消息是以整體發送的,而並非首先分成多個片段。

    由於面向連接的套接字所提供的保證,因此它們的設置以及對虛擬電路連接的維護需要大量的開銷。然而,數據報不需要這些開銷,即它的成本更加“低廉”

    實現這種連接類型的主要協議是用戶數據報協議 (UDP)。

    信號

    信號是軟件層次上對中斷機制的一種模擬,是一種異步通信方式,進程不必通過任何操作來等待信號的到達。信號可以在用戶空間進程和內核之間直接交互,內核可以利用信號來通知用戶空間的進程發生了哪些系統事件。

    信號來源:

    信號事件的發生有兩個來源:硬件來源,比如我們按下了鍵盤或者其它硬件故障;軟件來源,最常用發送信號的系統函數是 kill, raise, alarm 和 setitimer 以及 sigqueue 函數,軟件來源還包括一些非法運算等操作。

    進程對信號的響應:

    進程可以通過三種方式來響應信號:

    • 忽略信號,即對信號不做任何處理,但是有兩個信號是不能忽略的:SIGKLL 和 SIGSTOP;

    • 捕捉信號,定義信號處理函數,當信號發生時,執行相應的處理函數;

    • 執行缺省操作,Linux 對每種信號都規定了默認操作。

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

    【其他文章推薦】

    ※超省錢租車方案

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

    ※回頭車貨運收費標準

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

    FB行銷專家,教你從零開始的技巧

  • Spring Data JPA

    Spring Data JPA

    一、JPA概述:

      JPA的全稱是Java Persistence API, 即Java 持久化API,是SUN公司推出的一套基於ORM的規範,內部是由一系列的接口和抽象類構成。JPA通過JDK 5.0註解描述對象-關係表的映射關係,並將運行期的實體對象持久化到數據庫中。

      JPA的優勢:標準化、容器級特性的支持、簡單方便、查詢能力、高級特性

    二、JPA與Hibernate的關係:

      JPA規範本質上就是一種ORM規範,注意不是ORM框架——因為JPA並未提供ORM實現,它只是制訂了一些規範,提供了一些編程的API接口,但具體實現則由服務廠商來提供實現。JPAHibernate的關係就像JDBCJDBC驅動的關係,JPA是規範,Hibernate除了作為ORM框架之外,它也是一種JPA實現。

     

     

     

    三、JPA環境搭建:

    1、創建一個maven工程,在pom.xml中導入對應的坐標

     1    <properties>
     2         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
     3         <project.hibernate.version>5.0.7.Final</project.hibernate.version>
     4     </properties>
     5 
     6     <dependencies>
     7         <!-- junit -->
     8         <dependency>
     9             <groupId>junit</groupId>
    10             <artifactId>junit</artifactId>
    11             <version>4.12</version>
    12             <scope>test</scope>
    13         </dependency>
    14 
    15         <!-- hibernate對jpa的支持包 -->
    16         <dependency>
    17             <groupId>org.hibernate</groupId>
    18             <artifactId>hibernate-entitymanager</artifactId>
    19             <version>${project.hibernate.version}</version>
    20         </dependency>
    21 
    22         <!-- c3p0 -->
    23         <dependency>
    24             <groupId>org.hibernate</groupId>
    25             <artifactId>hibernate-c3p0</artifactId>
    26             <version>${project.hibernate.version}</version>
    27         </dependency>
    28 
    29         <!-- log日誌 -->
    30         <dependency>
    31             <groupId>log4j</groupId>
    32             <artifactId>log4j</artifactId>
    33             <version>1.2.17</version>
    34         </dependency>
    35 
    36         <!-- Mysql and MariaDB -->
    37         <dependency>
    38             <groupId>mysql</groupId>
    39             <artifactId>mysql-connector-java</artifactId>
    40             <version>5.1.6</version>
    41         </dependency>
    42     </dependencies>

    2、編寫實體類和數據表的映射配置,創建實體類以後,使用對應的註釋配置映射關係

         @Entity
                作用:指定當前類是實體類。
            @Table
                作用:指定實體類和表之間的對應關係。
                屬性:
                    name:指定數據庫表的名稱
            @Id
                作用:指定當前字段是主鍵。
            @GeneratedValue
                作用:指定主鍵的生成方式。。
                屬性:
                    strategy :指定主鍵生成策略。
            @Column
                作用:指定實體類屬性和數據庫表之間的對應關係
                屬性:
                    name:指定數據庫表的列名稱。
                    unique:是否唯一  
                    nullable:是否可以為空  
                    inserttable:是否可以插入  
                    updateable:是否可以更新  
                    columnDefinition: 定義建表時創建此列的DDL  
                    secondaryTable: 從表名。如果此列不建在主表上(默認建在主表),該屬性定義該列所在從表的名字搭建開發環境[重點]

    3、配置JPA的核心配置文件

    在java工程的src路徑下創建一個名為META-INF的文件夾,在此文件夾下創建一個名為persistence.xml的配置文件

    <?xml version="1.0" encoding="UTF-8"?>
    <persistence xmlns="http://java.sun.com/xml/ns/persistence"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://java.sun.com/xml/ns/persistence  
        http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
        version="2.0">
        <!--配置持久化單元 
            name:持久化單元名稱 
            transaction-type:事務類型
                 RESOURCE_LOCAL:本地事務管理 
                 JTA:分佈式事務管理 -->
        <persistence-unit name="myJpa" transaction-type="RESOURCE_LOCAL">
            <!--配置JPA規範的服務提供商 -->
            <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
            <properties>
                <!-- 數據庫驅動 -->
                <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver" />
                <!-- 數據庫地址 -->
                <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/jpa" />
                <!-- 數據庫用戶名 -->
                <property name="javax.persistence.jdbc.user" value="root" />
                <!-- 數據庫密碼 -->
                <property name="javax.persistence.jdbc.password" value="111111" />
    
                <!--jpa提供者的可選配置:我們的JPA規範的提供者為hibernate,所以jpa的核心配置中兼容hibernate的配 -->
    <property name="hibernate.show_sql" value="true" /> <property name="hibernate.format_sql" value="true" />
            
           <!--自動創建數據庫表:create(運行時創建表),update(如過有表則不創建表),none(不創建表) <property name="hibernate.hbm2ddl.auto" value="create" /> </properties> </persistence-unit> </persistence>

    4、測試數據庫操作

    通過調用EntityManager的方法完成獲取事務,以及持久化數據庫的操作

    方法說明:    
        getTransaction : 獲取事務對象
        persist : 保存操作
        merge : 更新操作
        remove : 刪除操作
        find/getReference : 根據id查詢
     1 @Test
     2     public void test() {
     3         /**
     4          * 創建實體管理類工廠,藉助Persistence的靜態方法獲取
     5          *         其中傳遞的參數為持久化單元名稱,需要jpa配置文件中指定
     6          */
     7         EntityManagerFactory factory = Persistence.createEntityManagerFactory("myJpa");
     8         //創建實體管理類
     9         EntityManager em = factory.createEntityManager();
    10         //獲取事務對象
    11         EntityTransaction tx = em.getTransaction();
    12         //開啟事務
    13         tx.begin();
    14         Customer c = new Customer();
    15         c.setCustName("天地壹號");
    16         //保存操作
    17         em.persist(c);
    18         //提交事務
    19         tx.commit();
    20         //釋放資源
    21         em.close();
    22         factory.close();
    23     }

    6、抽取JPAUtil工具類,通過工具類生成實體類管理器對象

    package top.biyenanhai.dao;
    
    import javax.persistence.EntityManager;
    import javax.persistence.EntityManagerFactory;
    import javax.persistence.Persistence;
    
    public final class JPAUtil {
        // JPA的實體管理器工廠:相當於Hibernate的SessionFactory
        private static EntityManagerFactory em;
        // 使用靜態代碼塊賦值
        static {
            // 注意:該方法參數必須和persistence.xml中persistence-unit標籤name屬性取值一致
            em = Persistence.createEntityManagerFactory("myPersistUnit");
        }
    
        /**
         * 使用管理器工廠生產一個管理器對象
         * 
         * @return
         */
        public static EntityManager getEntityManager() {
            return em.createEntityManager();
        }
    }

     

    三、Spring Data JPA概述:

      Spring Data JPA 是 Spring 基於 ORM 框架、JPA 規範的基礎上封裝的一套JPA應用框架,可使開發者用極簡的代碼即可實現對數據庫的訪問和操作。它提供了包括增刪改查等在內的常用功能,且易於擴展!學習並使用 Spring Data JPA 可以極大提高開發效率!Spring Data JPA 讓我們解脫了DAO層的操作,基本上所有CRUD都可以依賴於它來實現,在實際的工作工程中,推薦使用Spring Data JPA + ORM(如:hibernate)完成操作,這樣在切換不同的ORM框架時提供了極大的方便,同時也使數據庫層操作更加簡單,方便解耦。

    四、Spring Data JPA 與 JPA和hibernate之間的關係:

      JPA是一套規範,內部是有接口和抽象類組成的。hibernate是一套成熟的ORM框架,而且Hibernate實現了JPA規範,所以也可以稱hibernate為JPA的一種實現方式,我們使用JPA的API編程,意味着站在更高的角度上看待問題(面向接口編程)。Spring Data JPA是Spring提供的一套對JPA操作更加高級的封裝,是在JPA規範下的專門用來進行數據持久化的解決方案。

    五、Spring Data JPA快速搭建開發環境:

    1、創建maven工程,導入Spring Data JPA的坐標

      1    <properties>
      2         <spring.version>4.2.4.RELEASE</spring.version>
      3         <hibernate.version>5.0.7.Final</hibernate.version>
      4         <slf4j.version>1.6.6</slf4j.version>
      5         <log4j.version>1.2.12</log4j.version>
      6         <c3p0.version>0.9.1.2</c3p0.version>
      7         <mysql.version>5.1.6</mysql.version>
      8     </properties>
      9 
     10     <dependencies>
     11         <!-- junit單元測試 -->
     12         <dependency>
     13             <groupId>junit</groupId>
     14             <artifactId>junit</artifactId>
     15             <version>4.12</version>
     16             <scope>test</scope>
     17         </dependency>
     18         
     19         <!-- spring beg -->
     20         <dependency>
     21             <groupId>org.aspectj</groupId>
     22             <artifactId>aspectjweaver</artifactId>
     23             <version>1.6.8</version>
     24         </dependency>
     25 
     26         <dependency>
     27             <groupId>org.springframework</groupId>
     28             <artifactId>spring-aop</artifactId>
     29             <version>${spring.version}</version>
     30         </dependency>
     31 
     32         <dependency>
     33             <groupId>org.springframework</groupId>
     34             <artifactId>spring-context</artifactId>
     35             <version>${spring.version}</version>
     36         </dependency>
     37 
     38         <dependency>
     39             <groupId>org.springframework</groupId>
     40             <artifactId>spring-context-support</artifactId>
     41             <version>${spring.version}</version>
     42         </dependency>
     43 
     44         <dependency>
     45             <groupId>org.springframework</groupId>
     46             <artifactId>spring-orm</artifactId>
     47             <version>${spring.version}</version>
     48         </dependency>
     49 
     50         <dependency>
     51             <groupId>org.springframework</groupId>
     52             <artifactId>spring-beans</artifactId>
     53             <version>${spring.version}</version>
     54         </dependency>
     55 
     56         <dependency>
     57             <groupId>org.springframework</groupId>
     58             <artifactId>spring-core</artifactId>
     59             <version>${spring.version}</version>
     60         </dependency>
     61         
     62         <!-- spring end -->
     63 
     64         <!-- hibernate beg -->
     65         <dependency>
     66             <groupId>org.hibernate</groupId>
     67             <artifactId>hibernate-core</artifactId>
     68             <version>${hibernate.version}</version>
     69         </dependency>
     70         <dependency>
     71             <groupId>org.hibernate</groupId>
     72             <artifactId>hibernate-entitymanager</artifactId>
     73             <version>${hibernate.version}</version>
     74         </dependency>
     75         <dependency>
     76             <groupId>org.hibernate</groupId>
     77             <artifactId>hibernate-validator</artifactId>
     78             <version>5.2.1.Final</version>
     79         </dependency>
     80         <!-- hibernate end -->
     81 
     82         <!-- c3p0 beg -->
     83         <dependency>
     84             <groupId>c3p0</groupId>
     85             <artifactId>c3p0</artifactId>
     86             <version>${c3p0.version}</version>
     87         </dependency>
     88         <!-- c3p0 end -->
     89 
     90         <!-- log end -->
     91         <dependency>
     92             <groupId>log4j</groupId>
     93             <artifactId>log4j</artifactId>
     94             <version>${log4j.version}</version>
     95         </dependency>
     96 
     97         <dependency>
     98             <groupId>org.slf4j</groupId>
     99             <artifactId>slf4j-api</artifactId>
    100             <version>${slf4j.version}</version>
    101         </dependency>
    102 
    103         <dependency>
    104             <groupId>org.slf4j</groupId>
    105             <artifactId>slf4j-log4j12</artifactId>
    106             <version>${slf4j.version}</version>
    107         </dependency>
    108         <!-- log end -->
    109 
    110         
    111         <dependency>
    112             <groupId>mysql</groupId>
    113             <artifactId>mysql-connector-java</artifactId>
    114             <version>${mysql.version}</version>
    115         </dependency>
    116 
    117         <dependency>
    118             <groupId>org.springframework.data</groupId>
    119             <artifactId>spring-data-jpa</artifactId>
    120             <version>1.9.0.RELEASE</version>
    121         </dependency>
    122 
    123         <dependency>
    124             <groupId>org.springframework</groupId>
    125             <artifactId>spring-test</artifactId>
    126             <version>4.2.4.RELEASE</version>
    127         </dependency>
    128         
    129         <!-- el beg 使用spring data jpa 必須引入 -->
    130         <dependency>  
    131             <groupId>javax.el</groupId>  
    132             <artifactId>javax.el-api</artifactId>  
    133             <version>2.2.4</version>  
    134         </dependency>  
    135           
    136         <dependency>  
    137             <groupId>org.glassfish.web</groupId>  
    138             <artifactId>javax.el</artifactId>  
    139             <version>2.2.4</version>  
    140         </dependency> 
    141         <!-- el end -->
    142 
    143         <dependency>
    144             <groupId>javax.xml.bind</groupId>
    145             <artifactId>jaxb-api</artifactId>
    146             <version>2.3.0</version>
    147         </dependency>
    148         <dependency>
    149             <groupId>com.sun.xml.bind</groupId>
    150             <artifactId>jaxb-impl</artifactId>
    151             <version>2.3.0</version>
    152         </dependency>
    153         <dependency>
    154             <groupId>com.sun.xml.bind</groupId>
    155             <artifactId>jaxb-core</artifactId>
    156             <version>2.3.0</version>
    157         </dependency>
    158         <dependency>
    159             <groupId>javax.activation</groupId>
    160             <artifactId>activation</artifactId>
    161             <version>1.1.1</version>
    162         </dependency>
    163     </dependencies>
    164         

    2、整合Spring Data JPA與Spring

     1 <?xml version="1.0" encoding="UTF-8"?>
     2 <beans xmlns="http://www.springframework.org/schema/beans"
     3     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
     4     xmlns:context="http://www.springframework.org/schema/context"
     5     xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:tx="http://www.springframework.org/schema/tx"
     6     xmlns:jpa="http://www.springframework.org/schema/data/jpa" xmlns:task="http://www.springframework.org/schema/task"
     7     xsi:schemaLocation="
     8         http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
     9         http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
    10         http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
    11         http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
    12         http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
    13         http://www.springframework.org/schema/data/jpa 
    14         http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">
    15     
    16     <!-- 1.dataSource 配置數據庫連接池-->
    17     <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
    18         <property name="driverClass" value="com.mysql.jdbc.Driver" />
    19         <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/jpa" />
    20         <property name="user" value="root" />
    21         <property name="password" value="111111" />
    22     </bean>
    23     
    24     <!-- 2.配置entityManagerFactory -->
    25     <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    26         <property name="dataSource" ref="dataSource" />
    27         <property name="packagesToScan" value="cn.itcast.entity" />
    28         <property name="persistenceProvider">
    29             <bean class="org.hibernate.jpa.HibernatePersistenceProvider" />
    30         </property>
    31         <!--JPA的供應商適配器-->
    32         <property name="jpaVendorAdapter">
    33             <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
    34                 <property name="generateDdl" value="false" />
    35                 <property name="database" value="MYSQL" />
    36                 <property name="databasePlatform" value="org.hibernate.dialect.MySQLDialect" />
    37                 <property name="showSql" value="true" />
    38             </bean>
    39         </property>
    40         <property name="jpaDialect">
    41             <bean class="org.springframework.orm.jpa.vendor.HibernateJpaDialect" />
    42         </property>
    43     </bean>
    44     
    45     
    46     <!-- 3.事務管理器-->
    47     <!-- JPA事務管理器  -->
    48     <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    49         <property name="entityManagerFactory" ref="entityManagerFactory" />
    50     </bean>
    51     
    52     <!-- 整合spring data jpa-->
    53     <jpa:repositories base-package="top.biyenanhai.mapper"
    54         transaction-manager-ref="transactionManager"
    55         entity-manager-factory-ref="entityManagerFactory"></jpa:repositories>
    56         
    57     <!-- 4.txAdvice-->
    58     <tx:advice id="txAdvice" transaction-manager="transactionManager">
    59         <tx:attributes>
    60             <tx:method name="*" propagation="REQUIRED"/>
    61             <tx:method name="get*" read-only="true"/>
    62             <tx:method name="find*" read-only="true"/>
    63         </tx:attributes>
    64     </tx:advice>
    65     
    66     <!-- 5.aop-->
    67     <aop:config>
    68         <aop:pointcut id="pointcut" expression="execution(* top.biyenanhai.service.*.*(..))" />
    69         <aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut" />
    70     </aop:config>
    71     
    72     <context:component-scan base-package="cn.itcast"></context:component-scan>
    73     <!--6、配置包掃描-->
    74     <context:component-scan base-package="top.biyenanhai"/>
    75     <!--組裝其它 配置文件-->
    76     
    77 </beans>

    3、使用JPA註解配置映射關係

    package top.biyenanhai.entity;
    
    import javax.persistence.Column;
    import javax.persistence.Entity;
    import javax.persistence.GeneratedValue;
    import javax.persistence.GenerationType;
    import javax.persistence.Id;
    import javax.persistence.Table;
    
    /**
     * 
     *      * 所有的註解都是使用JPA的規範提供的註解,
     *      * 所以在導入註解包的時候,一定要導入javax.persistence下的
     */
    @Entity //聲明實體類
    @Table(name="cst_customer") //建立實體類和表的映射關係
    public class Customer {
        
        @Id//聲明當前私有屬性為主鍵
        @GeneratedValue(strategy=GenerationType.IDENTITY) //配置主鍵的生成策略
        @Column(name="cust_id") //指定和表中cust_id字段的映射關係
        private Long custId;
        
        @Column(name="cust_name") //指定和表中cust_name字段的映射關係
        private String custName;
        
        @Column(name="cust_source")//指定和表中cust_source字段的映射關係
        private String custSource;
        
        @Column(name="cust_industry")//指定和表中cust_industry字段的映射關係
        private String custIndustry;
        
        @Column(name="cust_level")//指定和表中cust_level字段的映射關係
        private String custLevel;
        
        @Column(name="cust_address")//指定和表中cust_address字段的映射關係
        private String custAddress;
        
        @Column(name="cust_phone")//指定和表中cust_phone字段的映射關係
        private String custPhone;
        
        //添加get,set方法,toString方法
    }

    3、編寫符合Spring Data JPA規範的Dao層接口,繼承JpaRepository<T,ID>和JpaSpecificationExecutor<T>

     *     JpaRepository<操作的實體類類型,實體類中主鍵屬性的類型>
     *              *封裝了基本CURD操作
     *     JpaSpecificationExecutor<操作的實體類類型>
     *              *封裝了複雜查詢(分頁)
    package top.biyenanhai.dao;
    
    import org.springframework.data.jpa.repository.JpaRepository;
    import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
    import org.springframework.data.jpa.repository.Modifying;
    import org.springframework.data.jpa.repository.Query;
    import top.biyenanhai.domain.Customer;
    
    import java.util.List;
    
    /**
     * Created with IntelliJ IDEA.
     *
     * @Auther: 畢業男孩
     *
     * 符合SpringDataJpa的dao接口規範
     *      JpaRepository<操作的實體類類型,實體類中主鍵屬性的類型>
     *              *封裝了基本CURD操作
     *      JpaSpecificationExecutor<操作的實體類類型>
     *              *封裝了複雜查詢(分頁)
     *
     */
    public interface CustomerDao extends JpaRepository<Customer, Long>, JpaSpecificationExecutor<Customer> {
    
        /**
         * 案例:根據客戶名稱查詢客戶
         *      使用jpql的形式查詢
         *
         *  jpql:from Customer where custName = ?
         *
         *  配置jpql語句,使用@Query註解
         */
        @Query(value="from Customer where custName = ? ")
        Customer findJpql(String custName);
    
        /**
         * 案例:根據客戶名稱和客戶id查詢客戶
         *      jqpl:from Customer where cutName = ? and custId = ?
         *
         *   對於多個佔位符參數
         *          賦值的時候,默認的情況下,佔位符的位置需要和方法參數中的位置保持一致
         *   可以指定佔位符參數的位置
         *          ?索引的方式,指定此佔位的取值來源
         */
        @Query(value = "from Customer where custName=?2 and custId=?1")
        Customer findCustNameAndCustId(Long id,String name);
    
    
        /**
         * 使用jpql完成更新操作
         *      案例:根據id更新,客戶的名稱
         *          更新4號客戶的名稱,將名稱改為“老男孩”
         *
         *
         * sql:update cst_customer set cust_name = ?where cust_id=?
         * jpql:update Customer set custName=? where custId=?
         *
         * @Query:代表的是進行查詢
         *      聲明此方法是用來更新操作
         * @Modifying:當前執行的是一個更新操作
         */
        @Query(value = "update Customer set custName=?2 where custId=?1")
        @Modifying
        void updateCustomer(long id, String custName);
    
    
        /**
         * 使用sql的形式查詢:
         *      查詢全部的客戶
         *      sql:select * from cst_custimer;
         *      Query:配置sql查詢
         *          value: sql語句
         *          nativeQuery: 查詢方式
         *              true:sql查詢
         *              false:jpql查詢
         */
    //    @Query(value = "select * from cst_customer", nativeQuery = true)  //查詢全部
    
        @Query(value = "select * from cst_customer where cust_name like ?1",nativeQuery = true) //條件查詢
        List<Object[]> findSql(String name);
    
    
        /**
         * 方法名的約定:
         *      findBy:查詢
         *          對象中的屬性名(首字母大寫):查詢的條件
         *          CustName
         *          *默認情況:使用 等於的方式查詢
         *              特殊的查詢方式
         * findByCustName --  根據客戶名稱查詢
         *
         * 在springdataJpa的運行階段
         *      會根據方法名稱進行解析  findBy  from xxx(實體類)
         *                                  屬性名稱    where custName =
         *
         *
         *      1、findBy + 屬性名稱(根據屬性名稱進行完成匹配的查詢=)
         *      2、findBy + 屬性名稱 + “查詢方式(Like|isnull)”
         *      3、多條件查詢
         *          findBy + 屬性名 + "查詢條件" + "多條件的連接符(and|or)" + 屬性名 + “查詢方式”
         *
         */
        Customer findByCustName(String custName);
    
        List<Customer> findByCustNameLike(String name);
    
        List<Customer> findByCustNameLikeAndCustIndustry(String name, String industry);
    
    }

    4、測試基本CRUD操作

    @RunWith(SpringJUnit4ClassRunner.class) //聲明spring提供的單元測試環境
    @ContextConfiguration(locations = "classpath:applicationContext.xml")//指定spring容器的配置信息
    public class CustomerDaoTest {
    
        @Autowired
        private CustomerDao customerDao;
    
        /**
         * 根據id查詢
         */
        @Test
        public void testFindOne(){
            Customer customer = customerDao.findOne(1l);
            System.out.println(customer);
        }
    ......
        
        /**
         * 測試jqpl的更新操作
         * 更新和刪除操作要加上事務的註解
         *
         * springDataJpa中使用jpql完成  更新/刪除操作
         *          需要手動添加事務的支持
         *          默認回執行結束之後,回滾事務
         *    @Rollback:設置是否自動回滾
         */
        @Test
        @Transactional//添加事務的支持
        @Rollback(false)
        public void testUpdate(){
            customerDao.updateCustomer(1l, "航空航天科技有限公司");
        }
    ......

    5、具體關鍵字,使用方法和生產成SQL如下錶所示

    Keyword

    Sample

    JPQL

    And

    findByLastnameAndFirstname

    … where x.lastname = ?1 and x.firstname = ?2

    Or

    findByLastnameOrFirstname

    … where x.lastname = ?1 or x.firstname = ?2

    Is,Equals

    findByFirstnameIs,

    findByFirstnameEquals

    … where x.firstname = ?1

    Between

    findByStartDateBetween

    … where x.startDate between ?1 and ?2

    LessThan

    findByAgeLessThan

    … where x.age < ?1

    LessThanEqual

    findByAgeLessThanEqual

    … where x.age ?1

    GreaterThan

    findByAgeGreaterThan

    … where x.age > ?1

    GreaterThanEqual

    findByAgeGreaterThanEqual

    … where x.age >= ?1

    After

    findByStartDateAfter

    … where x.startDate > ?1

    Before

    findByStartDateBefore

    … where x.startDate < ?1

    IsNull

    findByAgeIsNull

    … where x.age is null

    IsNotNull,NotNull

    findByAge(Is)NotNull

    … where x.age not null

    Like

    findByFirstnameLike

    … where x.firstname like ?1

    NotLike

    findByFirstnameNotLike

    … where x.firstname not like ?1

    StartingWith

    findByFirstnameStartingWith

    … where x.firstname like ?1 (parameter bound with appended %)

    EndingWith

    findByFirstnameEndingWith

    … where x.firstname like ?1 (parameter bound with prepended %)

    Containing

    findByFirstnameContaining

    … where x.firstname like ?1 (parameter bound wrapped in %)

    OrderBy

    findByAgeOrderByLastnameDesc

    … where x.age = ?1 order by x.lastname desc

    Not

    findByLastnameNot

    … where x.lastname <> ?1

    In

    findByAgeIn(Collection ages)

    … where x.age in ?1

    NotIn

    findByAgeNotIn(Collection age)

    … where x.age not in ?1

    TRUE

    findByActiveTrue()

    … where x.active = true

    FALSE

    findByActiveFalse()

    … where x.active = false

    IgnoreCase

    findByFirstnameIgnoreCase

    … where UPPER(x.firstame) = UPPER(?1)

     

    六、Specifications動態查詢:

      有時我們在查詢某個實體的時候,給定的條件是不固定的,這時就需要動態構建相應的查詢語句,在Spring Data JPA中可以通過JpaSpecificationExecutor接口查詢。相比JPQL,其優勢是類型安全,更加的面向對象。對於JpaSpecificationExecutor,這個接口基本是圍繞着Specification接口來定義的。我們可以簡單的理解為,Specification構造的就是查詢條件。

    /**
        *    root    :Root接口,代表查詢的根對象,可以通過root獲取實體中的屬性
        *    query    :代表一個頂層查詢對象,用來自定義查詢
        *    cb        :用來構建查詢,此對象里有很多條件方法
        **/
        public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb);

    案例:使用Specifications完成條件查詢

    //依賴注入customerDao
        @Autowired
        private CustomerDao customerDao;    
        @Test
        public void testSpecifications() {
              //使用匿名內部類的方式,創建一個Specification的實現類,並實現toPredicate方法
            Specification <Customer> spec = new Specification<Customer>() {
                public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                    //cb:構建查詢,添加查詢方式   like:模糊匹配
                    //root:從實體Customer對象中按照custName屬性進行查詢
                    return cb.like(root.get("custName").as(String.class), "航天航空%");
                }
            };
            Customer customer = customerDao.findOne(spec);
            System.out.println(customer);
        }

    案例:基於Specifications的分頁查詢

    @Test
        public void testPage() {
            //構造查詢條件
            Specification<Customer> spec = new Specification<Customer>() {
                public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                    return cb.like(root.get("custName").as(String.class), "航天%");
                }
            };
            
            /**
             * 構造分頁參數
             *         Pageable : 接口
             *             PageRequest實現了Pageable接口,調用構造方法的形式構造
             *                 第一個參數:頁碼(從0開始)
             *                 第二個參數:每頁查詢條數
             */
            Pageable pageable = new PageRequest(0, 5);
            
            /**
             * 分頁查詢,封裝為Spring Data Jpa 內部的page bean
             *         此重載的findAll方法為分頁方法需要兩個參數
             *             第一個參數:查詢條件Specification
             *             第二個參數:分頁參數
             */
            Page<Customer> page = customerDao.findAll(spec,pageable);
            
        }

    對於Spring Data JPA中的分頁查詢,是其內部自動實現的封裝過程,返回的是一個Spring Data JPA提供的pageBean對象。其中的方法說明如下:

    //獲取總頁數
    int getTotalPages();
    //獲取總記錄數    
    long getTotalElements();
    //獲取列表數據
    List<T> getContent();

    方法對應關係:

    方法名稱

    Sql對應關係

    equal

    filed = value

    gt(greaterThan )

    filed > value

    lt(lessThan )

    filed < value

    ge(greaterThanOrEqualTo )

    filed >= value

    le( lessThanOrEqualTo)

    filed <= value

    notEqual

    filed != value

    like

    filed like value

    notLike

    filed not like value

    七、JPA中的一對多:

    實體類(一對多,一的實體類)

    /**
     * 客戶的實體類
     * 明確使用的註解都是JPA規範的
     * 所以導包都要導入javax.persistence包下的
     */
    @Entity//表示當前類是一個實體類
    @Table(name="cst_customer")//建立當前實體類和表之間的對應關係
    public class Customer implements Serializable {
        
        @Id//表明當前私有屬性是主鍵
        @GeneratedValue(strategy=GenerationType.IDENTITY)//指定主鍵的生成策略
        @Column(name="cust_id")//指定和數據庫表中的cust_id列對應
        private Long custId;
        @Column(name="cust_name")//指定和數據庫表中的cust_name列對應
        private String custName;
        
        //配置客戶和聯繫人的一對多關係
        @OneToMany(targetEntity=LinkMan.class)
        @JoinColumn(name="lkm_cust_id",referencedColumnName="cust_id")
        private Set<LinkMan> linkmans = new HashSet<LinkMan>();

    ...get,set方法

    實體類(一對多,多的實體類)

    /**
     * 聯繫人的實體類(數據模型)
     */
    @Entity
    @Table(name="cst_linkman")
    public class LinkMan implements Serializable {
        @Id
        @GeneratedValue(strategy=GenerationType.IDENTITY)
        @Column(name="lkm_id")
        private Long lkmId;
        @Column(name="lkm_name")
        private String lkmName;
        @Column(name="lkm_gender")
        private String lkmGender;
        @Column(name="lkm_phone")
        private String lkmPhone;
    
        //多對一關係映射:多個聯繫人對應客戶
        @ManyToOne(targetEntity=Customer.class)
        @JoinColumn(name="lkm_cust_id",referencedColumnName="cust_id")
        private Customer customer;//用它的主鍵,對應聯繫人表中的外鍵

    ...get,set方法

    八、JPA中的多對多:

    /**
     * 用戶的數據模型
     */
    @Entity
    @Table(name="sys_user")
    public class SysUser implements Serializable {
        
        @Id
        @GeneratedValue(strategy=GenerationType.IDENTITY)
        @Column(name="user_id")
        private Long userId;
        @Column(name="user_name")
        private String userName;
    
        
        //多對多關係映射
        @ManyToMany(mappedBy="users")
        private Set<SysRole> roles = new HashSet<SysRole>();
    /**
     * 角色的數據模型
     */
    @Entity
    @Table(name="sys_role")
    public class SysRole implements Serializable {
        
        @Id
        @GeneratedValue(strategy=GenerationType.IDENTITY)
        @Column(name="role_id")
        private Long roleId;
        @Column(name="role_name")
        private String roleName;
        
        //多對多關係映射
        @ManyToMany
        @JoinTable(name="user_role_rel",//中間表的名稱
                  //中間表user_role_rel字段關聯sys_role表的主鍵字段role_id
                  joinColumns={@JoinColumn(name="role_id",referencedColumnName="role_id")},
                  //中間表user_role_rel的字段關聯sys_user表的主鍵user_id
                  inverseJoinColumns={@JoinColumn(name="user_id",referencedColumnName="user_id")}
        )
        private Set<SysUser> users = new HashSet<SysUser>();

     

    映射的註解說明

    @OneToMany:
           作用:建立一對多的關係映射
        屬性:
            targetEntityClass:指定多的多方的類的字節碼
            mappedBy:指定從表實體類中引用主表對象的名稱。
            cascade:指定要使用的級聯操作
            fetch:指定是否採用延遲加載
    
    @ManyToOne
        作用:建立多對一的關係
        屬性:
            targetEntityClass:指定一的一方實體類字節碼
            cascade:指定要使用的級聯操作
            fetch:指定是否採用延遲加載
            optional:關聯是否可選。如果設置為false,則必須始終存在非空關係。
    
    @JoinColumn
         作用:用於定義主鍵字段和外鍵字段的對應關係。
         屬性:
            name:指定外鍵字段的名稱
            referencedColumnName:指定引用主表的主鍵字段名稱
            unique:是否唯一。默認值不唯一
            nullable:是否允許為空。默認值允許。
            insertable:是否允許插入。默認值允許。
            updatable:是否允許更新。默認值允許。
            columnDefinition:列的定義信息。

     

    九、Spring Data JPA中的多表查詢:

      對象導航查詢:對象導航檢索方式是根據已經加載的對象,導航到他的關聯對象。它利用類與類之間的關係來檢索對象。例如:我們通過ID查詢方式查出一個客戶,可以調用Customer類中的getLinkMans()方法來獲取該客戶的所有聯繫人。對象導航查詢的使用要求是:兩個對象之間必須存在關聯關係。

    //查詢一個客戶,獲取該客戶下的所有聯繫人
    @Autowired
    private CustomerDao customerDao;
        
    @Test
    //由於是在java代碼中測試,為了解決no session問題,將操作配置到同一個事務中
    @Transactional 
    public void testFind() {
        Customer customer = customerDao.findOne(5l);
        Set<LinkMan> linkMans = customer.getLinkMans();//對象導航查詢
        for(LinkMan linkMan : linkMans) {
              System.out.println(linkMan);
        }
    }
    //查詢一個聯繫人,獲取該聯繫人的所有客戶
    @Autowired
    private LinkManDao linkManDao;
        
    @Test
    public void testFind() {
        LinkMan linkMan = linkManDao.findOne(4l);
        Customer customer = linkMan.getCustomer(); //對象導航查詢
        System.out.println(customer);
    }

    採用延遲加載的思想。通過配置的方式來設定當我們在需要使用時,發起真正的查詢。

    配置方式:

    /**
     * 在客戶對象的@OneToMany註解中添加fetch屬性
     *     FetchType.EAGER    :立即加載
     *     FetchType.LAZY            :延遲加載
     */
     @OneToMany(mappedBy="customer",fetch=FetchType.EAGER)
     private Set<LinkMan> linkMans = new HashSet<>();
        
    /**
     * 在聯繫人對象的@ManyToOne註解中添加fetch屬性
     *         FetchType.EAGER    :立即加載
     *         FetchType.LAZY    :延遲加載
     */
     @ManyToOne(targetEntity=Customer.class,fetch=FetchType.EAGER)
     @JoinColumn(name="cst_lkm_id",referencedColumnName="cust_id")
     private Customer customer;

    使用Specification查詢

    /**
     * Specification的多表查詢
     */
    @Test
    public void testFind() {
        Specification<LinkMan> spec = new Specification<LinkMan>() {
            public Predicate toPredicate(Root<LinkMan> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                //Join代錶鏈接查詢,通過root對象獲取
                //創建的過程中,第一個參數為關聯對象的屬性名稱,第二個參數為連接查詢的方式(left,inner,right)
                //JoinType.LEFT : 左外連接,JoinType.INNER:內連接,JoinType.RIGHT:右外連接
                Join<LinkMan, Customer> join = root.join("customer",JoinType.INNER);
                return cb.like(join.get("custName").as(String.class),"航空航天");
            }
        };
        List<LinkMan> list = linkManDao.findAll(spec);
        for (LinkMan linkMan : list) {
            System.out.println(linkMan);
        }
    }

     

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

    【其他文章推薦】

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

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

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

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

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

  • 【K8s學習筆記】K8s是如何部署應用的?

    【K8s學習筆記】K8s是如何部署應用的?

    本文內容

    本文致力於介紹K8s一些基礎概念與串聯部署應用的主體流程,使用Minikube實操

    基礎架構概念回顧

    溫故而知新,上一節【K8S學習筆記】初識K8S 及架構組件 我們學習了K8s的發展歷史、基礎架構概念及用途,本節講的內容建立在其上,有必要把之前的架構小節提出來回顧下:

    K8s架構分為控制平台(位於的Master節點)與執行節點Node

    控制平台包含:

    • kube-apiserver(訪問入口,接收命令)

    • etcd(KV數據庫,保存集群狀態與數據)

    • kube-scheduler(監控節點狀態,調度容器部署)

    • kube-controller-manager(監控集群狀態,控制節點、副本、端點、賬戶與令牌)

    • cloud-controller-manager(控制與雲交互的節點、路由、服務、數據卷)

    執行節點包含:

    • kubelet(監控與實際操作容器)

    • kube-proxy(每個節點上運行的網絡代理,維護網絡轉發規則,實現了Service)

    • 容器運行時環境CRI(支持多種實現K8s CRI的容器技術)

    接下來需要引入 Pod 與 Service 的概念,這也是在上一篇文章中未給出的

    Pod、Service與Label概念

    Pod概念與結構

    Pod 是 K8s最重要的基本概念,官網給出概念:Pod是Kubernates可調度的最小的、可部署的單元。怎麼理解呢?最簡單的理解是,Pod是一組容器。

    再詳細些,Pod是一組容器組成的概念,這些容器都有共同的特點:

    • 都有一個特殊的被稱為“根容器”的Pause容器。Pause容器鏡像屬於K8s平台的一部分
    • 包含一個或多個緊密相關的用戶業務容器。假設個場景:業務容器需要獨立的redis提供服務,這裏就可以將它們兩個部署在同一Pod中。

    下邊是Pod的組成示意圖:

    為什麼Kubernetes會設計出一個全新的概念與Pod會有這樣特殊的結構呢?

    • 原因之一:K8s需要將一組容器視為一個單元處理。當其中某些容器死亡了,此時無法判定這組容器的狀態。而當成一個單元,只要其中有一個容器掛了,這個單元就判定掛了。
    • 原因之二:通過Pause共享容器IP,共享Pause掛接的Volume,簡化密切關聯的業務容器之間的通信問題和文件共享問題

    K8s為每個Pod都分配了唯一的IP地址,稱為Pod IP,一個Pod里的多個容器共享Pod IP地址。需要牢記的一點是:在 kubernetes 里,一個 Pod 里的容器與另外主機上的 Pod 容器能夠直接通信。

    Pod的創建流程

    當一個普通的Pod被創建,就會被放入etcd中存儲,隨後被 K8s Master節點調度到某個具體的Node上並進行綁定(Binding),隨後該Pod被對應的Node上的kubelet進程實例化成一組相關的Docker容器並啟動。

    當Pod中有容器停止時,K8s 會自動檢測到這個問題並重啟這個 Pod(Pod里所有容器);如果Pod所在的Node宕機,就會將這個Node上的所有Pod重新調度到其他節點上。

    細心的讀者是否發現:

    當Pod越來越多,Pod IP 也越來越多,那是如何從茫茫IP地址中找到需要的服務呢?換句話來說,是否有一種提供唯一入口的機制,來完成對Pod的訪問,而無需關心訪問到具體是哪個Pod(who care :happy:)?

    Kubernetes 提供了這種機制,稱之為 Service。

    Service概念

    Service服務是Kubernetes里的核心資源對象之一,從名稱上來看,理解其為一個”服務“也不為過,Service的作用是為相同標識的一系列Pod提供唯一的訪問地址。

    Service使用的唯一地址稱為ClusterIP,僅當在集群內的容器才能通過此IP訪問到Service

    它具體實現對一系列Pod綁定,需要再引入Label的概念,才能做出解答。

    Label概念

    Kubernetes 提供了Label(標籤)來對Pod、Service、RC、Node等進行標記。相當於打標籤,Label可以是一組KV鍵值對,也可以是一個set

    一個資源對象可以定義任意數量的Label,同一個Label可以添加到任意數量的資源對象上。通常由定義資源對象時添加,創建后亦可動態添加或刪除。

    Service如何動態綁定Pods?

    原來,在定義 Pod 時,設置一系列 Label 標籤,Service 創建時定義了 Label Selector(Label Selector 可以類比為對 Label 進行 SQL 的 where 查詢),kube-proxy 進程通過 Service的Label Selector 來選擇對應的 Pod,自動建立每個 Service 到對應 Pod 的請求轉發路由表,從而實現 Service 的智能負載均衡機制。

    小結:Pod是K8s最小的執行單元,包含一個Pause容器與多個業務容器,每個Pod中容器共享Pod IP,容器之間可直接作用Pod IP通信;Label是一種標籤,它將標籤打在Pod上時,Service可以通過定義Label Selector(Label查詢規則)來選擇Pod,具體實現路由表建立的是kube-proxy

    部署應用實踐(Minikube)

    安裝kubectl需要安裝成本地服務,這裡是debian10,更多參考https://developer.aliyun.com/mirror/kubernetes

    sudo su -
    apt-get update && apt-get install -y apt-transport-https
    curl https://mirrors.aliyun.com/kubernetes/apt/doc/apt-key.gpg | apt-key add - 
    cat <<EOF >/etc/apt/sources.list.d/kubernetes.list
    deb https://mirrors.aliyun.com/kubernetes/apt/ kubernetes-xenial main
    EOF  
    apt-get update
    apt-get install -y kubelet kubectl
    exit
    

    下載安裝Minikube(阿里雲修改版):

    curl -Lo minikube-linux-amd64-1.11.0-aliyuncs http://kubernetes.oss-cn-hangzhou.aliyuncs.com/minikube/releases/v1.11.0/minikube-linux-amd64
    sudo install minikube-linux-amd64-1.11.0-aliyuncs /usr/local/bin/minikube
    

    使用魔改版是因為官方代碼里有些地方寫死了地址,而國內無法訪問

    部署k8s集群:

    minikube start --driver docker --image-repository registry.cn-hangzhou.aliyuncs.com/google_containers --kubernetes-version v1.18.3
    

    本地有docker時使用此driver,其他可選的虛擬化技術參考https://minikube.sigs.k8s.io/docs/drivers/ 選擇

    #部署一個Pod,Pod的deployment是一種默認的無狀態多備份的部署類型
    kubectl create deployment hello-minikube --image=registry.cn-hangzhou.aliyuncs.com/google_containers/echoserver:1.4
    #查看集群中當前的Pod列表
    kubectl get pods
    #創建的NodePort類型Service,將所有Node開放一個端口號,通過這個端口將流量轉發到Service以及下游Pods
    kubectl expose deployment hello-minikube --type=NodePort --port=8080
    #獲取暴露 Service 的 URL 以查看 Service 的詳細信息:
    minikube service hello-minikube --url
    #minikube提供的特色功能,直接通過瀏覽器打開剛才創建的Service的外部訪問地址
    minikube service hello-minikube
    

    自動打開瀏覽器訪問服務(Minikube特色功能)

    提示:這張圖中的request-uri的解釋是不正確的,但是與 <minikube這個唯一Node的IP>:<NodePort><Service的ClusterIP>:<ServicePort>都不是完全匹配的,不大理解這塊提示,有緣人希望可解我所惑,在此先行感謝

    查看Pod的描述信息

    kubectl describe pod hello-minikube
    

    最下方可以清楚得看到K8s集群default-scheduler成功指派我們要求的Pod在節點minikube上創建,kubelet收到信息后,拉取鏡像並啟動了容器

    部署流程原理簡述

    1. kubectl 發送創建 deployment 類型、名為hello-minikube的Pod 請求到 kube-apiserver
    2. kube-apiserver 將這條描述信息存到 etcd
    3. kube-scheduler 監控 etcd 得到描述信息,監測Node負載情況,創建Pod部署描述信息到etcd
    4. 待部署Node的kubelet監聽到 etcd 中有自己的部署Pod信息,取出信息並按圖拉取業務鏡像,創建pause容器與業務容器(此時集群外部無法訪問)
    5. kubectl 執行expose命令,同時將創建Service與NodePort信息存到etcd中
    6. 各節點kube-proxy監聽到etcd變化,創建邏輯上的Service與路由信息,開闢NodePort
    7. 當請求到達<任意Node節點IP>:<NodePort> 時,根據路由錶轉發到指定的Service上,負載均衡到Pod,提供服務

    集群外部訪問:

    參考

    • https://kubernetes.io/docs/concepts/workloads/pods/pod-overview/
    • https://kubernetes.io/docs/concepts/services-networking/
    • https://minikube.sigs.k8s.io/docs/start/
    • https://minikube.sigs.k8s.io/docs/handbook/controls/
    • 《Kubernetes權威指南》第 4 版

    行文過程中難免出現錯誤,還請讀者評論幫忙改正,大家共同進步,在此感謝

    轉載請註明出處,文章中概念引入《Kubernetes權威指南》很多,侵權改。

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

    【其他文章推薦】

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

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

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

    ※超省錢租車方案

    FB行銷專家,教你從零開始的技巧

  • async/await剖析

    async/await剖析

    JavaScript是單線程的,為了避免同步阻塞可能會帶來的一些負面影響,引入了異步非阻塞機制,而對於異步執行的解決方案從最早的回調函數,到ES6Promise對象以及Generator函數,每次都有所改進,但是卻又美中不足,他們都有額外的複雜性,都需要理解抽象的底層運行機制,直到在ES7中引入了async/await,他可以簡化使用多個Promise時的同步行為,在編程的時候甚至都不需要關心這個操作是否為異步操作。

    分析

    首先使用async/await執行一組異步操作,並不需要回調嵌套也不需要寫多個then方法,在使用上甚至覺得這本身就是一個同步操作,當然在正式使用上應該將await語句放置於 try...catch代碼塊中,因為await命令後面的Promise對象,運行結果可能是rejected

    function promise(){
        return new Promise((resolve, reject) => {
           var rand = Math.random() * 2; 
           setTimeout(() => resolve(rand), 1000);
        });
    }
    
    async function asyncFunct(){
        var r1 = await promise();
        console.log(1, r1);
        var r2 = await promise();
        console.log(2, r2);
        var r3 = await promise();
        console.log(3, r3);
    }
    
    asyncFunct();
    

    async/await實際上是Generator函數的語法糖,如Promises類似於結構化回調,async/await在實現上結合了Generator函數與Promise函數,下面使用Generator函數加Thunk函數的形式實現一個與上邊相同的例子,可以看到只是將async替換成了*放置在函數右端,並將await替換成了yield,所以說async/await實際上是Generator函數的語法糖,此處唯一不同的地方在於實現了一個流程的自動管理函數run,而async/await內置了執行器,關於這個例子的實現下邊會詳述。對比來看,asyncawait,比起*yield,語義更清楚,async表示函數里有異步操作,await表示緊跟在後面的表達式需要等待結果。

    function thunkFunct(index){
        return function f(funct){
            var rand = Math.random() * 2;
            setTimeout(() => funct(rand), 1000)
        }
    }
    
    function* generator(){ 
        var r1 = yield thunkFunct();
        console.log(1, r1);
        var r2 = yield thunkFunct();
        console.log(2, r2);
        var r3 = yield thunkFunct();
        console.log(3, r3);
    }
    
    function run(generator){
        var g = generator();
    
        var next = function(data){
            var res = g.next(data);
            if(res.done) return ;
            // console.log(res.value);
            res.value(next);
        }
    
        next();
    }
    
    run(generator);
    

    實現

    async函數內置了執行器,能夠實現函數執行的自動流程管理,通過Generator yield ThunkGenerator yield Promise實現一個自動流程管理,只需要編寫Generator函數以及Thunk函數或者Promise對象並傳入自執行函數,就可以實現類似於async/await的效果。

    Generator yield Thunk

    自動流程管理run函數,首先需要知道在調用next()方法時,如果傳入了參數,那麼這個參數會傳給上一條執行的yield語句左邊的變量,在這個函數中,第一次執行next時並未傳遞參數,而且在第一個yield上邊也並不存在接收變量的語句,無需傳遞參數,接下來就是判斷是否執行完這個生成器函數,在這裏並沒有執行完,那麼將自定義的next函數傳入res.value中,這裏需要注意res.value是一個函數,可以在下邊的例子中將註釋的那一行執行,然後就可以看到這個值是f(funct){...},此時我們將自定義的next函數傳遞后,就將next的執行權限交予了f這個函數,在這個函數執行完異步任務后,會執行回調函數,在這個回調函數中會觸發生成器的下一個next方法,並且這個next方法是傳遞了參數的,上文提到傳入參數後會將其傳遞給上一條執行的yield語句左邊的變量,那麼在這一次執行中會將這個參數值傳遞給r1,然後在繼續執行next,不斷往複,直到生成器函數結束運行,這樣就實現了流程的自動管理。

    function thunkFunct(index){
        return function f(funct){
            var rand = Math.random() * 2;
            setTimeout(() => funct(rand), 1000)
        }
    }
    
    function* generator(){ 
        var r1 = yield thunkFunct();
        console.log(1, r1);
        var r2 = yield thunkFunct();
        console.log(2, r2);
        var r3 = yield thunkFunct();
        console.log(3, r3);
    }
    
    function run(generator){
        var g = generator();
    
        var next = function(data){
            var res = g.next(data);
            if(res.done) return ;
            // console.log(res.value);
            res.value(next);
        }
    
        next();
    }
    
    run(generator);
    

    Generator yield Promise

    相對於使用Thunk函數來做流程自動管理,使用Promise來實現相對更加簡單,Promise實例能夠知道上一次回調什麼時候執行,通過then方法啟動下一個yield,不斷繼續執行,這樣就實現了流程的自動管理。

    function promise(){
        return new Promise((resolve,reject) => {
            var rand = Math.random() * 2;
            setTimeout( () => resolve(rand), 1000);
        })
    }
    
    function* generator(){ 
        var r1 = yield promise();
        console.log(1, r1);
        var r2 = yield promise();
        console.log(2, r2);
        var r3 = yield promise();
        console.log(3, r3);
    }
    
    function run(generator){
        var g = generator();
    
        var next = function(data){
            var res = g.next(data);
            if(res.done) return ;
            res.value.then(data => next(data));
        }
    
        next();
    }
    
    run(generator);
    
    // 比較完整的流程自動管理函數
    function promise(){
        return new Promise((resolve,reject) => {
            var rand = Math.random() * 2;
            setTimeout( () => resolve(rand), 1000);
        })
    }
    
    function* generator(){ 
        var r1 = yield promise();
        console.log(1, r1);
        var r2 = yield promise();
        console.log(2, r2);
        var r3 = yield promise();
        console.log(3, r3);
    }
    
    function run(generator){
        return new Promise((resolve, reject) => {
            var g = generator();
            
            var next = function(data){
                var res = null;
                try{
                    res = g.next(data);
                }catch(e){
                    return reject(e);
                }
                if(!res) return reject(null);
                if(res.done) return resolve(res.value);
                Promise.resolve(res.value).then(data => {
                    next(data);
                },(e) => {
                    throw new Error(e);
                });
            }
            
            next();
        })
       
    }
    
    run(generator).then( () => {
        console.log("Finish");
    });
    

    每日一題

    https://github.com/WindrunnerMax/EveryDay
    

    參考

    https://segmentfault.com/a/1190000007535316
    http://www.ruanyifeng.com/blog/2015/05/co.html
    http://www.ruanyifeng.com/blog/2015/05/async.html
    

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

    【其他文章推薦】

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

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

    ※超省錢租車方案

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

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

  • Tensorflow 中(批量)讀取數據的案列分析及TFRecord文件的打包與讀取

    Tensorflow 中(批量)讀取數據的案列分析及TFRecord文件的打包與讀取

    內容概要:

    單一數據讀取方式:

      第一種:slice_input_producer()

    # 返回值可以直接通過 Session.run([images, labels])查看,且第一個參數必須放在列表中,如[...]
    [images, labels] = tf.train.slice_input_producer([images, labels], num_epochs=None, shuffle=True)

      第二種:string_input_producer()

    # 需要定義文件讀取器,然後通過讀取器中的 read()方法來獲取數據(返回值類型 key,value),再通過 Session.run(value)查看
    file_queue = tf.train.string_input_producer(filename, num_epochs=None, shuffle=True)

    reader = tf.WholeFileReader() # 定義文件讀取器
    key, value = reader.read(file_queue)    # key:文件名;value:文件中的內容

      !!!num_epochs=None,不指定迭代次數,這樣文件隊列中元素個數也不限定(None*數據集大小)。

      !!!如果不是None,則此函數創建本地計數器 epochs,需要使用local_variables_initializer()初始化局部變量

      !!!以上兩種方法都可以生成文件名隊列。

    (隨機)批量數據讀取方式:

    batchsize=2  # 每次讀取的樣本數量
    tf.train.batch(tensors, batch_size=batchsize)
    tf.train.shuffle_batch(tensors, batch_size=batchsize, capacity=batchsize*10, min_after_dequeue=batchsize*5) # capacity > min_after_dequeue

      !!!以上所有讀取數據的方法,在Session.run()之前必須開啟文件隊列線程 tf.train.start_queue_runners()

     TFRecord文件的打包與讀取

     

     一、單一數據讀取方式

    第一種:slice_input_producer()

    def slice_input_producer(tensor_list, num_epochs=None, shuffle=True, seed=None, capacity=32, shared_name=None, name=None)

    案例1:

    import tensorflow as tf
    
    images = ['image1.jpg', 'image2.jpg', 'image3.jpg', 'image4.jpg']
    labels = [1, 2, 3, 4]
    
    # [images, labels] = tf.train.slice_input_producer([images, labels], num_epochs=None, shuffle=True)
    
    # 當num_epochs=2時,此時文件隊列中只有 2*4=8個樣本,所有在取第9個樣本時會出錯
    # [images, labels] = tf.train.slice_input_producer([images, labels], num_epochs=2, shuffle=True)
    
    data = tf.train.slice_input_producer([images, labels], num_epochs=None, shuffle=True)
    print(type(data))   # <class 'list'>
    
    with tf.Session() as sess:
        # sess.run(tf.local_variables_initializer())
        sess.run(tf.local_variables_initializer())
        coord = tf.train.Coordinator()  # 線程的協調器
        threads = tf.train.start_queue_runners(sess, coord)  # 開始在圖表中收集隊列運行器
    
        for i in range(10):
            print(sess.run(data))
    
        coord.request_stop()
        coord.join(threads)
    
    """
    運行結果:
    [b'image2.jpg', 2]
    [b'image1.jpg', 1]
    [b'image3.jpg', 3]
    [b'image4.jpg', 4]
    [b'image2.jpg', 2]
    [b'image1.jpg', 1]
    [b'image3.jpg', 3]
    [b'image4.jpg', 4]
    [b'image2.jpg', 2]
    [b'image3.jpg', 3]
    """

      !!!slice_input_producer() 中的第一個參數需要放在一個列表中,列表中的每個元素可以是 List 或 Tensor,如 [images,labels],

      !!!num_epochs設置

     

     第二種:string_input_producer()

    def string_input_producer(string_tensor, num_epochs=None, shuffle=True, seed=None, capacity=32, shared_name=None, name=None, cancel_op=None)

    文件讀取器

      不同類型的文件對應不同的文件讀取器,我們稱為 reader對象

      該對象的 read 方法自動讀取文件,並創建數據隊列,輸出key/文件名,value/文件內容;

    reader = tf.TextLineReader()      ### 一行一行讀取,適用於所有文本文件

    reader = tf.TFRecordReader() ### A Reader that outputs the records from a TFRecords file
    reader = tf.WholeFileReader() ### 一次讀取整個文件,適用圖片

     案例2:讀取csv文件

    iimport tensorflow as tf
    
    filename = ['data/A.csv', 'data/B.csv', 'data/C.csv']
    
    file_queue = tf.train.string_input_producer(filename, shuffle=True, num_epochs=2)   # 生成文件名隊列
    reader = tf.WholeFileReader()           # 定義文件讀取器(一次讀取整個文件)
    # reader = tf.TextLineReader()            # 定義文件讀取器(一行一行的讀)
    key, value = reader.read(file_queue)    # key:文件名;value:文件中的內容
    print(type(file_queue))
    
    init = [tf.global_variables_initializer(), tf.local_variables_initializer()]
    with tf.Session() as sess:
        sess.run(init)
        coord = tf.train.Coordinator()
        threads = tf.train.start_queue_runners(sess=sess, coord=coord)
        try:
            while not coord.should_stop():
                for i in range(6):
                    print(sess.run([key, value]))
                break
        except tf.errors.OutOfRangeError:
            print('read done')
        finally:
            coord.request_stop()
        coord.join(threads)
    
    """
    reader = tf.WholeFileReader()           # 定義文件讀取器(一次讀取整個文件)
    運行結果:
    [b'data/C.csv', b'7.jpg,7\n8.jpg,8\n9.jpg,9\n']
    [b'data/B.csv', b'4.jpg,4\n5.jpg,5\n6.jpg,6\n']
    [b'data/A.csv', b'1.jpg,1\n2.jpg,2\n3.jpg,3\n']
    [b'data/A.csv', b'1.jpg,1\n2.jpg,2\n3.jpg,3\n']
    [b'data/B.csv', b'4.jpg,4\n5.jpg,5\n6.jpg,6\n']
    [b'data/C.csv', b'7.jpg,7\n8.jpg,8\n9.jpg,9\n']
    """
    """
    reader = tf.TextLineReader()           # 定義文件讀取器(一行一行的讀)
    運行結果:
    [b'data/B.csv:1', b'4.jpg,4']
    [b'data/B.csv:2', b'5.jpg,5']
    [b'data/B.csv:3', b'6.jpg,6']
    [b'data/C.csv:1', b'7.jpg,7']
    [b'data/C.csv:2', b'8.jpg,8']
    [b'data/C.csv:3', b'9.jpg,9']
    """

    案例3:讀取圖片(每次讀取全部圖片內容,不是一行一行)

    import tensorflow as tf
    
    filename = ['1.jpg', '2.jpg']
    filename_queue = tf.train.string_input_producer(filename, shuffle=False, num_epochs=1)
    reader = tf.WholeFileReader()              # 文件讀取器
    key, value = reader.read(filename_queue)   # 讀取文件 key:文件名;value:圖片數據,bytes
    
    with tf.Session() as sess:
        tf.local_variables_initializer().run()
        coord = tf.train.Coordinator()      # 線程的協調器
        threads = tf.train.start_queue_runners(sess, coord)
    
        for i in range(filename.__len__()):
            image_data = sess.run(value)
            with open('img_%d.jpg' % i, 'wb') as f:
                f.write(image_data)
        coord.request_stop()
        coord.join(threads)

     

     二、(隨機)批量數據讀取方式:

      功能:shuffle_batch() 和 batch() 這兩個API都是從文件隊列中批量獲取數據,使用方式類似;

    案例4:slice_input_producer() 與 batch()

    import tensorflow as tf
    import numpy as np
    
    images = np.arange(20).reshape([10, 2])
    label = np.asarray(range(0, 10))
    images = tf.cast(images, tf.float32)  # 可以註釋掉,不影響運行結果
    label = tf.cast(label, tf.int32)     # 可以註釋掉,不影響運行結果
    
    batchsize = 6   # 每次獲取元素的數量
    input_queue = tf.train.slice_input_producer([images, label], num_epochs=None, shuffle=False)
    image_batch, label_batch = tf.train.batch(input_queue, batch_size=batchsize)
    
    # 隨機獲取 batchsize個元素,其中,capacity:隊列容量,這個參數一定要比 min_after_dequeue 大
    # image_batch, label_batch = tf.train.shuffle_batch(input_queue, batch_size=batchsize, capacity=64, min_after_dequeue=10)
    
    with tf.Session() as sess:
        coord = tf.train.Coordinator()      # 線程的協調器
        threads = tf.train.start_queue_runners(sess, coord)     # 開始在圖表中收集隊列運行器
        for cnt in range(2):
            print("第{}次獲取數據,每次batch={}...".format(cnt+1, batchsize))
            image_batch_v, label_batch_v = sess.run([image_batch, label_batch])
            print(image_batch_v, label_batch_v, label_batch_v.__len__())
    
        coord.request_stop()
        coord.join(threads)
    
    """
    運行結果:
    第1次獲取數據,每次batch=6...
    [[ 0.  1.]
     [ 2.  3.]
     [ 4.  5.]
     [ 6.  7.]
     [ 8.  9.]
     [10. 11.]] [0 1 2 3 4 5] 6
    第2次獲取數據,每次batch=6...
    [[12. 13.]
     [14. 15.]
     [16. 17.]
     [18. 19.]
     [ 0.  1.]
     [ 2.  3.]] [6 7 8 9 0 1] 6
    """

     案例5:從本地批量的讀取圖片 — string_input_producer() 與 batch()

     1 import tensorflow as tf
     2 import glob
     3 import cv2 as cv
     4 
     5 def read_imgs(filename, picture_format, input_image_shape, batch_size=1):
     6     """
     7     從本地批量的讀取圖片
     8     :param filename: 圖片路徑(包括圖片的文件名),[]
     9     :param picture_format: 圖片的格式,如 bmp,jpg,png等; string
    10     :param input_image_shape: 輸入圖像的大小; (h,w,c)或[]
    11     :param batch_size: 每次從文件隊列中加載圖片的數量; int
    12     :return: batch_size張圖片數據, Tensor
    13     """
    14     global new_img
    15     # 創建文件隊列
    16     file_queue = tf.train.string_input_producer(filename, num_epochs=1, shuffle=True)
    17     # 創建文件讀取器
    18     reader = tf.WholeFileReader()
    19     # 讀取文件隊列中的文件
    20     _, img_bytes = reader.read(file_queue)
    21     # print(img_bytes)    # Tensor("ReaderReadV2_19:1", shape=(), dtype=string)
    22     # 對圖片進行解碼
    23     if picture_format == ".bmp":
    24         new_img = tf.image.decode_bmp(img_bytes, channels=1)
    25     elif picture_format == ".jpg":
    26         new_img = tf.image.decode_jpeg(img_bytes, channels=3)
    27     else:
    28         pass
    29     # 重新設置圖片的大小
    30     # new_img = tf.image.resize_images(new_img, input_image_shape)
    31     new_img = tf.reshape(new_img, input_image_shape)
    32     # 設置圖片的數據類型
    33     new_img = tf.image.convert_image_dtype(new_img, tf.uint8)
    34 
    35     # return new_img
    36     return tf.train.batch([new_img], batch_size)
    37 
    38 
    39 def main():
    40     image_path = glob.glob(r'F:\demo\FaceRecognition\人臉庫\ORL\*.bmp')
    41     image_batch = read_imgs(image_path, ".bmp", (112, 92, 1), 5)
    42     print(type(image_batch))
    43     # image_path = glob.glob(r'.\*.jpg')
    44     # image_batch = read_imgs(image_path, ".jpg", (313, 500, 3), 1)
    45 
    46     sess = tf.Session()
    47     sess.run(tf.local_variables_initializer())
    48     tf.train.start_queue_runners(sess=sess)
    49 
    50     image_batch = sess.run(image_batch)
    51     print(type(image_batch))    # <class 'numpy.ndarray'>
    52 
    53     for i in range(image_batch.__len__()):
    54         cv.imshow("win_"+str(i), image_batch[i])
    55     cv.waitKey()
    56     cv.destroyAllWindows()
    57 
    58 def start():
    59     image_path = glob.glob(r'F:\demo\FaceRecognition\人臉庫\ORL\*.bmp')
    60     image_batch = read_imgs(image_path, ".bmp", (112, 92, 1), 5)
    61     print(type(image_batch))    # <class 'tensorflow.python.framework.ops.Tensor'>
    62 
    63 
    64     with tf.Session() as sess:
    65         sess.run(tf.local_variables_initializer())
    66         coord = tf.train.Coordinator()      # 線程的協調器
    67         threads = tf.train.start_queue_runners(sess, coord)     # 開始在圖表中收集隊列運行器
    68         image_batch = sess.run(image_batch)
    69         print(type(image_batch))    # <class 'numpy.ndarray'>
    70 
    71         for i in range(image_batch.__len__()):
    72             cv.imshow("win_"+str(i), image_batch[i])
    73         cv.waitKey()
    74         cv.destroyAllWindows()
    75 
    76         # 若使用 with 方式打開 Session,且沒加如下2行語句,則會出錯
    77         # ERROR:tensorflow:Exception in QueueRunner: Enqueue operation was cancelled;
    78         # 原因:文件隊列線程還處於工作狀態(隊列中還有圖片數據),而加載完batch_size張圖片會話就會自動關閉,同時關閉文件隊列線程
    79         coord.request_stop()
    80         coord.join(threads)
    81 
    82 
    83 if __name__ == "__main__":
    84     # main()
    85     start()

    從本地批量的讀取圖片案例

     

    案列6:TFRecord文件打包與讀取

     1 def write_TFRecord(filename, data, labels, is_shuffler=True):
     2     """
     3     將數據打包成TFRecord格式
     4     :param filename: 打包後路徑名,默認在工程目錄下創建該文件;String
     5     :param data: 需要打包的文件路徑名;list
     6     :param labels: 對應文件的標籤;list
     7     :param is_shuffler:是否隨機初始化打包后的數據,默認:True;Bool
     8     :return: None
     9     """
    10     im_data = list(data)
    11     im_labels = list(labels)
    12 
    13     index = [i for i in range(im_data.__len__())]
    14     if is_shuffler:
    15         np.random.shuffle(index)
    16 
    17     # 創建寫入器,然後使用該對象寫入樣本example
    18     writer = tf.python_io.TFRecordWriter(filename)
    19     for i in range(im_data.__len__()):
    20         im_d = im_data[index[i]]    # im_d:存放着第index[i]張圖片的路徑信息
    21         im_l = im_labels[index[i]]  # im_l:存放着對應圖片的標籤信息
    22 
    23         # # 獲取當前的圖片數據  方式一:
    24         # data = cv2.imread(im_d)
    25         # # 創建樣本
    26         # ex = tf.train.Example(
    27         #     features=tf.train.Features(
    28         #         feature={
    29         #             "image": tf.train.Feature(
    30         #                 bytes_list=tf.train.BytesList(
    31         #                     value=[data.tobytes()])), # 需要打包成bytes類型
    32         #             "label": tf.train.Feature(
    33         #                 int64_list=tf.train.Int64List(
    34         #                     value=[im_l])),
    35         #         }
    36         #     )
    37         # )
    38         # 獲取當前的圖片數據  方式二:相對於方式一,打包文件佔用空間小了一半多
    39         data = tf.gfile.FastGFile(im_d, "rb").read()
    40         ex = tf.train.Example(
    41             features=tf.train.Features(
    42                 feature={
    43                     "image": tf.train.Feature(
    44                         bytes_list=tf.train.BytesList(
    45                             value=[data])), # 此時的data已經是bytes類型
    46                     "label": tf.train.Feature(
    47                         int64_list=tf.train.Int64List(
    48                             value=[im_l])),
    49                 }
    50             )
    51         )
    52 
    53         # 寫入將序列化之後的樣本
    54         writer.write(ex.SerializeToString())
    55     # 關閉寫入器
    56     writer.close()

    TFRecord文件打包案列

     

     1 import tensorflow as tf
     2 import cv2
     3 
     4 def read_TFRecord(file_list, batch_size=10):
     5     """
     6     讀取TFRecord文件
     7     :param file_list: 存放TFRecord的文件名,List
     8     :param batch_size: 每次讀取圖片的數量
     9     :return: 解析後圖片及對應的標籤
    10     """
    11     file_queue = tf.train.string_input_producer(file_list, num_epochs=None, shuffle=True)
    12     reader = tf.TFRecordReader()
    13     _, ex = reader.read(file_queue)
    14     batch = tf.train.shuffle_batch([ex], batch_size, capacity=batch_size * 10, min_after_dequeue=batch_size * 5)
    15 
    16     feature = {
    17         'image': tf.FixedLenFeature([], tf.string),
    18         'label': tf.FixedLenFeature([], tf.int64)
    19     }
    20     example = tf.parse_example(batch, features=feature)
    21 
    22     images = tf.decode_raw(example['image'], tf.uint8)
    23     images = tf.reshape(images, [-1, 32, 32, 3])
    24 
    25     return images, example['label']
    26 
    27 
    28 
    29 def main():
    30     # filelist = ['data/train.tfrecord']
    31     filelist = ['data/test.tfrecord']
    32     images, labels = read_TFRecord(filelist, 2)
    33     with tf.Session() as sess:
    34         sess.run(tf.local_variables_initializer())
    35         coord = tf.train.Coordinator()
    36         threads = tf.train.start_queue_runners(sess=sess, coord=coord)
    37 
    38         try:
    39             while not coord.should_stop():
    40                 for i in range(1):
    41                     image_bth, _ = sess.run([images, labels])
    42                     print(_)
    43 
    44                     cv2.imshow("image_0", image_bth[0])
    45                     cv2.imshow("image_1", image_bth[1])
    46                 break
    47         except tf.errors.OutOfRangeError:
    48             print('read done')
    49         finally:
    50             coord.request_stop()
    51         coord.join(threads)
    52         cv2.waitKey(0)
    53         cv2.destroyAllWindows()
    54 
    55 if __name__ == "__main__":
    56     main()

    TFReord文件的讀取案列

     

    ,

    內容概要:

    單一數據讀取方式:

      第一種:slice_input_producer()

    # 返回值可以直接通過 Session.run([images, labels])查看,且第一個參數必須放在列表中,如[...]
    [images, labels] = tf.train.slice_input_producer([images, labels], num_epochs=None, shuffle=True)

      第二種:string_input_producer()

    # 需要定義文件讀取器,然後通過讀取器中的 read()方法來獲取數據(返回值類型 key,value),再通過 Session.run(value)查看
    file_queue = tf.train.string_input_producer(filename, num_epochs=None, shuffle=True)

    reader = tf.WholeFileReader() # 定義文件讀取器
    key, value = reader.read(file_queue)    # key:文件名;value:文件中的內容

      !!!num_epochs=None,不指定迭代次數,這樣文件隊列中元素個數也不限定(None*數據集大小)。

      !!!如果不是None,則此函數創建本地計數器 epochs,需要使用local_variables_initializer()初始化局部變量

      !!!以上兩種方法都可以生成文件名隊列。

    (隨機)批量數據讀取方式:

    batchsize=2  # 每次讀取的樣本數量
    tf.train.batch(tensors, batch_size=batchsize)
    tf.train.shuffle_batch(tensors, batch_size=batchsize, capacity=batchsize*10, min_after_dequeue=batchsize*5) # capacity > min_after_dequeue

      !!!以上所有讀取數據的方法,在Session.run()之前必須開啟文件隊列線程 tf.train.start_queue_runners()

     TFRecord文件的打包與讀取

     

     一、單一數據讀取方式

    第一種:slice_input_producer()

    def slice_input_producer(tensor_list, num_epochs=None, shuffle=True, seed=None, capacity=32, shared_name=None, name=None)

    案例1:

    import tensorflow as tf
    
    images = ['image1.jpg', 'image2.jpg', 'image3.jpg', 'image4.jpg']
    labels = [1, 2, 3, 4]
    
    # [images, labels] = tf.train.slice_input_producer([images, labels], num_epochs=None, shuffle=True)
    
    # 當num_epochs=2時,此時文件隊列中只有 2*4=8個樣本,所有在取第9個樣本時會出錯
    # [images, labels] = tf.train.slice_input_producer([images, labels], num_epochs=2, shuffle=True)
    
    data = tf.train.slice_input_producer([images, labels], num_epochs=None, shuffle=True)
    print(type(data))   # <class 'list'>
    
    with tf.Session() as sess:
        # sess.run(tf.local_variables_initializer())
        sess.run(tf.local_variables_initializer())
        coord = tf.train.Coordinator()  # 線程的協調器
        threads = tf.train.start_queue_runners(sess, coord)  # 開始在圖表中收集隊列運行器
    
        for i in range(10):
            print(sess.run(data))
    
        coord.request_stop()
        coord.join(threads)
    
    """
    運行結果:
    [b'image2.jpg', 2]
    [b'image1.jpg', 1]
    [b'image3.jpg', 3]
    [b'image4.jpg', 4]
    [b'image2.jpg', 2]
    [b'image1.jpg', 1]
    [b'image3.jpg', 3]
    [b'image4.jpg', 4]
    [b'image2.jpg', 2]
    [b'image3.jpg', 3]
    """

      !!!slice_input_producer() 中的第一個參數需要放在一個列表中,列表中的每個元素可以是 List 或 Tensor,如 [images,labels],

      !!!num_epochs設置

     

     第二種:string_input_producer()

    def string_input_producer(string_tensor, num_epochs=None, shuffle=True, seed=None, capacity=32, shared_name=None, name=None, cancel_op=None)

    文件讀取器

      不同類型的文件對應不同的文件讀取器,我們稱為 reader對象

      該對象的 read 方法自動讀取文件,並創建數據隊列,輸出key/文件名,value/文件內容;

    reader = tf.TextLineReader()      ### 一行一行讀取,適用於所有文本文件

    reader = tf.TFRecordReader() ### A Reader that outputs the records from a TFRecords file
    reader = tf.WholeFileReader() ### 一次讀取整個文件,適用圖片

     案例2:讀取csv文件

    iimport tensorflow as tf
    
    filename = ['data/A.csv', 'data/B.csv', 'data/C.csv']
    
    file_queue = tf.train.string_input_producer(filename, shuffle=True, num_epochs=2)   # 生成文件名隊列
    reader = tf.WholeFileReader()           # 定義文件讀取器(一次讀取整個文件)
    # reader = tf.TextLineReader()            # 定義文件讀取器(一行一行的讀)
    key, value = reader.read(file_queue)    # key:文件名;value:文件中的內容
    print(type(file_queue))
    
    init = [tf.global_variables_initializer(), tf.local_variables_initializer()]
    with tf.Session() as sess:
        sess.run(init)
        coord = tf.train.Coordinator()
        threads = tf.train.start_queue_runners(sess=sess, coord=coord)
        try:
            while not coord.should_stop():
                for i in range(6):
                    print(sess.run([key, value]))
                break
        except tf.errors.OutOfRangeError:
            print('read done')
        finally:
            coord.request_stop()
        coord.join(threads)
    
    """
    reader = tf.WholeFileReader()           # 定義文件讀取器(一次讀取整個文件)
    運行結果:
    [b'data/C.csv', b'7.jpg,7\n8.jpg,8\n9.jpg,9\n']
    [b'data/B.csv', b'4.jpg,4\n5.jpg,5\n6.jpg,6\n']
    [b'data/A.csv', b'1.jpg,1\n2.jpg,2\n3.jpg,3\n']
    [b'data/A.csv', b'1.jpg,1\n2.jpg,2\n3.jpg,3\n']
    [b'data/B.csv', b'4.jpg,4\n5.jpg,5\n6.jpg,6\n']
    [b'data/C.csv', b'7.jpg,7\n8.jpg,8\n9.jpg,9\n']
    """
    """
    reader = tf.TextLineReader()           # 定義文件讀取器(一行一行的讀)
    運行結果:
    [b'data/B.csv:1', b'4.jpg,4']
    [b'data/B.csv:2', b'5.jpg,5']
    [b'data/B.csv:3', b'6.jpg,6']
    [b'data/C.csv:1', b'7.jpg,7']
    [b'data/C.csv:2', b'8.jpg,8']
    [b'data/C.csv:3', b'9.jpg,9']
    """

    案例3:讀取圖片(每次讀取全部圖片內容,不是一行一行)

    import tensorflow as tf
    
    filename = ['1.jpg', '2.jpg']
    filename_queue = tf.train.string_input_producer(filename, shuffle=False, num_epochs=1)
    reader = tf.WholeFileReader()              # 文件讀取器
    key, value = reader.read(filename_queue)   # 讀取文件 key:文件名;value:圖片數據,bytes
    
    with tf.Session() as sess:
        tf.local_variables_initializer().run()
        coord = tf.train.Coordinator()      # 線程的協調器
        threads = tf.train.start_queue_runners(sess, coord)
    
        for i in range(filename.__len__()):
            image_data = sess.run(value)
            with open('img_%d.jpg' % i, 'wb') as f:
                f.write(image_data)
        coord.request_stop()
        coord.join(threads)

     

     二、(隨機)批量數據讀取方式:

      功能:shuffle_batch() 和 batch() 這兩個API都是從文件隊列中批量獲取數據,使用方式類似;

    案例4:slice_input_producer() 與 batch()

    import tensorflow as tf
    import numpy as np
    
    images = np.arange(20).reshape([10, 2])
    label = np.asarray(range(0, 10))
    images = tf.cast(images, tf.float32)  # 可以註釋掉,不影響運行結果
    label = tf.cast(label, tf.int32)     # 可以註釋掉,不影響運行結果
    
    batchsize = 6   # 每次獲取元素的數量
    input_queue = tf.train.slice_input_producer([images, label], num_epochs=None, shuffle=False)
    image_batch, label_batch = tf.train.batch(input_queue, batch_size=batchsize)
    
    # 隨機獲取 batchsize個元素,其中,capacity:隊列容量,這個參數一定要比 min_after_dequeue 大
    # image_batch, label_batch = tf.train.shuffle_batch(input_queue, batch_size=batchsize, capacity=64, min_after_dequeue=10)
    
    with tf.Session() as sess:
        coord = tf.train.Coordinator()      # 線程的協調器
        threads = tf.train.start_queue_runners(sess, coord)     # 開始在圖表中收集隊列運行器
        for cnt in range(2):
            print("第{}次獲取數據,每次batch={}...".format(cnt+1, batchsize))
            image_batch_v, label_batch_v = sess.run([image_batch, label_batch])
            print(image_batch_v, label_batch_v, label_batch_v.__len__())
    
        coord.request_stop()
        coord.join(threads)
    
    """
    運行結果:
    第1次獲取數據,每次batch=6...
    [[ 0.  1.]
     [ 2.  3.]
     [ 4.  5.]
     [ 6.  7.]
     [ 8.  9.]
     [10. 11.]] [0 1 2 3 4 5] 6
    第2次獲取數據,每次batch=6...
    [[12. 13.]
     [14. 15.]
     [16. 17.]
     [18. 19.]
     [ 0.  1.]
     [ 2.  3.]] [6 7 8 9 0 1] 6
    """

     案例5:從本地批量的讀取圖片 — string_input_producer() 與 batch()

     1 import tensorflow as tf
     2 import glob
     3 import cv2 as cv
     4 
     5 def read_imgs(filename, picture_format, input_image_shape, batch_size=1):
     6     """
     7     從本地批量的讀取圖片
     8     :param filename: 圖片路徑(包括圖片的文件名),[]
     9     :param picture_format: 圖片的格式,如 bmp,jpg,png等; string
    10     :param input_image_shape: 輸入圖像的大小; (h,w,c)或[]
    11     :param batch_size: 每次從文件隊列中加載圖片的數量; int
    12     :return: batch_size張圖片數據, Tensor
    13     """
    14     global new_img
    15     # 創建文件隊列
    16     file_queue = tf.train.string_input_producer(filename, num_epochs=1, shuffle=True)
    17     # 創建文件讀取器
    18     reader = tf.WholeFileReader()
    19     # 讀取文件隊列中的文件
    20     _, img_bytes = reader.read(file_queue)
    21     # print(img_bytes)    # Tensor("ReaderReadV2_19:1", shape=(), dtype=string)
    22     # 對圖片進行解碼
    23     if picture_format == ".bmp":
    24         new_img = tf.image.decode_bmp(img_bytes, channels=1)
    25     elif picture_format == ".jpg":
    26         new_img = tf.image.decode_jpeg(img_bytes, channels=3)
    27     else:
    28         pass
    29     # 重新設置圖片的大小
    30     # new_img = tf.image.resize_images(new_img, input_image_shape)
    31     new_img = tf.reshape(new_img, input_image_shape)
    32     # 設置圖片的數據類型
    33     new_img = tf.image.convert_image_dtype(new_img, tf.uint8)
    34 
    35     # return new_img
    36     return tf.train.batch([new_img], batch_size)
    37 
    38 
    39 def main():
    40     image_path = glob.glob(r'F:\demo\FaceRecognition\人臉庫\ORL\*.bmp')
    41     image_batch = read_imgs(image_path, ".bmp", (112, 92, 1), 5)
    42     print(type(image_batch))
    43     # image_path = glob.glob(r'.\*.jpg')
    44     # image_batch = read_imgs(image_path, ".jpg", (313, 500, 3), 1)
    45 
    46     sess = tf.Session()
    47     sess.run(tf.local_variables_initializer())
    48     tf.train.start_queue_runners(sess=sess)
    49 
    50     image_batch = sess.run(image_batch)
    51     print(type(image_batch))    # <class 'numpy.ndarray'>
    52 
    53     for i in range(image_batch.__len__()):
    54         cv.imshow("win_"+str(i), image_batch[i])
    55     cv.waitKey()
    56     cv.destroyAllWindows()
    57 
    58 def start():
    59     image_path = glob.glob(r'F:\demo\FaceRecognition\人臉庫\ORL\*.bmp')
    60     image_batch = read_imgs(image_path, ".bmp", (112, 92, 1), 5)
    61     print(type(image_batch))    # <class 'tensorflow.python.framework.ops.Tensor'>
    62 
    63 
    64     with tf.Session() as sess:
    65         sess.run(tf.local_variables_initializer())
    66         coord = tf.train.Coordinator()      # 線程的協調器
    67         threads = tf.train.start_queue_runners(sess, coord)     # 開始在圖表中收集隊列運行器
    68         image_batch = sess.run(image_batch)
    69         print(type(image_batch))    # <class 'numpy.ndarray'>
    70 
    71         for i in range(image_batch.__len__()):
    72             cv.imshow("win_"+str(i), image_batch[i])
    73         cv.waitKey()
    74         cv.destroyAllWindows()
    75 
    76         # 若使用 with 方式打開 Session,且沒加如下2行語句,則會出錯
    77         # ERROR:tensorflow:Exception in QueueRunner: Enqueue operation was cancelled;
    78         # 原因:文件隊列線程還處於工作狀態(隊列中還有圖片數據),而加載完batch_size張圖片會話就會自動關閉,同時關閉文件隊列線程
    79         coord.request_stop()
    80         coord.join(threads)
    81 
    82 
    83 if __name__ == "__main__":
    84     # main()
    85     start()

    從本地批量的讀取圖片案例

     

    案列6:TFRecord文件打包與讀取

     1 def write_TFRecord(filename, data, labels, is_shuffler=True):
     2     """
     3     將數據打包成TFRecord格式
     4     :param filename: 打包後路徑名,默認在工程目錄下創建該文件;String
     5     :param data: 需要打包的文件路徑名;list
     6     :param labels: 對應文件的標籤;list
     7     :param is_shuffler:是否隨機初始化打包后的數據,默認:True;Bool
     8     :return: None
     9     """
    10     im_data = list(data)
    11     im_labels = list(labels)
    12 
    13     index = [i for i in range(im_data.__len__())]
    14     if is_shuffler:
    15         np.random.shuffle(index)
    16 
    17     # 創建寫入器,然後使用該對象寫入樣本example
    18     writer = tf.python_io.TFRecordWriter(filename)
    19     for i in range(im_data.__len__()):
    20         im_d = im_data[index[i]]    # im_d:存放着第index[i]張圖片的路徑信息
    21         im_l = im_labels[index[i]]  # im_l:存放着對應圖片的標籤信息
    22 
    23         # # 獲取當前的圖片數據  方式一:
    24         # data = cv2.imread(im_d)
    25         # # 創建樣本
    26         # ex = tf.train.Example(
    27         #     features=tf.train.Features(
    28         #         feature={
    29         #             "image": tf.train.Feature(
    30         #                 bytes_list=tf.train.BytesList(
    31         #                     value=[data.tobytes()])), # 需要打包成bytes類型
    32         #             "label": tf.train.Feature(
    33         #                 int64_list=tf.train.Int64List(
    34         #                     value=[im_l])),
    35         #         }
    36         #     )
    37         # )
    38         # 獲取當前的圖片數據  方式二:相對於方式一,打包文件佔用空間小了一半多
    39         data = tf.gfile.FastGFile(im_d, "rb").read()
    40         ex = tf.train.Example(
    41             features=tf.train.Features(
    42                 feature={
    43                     "image": tf.train.Feature(
    44                         bytes_list=tf.train.BytesList(
    45                             value=[data])), # 此時的data已經是bytes類型
    46                     "label": tf.train.Feature(
    47                         int64_list=tf.train.Int64List(
    48                             value=[im_l])),
    49                 }
    50             )
    51         )
    52 
    53         # 寫入將序列化之後的樣本
    54         writer.write(ex.SerializeToString())
    55     # 關閉寫入器
    56     writer.close()

    TFRecord文件打包案列

     

     1 import tensorflow as tf
     2 import cv2
     3 
     4 def read_TFRecord(file_list, batch_size=10):
     5     """
     6     讀取TFRecord文件
     7     :param file_list: 存放TFRecord的文件名,List
     8     :param batch_size: 每次讀取圖片的數量
     9     :return: 解析後圖片及對應的標籤
    10     """
    11     file_queue = tf.train.string_input_producer(file_list, num_epochs=None, shuffle=True)
    12     reader = tf.TFRecordReader()
    13     _, ex = reader.read(file_queue)
    14     batch = tf.train.shuffle_batch([ex], batch_size, capacity=batch_size * 10, min_after_dequeue=batch_size * 5)
    15 
    16     feature = {
    17         'image': tf.FixedLenFeature([], tf.string),
    18         'label': tf.FixedLenFeature([], tf.int64)
    19     }
    20     example = tf.parse_example(batch, features=feature)
    21 
    22     images = tf.decode_raw(example['image'], tf.uint8)
    23     images = tf.reshape(images, [-1, 32, 32, 3])
    24 
    25     return images, example['label']
    26 
    27 
    28 
    29 def main():
    30     # filelist = ['data/train.tfrecord']
    31     filelist = ['data/test.tfrecord']
    32     images, labels = read_TFRecord(filelist, 2)
    33     with tf.Session() as sess:
    34         sess.run(tf.local_variables_initializer())
    35         coord = tf.train.Coordinator()
    36         threads = tf.train.start_queue_runners(sess=sess, coord=coord)
    37 
    38         try:
    39             while not coord.should_stop():
    40                 for i in range(1):
    41                     image_bth, _ = sess.run([images, labels])
    42                     print(_)
    43 
    44                     cv2.imshow("image_0", image_bth[0])
    45                     cv2.imshow("image_1", image_bth[1])
    46                 break
    47         except tf.errors.OutOfRangeError:
    48             print('read done')
    49         finally:
    50             coord.request_stop()
    51         coord.join(threads)
    52         cv2.waitKey(0)
    53         cv2.destroyAllWindows()
    54 
    55 if __name__ == "__main__":
    56     main()

    TFReord文件的讀取案列

     

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

    【其他文章推薦】

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

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

    ※回頭車貨運收費標準

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

    ※超省錢租車方案

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

  • 10.DRF-認證

    10.DRF-認證

    Django rest framework源碼分析(1)—-認證

    一、基礎

    1.1.安裝

    兩種方式:

    • github
    • pip直接安裝
    pip install djangorestframework
    

    1.2.需要先了解的一些知識

    理解下面兩個知識點非常重要,django-rest-framework源碼中到處都是基於CBV和面向對象的封裝

    (1)面向對象封裝的兩大特性

    把同一類方法封裝到類中
    
    將數據封裝到對象中
    

    (2)CBV

    基於反射實現根據請求方式不同,執行不同的方法

    原理:url–>view方法–>dispatch方法(反射執行其它方法:GET/POST/PUT/DELETE等等)

    二、簡單實例

    2.1.settings

    先創建一個project和一個app(我這裏命名為API)

    首先要在settings的app中添加

    INSTALLED_APPS = [
        'rest_framework',
    ]
    

    2.2.url

    from django.contrib import admin
    from django.urls import path
    from API.views import AuthView
    
    urlpatterns = [
        path('admin/', admin.site.urls),
        path('api/v1/auth/',AuthView.as_view()),
    ]
    

    2.3.models

    一個保存用戶的信息

    一個保存用戶登錄成功后的token

    from django.db import models
    
    class UserInfo(models.Model):
        USER_TYPE = (
            (1,'普通用戶'),
            (2,'VIP'),
            (3,'SVIP')
        )
    
        user_type = models.IntegerField(choices=USER_TYPE)
        username = models.CharField(max_length=32)
        password = models.CharField(max_length=64)
    
    class UserToken(models.Model):
        user = models.OneToOneField(UserInfo,on_delete=models.CASCADE)
        token = models.CharField(max_length=64)
    

    2.4.views

    用戶登錄(返回token並保存到數據庫)

    from django.shortcuts import render
    from django.http import JsonResponse
    from rest_framework.views import APIView
    from API import models
    
    def md5(user):
        import hashlib
        import time
        #當前時間,相當於生成一個隨機的字符串
        ctime = str(time.time())
        m = hashlib.md5(bytes(user,encoding='utf-8'))
        m.update(bytes(ctime,encoding='utf-8'))
        return m.hexdigest()
    
    class AuthView(object):
        def post(self,request,*args,**kwargs):
            ret = {'code':1000,'msg':None}
            try:
                user = request._request.POST.get('username')
                pwd = request._request.POST.get('password')
                obj = models.UserInfo.objects.filter(username=user,password=pwd).first()
                if not obj:
                    ret['code'] = 1001
                    ret['msg'] = '用戶名或密碼錯誤'
                #為用戶創建token
                token = md5(user)
                #存在就更新,不存在就創建
                models.UserToken.objects.update_or_create(user=obj,defaults={'token':token})
                ret['token'] = token
            except Exception as e:
                ret['code'] = 1002
                ret['msg'] = '請求異常'
            return JsonResponse(ret)
    

    2.5.利用postman發請求

    如果用戶名和密碼正確的話 會生成token值,下次該用戶再登錄時,token的值就會更新

    數據庫中可以看到token的值

    當用戶名或密碼錯誤時,拋出異常

    三、添加認證

    基於上面的例子,添加一個認證的類

    3.1.url

    path('api/v1/order/',OrderView.as_view()),
    

    3.2.views

    from django.shortcuts import render,HttpResponse
    from django.http import JsonResponse
    from rest_framework.views import APIView
    from API import models
    from rest_framework.request import Request
    from rest_framework import exceptions
    from rest_framework.authentication import BasicAuthentication
    
    ORDER_DICT = {
        1:{
            'name':'apple',
            'price':15
        },
        2:{
            'name':'dog',
            'price':100
        }
    }
    
    def md5(user):
        import hashlib
        import time
        #當前時間,相當於生成一個隨機的字符串
        ctime = str(time.time())
        m = hashlib.md5(bytes(user,encoding='utf-8'))
        m.update(bytes(ctime,encoding='utf-8'))
        return m.hexdigest()
    
    class AuthView(object):
        '''用於用戶登錄驗證'''
        def post(self,request,*args,**kwargs):
            ret = {'code':1000,'msg':None}
            try:
                user = request._request.POST.get('username')
                pwd = request._request.POST.get('password')
                obj = models.UserInfo.objects.filter(username=user,password=pwd).first()
                if not obj:
                    ret['code'] = 1001
                    ret['msg'] = '用戶名或密碼錯誤'
                #為用戶創建token
                token = md5(user)
                #存在就更新,不存在就創建
                models.UserToken.objects.update_or_create(user=obj,defaults={'token':token})
                ret['token'] = token
            except Exception as e:
                ret['code'] = 1002
                ret['msg'] = '請求異常'
            return JsonResponse(ret)
    
    
    class Authentication(APIView):
        '''認證'''
        def authenticate(self,request):
            token = request._request.GET.get('token')
            token_obj = models.UserToken.objects.filter(token=token).first()
            if not token_obj:
                raise exceptions.AuthenticationFailed('用戶認證失敗')
            #在rest framework內部會將這兩個字段賦值給request,以供後續操作使用
            return (token_obj.user,token_obj)
    
        def authenticate_header(self, request):
            pass
    
    class OrderView(APIView):
        '''訂單相關業務'''
    
        authentication_classes = [Authentication,]    #添加認證
        def get(self,request,*args,**kwargs):
            #request.user
            #request.auth
            ret = {'code':1000,'msg':None,'data':None}
            try:
                ret['data'] = ORDER_DICT
            except Exception as e:
                pass
            return JsonResponse(ret)
    

    3.3用postman發get請求

    請求的時候沒有帶token,可以看到會显示“用戶認證失敗”

    這樣就達到了認證的效果,django-rest-framework的認證是怎麼實現的呢,下面基於這個例子來剖析drf的源碼。

    四、drf的認證源碼分析

    源碼流程圖

    請求先到dispatch

    dispatch()主要做了兩件事

    • 封裝request
    • 認證  

    具體看我寫的代碼裏面的註釋

    def dispatch(self, request, *args, **kwargs):
    	"""
    	`.dispatch()` is pretty much the same as Django's regular dispatch,
    	but with extra hooks for startup, finalize, and exception handling.
    	 """
    	self.args = args
        self.kwargs = kwargs
        #對原始request進行加工,豐富了一些功能
        #Request(
        #     request,
        #     parsers=self.get_parsers(),
        #     authenticators=self.get_authenticators(),
        #     negotiator=self.get_content_negotiator(),
        #     parser_context=parser_context
        # )
        #request(原始request,[BasicAuthentications對象,])
        #獲取原生request,request._request
        #獲取認證類的對象,request.authticators
        #1.封裝request
        request = self.initialize_request(request, *args, **kwargs)
        self.request = request
        self.headers = self.default_response_headers  # deprecate?
    
        try:
            #2.認證
            self.initial(request, *args, **kwargs)
    
            # Get the appropriate handler method
            if request.method.lower() in self.http_method_names:
                handler = getattr(self, request.method.lower(),
                                      self.http_method_not_allowed)
            else:
                handler = self.http_method_not_allowed
    
            response = handler(request, *args, **kwargs)
    
    	except Exception as exc:
            response = self.handle_exception(exc)
    
    	self.response = self.finalize_response(request, response, *args, **kwargs)
        return self.response
    

    4.1.reuqest

    (1)initialize_request()

    可以看到initialize()就是封裝原始request

    def initialize_request(self, request, *args, **kwargs):
        """
        Returns the initial request object.
        """
        parser_context = self.get_parser_context(request)
    
        return Request(
            request,
            parsers=self.get_parsers(),
            #[BasicAuthentication(),],把對象封裝到request裏面了
            authenticators=self.get_authenticators(),    
            negotiator=self.get_content_negotiator(), parser_context=parser_context )
    

    (2)get_authenticators()

    通過列表生成式,返回對象的列表

    def get_authenticators(self):
        """
        Instantiates and returns the list of authenticators that this view can use.
        """
        return [auth() for auth in self.authentication_classes]
    

    (3)authentication_classes

    APIView裏面有個 authentication_classes 字段

    可以看到默認是去全局的配置文件找(api_settings)

    class APIView(View):
    
        # The following policies may be set at either globally, or per-view.
        renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES
        parser_classes = api_settings.DEFAULT_PARSER_CLASSES
        authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
        throttle_classes = api_settings.DEFAULT_THROTTLE_CLASSES
        permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES
        content_negotiation_class = api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS
        metadata_class = api_settings.DEFAULT_METADATA_CLASS
        versioning_class = api_settings.DEFAULT_VERSIONING_CLASS
    

    4.2.認證

    self.initial(request, *args, **kwargs)

    def dispatch(self, request, *args, **kwargs):
        """
        `.dispatch()` is pretty much the same as Django's regular dispatch,
        but with extra hooks for startup, finalize, and exception handling.
        """
        self.args = args
        self.kwargs = kwargs
        #對原始request進行加工,豐富了一些功能
        #Request(
        #     request,
        #     parsers=self.get_parsers(),
        #     authenticators=self.get_authenticators(),
        #     negotiator=self.get_content_negotiator(),
        #     parser_context=parser_context
        # )
        #request(原始request,[BasicAuthentications對象,])
        #獲取原生request,request._request
        #獲取認證類的對象,request.authticators
        #1.封裝request
        request = self.initialize_request(request, *args, **kwargs)
        self.request = request
        self.headers = self.default_response_headers  # deprecate?
    
        try:
            #2.認證
            self.initial(request, *args, **kwargs)
    
            # Get the appropriate handler method
            if request.method.lower() in self.http_method_names:
                handler = getattr(self, request.method.lower(),
                                      self.http_method_not_allowed)
            else:
                handler = self.http_method_not_allowed
    
            response = handler(request, *args, **kwargs)
    
    	except Exception as exc:
            response = self.handle_exception(exc)
    
    	self.response = self.finalize_response(request, response, *args, **kwargs)
        return self.response
    

    (1)initial()

    主要看 self.perform_authentication(request),實現認證

    def initial(self, request, *args, **kwargs):
        """
        Runs anything that needs to occur prior to calling the method handler.
        """
        self.format_kwarg = self.get_format_suffix(**kwargs)
    
        # Perform content negotiation and store the accepted info on the request
        neg = self.perform_content_negotiation(request)
        request.accepted_renderer, request.accepted_media_type = neg
    
        # Determine the API version, if versioning is in use.
        version, scheme = self.determine_version(request, *args, **kwargs)
        request.version, request.versioning_scheme = version, scheme
    
        # Ensure that the incoming request is permitted
        #3.實現認證
        self.perform_authentication(request)
        self.check_permissions(request)
        self.check_throttles(request)
    

    (2)perform_authentication()

    調用了request.user

    def perform_authentication(self, request):
        """
        Perform authentication on the incoming request.
    
    	Note that if you override this and simply 'pass', then authentication
    	will instead be performed lazily, the first time either
    	`request.user` or `request.auth` is accessed.
    	"""
        request.user
    

    (3)user

    request.user的request的位置

    點進去可以看到Request有個user方法,加 @property 表示調用user方法的時候不需要加括號“user()”,可以直接調用:request.user

    @property
    def user(self):
        """
        Returns the user associated with the current request, as authenticated
        by the authentication classes provided to the request.
        """
        if not hasattr(self, '_user'):
            with wrap_attributeerrors():
                #獲取認證對象,進行一步步的認證
                self._authenticate()
        return self._user
    

    (4)_authenticate()

    循環所有authenticator對象

    def _authenticate(self):
        """
        Attempt to authenticate the request using each authentication instance
        in turn.
        """
        #循環認證類的所有對象
        #執行對象的authenticate方法
        for authenticator in self.authenticators:
            try:
                #執行認證類的authenticate方法
                #這裏分三種情況
                #1.如果authenticate方法拋出異常,self._not_authenticated()執行
                #2.有返回值,必須是元組:(request.user,request.auth)
                #3.返回None,表示當前認證不處理,等下一個認證來處理
                user_auth_tuple = authenticator.authenticate(self)
            except exceptions.APIException:
                self._not_authenticated()
                raise
    
            if user_auth_tuple is not None:
                self._authenticator = authenticator
                self.user, self.auth = user_auth_tuple
                return
    
    	self._not_authenticated()
    

    返回值就是例子中的:

    token_obj.user-->>request.user
    token_obj-->>request.auth
    #在rest framework內部會將這兩個字段賦值給request,以供後續操作使用
    return (token_obj.user,token_obj)     #例子中的return
    

    當都沒有返回值,就執行self._not_authenticated(),相當於匿名用戶,沒有通過認證

    def _not_authenticated(self):
        """
        Set authenticator, user & authtoken representing an unauthenticated request.
    
    	Defaults are None, AnonymousUser & None.
    	"""
        self._authenticator = None
    
        if api_settings.UNAUTHENTICATED_USER:
            self.user = api_settings.UNAUTHENTICATED_USER()   #AnonymousUser匿名用戶
        else:
            self.user = None
    
    	if api_settings.UNAUTHENTICATED_TOKEN:
            self.auth = api_settings.UNAUTHENTICATED_TOKEN()  #None
    	else:
            self.auth = None
    

    面向對象知識:

    子類繼承 父類,調用方法的時候:

    • 優先去自己裏面找有沒有這個方法,有就執行自己的
    • 只有當自己裏面沒有這個方法的時候才會去父類找

    因為authenticate方法我們自己寫,所以當執行authenticate()的時候就是執行我們自己寫的認證

    父類中的authenticate方法

    def authenticate(self, request):
        return (self.force_user, self.force_token)
    

    我們自己寫的

    class Authentication(APIView):
        '''用於用戶登錄驗證'''
        def authenticate(self,request):
            token = request._request.GET.get('token')
            token_obj = models.UserToken.objects.filter(token=token).first()
            if not token_obj:
                raise exceptions.AuthenticationFailed('用戶認證失敗')
            #在rest framework內部會將這兩個字段賦值給request,以供後續操作使用
            return (token_obj.user,token_obj)
    

    認證的流程就是上面寫的,弄懂了原理,再寫代碼就更容易理解為什麼了。

    4.3.配置文件

    繼續解讀源碼

    默認是去全局配置文件中找,所以我們應該在settings.py中配置好路徑

    api_settings源碼

    api_settings = APISettings(None, DEFAULTS, IMPORT_STRINGS)
    
    def reload_api_settings(*args, **kwargs):
        setting = kwargs['setting']
        if setting == 'REST_FRAMEWORK':
            api_settings.reload()
    

    setting中‘REST_FRAMEWORK’中找

    全局配置方法:

    API文件夾下面新建文件夾utils,再新建auth.py文件,裏面寫上認證的類

    settings.py

    #設置全局認證
    REST_FRAMEWORK = {
        "DEFAULT_AUTHENTICATION_CLASSES":['API.utils.auth.Authentication',]   #裏面寫你的認證的類的路徑
    }
    

    auth.py

    # API/utils/auth.py
    
    from rest_framework import exceptions
    from API import models
    
    
    class Authentication(object):
        '''用於用戶登錄驗證'''
        def authenticate(self,request):
            token = request._request.GET.get('token')
            token_obj = models.UserToken.objects.filter(token=token).first()
            if not token_obj:
                raise exceptions.AuthenticationFailed('用戶認證失敗')
            #在rest framework內部會將這兩個字段賦值給request,以供後續操作使用
            return (token_obj.user,token_obj)
    
        def authenticate_header(self, request):
            pass
    

    在settings裏面設置的全局認證,所有業務都需要經過認證,如果想讓某個不需要認證,只需要在其中添加下面的代碼:

    authentication_classes = []    #裏面為空,代表不需要認證
    
    from django.shortcuts import render,HttpResponse
    from django.http import JsonResponse
    from rest_framework.views import APIView
    from API import models
    from rest_framework.request import Request
    from rest_framework import exceptions
    from rest_framework.authentication import BasicAuthentication
    
    ORDER_DICT = {
        1:{
            'name':'apple',
            'price':15
        },
        2:{
            'name':'dog',
            'price':100
        }
    }
    
    def md5(user):
        import hashlib
        import time
        #當前時間,相當於生成一個隨機的字符串
        ctime = str(time.time())
        m = hashlib.md5(bytes(user,encoding='utf-8'))
        m.update(bytes(ctime,encoding='utf-8'))
        return m.hexdigest()
    
    class AuthView(APIView):
        '''用於用戶登錄驗證'''
    
        authentication_classes = []    #裏面為空,代表不需要認證
    
        def post(self,request,*args,**kwargs):
            ret = {'code':1000,'msg':None}
            try:
                user = request._request.POST.get('username')
                pwd = request._request.POST.get('password')
                obj = models.UserInfo.objects.filter(username=user,password=pwd).first()
                if not obj:
                    ret['code'] = 1001
                    ret['msg'] = '用戶名或密碼錯誤'
                #為用戶創建token
                token = md5(user)
                #存在就更新,不存在就創建
                models.UserToken.objects.update_or_create(user=obj,defaults={'token':token})
                ret['token'] = token
            except Exception as e:
                ret['code'] = 1002
                ret['msg'] = '請求異常'
            return JsonResponse(ret)
    
    
    
    
    class OrderView(APIView):
        '''訂單相關業務'''
    
    
        def get(self,request,*args,**kwargs):
            # self.dispatch
            #request.user
            #request.auth
            ret = {'code':1000,'msg':None,'data':None}
            try:
                ret['data'] = ORDER_DICT
            except Exception as e:
                pass
            return JsonResponse(ret)
    
    API/view.py代碼
    

    再測試一下我們的代碼

    不帶token發請求

    帶token發請求

    五、drf的內置認證

    rest_framework裏面內置了一些認證,我們自己寫的認證類都要繼承內置認證類 “BaseAuthentication”

    4.1.BaseAuthentication源碼:

    class BaseAuthentication(object):
        """
        All authentication classes should extend BaseAuthentication.
        """
    
        def authenticate(self, request):
            """
            Authenticate the request and return a two-tuple of (user, token).
            """
            #內置的認證類,authenticate方法,如果不自己寫,默認則拋出異常
            raise NotImplementedError(".authenticate() must be overridden.")
    
        def authenticate_header(self, request):
            """
            Return a string to be used as the value of the `WWW-Authenticate`
            header in a `401 Unauthenticated` response, or `None` if the
            authentication scheme should return `403 Permission Denied` responses.
            """
            #authenticate_header方法,作用是當認證失敗的時候,返回的響應頭
            pass
    

    4.2.修改自己寫的認證類

    自己寫的Authentication必須繼承內置認證類BaseAuthentication

    # API/utils/auth/py
    
    from rest_framework import exceptions
    from API import models
    from rest_framework.authentication import BaseAuthentication
    
    
    class Authentication(BaseAuthentication):
        '''用於用戶登錄驗證'''
        def authenticate(self,request):
            token = request._request.GET.get('token')
            token_obj = models.UserToken.objects.filter(token=token).first()
            if not token_obj:
                raise exceptions.AuthenticationFailed('用戶認證失敗')
            #在rest framework內部會將這兩個字段賦值給request,以供後續操作使用
            return (token_obj.user,token_obj)
    
        def authenticate_header(self, request):
            pass
    

    4.3.其它內置認證類

    rest_framework裏面還內置了其它認證類,我們主要用到的就是BaseAuthentication,剩下的很少用到

    六、總結

    自己寫認證類方法梳理

    (1)創建認證類

    • 繼承BaseAuthentication —>>1.重寫authenticate方法;2.authenticate_header方法直接寫pass就可以(這個方法必須寫)

    (2)authenticate()返回值(三種)

    • None —–>>>當前認證不管,等下一個認證來執行
    • raise exceptions.AuthenticationFailed(‘用戶認證失敗’) # from rest_framework import exceptions
    • 有返回值元祖形式:(元素1,元素2) #元素1複製給request.user; 元素2複製給request.auth

    (3)局部使用

    • authentication_classes = [BaseAuthentication,]

    (4)全局使用

    #設置全局認證
    REST_FRAMEWORK = {
        "DEFAULT_AUTHENTICATION_CLASSES":['API.utils.auth.Authentication',]
    }
    

    源碼流程

    —>>dispatch

        –封裝request

           —獲取定義的認證類(全局/局部),通過列表生成式創建對象 

         —initial

           —-peform_authentication

             —–request.user (每部循環創建的對象)

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

    【其他文章推薦】

    ※超省錢租車方案

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

    ※回頭車貨運收費標準

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

    FB行銷專家,教你從零開始的技巧

  • nuget 包是如何還原的

    nuget 包是如何還原的

    Intro

    一直以來從來都只是簡單的用 nuget 包,最近想折騰一個東西,需要自己搞一個 nuget 包的解析,用戶指定 nuget 包的名稱和版本,然後去解析對應的 nuget 包並添加引用到項目,
    於是就想搞明白 nuget 包是怎麼還原的,對於本地已經下載了的 nuget 包又是怎麼找的

    Nuget 包的引用

    對於 dotnetcore 項目(這裏不算之前那種 project.json 的項目,只討論 *.csproj 這種項目),都是使用新的項目格式,PackageReference 模式

    示例:

    <PackageReference Include="WeihanLi.Common" Version="1.0.39" /> 
    

    對於 dotnet framework 項目,如果使用 PackageReference 包格式和上面一樣,如果是傳統的 packages.config 包形式,會有一個 packages.config 的文件包含引用的 nuget 包,文件內容示例:

    <?xml version="1.0" encoding="utf-8"?>
    <packages>
      <package id="Newtonsoft.Json" version="9.0.1" targetFramework="net452" />
    </packages>
    

    本文主要說明 dotnetcore 這種 PackageReference 這種形式

    nuget 包的還原

    nuget 包在第一次從 nuget.org 或自己的包源上下載之後會存放在本地的一個文件夾中,下次再需要相同版本的包還原時就會直接從本地的包中獲取,而這個保存的文件夾是 nuget 配置的一部分,在網上可以找到一些修改 nuget 默認保存 packages 文件夾的位置,但是這些文章都很類似,都只是給出了一個解決方案然而並沒有說明為什麼要這麼做,這麼做的根據是什麼並沒有說明,其實這種解決方案是添加了一個默認的 nuget 配置文件,修改了 nuget 包保存的位置

    nuget 配置

    默認配置

    nuget 會有一些默認的配置,可以參考官方文檔: https://docs.microsoft.com/en-us/nuget/reference/nuget-config-file#config-section

    nuget 配置中有一個 globalPackagesFolder 的配置,是用來指定默認的 nuget 包保存的位置,在 Windows 上默認的保存位置是 %userprofile%\.nuget\packages,在 Linux/Mac 上默認的保存位置是 ~/.nuget/packages,可以使用 nuget.configNuGet.Config 配置文件來修改默認的保存文件,除此之外,還可以通過環境變量的方式,配置 NUGET_PACKAGES 來修改默認 nuget 包保存的位置

    默認配置文件

    nuget 配置的默認配置文件,官方文檔:https://docs.microsoft.com/en-us/nuget/reference/cli-reference/cli-ref-config#options

    Windows 上默認配置文件的位置是 %AppData%\NuGet\NuGet.Config 這也是現在網上那些修改默認保存 nuget 包位置的解決方案,
    Linux/Mac 上大多是 ~/.config/NuGet/NuGet.Config,有的可能是 ~/.nuget/NuGet/NuGet.Config(和系統版本有關係)

    Windows 上默認是沒有這個配置文件的,添加這個默認配置文件之後就是全局作用的

    創建 %AppData%\NuGet\NuGet.Config 這個默認的配置文件,然後在這個配置文件里配置 globalPackagesFolder 來修改默認的 nuget 包保存路徑

    示例:

    <?xml version="1.0" encoding="utf-8"?>
    <configuration>
      <packageSources>
        <add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
      </packageSources>
      <config> 
        <add key="globalPackagesFolder" value="D:\nuget\packages" />
      </config>
    </configuration>
    

    Reference

    • https://docs.microsoft.com/en-us/nuget/reference/nuget-config-file#config-section
    • https://docs.microsoft.com/en-us/nuget/reference/cli-reference/cli-ref-config

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

    【其他文章推薦】

    ※帶您來了解什麼是 USB CONNECTOR  ?

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

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

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

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

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