003.OpenShift網絡

一 OpenShift網絡實現

1.1 軟件定義網絡(SDN)


默認情況下,Docker網絡使用僅使用主機虛機網橋bridge,主機內的所有容器都連接至該網橋。連接到此橋的所有容器都可以彼此通信,但不能與不同主機上的容器通信。通常,這種通信使用端口映射來處理,其中容器端口綁定到主機上的端口,所有通信都通過物理主機上的端口路由。

當有大量主機和容器時,使用此模式,需要手動管理所有端口綁定非常不現實。

為了支持跨集群的容器之間的通信,OpenShift容器平台使用了軟件定義的網絡(SDN)方法。軟件定義的網絡是一種網絡模型,它通過幾個網絡層的抽象來管理網絡服務。SDN將處理流量的軟件(稱為控制平面)和路由流量的底層機制(稱為數據平面)解耦。SDN支持控制平面和數據平面之間的通信。

在OpenShift Container Platform 3.9中(之後簡稱OCP),管理員可以為pod網絡配置三個SDN插件:

  1. ovs-subnet:默認插件,子網提供了一個flat pod網絡,其中每個pod可以與其他pod和service通信。
  2. ovs-multitenant:該為pod和服務提供了額外的隔離層。當使用此插件時,每個project接收一個惟一的虛擬網絡ID (VNID),該ID標識來自屬於該project的pod的流量。通過使用VNID,來自不同project的pod不能與其他project的pod和service通信。
  3. ovs-networkpolicy:此插件允許管理員使用NetworkPolicy對象定義自己的隔離策略。


cluster network由OpenShift SDN建立和維護,它使用Open vSwitch創建overlay網絡,master節點不能通過集群網絡訪問容器,除非master同時也為node節點。

注意:VNID為0的project可以與所有其他pod通信,在OpenShift容器平台中,默認項目的VNID為0。

1.2 Kubernetes SDN Pod




在默認的OpenShift容器平台安裝中,每個pod都有一個惟一的IP地址。pod中的所有容器都對外表現在相同的主機上。給每個pod提供自己的IP地址意味着,在端口分配、網絡、DNS、負載平衡、應用程序配置和遷移方面,pod被視為物理主機或虛擬機的獨立節點(僅從網絡層面看待)。

Kubernetes提供了service的概念,在任何OpenShift應用程序中,service都是必不可少的資源。service充當一個或多個pod前的負載平衡器。該service提供一個固定的IP地址,並且允許與pod通信,而不必跟蹤單獨的pod IP地址。



大多數實際應用程序都不是作為單個pod運行的。它們需要水平伸縮,這樣應用程序就可以在許多pod上運行,以滿足不斷增長的用戶需求。在OpenShift集群中,pod不斷地在集群中的節點之間創建和銷毀。每次創建pod時,它們都會獲得一個不同的IP地址。一個service提供一個單獨的、惟一的IP地址供其他pod使用,而不依賴於pod運行的節點,因此一個pod不必一定需要發現另一個pod的IP地址。客戶端通過service的請求在不同pod之間實現負載均衡。

1.3 Kubernetes SDN Service


service背後運行的一組pod由OpenShift容器平台自動管理。每個service都被分配了一個唯一的IP地址供客戶端連接。這個IP地址也來自OpenShift SDN,它與pod的內部網絡不同,也只在集群中可見。每個與selector匹配的pod都作為endpoint添加到service資源中。當創建和銷毀pods時,service後面的endpoint將自動更新。

service yaml語法:

  1 - apiVersion: v1
  2   kind: Service			#聲明資源類型
  3   metadata:
  4     labels:
  5       app: hello-openshift
  6       name: hello-openshift	#服務的唯一名稱
  7   spec:
  8     ports:,
  9     - name: 8080-tcp
 10       port: 8080		#服務對外公開的端口客戶機連接到服務端口
 11       protocol: TCP
 12       targetPort: 8080		#targetPort屬性必須匹配pod容器定義中的containerPort,服務將數據包轉發到pod中定義的目標端口。
 13     selector:			#該服務使用selector屬性查找要轉發數據包的pod。目標pod的元數據中需要有匹配的標籤。如果服務發現多個具有匹配標籤的pod,它將在它們之間實現負載
 14       app: hello-openshift
 15       deploymentconfig: hello-openshift


1.4 service對外暴露


默認情況下,pod和service IP地址不能從OpenShift集群外部訪問。對於需要從OpenShift集群外部訪問服務的應用程序,可以通過以下三種方式。

HostPort/HostNetwork:在這種方法中,client可以通過主機上的網絡端口直接訪問集群中的應用程序pod。應用程序pod中的端口被綁定到運行該pod的主機上的端口。這種方法在集群中運行大量pod時,存在端口衝突的風險。

NodePort:這是一種較老的基於Kubernetes的方法,通過綁定到node主機上的可用端口,將service公開給外部客戶端,然後node主機代理到service IP地址的連接。使用oc edit svc命令編輯服務屬性,指定NodePort的類型,併為NodePort屬性提供端口值。OpenShift然後通過node主機的公共IP地址和nodePort中設置的端口值代理到服務的連接。這種方法支持非http通信。

OpenShift routes:OpenShift中的推薦方式。它使用唯一的URL公開服務。使用oc expose命令公開用於外部訪問的服務,或者從OpenShift web控制台公開服務。在這種方法中,目前只支持HTTP、HTTPS、TLS whit SNI和WebSockets。

附圖:显示了NodePort服務如何允許外部訪問Kubernetes服務。



service nodeport yaml語法:

  1 apiVersion: v1
  2 kind: Service
  3 metadata:
  4 ...
  5 spec:
  6   ports:
  7   - name: 3306-tcp
  8     port: 3306
  9     protocol: TCP
 10     targetPort: 3306	#pod目標端口,即需要和pod定義的端口匹配
 11     nodePort: 30306	#OpenShift集群中主機上的端口,暴露給外部客戶端
 12   selector:
 13     app: mysqldb
 14     deploymentconfig: mysqldb
 15     sessionAffinity: None
 16   type: NodePort	#服務的類型,如NodePort
 17 ...



OpenShift將服務綁定到服務定義的nodePort屬性中定義的值,併為集群中所有node(包括master)上的流量打開該端口。外部客戶端可以連接到node端口上的任何節點的公共IP地址來訪問服務。請求會在服務後面的各個pod之間實現輪詢的負載平衡。

OpenShift route主要限於HTTP和HTTPS流量,但是節點端口可以處理非HTTP流量,當設置好公開的端口后,客戶機可以使用TCP或UDP的協議連接到該端口。

提示:缺省情況下,NodePort屬性的端口號限制在30000-32767之間,可通過在OpenShift主配置文件中配置範圍。

node port在集群中的所有node上都是打開的,包括master節點。如果沒有提供node端口值,OpenShift將自動在配置範圍內分配一個隨機端口

1.5 pod訪問外部網絡


pod可以使用其主機的地址與外部網絡通信。只要主機能夠解析pod需要到達的服務器,pod就可以使用網絡地址轉換(network address translation, NAT)機制與目標服務器通信。

二 OpenShift SDN練習

2.1 前置準備


[student@workstation ~]$ lab install-prepare setup

[student@workstation ~]$ cd /home/student/do280-ansible

[student@workstation do280-ansible]$ ./install.sh

提示:以上準備為部署一個正確的OpenShift平台。

2.2 本練習準備


[student@workstation ~]$ lab openshift-network setup #準備本實驗環境

2.3 創建應用


[student@workstation ~]$ oc login -u developer -p redhat https://master.lab.example.com

[student@workstation ~]$ oc new-project network-test #創建project

[student@workstation ~]$ oc new-app –name=hello -i php:7.0 http://registry.lab.example.com/scaling

[student@workstation ~]$ oc get pods

NAME READY STATUS RESTARTS AGE

hello-1-build 1/1 Running 0 8s

2.4 擴展應用


[student@workstation ~]$ oc scale –replicas=2 dc hello

[student@workstation ~]$ oc get pods -o wide

NAME READY STATUS RESTARTS AGE IP NODE

hello-1-kszfh 1/1 Running 0 11m 10.128.0.21 node1.lab.example.com

hello-1-q7wk2 1/1 Running 0 11m 10.129.0.37 node2.lab.example.com

2.5 測試訪問


[student@workstation ~]$ curl http://10.128.0.21:8080

curl: (7) Failed connect to 10.128.0.21:8080; Network is unreachable

[root@node1 ~]# curl http://10.128.0.21:8080

  1 <html>
  2  <head>
  3   <title>PHP Test</title>
  4  </head>
  5  <body>
  6  <br/> Server IP: 10.128.0.21
  7  </body>
  8 </html>
  9 [root@node1 ~]# curl http://10.129.0.37:8080
 10 <html>
 11  <head>
 12   <title>PHP Test</title>
 13  </head>
 14  <body>
 15  <br/> Server IP: 10.129.0.37
 16  </body>
 17 </html>



提示:默認情況下,pod的ip屬於內部,集群內部節點可以使用pod ip訪問,集群外部(如workstation)無法訪問。

[student@workstation ~]$ oc get svc hello

NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE

hello ClusterIP 172.30.253.212 <none> 8080/TCP 14m

[student@workstation ~]$ curl http://172.30.253.212:8080

curl: (7) Failed connect to 172.30.253.212:8080; Network is unreachable

[root@node1 ~]# curl http://172.30.253.212:8080 #驗證負載均衡

  1 <html>
  2  <head>
  3   <title>PHP Test</title>
  4  </head>
  5  <body>
  6  <br/> Server IP: 10.128.0.21
  7  </body>
  8 </html>
  9 [root@node1 ~]# curl http://172.30.253.212:8080		#驗證負載均衡
 10 <html>
 11  <head>
 12   <title>PHP Test</title>
 13  </head>
 14  <body>
 15  <br/> Server IP: 10.129.0.37
 16  </body>
 17 </html>



提示:默認情況下,cluster的ip屬於內部,集群內部節點可以使用cluster ip訪問,集群外部(如workstation)無法訪問。

2.6 檢查服務


[student@workstation ~]$ oc describe svc hello

Name: hello

Namespace: network-test

Labels: app=hello

Annotations: openshift.io/generated-by=OpenShiftNewApp

Selector: app=hello,deploymentconfig=hello

Type: ClusterIP

IP: 172.30.253.212

Port: 8080-tcp 8080/TCP

TargetPort: 8080/TCP

Endpoints: 10.128.0.21:8080,10.129.0.37:8080

Session Affinity: None

Events: <none>

解釋:

endpoint:显示請求路由到的pod IP地址列表。當pod有更新后,endpoint將自動更新。

Selector:OpenShift使用為pods定義的選擇器和標籤來使用給定的集群IP,以便實現應用的負載均衡。如上所示為OpenShift將此服務的請求路由到所有標記為app=hello和deploymentconfig=hello的pod。

2.7 檢查pod


[student@workstation ~]$ oc describe pod hello-1-kszfh

2.8 設置外部訪問


使用NodePort方式設置外部訪問。

[student@workstation ~]$ oc edit svc hello

  1 apiVersion: v1
  2 kind: Service
  3 metadata:
  4   annotations:
  5     openshift.io/generated-by: OpenShiftNewApp
  6   creationTimestamp: 2019-07-19T15:50:09Z
  7   labels:
  8     app: hello
  9   name: hello
 10   namespace: network-test
 11   resourceVersion: "24496"
 12   selfLink: /api/v1/namespaces/network-test/services/hello
 13   uid: e348e2a3-aa3c-11e9-b230-52540000fa0a
 14 spec:
 15   clusterIP: 172.30.253.212
 16   ports:
 17   - name: 8080-tcp
 18     port: 8080
 19     protocol: TCP
 20     targetPort: 8080
 21     nodePort: 30800
 22   selector:
 23     app: hello
 24     deploymentconfig: hello
 25   sessionAffinity: None
 26   type: NodePort
 27 status:



[student@workstation ~]$ oc describe svc hello

Name: hello

Namespace: network-test

Labels: app=hello

Annotations: openshift.io/generated-by=OpenShiftNewApp

Selector: app=hello,deploymentconfig=hello

Type: NodePort #驗證是否為NodePort

IP: 172.30.253.212

Port: 8080-tcp 8080/TCP

TargetPort: 8080/TCP

NodePort: 8080-tcp 30800/TCP

Endpoints: 10.128.0.21:8080,10.129.0.37:8080

Session Affinity: None

External Traffic Policy: Cluster

Events: <none>

2.9 驗證外部訪問


[student@workstation ~]$ curl http://node1.lab.example.com:30800

  1 <html>
  2  <head>
  3   <title>PHP Test</title>
  4  </head>
  5  <body>
  6  <br/> Server IP: 10.128.0.21
  7  </body>
  8 </html>



[student@workstation ~]$ curl http://node2.lab.example.com:30800

  1 <html>
  2  <head>
  3   <title>PHP Test</title>
  4  </head>
  5  <body>
  6  <br/> Server IP: 10.129.0.37
  7  </body>
  8 </html>


2.10 使用pod shell


[student@workstation ~]$ oc rsh hello-1-kszfh #使用pod的shell

sh-4.2$ curl http://services.lab.example.com

三 OpenShift router

3.1 OpenShift route概述


OpenShift service允許在OpenShift中的pod之間進行網絡訪問;

OpenShift routes允許從OpenShift外部對pods進行網絡訪問。



路由概念上是通過連接公網IP和DNS主機名訪問內網service IP。在實踐中,為了提高性能和減少延遲,OpenShift route通過OpenShift創建的網絡直接連接到pod,使用該服務只查找endpoint,service只是協助查詢Pod地址。

OpenShift 路由功能由router service提供,該服務在OpenShift實例中作為一個pod運行,可以像任何其他常規pod一樣伸縮和複製。router service基於開源軟件HAProxy實現。

OpenShift route配置的公共DNS主機名需要指向運行router的節點的公共IP地址。route pod與常規應用程序pod不同,它綁定到節點的公共IP地址,而不是內部pod網絡。這通常使用DNS通配符配置。

  1 - apiVersion: v1
  2   kind: Route				#聲明為route類型
  3   metadata:
  4     creationTimestamp: null
  5     labels:
  6       app: quoteapp
  7     name: quoteapp				#路由器名字
  8   spec:
  9     host: quoteapp.apps.lab.example.com	#與route關聯的FQDN,必須預先配置,以解析到OpenShift route pod運行的節點的IP地址
 10     port:
 11       targetPort: 8080-tcp
 12   to:					#一個對象,該對象聲明此route指向的資源類型(在本例中是OpenShift service),以及該資源的名稱(quoteapp)
 13     kind: Service
 14     name: quoteapp



提示:不同資源類型可以使用相同的名稱,如一個名為quoteapp的route可以指向一個名為quoteapp的service。

service通過selector與pod的label進行匹配,router通過name與service的name匹配。

3.2 創建route


創建route最簡單和推薦的方法是使用oc expose命令,將service資源名稱作為輸入參數。–name選項可用於控制route資源的名稱,–hostname選項可用於為route提供自定義主機名。

示例:

[user@demo ~]$ oc expose service quote \

–name quote –hostname=quoteapp.apps.lab.example.com

從模板或不帶–hostname參數的oc expose命令創建的路由,命名方式為:

<route-name>-<project-name>.<default-domain>

解釋

route-name:route的名稱,或原始資源的名稱;

project-name:包含資源的項目的名稱;

default-domain:該值是在OpenShift master上配置的,它對應於作為安裝OpenShift先決條件列出的通配符DNS域。

例如,在OpenShift集群中名為test的project中創建一條名為quote的路由,其中子域為apps.example.com,則FQDN為quote-test.apps.example.com

注意:承載通配符域的DNS服務器不知道任何route的主機名,它只將任何名稱解析為已配置的ip。只有OpenShift route知道route主機名,將每個主機都當作HTTP虛擬主機。無效的通配符域主機名,即不與任何route對應的主機名,將被OpenShift路由器阻塞。

通過向oc create提供JSON或YAML資源定義文件,也可以像其他OpenShift資源一樣創建route資源。

oc new-app命令在從容器映像、Dockerfiles或應用程序源代碼構建pod時不創建route資源。

oc new-app命令不知道pod是否打算從OpenShift實例外部訪問。當oc new-app命令從模板創建一組pod時,沒有什麼可以阻止模板將路由資源包含到應用程序中。

3.3 查找默認subdomain


默認路由子域是在OpenShift配置文件master-config.yaml中的routingConfig字段中定義,使用關鍵字subdomain。

routingConfig:

subdomain: apps.example.com

默認情況下,OpenShift HAProxy route綁定到主機端口80 (HTTP)和443 (HTTPS)。route必須放置在這些端口不使用的節點上。或者,可以通過設置ROUTER_SERVICE_HTTP_PORT和ROUTER_SERVICE_HTTPS_PORT環境變量來配置路由器監聽其他端口.

路由器支持以下協議:

  • HTTP


  • HTTPS with SNI
  • WebSockets
  • TLS with SNI

3.4 routing類型和選項


路由可以是安全的,也可以是非安全的。安全route提供了使用幾種類型的TLS方式來向客戶端提供證書的能力。不安全路由是最容易配置的,因為它們不需要密鑰或證書,但是安全路由會加密進出pod的流量。

在創建安全路由之前,需要生成TLS證書。

示例:如下步驟創建名為test.example.com的路由創建一個簡單的自簽名證書。

  • 使用openssl命令創建私鑰:


[user@demo ~]$ openssl genrsa -out example.key 2048

  • 使用生成的私鑰創建證書籤名請求(CSR):


[user@demo ~]$ openssl req -new -key example.key -out example.csr -subj “/C=US/ST=CA/L=Los Angeles/O=Example/OU=IT/CN=test.example.com”

  • 使用密鑰和CSR生成證書


[user@demo ~]$ openssl x509 -req -days 366 -in example.csr -signkey example.key -out example.crt

  • 當證書準備好時,創建一個edge-terminated的路由


[user@demo ~]$ oc create route edge –service=test \

–hostname=test.example.com \

–key=example.key –cert=example.crt

3.5 通配符子域


wildcard policy允許用戶定義domain中所有主機的route。route可以使用wildcardPolicy字段將wildcard policy指定為其配置的一部分。OpenShift路由器支持通配符路由,通過設置路由器部署配置中的ROUTER_ALLOW_WILDCARD_ROUTES環境變量為true,從而可將wildcardPolicy屬性設置為子域的任何route都由路由器提供服務。路由器根據route的通配符策略暴露相關的service。

示例:如下下示例表示對於三個不同的路由,a.lab.example.com、b.lab.example.com和c.lab.example.com,它們應該路由到一個名為test的OpenShift服務,可以使用通配符策略配置路由。

  • 將路由器作為集群管理用戶處理通配符路由


[user@demo ~]$ oc scale dc/router –replicas=0

[user@demo ~]$ oc set env dc/router ROUTER_ALLOW_WILDCARD_ROUTES=true

[user@demo ~]$ oc scale dc/router –replicas=1

  • 使用通配符策略創建新路由


[user@demo ~]$ oc expose svc test –wildcard-policy=Subdomain \

–hostname=’www.lab.example.com’

3.6 管理route


在master節點上,在default中查找router

[root@master]# oc project default

[root@master]# oc get pods

在master節點上,檢查路由器環境變量,以找到運行在pod中的HAProxy進程的連接參數

[root@master]# oc env pod router-1-32toa –list | tail -n 6

提示:當創建路由器時,STATS_PASSWORD變量中的密碼是隨機生成的。STATS_USERNAME和STATS_PORT變量有固定的默認值,但是它們都可以在路由器創建時更改。

在router運行的節點上,配置firewall-cmd以打開STATS_PORT變量指定的端口。

[root@node ~]# firewall-cmd –permanent –zone=public –add-port=1936

[root@node ~]# firewall-cmd –reload

打開web瀏覽器並訪問HAProxy statistics URL 為 http://nodeIP:STATS_PORT/。

在User Name字段中輸入STATS_USERNAME的值,在Password字段中輸入STATS_PASSWORD的值,然後單擊OK。則會显示的HAProxy metrics頁面。

四 創建Route練習

4.1 前置準備


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

4.2 本練習準備


[student@workstation ~]$ lab secure-route setup #準備本實驗環境

4.3 創建應用


[student@workstation ~]$ oc login -u developer -p redhat https://master.lab.example.com

[student@workstation ~]$ oc new-project secure-route #創建project

[student@workstation ~]$ oc new-app –docker-image=registry.lab.example.com/openshift/hello-openshift –name=hello

[student@workstation ~]$ oc get pods -o wide

NAME READY STATUS RESTARTS AGE IP NODE

hello-1-wwgkr 1/1 Running 0 20s 10.129.0.38 node2.lab.example.com

4.4 創建TLS證書


[student@workstation ~]$ cd /home/student/DO280/labs/secure-route/ #使用環境中的腳本快速創建TLS自簽名證書

[student@workstation secure-route]$ ./create-cert.sh

4.5 創建route


[student@workstation secure-route]$ ll

-rw-r–r–. 1 student student 550 Aug 7 2018 commands.txt

-rwxr-xr-x. 1 student student 506 Jul 19 2018 create-cert.sh

-rw-rw-r–. 1 student student 1224 Jul 20 10:43 hello.apps.lab.example.com.crt

-rw-rw-r–. 1 student student 1017 Jul 20 10:43 hello.apps.lab.example.com.csr

-rw-rw-r–. 1 student student 1679 Jul 20 10:43 hello.apps.lab.example.com.key

[student@workstation secure-route]$ oc create route edge \

> –service=hello –hostname=hello.apps.lab.example.com \

> –key=hello.apps.lab.example.com.key \

> –cert=hello.apps.lab.example.com.crt

4.6 確認驗證


[student@workstation secure-route]$ oc get route

NAME HOST/PORT PATH SERVICES PORT TERMINATION WILDCARD

hello hello.apps.lab.example.com hello 8080-tcp edge None

[student@workstation secure-route]$ oc get route hello -o yaml #以yaml格式查看route

4.7 測試訪問


[student@workstation ~]$ curl http://hello.apps.lab.example.com #以http形式訪問會無法轉發至後端任何pod

  1 ……
  2       <h1>Application is not available</h1>
  3       <p>The application is currently not serving requests at this endpoint. It may not have been started or is still starting.</p>
  4 ……



[student@workstation ~]$ curl -k -vvv https://hello.apps.lab.example.com #以https形式訪問

  1 ……
  2 Hello OpenShift!
  3 * Connection #0 to host hello.apps.lab.example.com left intact
  4 ……


4.8 非安全形式訪問


由於加密的通信在路由器上終止,並且請求使用不安全的HTTP轉發到pods,所以可以使用pod IP地址通過普通HTTP訪問應用程序。為此,請使用oc get pods -o命令中指定的IP地址。

[student@workstation secure-route]$ oc get pod -o wide

NAME READY STATUS RESTARTS AGE IP NODE

hello-1-wwgkr 1/1 Running 0 21m 10.129.0.38 node2.lab.example.com

[root@node1 ~]# curl -vvv http://10.129.0.38:8080


五 OpenShift網絡綜合實驗

5.1 前置準備


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

5.2 本練習準備


[student@workstation ~]$ lab network-review setup

5.3 驗證所需資源


[student@workstation ~]$ oc login -u developer -p redhat \

https://master.lab.example.com

[student@workstation ~]$ oc get pod -o wide

NAME READY STATUS RESTARTS AGE IP NODE

hello-openshift-1-6ls8z 1/1 Running 0 2m 10.128.0.23 node1.lab.example.com

[student@workstation ~]$ oc get svc

NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE

hello-openshift ClusterIP 172.30.124.237 <none> 8080/TCP,8888/TCP 2m

[student@workstation ~]$ oc get route

NAME HOST/PORT PATH SERVICES PORT TERMINATION WILDCARD

hello-openshift hello.apps.lab.example.com hello-opensift 8080-tcp None

5.4 測試訪問


[student@workstation ~]$ curl http://hello.apps.lab.example.com #測試http訪問

  1 ……
  2       <h1>Application is not available</h1>
  3       <p>The application is currently not serving requests at this endpoint. It may not have been started or is still starting.</p>
  4 ……

[root@node1 ~]# curl http://10.128.0.23:8080 #測試使用pod ip訪問

Hello OpenShift!

[root@node1 ~]# curl http://172.30.124.237:8080 #測試使用cluster ip訪問

curl: (7) Failed connect to 172.30.124.237:8080; Connection refused

5.5 TS cluster故障


[student@workstation ~]$ oc describe svc hello-openshift -n network-review



提示:由上可知,沒有endpoint,endpoint是使用selector對pod的label進行匹配。

[student@workstation ~]$ oc describe pod hello-openshift-1-6ls8z #查看pod詳情



故障點:由上可知,Selector的label不一致,則沒有標記為hello_openshift的pod能進行匹配。

[student@workstation ~]$ oc edit svc hello-openshift

  1 ……
  2   selector:
  3     app: hello-openshift
  4     deploymentconfig: hello-openshift
  5   sessionAffinity: None
  6 ……


5.6 測試訪問


[root@node1 ~]# curl http://10.128.0.23:8080 #測試使用pod ip訪問

Hello OpenShift!

[root@node1 ~]# curl http://172.30.124.237:8080 #再次測試

Hello OpenShift!

[student@workstation ~]$ curl http://hello.apps.lab.example.com #測試http訪問

  1 ……
  2       <h1>Application is not available</h1>
  3       <p>The application is currently not serving requests at this endpoint. It may not have been started or is still starting.</p>
  4 ……


5.7 TS route故障


[student@workstation ~]$ oc describe route hello-openshift



故障點:由上可知,此路由沒有endpoint。即對route的URL請求沒有後端endpoint進行響應。路由器查詢service的endpoint,並註冊有效的endpoint來實現負載平衡。同時發現service名稱中有一個拼寫錯誤,它應該是hello-openshift。

[student@workstation ~]$ oc edit route hello-openshift

  1 ……
  2 spec:
  3   host: hello.apps.lab.example.com
  4   port:
  5     targetPort: 8080-tcp
  6   to:
  7     kind: Service
  8     name: hello-openshift
  9     weight: 100
 10   wildcardPolicy: None
 11 ……



[root@node1 ~]# curl http://hello.apps.lab.example.com #再次測試

Hello OpenShift!

5.8 確認驗證


[student@workstation ~]$ lab network-review grade #使用腳本判斷 本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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

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

聚甘新

年僅 28 歲就宣布從字節跳動退休?

這两天,互聯網熱議最大的一個話題除了阿里 P8 程序員找生活助理的事,另外一個就是 28 歲的郭宇宣布從字節跳動退休,稱選擇經營溫泉旅行,選擇成為一名職業作家。

我看到這個話題的時候,情不自禁地“嘖嘖”了兩聲,真特么酸了,28 歲就退休了,我已經 31 歲了,還在辛苦打拚的路上,除了要忙工作,還要高產似母豬地更文,然而,即便我這麼努力,還是沒能成為一名“職業作家”,退休更是遙遙無期。

郭大佬非常牛逼的一點在於,高考之後就開始敲代碼,上了大學之後依然敲代碼,大三就在支付寶干過,然後創業的一家公司被字節跳動收購,再然後嘛,就財務自由退休了——28 歲,重新定義了退休的年紀。

字節跳動這家公司發展的真的是巨快,明星產品今日頭條和抖音,真的是國民級應用。反正我父母都是這兩款產品的忠實用戶,我妹妹雖然不玩今日頭條,但抖音玩得那叫一個熱火朝天。

我自己是不玩抖音也不看今日頭條的,因為覺得這種短視頻,或者說亂七八糟的新聞熱點有點浪費生命的感覺,所以一直很抵觸。

當然了,我如果說我一次也沒玩過,有點聖人的感覺,我做不到。但每次無聊到刷上倆小時的抖音,我就會噁心到把這款軟件卸載掉。尤其是聽到那些無厘頭的狂笑,我感覺到娛樂在致死。

這不是抖音的問題,是我的問題,是人性的問題,抖音就抓住了人性的弱點,讓你沉浸其中,忘乎所以。

抨擊歸抨擊,但我不能忽視的事實是,字節跳動是真的牛逼,郭大佬是真的有錢了。

每個人都有自己人生,郭大佬有實力又有運氣,他過的是一種極致的人生。

我是 2014 年回的洛陽,一回來就跟着一個老闆做創業項目,依稀還記得當初他給我許下的承諾:三年後讓你在洛陽買房買車,五年後帶你走上人生巔峰。

2015 年,我買了房,靠的是我和老婆辛苦攢下的一些積蓄,還有父母義無反顧的支持。老闆也借給了我兩三萬,一年後我就還他了,所以在買房這個承諾上,他有幫助,但遠非承諾中的那樣。

2016 年,我買了車,分期付款的那種,和老闆沒有一點關係。

至於五年後走上人生巔峰,更是瞎扯淡。我現在還是一名普通的程序員,生活的幸福指數也完全靠的是自己的付出。

這些年裡,老闆無數次胯下海口,聽得我耳朵都膩了。至於我為什麼還沒有離職,並不是我沉浸在溫柔故鄉,而是洛陽的軟件環境整體就這麼個樣,去哪都是打工,還不如自己踏踏實實做一些事情,比如說寫作。

對比我倆,就會發現一些很有意思的點,我來給同學們剖析一下。

1)學歷重不重要

很重要,郭大佬讀過深圳高級中學,深圳最好的高中之一,大學是暨南大學,211。

我呢,高中雖然是保送的,但那時候的學校已經走了下坡路,很動蕩,師資和校領導換了好幾波;然後我上的是一所大專。

所以我大學那會很自卑,即便專業是計算機網絡,也沒多少心思學習。而郭大佬就完全不一樣了,沉下一門心思學編程,為此還掛科了好幾門。因為他是非科班出身,專業是政治與行政管理。

假如,請允許我假如一下,給低學歷的同學們一點點信心。

假如我上大學那會一門心思撲倒編程上,大三也不至於出去參加培訓,真的,大把大把的時光我都浪費了。除了談戀愛是正事,我就只會打遊戲了。

假如拿現在的心態去過大學兩年的時光,我堅信,我一定能進阿里,因為拼過和沒拼過的人生差別巨大。

我就認識這樣一個初中小妹妹,平常老喊我二叔,搞得我都逆來順受了。她的成績非常優異,全年級第二名,為什麼不是第一名,因為語文成績拖了後腿。這不是關鍵,關鍵是小妹妹現在就開始學編程了,還去給初一的同學授過課。

後生可畏。

所以,我的結論就是,能通過學習改變命運,就下勁學,錯過這個年紀就真的沒機會了。如果真的上了大專,上了一般的本科,也不是沒有機會,別整天喊自己迷茫,誰的青春沒有迷茫過,關鍵是要發掘自己的興趣點,如果要從事程序員這個行業,就好好學編程。

2)要不要創業

十個創業九個坑,我只能這麼說,能進大廠進大廠,進不去進中廠,進不去中廠進小廠。如果非要創業,也得你自身實力夠硬,假如創業失敗,你還有出路,或者實在是沒有其他更好的選擇了,再選擇創業。

職場新人最好不要被忽悠去創業,太慘了。

你看,人家郭大佬在支付寶鍍了一層金,然後所在公司稀里糊塗被字節跳動收購了,這是運氣,沒得說。

大部分人的命運可能像我一樣,在日企待過三年半,有了一些資歷,然後作為技術大拿參与到創業公司,一開始老闆牛逼吹上天,最後,啥也沒撈着。

青春荒廢了,人際關係荒廢了,程序員的黃金年齡段也荒廢了。

3)要不要提前退休

有不少同學問過我這樣的話題,“二哥,我馬上到了結婚的年紀,雖然在一線城市掙得還可以,但遠沒到能買得起房子的水準,可能這輩子都不可能了,我想現在回二線城市或者三線城市,你看可行嗎?”

這種想法,其實就和郭大佬退休的想法是一致的,只不過郭財務自由了。

對於普通人來說,我的建議是這樣的,請認認真真做好筆記。

第一,不要盲目回二三線。

拿洛陽來說吧,一般程序員的極限工資就是一萬塊,撐死的那種。五險一金,包括獎金,能沒有公司就考慮沒有。

捫心自問一下,自己能否承受得起這份清心寡慾。另外,二三線城市也是會加班的,關鍵是不一定有加班工資。

第二,搞一份副業吧,同學們。

在一線城市,你可能沒有精力和時間搞副業,另外,主業的成長潛力並不比副業差,搞的意義不是特別大。但如果要回二三線,副業必須得搞,哪怕掙個零花錢,心裏不慌。

幸福指數高不高,離不開錢那,雖然很俗。粗茶淡飯沒問題,二三線城市的生活成本相對較低是真的,但你的掙錢能力也得匹配上吧,匹配不上的話,活得就會很累的。

我羡慕郭大佬,有些同學可能羡慕我,覺得我的幸福指數也很高。那我要告訴你的真相就是,我既要忙工作,還要讀書寫作,也是很拼的。

人生就是這樣,為別人的成功送上祝福的同時,不要忘記腳踏實地地活着。

如果覺得文章對你有點幫助,請微信搜索「 沉默王二 」第一時間閱讀。

本文已收錄 GitHub,傳送門~ ,裏面更有大廠面試完整考點,歡迎 Star。

我是沉默王二,一枚有顏值卻靠才華苟且的程序員。關注即可提升學習效率,別忘了三連啊,點贊、收藏、留言,我不挑,嘻嘻

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

【其他文章推薦】

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

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

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

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

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

聚甘新

Jmeter系列(21)- 詳解 HTTP Request

如果你想從頭學習Jmeter,可以看看這個系列的文章哦

https://www.cnblogs.com/poloyy/category/1746599.html

 

HTTP Request 介紹

用來發送 HTTP、HTTPS 協議請求

 

HTTP Request 界面

字段名 作用
名稱 不多介紹啦,建議自定義一個識別度高的名稱
註釋 對於測試沒有任何影響,僅記錄作用
協議

http或https,大小寫不敏感

默認:http

服務器名稱或IP
  • 服務器 host 或者 ip,不包括協議
  • 比如:www.baidu.com、192.168.196.128
端口號 目標服務器的端口號,默認:80
方法 發送 http 請求的方法
路徑
  • 目標請求的 URL 路徑
  • 不包括協議、host、ip、端口
內容編碼 請求的編碼方式,默認:iso8859
自動重定向
  • 發出的請求的響應碼是3**,會自動跳轉到新目標頁面
  • 只記錄最終頁面的返回結果
跟隨重定向
  • 和自動重定向唯一不同的是:
  • 會記錄重定向過程中的的所有請求的響應結果
使用 KeepAlive
  • jmeter 和目標服務器之間使用 Keep-Alive 方式進行 HTTP 通信
  • 真正做性能測試強烈建議不勾選
對POST使用multipart/form-data post 請求需要上傳文件時勾選
與瀏覽器兼容的頭
  • 當勾選 multipart/form-data 時,勾選此項
  • http請求頭中的 Content-Type 和Content-Transfer-Encoding 被忽略
  • 而只發送 Content-Disposition 部分

 

Parameters 講解

字段 描述
Name 參數名
Value 參數值
URL Encode?
  • 是否要 URL 編碼?
  • 重點:如果參數值包含了中文、特殊字符(非数字字母以外),最好勾上,當然全都勾上最穩妥
Content-Type
  • 參數值的資源類型
  • 默認:text/plain
Include Equals?
  • 當你的參數值為空的時候,可以選擇不包含=,默認勾選
  • 如果參數值不為空,則不可以取消勾選

 

什麼是 URL 編碼

  • URL 編碼解碼又叫百分號編碼,是統一資源定位(URL)的編碼方式
  • URL 地址(常說網址)規定了数字,字母可以直接使用,另外一批作為特殊用戶字符也可以直接用( / , : @  等),剩下的其它所有字符必須通過 %xx 編碼處理
  • 編碼方法很簡單,在該字符ascii碼的的16進制字符前面加%,如空格字符,ascii碼是32,對應16進制是20,那麼 urlencode 編碼結果是 %20 

 

URL 編碼的栗子

直接在網上搜在線 URL 編解碼

 

include equals 的栗子

參數值為空,且勾選 Include equals

 

參數值為空,但不勾選  Include equals

 

其實說的就是等於號而已,一般也不會傳空值,即使傳了也會帶上=

 

Body Data 講解

  • 沒啥好說的,傳 json 字符串就行了,注意下格式,後面再講具體栗子
  • 不過倒有個重點:如果 Parameters 有參數列表的話,是無法切換到 Body Data 的哦

 

Files Upload 講解

字段 描述
File Path 文件的本地路徑
Parameter Name 參數名
MIME Type 資源媒體類型

 

常見資源媒體類型

類型 文件後綴 格式
超文本標記語言文本 .html text/html
普通文本 .txt text/plain
XML 文件 .xml text/xml
PNG 圖片 .png image/png
GIF .gif image/gif
JPEG 圖片 .jpeg、jpg  image/jpeg

 

類型 文件後綴 格式
表單中進行文件上傳   multipart/form-data
表單默認提交數據的格式   application/x-www-form-urlencoded
XML 數據格式   application/xml
JSON 數據格式   application/json
PDF 文件 .pdf application/pdf
RTF 文本 .rtf application/rtf
GZIP 文件  .gz application/x-gzip
TAR 文件 .tar application/x-tar
AVI 文件 .avi  video/x-msvideo
MPEG 文件 .mpg、.mpeg video/mpeg

 

不同的content-type在jmeter中如何輸入參數

前提

因為是需要真實接口進行測試的,這裏提供兩種方案

  • 自己用 Flask 框架開發了本地的接口進行測試, 如果有需要的同學進群領取哦:870155189
  • 或者進入 http://open.yesapi.cn/?r=user/registration&from=wx_837493986,直接註冊個賬號,弄個免費會員,有在線免費的接口提供測試哦

 

application/x-www-form-urlencoded 的栗子

備註:也是表單提交最常見的栗子

 

Parameters 方式傳參

 

總結

  • 最終表單的參數列表會拼接到 URL 中,所以如果包含了中文、特殊字符就要勾選編碼?
  • 這裏不可以通過 Body Data 傳遞參數哦,會無法識別到參數,已實踐過(即使加了 HTTP請求頭也不行),乖乖用 Parameters 的方式傳參

 

content-type:application/json 的栗子

Body Data 方式傳參

 

添加 HTTP請求頭

 

請求體

 

請求頭

 

結論

重點就是添加 HTTP請求頭,指明 Content-type 是 json 格式

 

content-type:multipart/form-data

重點:用於 post 請求,需要文件上傳的場景;記住不是 get 請求

 

請求參數列表

如果選了 get 方法的話,文件參數是不會生效哦

 

文件參數

 

請求體

 

重點

  • 如果添加了 HTTP請求頭,請務必不要添加 content-type : multipart/form-data 
  • 如果加了的話:那麼所有的請求參數都會被當成文件以二進制形式傳輸,我們 parameters 里的文本格式參數就不會被識別,接口會提示參數為空

 

HTTP Request Advance

說實話我還沒用過這部分的內容,不過還是得了解下每個配置項是什麼意思哦

 

Client implemention 和 Timeouts

字段 描述
implementation 發送http請求的方式,可選項為 java、HttpClient4(默認)
Connect 連接超時時間,單位毫秒
Respones 響應等待超時時間,單位毫秒

 

Embedded Resources from HTML Files

  • 從HTML文件獲取所有內含的資源
  • jmeter 在發出的  HTTP請求獲得響應的 HTML文件內容后,對 HTML進行解析並獲取HTML中包含的所有資源(圖片、flash等)
字段 描述
Retrieve All Embedded Resources 發送http請求的方式,可選項為 java、HttpClient4(默認)
Parallel downloadds. Number

是否使用自設資源處。啟用后可以設置資源池大小,默認為6

URLs must match URL 匹配過濾,填寫此項則只會下載與此內容項匹配的 url 的資源

 

Source address

只用於 HTTP協議且 implemention = HttpClient4 時

字段 描述
IP/Hostname IP /主機名以使用特定的IP地址或(本地)主機名
Device 選擇設備以選擇該接口的第一個可用地址,該設備可以是IPv4或IPv6
Device IPv4 選擇IPv4設備來選擇名稱設備的IPv4地址(如eth0, lo, em0)
Device IPv6 選擇IPv6設備來選擇名稱設備的IPv6地址(如eth0, lo, em0)

 

Proxy Server

代理服務器

字段 描述
Server Name or IP 代理服務器的名稱或者IP地址
Port Number 代理服務器的端口號
Username 代理服務器的用戶名
Password 代理服務器的密碼

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

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

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

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

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

※回頭車貨運收費標準

聚甘新

2020年最佳Java調試工具(翻譯)

調試是應用程序開發周期不可或缺的一部分。用Java或任何其他語言編寫程序時,每個開發人員應解決的首要問題之一是可靠的調試工具的可用性。

所使用的工具類型可能影響或破壞應用程序的調試過程,因此至關重要的是,要了解根據用例而定最佳選擇。

在這篇文章中,我們概述了2020年最好的7種Java調試工具。在開發,生產環境中查找,診斷和修復問題時,這些工具中的大多數將派上用場。

NetBeans

NetBeans是運行在Linux,Windows,MacOS和Solaris上的頂級,使用最廣泛的Java IDE之一。正如預期的那樣,它具有可視化調試器和代碼概要分析器,使開發人員可以調試可執行的Java類,單元測試和整個項目。

NetBeans調試器允許您在Java代碼中放置斷點,運行方法,添加字段監視,監視執行以及在調試會話期間拍攝快照。

Rookout

Rookout是一個很棒的Java調試選項,可以在開發和生產中很好地工作。它在包括無服務器和容器的各種環境中提供了強大的調試功能。

通過收集和流水線化關鍵數據,淘汰工作超越了標準調試功能。這使開發人員無需編寫代碼,重新部署或重新啟動應用程序即可了解軟件執行問題並解決錯誤。

藉助Rookout,開發人員可以消除冗長,複雜且資源密集的數據探索和錯誤查找過程。

Eclipse

Eclipse是帶有內置Java調試器的著名開源IDE。自成立以來,Eclipse一直保持其作為開發現代應用程序最強大的跨平台IDE之一的聲譽。

它提供了標準的調試功能,例如設置斷點,執行步驟執行,檢查變量和值,掛起和恢複線程等功能。

Eclipse平台還方便了遠程調試。儘管它主要是Java IDE,但Eclipse調試視圖還支持PHP,C,C ++和許多其他編程語言。

IntelliJ IDEA

IntelliJ IDEA是具有功能強大的調試器的高度流行的Java IDE。該工具使開發人員可以輕鬆調試簡單代碼以及多線程Java應用程序。

使用IntelliJ調試器,您可以設置斷點,單步執行代碼,評估表達式,檢查變量以及執行一系列其他調試過程。它可以更輕鬆地檢測意外的流量和狀態,死鎖,活動鎖等。

IntelliJ IDEA的核心旨在改善Java開發團隊的工作流程和生產力。

Java調試器(JDB)

Java調試器(JDB)是允許開發人員在命令行中調試Java代碼的工具。通過Java調試接口(JDI)(高級前端接口),開發人員可以檢測並修復程序中的錯誤。該工具還可用於檢查和調試遠程Java虛擬機中的代碼。

像大多數命令行調試器一樣,JDB具有學習曲線,因此新用戶需要花費一些時間來適應JDB。但是,一旦掌握了JDB命令,就可以輕鬆設置斷點,單步執行代碼並執行其他調試操作。

Fusion Reactor(聚變反應堆??)

Fusion Reactor是針對開發,測試和生產環境中的Java應用程序的創新性能監視解決方案。該工具配備了一組令人印象深刻的功能,這些功能可提供Java開發人員在APM工具中所需的一切。

Fusion Reactor開發版,您可以開發,測試,並在非生產環境分析應用。使用此工具,在將應用程序部署到生產環境之前,更容易發現問題並提高代碼質量。

另一個值得注意的功能是生產調試器,它使開發人員在與代碼交互並修復錯誤時獲得最大的控制權。Fusion Reactor還支持遠程調試。

JDeveloper

Oracle的JDeveloper是一種免費的IDE,可解決應用程序開發生命周期中從編碼到設計,性能分析,調試,優化和部署的每個步驟。

使用JDeveloper進行調試時,可以設置斷點和觀察點,分析調用堆棧,檢查和操作變量,並逐步研究代碼執行情況。除了Java,它還可以用於調試HTML,PHP,JavaScript,SQL和XML。

現在,您可以繼續使用上述工具,以更高的速度和效率來檢測,診斷和解決Java應用程序中的問題。

翻譯原文

Top Java Debugging Tools for 2020

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

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

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

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

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

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

※超省錢租車方案

聚甘新

二叉樹的創建與遍歷(遞歸實現)

在樹的基本概念和術語總結一文中介紹了二叉樹的基本結構。

在不知道怎樣用遞歸?按步驟來!一文中介紹了如何使用遞歸。

二叉樹的結構是遞歸的,所以創建、遍歷也可以通過遞歸實現。

下面是一顆二叉樹:

結點的定義:

public class Node {
    Integer value;
    Node leftChild;
    Node rightChild;

    public Node(Integer value) {
        this.value = value;
    }
}

創建

各個結點的值用一個ArrayList集合來保存,根據該集合來創建二叉樹。

按照不知道怎樣用遞歸?按步驟來!中的方法分析如何遞歸地創建一顆二叉樹。

第一步:找到大問題是什麼?

創建一顆二叉樹。

private Node createBinaryTree(ArrayList<Integer> inputList) {
        
}

第二步:找到最簡單問題是什麼?滿足最簡單問題時應該做什麼?

「創建一個空二叉樹」是最簡單的問題,當滿足時,直接返回null

private Node createBinaryTree(ArrayList<Integer> inputList) {   
    if (inputList == null || inputList.isEmpty()) {//最簡單問題
        return null;
    }
}

第三步:找到重複邏輯是什麼?

因為我們把每個結點的值都放在ArrayList集合中了,所以,每創建一個二叉樹結點,都需要從集合中拿值。

對於每個結點而言,它一定有左孩子和右孩子(上圖中結點3的左孩子和右孩子可以看成「值為null的結點」),

所以要確定每個結點的左孩子和右孩子是誰。

所以重複邏輯是:

  1. 從集合中拿值,創建結點。
  2. 確定該結點的左孩子和右孩子。
//大問題
private Node createBinaryTree(ArrayList<Integer> inputList) {
    if (inputList == null || inputList.isEmpty()) {//最簡單問題
        return null;
    }
    Node node = null;//重複邏輯
    Integer value = inputList.remove(0);//重複邏輯
    if (value != null) {
        node = new Node(value);//重複邏輯
        node.leftChild = ?;//重複邏輯
        node.rightChild = ?;//重複邏輯
    }

}

第四步:自己調用自己

先解釋一下上個代碼片段中的問號。

要確定一個結點的左孩子和右孩子是誰,其實就是一個賦值操作,那麼就一定要先有一些可選的結點

比如說,如果我們要確定結點1的左右孩子,那麼結點2、結點5就必須已經被創建出來了,這樣才能進行賦值操作。

那麼如何在進行賦值操作之前創建結點2、結點5呢?答案是自己調用自己。

我們可以把結點2、結點5看成另一顆二叉樹的根結點,只要我們創建好以結點2或結點5為根結點的二叉樹,那麼結點2和結點5自然就被創建出來了。

確定結點2和結點5的左右孩子同理,這樣一直分解下去,直到分解成最簡單問題,或者從集合中拿到null為止。

注意:自己調用自己時參數的變小是通過inputList.remove(0)實現的。

//大問題
private Node createBinaryTree(ArrayList<Integer> inputList) {
    if (inputList == null || inputList.isEmpty()) {//最簡單問題
        return null;
    }
    Node node = null;//重複邏輯
    Integer value = inputList.remove(0);//重複邏輯
    if (value != null) {
        node = new Node(value);//重複邏輯
        node.leftChild = createBinaryTree(inputList);//重複邏輯,自己調用自己
        node.rightChild = createBinaryTree(inputList);//重複邏輯,自己調用自己
    }

}

第五步:返回

返回的是根結點,該根結點被確定為左孩子或右孩子,從而構成一顆更大的二叉樹,直到滿足最大問題的那顆二叉樹被創建成功,此時返回的根結點是真正的解。

//大問題
private Node createBinaryTree(ArrayList<Integer> inputList) {
    if (inputList == null || inputList.isEmpty()) {//最簡單問題
        return null;
    }
    Node node = null;//重複邏輯
    Integer value = inputList.remove(0);//重複邏輯
    if (value != null) {
        node = new Node(value);//重複邏輯
        node.leftChild = createBinaryTree(inputList);//重複邏輯,自己調用自己
        node.rightChild = createBinaryTree(inputList);//重複邏輯,自己調用自己
    }
	return node;//返回
}

遍歷

先序遍歷

第一步:找到大問題是什麼?

先序遍歷一顆二叉樹,打印出每個結點的值。

public void preOrderTraveral(Node node) {
    
}

第二步:找到最簡單問題是什麼?滿足最簡單問題時應該做什麼?

「遍歷一顆空二叉樹」是最簡單問題,此時任何操作都不用做。

public void preOrderTraveral(Node node) {
    if (node == null) {//最簡單問題
        return;
    }
}

第三步:找到重複邏輯是什麼?

打印每個結點的值

public void preOrderTraveral(Node node) {
    if (node == null) {//最簡單問題
        return;
    }
    System.out.print(node.value);//重複邏輯
}

第四步:自己調用自己

先序遍歷的過程:

  1. 遍歷根結點
  2. 先序遍歷左子樹
  3. 先序遍歷右子樹
public void preOrderTraveral(Node node) {
    if (node == null) {//最簡單問題
        return;
    }
    System.out.print(node.value);//重複邏輯
    preOrderTraversal(node.leftChild);//自己調用自己
    preOrderTraversal(node.rightChild);//自己調用自己
}

自己調用自己時參數通過node.leftChildnode.rightChild不斷變小

第五步:返回

不需要返回值。

中序遍歷和後序遍歷同理

完整代碼

//二叉樹結點
public class Node {
    Integer value;
    Node leftChild;
    Node rightChild;

    public Node(Integer value) {
        this.value = value;
    }
}
//二叉樹
public class BinaryTree {

    private Node root;

    public Node getRoot() {
        return root;
    }

    public BinaryTree(ArrayList<Integer> inputList) {
        Node root = createBinaryTree(inputList);
        this.root = root;
    }

	//創建二叉樹
    private Node createBinaryTree(ArrayList<Integer> inputList) {
        if (inputList == null || inputList.isEmpty()) {
            return null;
        }
        Node node = null;
        Integer value = inputList.remove(0);
        if (value != null) {
            node = new Node(value);
            node.leftChild = createBinaryTree(inputList);
            node.rightChild = createBinaryTree(inputList);
        }
        return node;
    }

    //先序遍歷
    public void preOrderTraversal(Node node) {
        if (node == null) {
            return;
        }
        System.out.print(node.value);
        preOrderTraversal(node.leftChild);
        preOrderTraversal(node.rightChild);
    }
	
    //中序遍歷
    public void inOrderTraversal(Node node) {
        if (node == null) {
            return;
        }
        inOrderTraversal(node.leftChild);
        System.out.print(node.value);
        inOrderTraversal(node.rightChild);
    }

    //後序遍歷
    public void postOrderTraversal(Node node) {
        if (node == null) {
            return;
        }
        postOrderTraversal(node.leftChild);
        postOrderTraversal(node.rightChild);
        System.out.print(node.value);
    }
}
//測試
public static void main(String[] args) {
    List<Integer> list = Arrays.asList(new Integer[]{1, 2, 3, null, null, 4, null, null, 5, null, 6});
    ArrayList inputList = new ArrayList(list);

    BinaryTree binaryTree = new BinaryTree(inputList);
    Node root = binaryTree.getRoot();
    System.out.print("先序遍歷:");
    binaryTree.preOrderTraversal(root);
    System.out.print("\n中序遍歷:");
    binaryTree.inOrderTraversal(root);
    System.out.print("\n後序遍歷:");
    binaryTree.postOrderTraversal(root);
}

如有錯誤,還請指正。

文章首發於微信公眾號『行人觀學』

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

【其他文章推薦】

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

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

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

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

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

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

聚甘新

java併發之synchronized

Java為我們提供了隱式(synchronized聲明方式)和顯式(java.util.concurrentAPI編程方式)兩種工具來避免線程爭用。

本章節探索Java關鍵字synchronized。主要包含以下幾個內容。

  • synchronized關鍵字的使用;
  • synchronized背後的Monitor(管程);
  • synchronized保證可見性和防重排序;
  • 使用synchronized注意嵌套鎖定。

使用方式

synchronized 關鍵字有以下四種使用方式。

  1. 實例方法
  2. 靜態方法
  3. 實例方法中的代碼塊
  4. 靜態方法中的代碼塊
// 實例方法同步和實例方法代碼塊同步
public class SynchronizedTest {
    private int count;
    public void setCountPart(int num) {
        synchronized (this) {
            this.count += num;
        }
    }
    public synchronized void setCount(int num) {
        this.count += num;
    }
}
// 靜態方法同步和靜態方法代碼塊同步
public class SynchronizedTest {
    private static int count;
    public static void setCountPart(int num) {
        synchronized (SynchronizedTest.class) {
            count += num;
        }
    }
    public static synchronized void setCount(int num) {
        count += num;
    }
}

使用關鍵字synchronized實現同步是在JVM內部實現處理,對於應用開發人員來說它是隱式進行的。

每個Java對象都有一個與之關聯的monitor。

當線程調用實例同步方法時,會自動獲取實例對象的monitor。

當線程調用靜態同步方法時,會自動獲取該類Class實例對象的monitor。

Class實例:JVM為每個加載的class創建了對應的Class實例來保存class及interface的所有信息;

Monitor(管程)

Monitor 直譯為監視器,中文圈裡稱為管程。它的作用是讓線程互斥,保護共享數據,另外也可以向其它線程發送滿足條件的信號

如下圖,線程通過入口隊列(Entry Queue)到達訪問共享數據,若有線程佔用轉移等待隊列(Wait Queue),線程訪問共享數據完后觸發通知或轉移到信號隊列(Signal Queue)。

關於管程模型

網上查詢很多文章,大多數羅列 “ Hasen 模型、Hoare 模型和 MESA模型 ”這些名詞,看過之後我還是一知半解。本着對知識的求真,查找溯源,找到了以下資料。

為什麼會有這三種模型?

假設有兩個線程A和B,線程B先進入monitor執行,線程A處於等待。當線程A執行完準備退出的時候,是先退出monitor還是先喚醒線程A?這時就出現了Mesa語義, Hoare語義和Brinch Hansen語義 三種不同版本的處理方式。

Mesa Semantics

Mesa模型中 線程只會出現在WaitQueue,EntryQueue,Monitor。

當線程B發出信號告知線程A時,線程A從WaitQueue 轉移到EntryQueue並等待線程B退出Monitor之後再進入Monitor。也就是先通知再退出。

Brinch Hanson Semantics

Brinch Hanson模型和Mesa模型類似區別在於僅允許線程B退出Monitor后才能發送信號給線程A。也就是先退出再通知。

Hoare Semantics

Hoare模型中 線程會分別出現在WaitQueue,EntryQueue,SignalQueue,Monitor中。

當線程B發出信號告知線程A並且退出Monitor轉移到SignalQueue,線程A進入Monitor。當線程A離開Monitor后,線程B再次回到Monitor。

https://www.andrew.cmu.edu/course/15-440-kesden/applications/ln/lecture6.html

https://cseweb.ucsd.edu/classes/sp17/cse120-a/applications/ln/lecture8.html

Java裏面monitor是如何處理?

我們通過反編譯class文件看下Synchronized工作原理。

public class SynchronizedTest {
    private int count;
    public void setCountPart(int num) {
        synchronized (this) {
            this.count += num;
        }
    }
}

編譯和反編譯命令

javac SynchronizedTest.java
javap -v SynchronizedTest

我們看到兩個關鍵指令 monitorentermonitorexit

monitorenter

Each object has a monitor associated with it. The thread that executes monitorenter gains ownership of the monitor associated with objectref. If another thread already owns the monitor associated with objectref, the current thread ……

每個對象都有一個關聯monitor。

線程執行 monitorenter 時嘗試獲取關聯對象的monitor。

獲取時如果對象的monitor被另一個線程佔有,則等待對方釋放monitor后再次嘗試獲取。

如果獲取成功則monitor計數器設置為1並將當前線程設為monitor擁有者,如果線程再次進入計數器自增,以表示進入次數。

monitorexit

The current thread should be the owner of the monitor associated with the instance referenced by objectref……

線程執行monitorexit 時,monitor計數器自減,當計數器變為0時釋放對象monitor。

原文:https://docs.oracle.com/javase/specs/jvms/se6/html/Instructions2.doc9.html

可見性和重排序

在介紹Java併發之內存模型的時候,我們提到過線程訪問共享對象時會先拷貝副本到CPU緩存,修改后返回CPU緩存,然後等待時機刷新到主存。這樣一來另外線程讀到的數據副本就不是最新,導致了數據的不一致,一般也將這種問題稱為線程可見性問題

不過在使用synchronized關鍵字的時候,情況有所不同。線程在進入synchronized後會同步該線程可見的所有變量,退出synchronized后,會將所有修改的變量直接同步到主存,可視為跳過了CPU緩存,這樣一來就避免了可見性問題。

另外Java編譯器和Java虛擬機為了達到優化性能的目的會對代碼中的指令進行重排序。但是重排序會導致多線程執行出現意想不到的錯誤。使用synchronized關鍵字可以消除對同步塊共享變量的重排序。

局限與性能

synchronized給我們提供了同步處理的便利,但是它在某些場景下也存在局限性,比如以下場景。

  • 讀多寫少場景。讀動作其實是安全,我們應該嚴格控制寫操作。替代方案使用讀寫鎖readwritelock。如果只有一個線程進行寫操作,可使用volatile關鍵字替代。
  • 允許多個線程同時進入場景。synchronized限制了每次只有一個線程可進入。替代方案使用信號量semaphore。
  • 需要保證搶佔資源公平性。synchronized並不保證線程進入的公平性。替代方案公平鎖FairLock。

關於性能問題。進入和退出同步塊操作性能開銷很小,但是過大範圍設置同步或者在頻繁的循環中使用同步可能會導致性能問題。

可重入,在monitorenter指令解讀中,可以看出synchronized是可重入,重入一般發生在同步方法嵌套調用中。不過要防止嵌套monitor死鎖問題。

比如下面代碼會直接造成死鎖。

    private final Object lock1 = new Object();
    private final Object lock2 = new Object();
    public void method1()   {
        synchronized (lock1) {
            synchronized (lock2) {
            }
        }
    }
    public void method2()   {
        synchronized (lock2) {
            synchronized (lock1) {
            }
        }
    }

現實情況中,開發一般都不會出現以上代碼。但在使用 wait() notify() 很可能會出現阻塞鎖定。下面是一個模擬鎖的實現。

  1. 線程A調用lock(),進入鎖定代碼執行。
  2. 線程B調用lock(),得到monitorObj的monitor后等待線程B喚醒。
  3. 線程A執行完鎖定代碼后,調用unlock(),在嘗試獲取monitorObj的monitor時,發現有線程佔用,也一直掛起。
  4. 這樣線程A B 就互相干瞪眼!
public class Lock{
protected MonitorObj monitorObj = new MonitorObj();
    protected boolean isLocked = false;
    public void lock() throws InterruptedException{
        synchronized(this){
            while(isLocked){
                synchronized(this.monitorObj){
                    this.monitorObj.wait();
                }
            }
            isLocked = true;
        }
    }
    public void unlock(){
        synchronized(this){
            this.isLocked = false;
            synchronized(this.monitorObj){
                this.monitorObj.notify();
            }
        }
    }
}

總結

本文記錄Java併發編程中synchronized相關的知識點。

歡迎大家留言交流,一起學習分享!!!

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

【其他文章推薦】

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

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

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

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

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

聚甘新

這樣用技術,程序猿更幸福

作為久經職場而又富有責任心的猿外,每天上班的第一件事,便是連上生產服務器,top free ps 一頓命令熱身猛如虎,然後匯總出業務服務的狀態、系統指標等,看到一切正常,心裏才算踏實。

不知道有多少盡職盡責的朋友們,每天都重複的做着如此机械而又簡單的事情。說句實話,其實和机械狗沒啥區別。本着做事認真、追求極致的態度,那我們為什麼不能打造一款這樣的机械狗呢?

關注過猿外的朋友們都應該知道,在之前的文章中,猿外曾經提到過,守護服務的監控應用——看門狗。不錯,要打造的机械狗,就是看門狗。

接下來就給大家簡單聊一聊,看門狗的背景來源以及實現思路,希望能給正在尋找守護服務監控解決方案的朋友們,一點實現思路。下面的內容十分燒腦,請大家坐穩扶好。

1. 什麼是看門狗?

在由單片機構成的微型計算機系統中,由於單片機的工作,常常會受到來自外界電磁場的干擾,造成程序的跑飛,而陷入死循環,程序的正常運行被打斷,由單片機控制的系統無法繼續工作,會造成整個系統的陷入停滯狀態,發生不可預料的後果,所以出於對單片機運行狀態進行實時監測的考慮,便產生了一種專門用於監測單片機程序運行狀態的芯片,稱“看門狗”——摘自“百度百科”。

相信有不少朋友,看到上面一段非人話的描述,就想直接刪了此篇熊文,溜之大吉。大家,心莫慌,猿外再舉個貼近生活的栗子解釋一下。

猿外家養了一條 dog ,經常陪孩子玩,孩子時不時的撒一把狗糧給 dog;
如果孩子睡着,長時間未撒狗糧,dog 就主動把孩子叫醒;
繼續讓孩子撒狗糧,然後 dog 吃到孩子撒的狗糧,就高興的狂吠;
如果 dog 跑走了,孩子也不撒狗糧了,孩子也就可以睡覺了。

剝開栗子,內行看門道,外行看熱鬧,把上面內容翻譯一遍:

原話:猿外家養了一條 dog,經常陪孩子玩,孩子時不時的撒一把狗糧給 dog;
翻譯:存在兩個進程:子進程(應用服務)、父進程(守護進程),子進程和父進程兩個進程間一直保持心跳通訊;

原話:如果孩子睡着,長時間未撒狗糧, dog 就主動把孩子叫醒;
翻譯:子進程與父進程心跳通訊中斷,父進程則負責啟動子進程,完成子進程的服務守護;

原話:繼續讓孩子撒狗糧,然後 dog 吃到孩子撒的狗糧,就高興的狂吠;
翻譯:子進程與父進程實時通訊,父進程實時監控子進程的狀態,並實時進行報警通知;

原話:如果 dog 跑走了,孩子也不撒狗糧了,孩子也就可以睡覺了。
翻譯:父進程down了,子進程與父進程的通訊也就無法保持了,子進程也一併退出。

不知道朋友們通過上面的栗子剖析,對看門狗了解了多少呢?為了更清晰的給你們說清楚,猿外再給大家上個一目瞭然的圖吧。

如上圖所示,猿外主要把看門狗定位為:集服務守護、指標採集、日誌歸集、自動化報警於一體的監控系統。說白了,有了看門狗,媽媽再也不用擔心我的應用服務出問題了。

洋洋洒洒鋪墊這麼多,那到底該如何實現呢?

2. 如何實現?

猿外先給大家畫個腦圖,主要分五步走,希望朋友們跟着猿外的腳步,莫掉隊。

相信絕大部分朋友通過猿外的腦圖,已經對看門狗應用了解個八九不離十。猿外再稍微闡述一下:

實現方式:看門狗應用在實現過程中也走了一些彎路。剛開始技術選型的時候,考慮到看門狗應該對應用零侵入、低耦合,於是採用javaagent植入方式進行實現,但是萬萬沒想到的是開發、調試過程比較麻煩,一遇到問題,小夥伴就排查半天,在第一版上線后,猿外迅速帶領小夥伴,採用插件化方式進行第二版迭代。

服務守護:採用 J2SE 1.5 java.lang 包中新添加的 ProcessBuilder 類來完成子進程的啟動、停止、重啟(完成子服務的守護功能)。

指標採集:通過 JMX 方式連接子服務,獲取子服務的內存佔用、CPU 使用、線程數等指標。

日誌歸集:主要站在 flume 的肩膀上,集成到項目中並進行二次開發,存儲採用 elasticsearch。

報警通知:主要提供郵箱通知、QQ 通知、微信實時通知。

其中每個實現細節猿外就不再進行深入展開了,感興趣的朋友歡迎關注微信公眾號四猿外(si-yuanwai),後台留言進行交流。

羅里吧嗦,寫了這麼多。羅馬並非一日建成的,實現途中踩過的坑、吃過的苦頭,剎那間全部湧上心頭。不過看門狗應用,已經在生產上推廣使用,自上線以來還廣受好評,猿外現在每天來的第一件事,由原來的系統巡檢變成了沖咖啡,幸福指數陡然上升。

最後,猿外想說的是:簡單的事重複做,你就是專家;重複的事用心做,你就是贏家。沒錯,這話說的沒毛病,不過成為專家之餘,大家不妨稍做創新,看看有沒有可以用的技術輪子,說不定會改變机械的工作方式,提升工作效率,提高幸福指數呢。

歡迎大家關注我的公眾號:四猿外。
我準備了一些純手打的高質量PDF,有好友贊助的也有我自己的,大家可以免費領取:
深入淺出Java多線程、HTTP超全匯總、Java基礎核心總結、程序員必知的硬核知識大全、簡歷面試談薪的超全乾貨。
別看數量不多,但篇篇都是乾貨,看完的都說很肝。

領取方式:掃碼關注后,在公眾號後台回復:666

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

【其他文章推薦】

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

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

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

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

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

聚甘新

Oracle SQL調優系列之SQL Monitor Report

@

目錄

  • 1、SQL Monitor簡介
  • 2、捕捉sql的前提
  • 3、SQL Monitor 參數設置
  • 4、SQL Monitor Report
    • 4.1、SQL_ID獲取
    • 4.2、Text文本格式
    • 4.3、Html格式
    • 4.4、ACTIVE格式
    • 4.5 SQL Monitoring list
  • 5、SQL Monitor Report查詢
    • 5.1、查看所有的sql monitor report
    • 5.2、查看某個sql的sql monitor report
    • 5.3、查看某個sql的整體性能
    • 5.4、查看整個系統的性能

1、SQL Monitor簡介

sql monitor是oracle官方提供的自動監控符合特定條件的SQL,用於收集執行時的細節信息的監控工具,常用於sql調優和系統性能監控

2、捕捉sql的前提

sql monitor 捕捉sql的前提:

  • 并行執行的sql語句
  • 單次執行消耗的CPU或IO超過5秒
  • statistics_level級別必須是TYPICAL 或者ALL
  • 使用/* +MONITOR*/ HINT的SQL語句

3、SQL Monitor 參數設置

  • STATISTICS_LEVEL必須設置為:’TYPICAL’(缺省)或者 ‘ALL’
  • CONTROL_MANAGEMENT_PACK_ACCESS設置為:’DIAGNOSTIC+TUNING’

查看statistics_level參數

show parameter statistics_level;

建議還是改變Session就可以

alter session set statistics_level=ALL;

查看參數CONTROL_MANAGEMENT_PACK_ACCESS

show parameter CONTROL_MANAGEMENT_PACK_ACCESS;

4、SQL Monitor Report

本博客採用DBMS_SQLTUNE包DBMS_SQLTUNE.report_sql_monitor的方式獲取,報告格式有:’TEXT’,’HTML’,’XML’ ,’ACTIVE’,其中’ACTIVE’只在11g R2以後才支持

4.1、SQL_ID獲取

sql monitor使用,必須在sql中使用/* +MONITOR*/ Hint,然後數據會存在v$sql_monitor表裡

隨意找條sql,注意要加/*+ moniotr*/


select /*+ moniotr*/ a.user_code, a.full_name, a.user_pwd, c.unit_code, c.unit_name
  from base_user a
  left join (select ur.user_code, ur.unit_code
               from t_user_role ur
              where ur.user_role < 10) b
    on a.user_code = b.user_code
  left join t_unit_info c
    on b.unit_code = c.unit_code
 where c.unit_code in
       (select uinfo.unit_code
          from t_unit_info uinfo
         start with uinfo.unit_code = '15803'
        connect by prior uinfo.unit_code = uinfo.para_unit_code);
     

提供sql查詢,獲取sql_id

select sql_id,sql_text from v$sql_monitor where sql_text like '%t_unit_info%

4.2、Text文本格式

將上面查詢到的sql_id改下,然後執行如下SQL:

SET LONG 1000000
SET LONGCHUNKSIZE 1000000
SET LINESIZE 1000
SET PAGESIZE 0
SET TRIM ON
SET TRIMSPOOL ON
SET ECHO OFF
SET FEEDBACK OFF
spool report_sql_monitor_text.txt
SELECT DBMS_SQLTUNE.REPORT_SQL_MONITOR(
  SQL_ID => 'g9rtj389t0g66',
  TYPE => 'TEXT',
  REPORT_LEVEL => 'ALL') AS REPORT
FROM dual;
spool off

獲取到text格式的sql monitor

4.3、Html格式

SET LONG 1000000
SET LONGCHUNKSIZE 1000000
SET LINESIZE 1000
SET PAGESIZE 0
SET TRIM ON
SET TRIMSPOOL ON
SET ECHO OFF
SET FEEDBACK OFF
spool report_sql_monitor_html.html
SELECT DBMS_SQLTUNE.REPORT_SQL_MONITOR(
  SQL_ID => 'g9rtj389t0g66',
  TYPE => 'HTML',
  REPORT_LEVEL => 'ALL') AS REPORT
FROM dual;
spool off

獲取到對應報告,可以看到執行計劃、Buffer Gets 等等信息

4.4、ACTIVE格式

ACTIVE格式需要下載相應的flash組件、腳本,詳細見SQL Monitor Report 使用詳解

SET LONG 1000000
SET LONGCHUNKSIZE 1000000
SET LINESIZE 1000
SET PAGESIZE 0
SET TRIM ON
SET TRIMSPOOL ON
SET ECHO OFF
SET FEEDBACK OFF
spool report_sql_monitor_active.html
SELECT DBMS_SQLTUNE.REPORT_SQL_MONITOR(
  SQL_ID => '2rjh5d5k2yujz',
  TYPE => 'ACTIVE',
  REPORT_LEVEL => 'ALL',
  BASE_PATH => 'http://ip/script') AS REPORT
FROM dual;
spool off

4.5 SQL Monitoring list

如果要獲取所有sql monitor,就可以使用如下SQL:

SET LONG 1000000
SET LONGCHUNKSIZE 1000000
SET LINESIZE 1000
SET PAGESIZE 0
SET TRIM ON
SET TRIMSPOOL ON
SET ECHO OFF
SET FEEDBACK OFF
SPOOL report_sql_monitor_list.html
SELECT dbms_sqltune.report_sql_monitor_list(
  type         => 'HTML',
  report_level => 'ALL') AS report
FROM dual;
SPOOL OFF

5、SQL Monitor Report查詢

提供sql monitor常用的查詢腳本

5.1、查看所有的sql monitor report

  • 查看所有的sql monitor report
   select dbms_sqltune.report_sql_monitor from dual;

5.2、查看某個sql的sql monitor report

  • 查看某個sql的sql monitor report
  SELECT DBMS_SQLTUNE.report_sql_monitor(sql_id => '2rjh5d5k2yujz', type => 'TEXT') from dual;

5.3、查看某個sql的整體性能

  • 查看某個sql的整體性能
   SELECT DBMS_SQLTUNE.report_sql_monitor_list(sql_id=>'2rjh5d5k2yujz',type =>'TEXT',report_level => 'ALL') AS report FROM dual;

5.4、查看整個系統的性能

  • 查看整個系統的性能
   SELECT DBMS_SQLTUNE.report_sql_monitor_list(type =>'TEXT',report_level => 'ALL') AS report FROM dual;

相關SQL腳本下載:sql download

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

【其他文章推薦】

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

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

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

※超省錢租車方案

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

聚甘新

基礎拾遺—-多線程,基礎拾遺—-多線程

基礎拾遺

基礎拾遺——特性詳解

基礎拾遺——webservice詳解

基礎拾遺——redis詳解

基礎拾遺——反射詳解

基礎拾遺——委託、事件詳解

基礎拾遺——接口詳解

基礎拾遺——泛型詳解

基礎拾遺—–依賴注入

基礎拾遺—–數據註解與驗證

基礎拾遺—–mongoDB操作

基礎拾遺—-RabbitMQ

基礎拾遺—CLR

基礎拾遺—-多線程

前言

我們知道c# 程序是自上而下的,但有的時候部分程序使用時間較長比如下載文檔什麼的。這是就可以用到線程。線程可以理解為是程序的執行路徑,每個線程都定義了一個獨特的控制流。如果您的應用程序涉及到複雜的和耗時的操作,那麼設置不同的線程執行路徑往往是有益的,每個線程執行特定的工作。

1.線程的生命周期

線程生命周期開始於 System.Threading.Thread 類的對象被創建時,結束於線程被終止或完成執行時。

下面列出了線程生命周期中的各種狀態:

未啟動狀態:當線程實例被創建但 Start 方法未被調用時的狀況。

就緒狀態:當線程準備好運行並等待 CPU 周期時的狀況。

不可運行狀態:下面的幾種情況下線程是不可運行的:

  已經調用 Sleep 方法

  已經調用 Wait 方法

  通過 I/O 操作阻塞

死亡狀態:當線程已完成執行或已中止時的狀況。

2.多線程的優缺點

2.1.優點

  1. 可以使用線程將代碼同其他代碼隔離,提高應用程序的可靠性。
  2. 可以使用線程來簡化編碼。
  3. 可以使用線程來實現併發執行。
  4. 可以提高CPU的利用率

2.2.缺點

  1. 線程開的越多,內存佔用越大。
  2. 協調和管理代碼的難度加大,需要CPU時間跟蹤線程。
  3. 線程之間對共享資源的訪問會相互影響,必須解決競用共享資源的問題。
  4. 銷毀線程需要了解可能發生的問題並對那些問題進行處理。

3.線程的實現

3.1.異步委託

關於委託基礎拾遺——委託、事件詳解這有詳細介紹,我們都知道調用委託Delegate()或者Delegate?.Invoke()。進行執行,但是主線程的代碼是從上至下進行執行的,那麼我們想要委託方法進行一個新的線程只需BeginInvoke生成異步方法調用即可。

3.3.1.實現

        public delegate void ThreadDelegate();
        static void MethodDelegata()
        {
            Console.WriteLine("MethodDelegata");
        }
        static void Main(string[] args)
        {
            ThreadDelegate d = MethodDelegata;
            //BeginInvoke 兩個參數一個是執行完后回調方法,一個是返回結果,如委託有參數載氣前方添加即可。
            d.BeginInvoke(null,null);
            Console.WriteLine("Main");
            Console.ReadKey();
        } 

執行結果如下

 3.1.1.獲取線程返回值

線程執行時有可能執行時間過長,如果我們要獲取線程的返回值,這是就需要不回線程的狀態和利用線程的回調方法。

  • 檢測等待線程狀態
       public delegate int ThreadDelegate(int i);
        static int MethodDelegata(int i)
        {
            Console.WriteLine("MethodDelegata" + i);
            Thread.Sleep(1000);
            return 100;

        }
        static void Main(string[] args)
        {
            ThreadDelegate d = MethodDelegata;
            //BeginInvoke 兩個參數一個是執行完后回調方法,一個是返回結果
            IAsyncResult ar = d?.BeginInvoke(1, null, null);//獲取線程執行狀態
            Console.WriteLine("Main");
            while (!ar.IsCompleted) {//線程是否已執行完成,未完成執行
                Console.WriteLine(".");
                Thread.Sleep(10);//減少線程監測頻率
            }
            int res = d.EndInvoke(ar);//獲取線程的返回值
            Console.WriteLine(res);
            Console.ReadKey();
        }

結果如下

·  

我們如果不用while 的方式去等待方法執行結束,可以  ar.AsyncWaitHandle.WaitOne(1000); 但我們預估執行時間如果小於實際執行時間的化,返回值就獲取不到了。

 bool isEnd = ar.AsyncWaitHandle.WaitOne(1000);
                if (isEnd)
            {
                int res = d.EndInvoke(ar);//獲取線程的返回值
                Console.WriteLine(res);

            }

View Code

  • 利用 d?.BeginInvoke(1, callBackobject) 回調方法
       public delegate int ThreadDelegate(int i);
        static int MethodDelegata(int i)
        {
            Console.WriteLine("MethodDelegata" + i);
            Thread.Sleep(1000);
            return 100;

        }
        static void Main(string[] args)
        {
            ThreadDelegate d = MethodDelegata;
            //BeginInvoke 兩個參數一個是執行完后回調方法,一個是返回結果
            IAsyncResult ar = d?.BeginInvoke(1, CallBack, d);//獲取線程執行狀態
            Console.WriteLine("Main");
         
            Console.ReadKey();
        }
        /// <summary>
        /// 結束回調方法
        /// </summary>
        /// <param name="ar"></param>
        private static void CallBack(IAsyncResult ar)
        {
            var obj=ar.AsyncState as ThreadDelegate;
            int res = obj.EndInvoke(ar);
            Console.WriteLine("線程結束,結果為:"+res);
        }

 我們通過lamda表達式優化一下上面的代碼

     static void Main(string[] args)
        {
            ThreadDelegate d = MethodDelegata;
            //BeginInvoke 兩個參數一個是執行完后回調方法,一個是返回結果
            //IAsyncResult ar = d?.BeginInvoke(1, CallBack, d);//獲取線程執行狀態
            d?.BeginInvoke(1, ar => {
                int res = d.EndInvoke(ar);
                Console.WriteLine("線程結束,結果為:" + res);
            }, null);
            Console.WriteLine("Main");
            Console.ReadKey();
        }

3.2.Thread 類

3.2.1.不帶參數

        static void MethodThread()
        {
            Console.WriteLine("MethodDelegata");//第二個參數最多執行時間
            Thread.Sleep(1000);
        }
        static void Main(string[] args)
        {
            Thread t = new Thread(MethodThread);//創建了thread 對象單位啟動
        //Thread t = new Thread(()=> { Console.WriteLine("MethodDelegata"); Thread.Sleep(1000);});//可直接用lamda表達式 
            t.Start();
            Console.WriteLine("Main");
            Console.ReadKey();
        }

3.2.2.帶參數 

Start(obj) 傳參:定義方法如果有參數必須object
        static void MethodThread(object s) { Console.WriteLine("MethodDelegata"); Thread.Sleep(1000); } static void Main(string[] args) { //創建了thread 對象單位啟動
            Thread t = new Thread(MethodThread); t.Start("wokao");//傳遞參數
            Console.WriteLine("Main"); Console.ReadKey(); }
對象傳參:定義存放數據和線程方法的類
   class Program
    {
       
        static void Main(string[] args)
        {
            //創建了thread 對象單位啟動
            ClassThead c = new ClassThead("1");
            Thread t = new Thread(c.MethodThread);
            t.Start();//傳遞參數
            Console.WriteLine("Main");
            Console.ReadKey();
        }

    }
    public class ClassThead
    {
        private string wr;
        public ClassThead(string s)
        {
            this.wr = s;
        }
        public  void MethodThread()
        {
            Console.WriteLine("MethodDelegata");
            Thread.Sleep(1000);

        }
    }

3.2.3 前台線程和後台線程

  1.     前台線程:只要存在有一個前台線程在運行,應用程序就在運行。
  2.     後台線程:應用程序關閉時,如果後台線程沒有執行完,會被強制性的關閉
  3.     默認情況下,用Thread類創建的線程是前台線程,線程池中的線程總是後台線程。
  4. thread.IsBackground = true; 設置為後台程序
   static void MethodThread()
        {
            Console.WriteLine("MethodDelegata");
            Thread.Sleep(10000);

            Console.ReadKey();

        }
        static void Main(string[] args)
        {
            Thread t = new Thread(MethodThread);
            t.IsBackground = true;//當main執行結束后,不管t是否執行結束程序都關閉
            t.Start();//傳遞參數
            Console.WriteLine("Main");
        }

thread.Abort() 終止線程的執行。調用這個方法,會拋出一個ThreadAbortException類型的異常。

thread.Join() 將當前線程睡眠,等待thread線程執行完,然後再繼續執行當前線程。

3.3.線程池threadPool

上面已經說了線程是為後台線程,在這多線程的操作推薦使用線程池線程而非新建線程。因為就算只是單純的新建一個線程,這個線程什麼事情也不做,都大約需要1M的內存空間來存儲執行上下文數據結構,並且線程的創建與回收也需要消耗資源,耗費時間。而線程池的優勢在於線程池中的線程是根據需要創建與銷毀,是最優的存在。但是這也有個問題,那就是線程池線程都是後台線程,主線程執行完畢后,不會等待後台線程而直接結束程序。

 //如果帶參數必須為object
        static void MethodThreadPool(object obj)
        {
            Console.WriteLine("MethodDelegata"+Thread.CurrentThread.ManagedThreadId);//當前線程id
            Thread.Sleep(1000);
         }
        static void Main(string[] args)
        {
            ThreadPool.QueueUserWorkItem(MethodThreadPool);// 必須帶參數
            ThreadPool.QueueUserWorkItem(MethodThreadPool);
            ThreadPool.QueueUserWorkItem(MethodThreadPool);
            ThreadPool.QueueUserWorkItem(MethodThreadPool);
            ThreadPool.QueueUserWorkItem(MethodThreadPool);
            Console.WriteLine("Main");
            Console.ReadKey();
        }

3.4. Task

  1. Task是架構在Thread之上的,也就是說任務最終還是要拋給線程去執行。
  2. Task跟Thread不是一對一的關係,比如開10個任務並不是說會開10個線程,這一點任務有點類似線程池,但是任務相比線程池有很小的開銷和精確的控制
  3. 可以將任務入隊到線程池中異步執行。
  4. 線程池入隊的任務無法取消
  5. 沒有回調方法,可以使用委託實現回調

3.4.1.任務的定義

方法1

var t1 = new Task(() => TaskMethod("Task 1"));
t1.Start();
Task.WaitAll(t1);//等待所有任務結束 

方法2

Task.Run(() => TaskMethod("Task 2"));

方法3

  Task.Factory.StartNew(() => TaskMethod("Task 3")); 直接異步的方法 
  //或者
  var t3=Task.Factory.StartNew(() => TaskMethod("Task 3"));
  Task.WaitAll(t3);//等待所有任務結束

案列

 static void Main(string[] args)
        {
            var t1 = new Task(() => TaskMethod("Task 1"));
            var t2 = new Task(() => TaskMethod("Task 2"));
            t2.Start();
            t1.Start();
            Task.WaitAll(t1, t2);
            Task.Run(() => TaskMethod("Task 3"));
            Task.Factory.StartNew(() => TaskMethod("Task 4"));
            //標記為長時間運行任務,則任務不會使用線程池,而在單獨的線程中運行。
            Task.Factory.StartNew(() => TaskMethod("Task 5"), TaskCreationOptions.LongRunning);

            Console.WriteLine("主線程執行業務處理.");
            //創建任務
            Task task = new Task(() =>
            {
                Console.WriteLine("使用System.Threading.Tasks.Task執行異步操作.");
                for (int i = 0; i < 10; i++)
                {
                    Console.WriteLine(i);
                }
            });
            //啟動任務,並安排到當前任務隊列線程中執行任務(System.Threading.Tasks.TaskScheduler)
            task.Start();
            Console.WriteLine("主線程執行其他處理");
            task.Wait();
            Thread.Sleep(TimeSpan.FromSeconds(1));
            Console.ReadLine();
        }

        static void TaskMethod(string name)
        {
            Console.WriteLine("Task {0} is running on a thread id {1}. Is thread pool thread: {2}",
                name, Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread);
        }

View Code

3.4.2.async/await

async是contextual關鍵字,await是運算符關鍵字。

async/await 結構可分成三部分:

  1. 調用方法:該方法調用異步方法,然後在異步方法執行其任務的時候繼續執行;
  2. 異步方法:該方法異步執行工作,然後立刻返回到調用方法;
  3. await 表達式:用於異步方法內部,指出需要異步執行的任務。一個異步方法可以包含多個 await 表達式(不存在 await 表達式的話 IDE 會發出警告)。
class Program
    {
        async static void AsyncFunction()
        {
            await Task.Delay(1);
            Console.WriteLine("使用System.Threading.Tasks.Task執行異步操作.");
            for (int i = 0; i < 10; i++)
            {
                Console.WriteLine(string.Format("AsyncFunction:i={0}", i));
            }
        }
        public static void Main()
        {
            Console.WriteLine("主線程執行業務處理.");
            AsyncFunction();
            Console.WriteLine("主線程執行其他處理");
            for (int i = 0; i < 10; i++)
            {
                Console.WriteLine(string.Format("Main:i={0}", i));
            }
            Console.ReadLine();
        }
    }

4.線程爭用與死鎖

  class Program
    {
        static void ChangeState(object obj)
        {
            ClassThead c = obj as ClassThead;
            while (true)
            {
                c.MethodThread();
            }
        }
        //如果帶參數必須為object

        static void Main(string[] args)
        {
            ClassThead c = new ClassThead();
            Thread t = new Thread(ChangeState);
            t.Start(c);
            Console.WriteLine("Main");
            Console.ReadKey();
        }

    }
    public class ClassThead
    {
        private int state = 6;
        public void MethodThread()
        {
            state++;
            if (state == 6)
            {
                Console.WriteLine("MethodDelegata");
                Thread.Sleep(1000);
            }
            state = 6;
        }
    }

View Code

可以從上面的方法中看到執行結果為空,雖然他在執行但是state一直都是>6的。所以是不執行的。

但如果開啟兩個線程的結果是什麼呢?

是執行的因為多個線程有可能是在執行時另一個線程給他賦值了。所以我們就要給對象加鎖

        static void ChangeState(object obj)
        {
            ClassThead c = obj as ClassThead;
            while (true)
            {
                lock (c)
                {
                    c.MethodThread();
                }
            }
        }

注:但是有可能會出現線程爭用一直等待的情況,所以在編程過程設計好鎖的順序

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

【其他文章推薦】

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

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

※超省錢租車方案

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

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

聚甘新

測試人員遇到Android APP崩潰和無響應手足無措?,基於Python的Appium環境搭建合集

這2天,在測APP兼容性時,遇到APP奔潰閃退的情況。將問題反饋給開發后,開發自己調試后,沒有復現。由於又是遠程,base地不在一塊,我總不能把手機寄過去吧,那也太費事了。

所以就想到,提供明確的報錯日誌,讓開發定位問題,豈不是就很方便了,也解決了遠程的問題。

那如何抓取到Crash日誌呢,我又沒開發調試工具,也不可能在短時間內搭建一套開發環境。尋思答案后,最終得到了完美解決,且聽細細道來。

了解Crash

我們先來簡單了解下Crash:Crash,就是崩潰。anr(Application Not Responding — 程序無響應)是Crash的一種。程序正常運行中,可能會出現未捕獲到的異常,這就會造成崩潰。

常見Crash異常

NullPointerException  空指針

ClassCastException  類型轉換異常

IndexOutOfBoundsException  下標越界異常

ActivityNotFoundException Activity  未找到異常

IllegalStateException  非法狀態異常

ArrayIndexOutOfBoundsException  數組越界異常

SecurityException  安全異常

NoSuchMethodException  方法未找到異常

SQLException  操作數據庫異常

抓取奔潰和無響應日誌

對於開發人員來說,抓取日誌是很方便的,但對於測試人員來說,就不是太方便了。大多都是直接dos窗口下執行adb命令來抓取日誌,而每次都敲命令也是很麻煩。

所以通過adb程序與bat命令組合使用來抓取日誌,就要方便很多了,短短几秒鐘,可以輕鬆搞定日誌的抓取,期不期待。

環境準備

安裝JDK和ADB,這個安裝很簡單,可參見以前的博文,基於Python的Appium環境搭建合集。

bat文件製作

環境準備好后,就來寫bat文件了。

捕獲Crash異常的bat文件命令

命令參考如下,製作成logcat.bat文件,logcat.bat文件可以放置任意位置。注意:如果adb沒有配置到環境變量中,則需要將logcat.bat文件放到adb對應文件夾中。

@ECHO OFF
for /f "tokens=2 delims==" %%a in ('wmic OS Get localdatetime /value') do set "dt=%%a"

SET timeStamp=%dt:~0,4%-%dt:~4,2%-%dt:~6,2%_%dt:~8,2%-%dt:~10,2%-%dt:~12,2%
SET mutID=_mut

@ECHO ON
adb logcat -v time > .\"%mutID%_%timeStamp%_logcat.log"

pause

上述命令實現原理:該工具的原理是bat文件調用adb工具,將手機運行日誌拉到本地,並將實時日誌也記錄到本地。

當手機需要重現Crash、或者某一段時間內已經發生過Crash,點擊我們製作的bat文件,logcat文件中的命令會將手機的logcat日誌拉下來並實時記錄,直到你關閉cmd窗口。

然後在拉下來的txt中尋找FATAL關鍵字,附近上下文即為Crash日誌。

捕獲ANR異常的bat文件命令

anr:全稱為Application Not Responding,意思為程序無響應。

命令參考如下,製作成anr.bat文件,anr.bat文件可以放置任意位置。注意:如果adb沒有配置到環境變量中,則需要將anr.bat文件放到adb對應文件夾中。

@ECHO OFF
for /f "tokens=2 delims==" %%a in ('wmic OS Get localdatetime /value') do set "dt=%%a"
SET timeStamp=%dt:~0,4%-%dt:~4,2%-%dt:~6,2%_%dt:~8,2%-%dt:~10,2%-%dt:~12,2%
SET mutID=_mut
@ECHO ON
adb pull data/anr/traces.txt traces_%timeStamp%.txt

實現原理與捕獲Crash異常是一樣的,只是該命令是針對發生anr的情況。

具體實踐

捕獲Crash異常具體用法:

①將android手機連接電腦,開啟開發者模式並允許usb調試;

②運行logcat.bat文件

③如果手機程序已經發生過crash,10秒后關閉cmd窗口;如果是想重現crash,則在手機端重現后即可關閉cmd窗口;

④在logcat.bat的同級目錄下會生成一份log文件,從文件中搜查FATAL關鍵字,便可找到崩潰代碼。

查看報錯日誌,報錯如下所示:

如上所示截圖,就是測試過程中,發生奔潰的日誌了,將日誌貼在bug里,既方便開發排查問題,又節約協作時間。

使用優點

使用bat文件捕獲日誌,有如下幾個有點:手機無需root;無需開發環境支持;方便保存、查找日誌;操作簡單。

以上就是捕獲報錯日誌的操作步驟了,希望對有需要的博友有所幫助。

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

聚甘新