環境資訊中心綜合外電;姜唯 編譯;林大利 審校
本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※為什麼 USB CONNECTOR 是電子產業重要的元件?
※網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!
※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光
※想知道最厲害的台北網頁設計公司推薦、台中網頁設計公司推薦專業設計師”嚨底家”!!
環境資訊中心綜合外電;姜唯 編譯;林大利 審校
本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※為什麼 USB CONNECTOR 是電子產業重要的元件?
※網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!
※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光
※想知道最厲害的台北網頁設計公司推薦、台中網頁設計公司推薦專業設計師”嚨底家”!!
摘錄自2020年2月19日中央社報導
聯合國今(18日)發布一份具里程碑意義的報告提出警告,全球未能保護兒童免受氣候變遷和不良飲食帶來的健康危害,每位兒童面臨「立即的威脅」。
這份報告由世界衛生組織(WHO)和聯合國兒童基金會(UNICEF)委託完成。另外,報告中也強調,孩童面臨高脂高糖食品、酒類及菸草有害行銷手法的威脅。
這份發表在英國醫學期刊「刺胳針」(The Lancet)的報告,針對180個國家的孩童存活率、受教育和營養比率進行排名。
在這些指標下,和挪威、荷蘭等富裕國家相比,諸如中非共和國和查德等低度開發國家表現尤其差。不過,評估人均碳排放量對空氣污染造成的影響時,排名產生反轉。
本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!
※網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!
※想知道最厲害的台北網頁設計公司推薦、台中網頁設計公司推薦專業設計師”嚨底家”!!
kubelet 運行在每個 worker 節點上,接收 kube-apiserver 發送的請求,管理 Pod 容器,執行交互式命令,如 exec、run、logs 等。
kubelet 啟動時自動向 kube-apiserver 註冊節點信息,內置的 cadvisor 統計和監控節點的資源使用情況。
為確保安全,部署時關閉了 kubelet 的非安全 http 端口,對請求進行認證和授權,拒絕未授權的訪問(如 apiserver、heapster 的請求)。
提示:k8smaster01節點已下載相應二進制,可直接分發至node節點。
1 [root@k8smaster01 ~]# cd /opt/k8s/work 2 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh 3 [root@k8smaster01 work]# for all_ip in ${ALL_IPS[@]} 4 do 5 echo ">>> ${all_ip}" 6 scp kubernetes/server/bin/kubelet root@${all_ip}:/opt/k8s/bin/ 7 ssh root@${all_ip} "chmod +x /opt/k8s/bin/*" 8 done
1 [root@k8smaster01 ~]# cd /opt/k8s/work 2 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh 3 [root@k8smaster01 work]# for all_name in ${ALL_NAMES[@]} 4 do 5 echo ">>> ${all_name}" 6 7 # 創建 token 8 export BOOTSTRAP_TOKEN=$(kubeadm token create \ 9 --description kubelet-bootstrap-token \ 10 --groups system:bootstrappers:${all_name} \ 11 --kubeconfig ~/.kube/config) 12 13 # 設置集群參數 14 kubectl config set-cluster kubernetes \ 15 --certificate-authority=/etc/kubernetes/cert/ca.pem \ 16 --embed-certs=true \ 17 --server=${KUBE_APISERVER} \ 18 --kubeconfig=kubelet-bootstrap-${all_name}.kubeconfig 19 20 # 設置客戶端認證參數 21 kubectl config set-credentials kubelet-bootstrap \ 22 --token=${BOOTSTRAP_TOKEN} \ 23 --kubeconfig=kubelet-bootstrap-${all_name}.kubeconfig 24 25 # 設置上下文參數 26 kubectl config set-context default \ 27 --cluster=kubernetes \ 28 --user=kubelet-bootstrap \ 29 --kubeconfig=kubelet-bootstrap-${all_name}.kubeconfig 30 31 # 設置默認上下文 32 kubectl config use-context default --kubeconfig=kubelet-bootstrap-${all_name}.kubeconfig 33 done
解釋:
向 kubeconfig 寫入的是 token,bootstrap 結束后 kube-controller-manager 為 kubelet 創建 client 和 server 證書。
token 有效期為 1 天,超期后將不能再被用來 boostrap kubelet,且會被 kube-controller-manager 的 tokencleaner 清理;
kube-apiserver 接收 kubelet 的 bootstrap token 后,將請求的 user 設置為 system:bootstrap:<Token ID>,group 設置為 system:bootstrappers,後續將為這個 group 設置 ClusterRoleBinding。
1 [root@k8smaster01 work]# kubeadm token list --kubeconfig ~/.kube/config #查看 kubeadm 為各節點創建的 token 2 [root@k8smaster01 work]# kubectl get secrets -n kube-system|grep bootstrap-token #查看各 token 關聯的 Secret
1 [root@k8smaster01 ~]# cd /opt/k8s/work 2 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh 3 [root@k8smaster01 work]# for all_name in ${ALL_NAMES[@]} 4 do 5 echo ">>> ${all_name}" 6 scp kubelet-bootstrap-${all_name}.kubeconfig root@${all_name}:/etc/kubernetes/kubelet-bootstrap.kubeconfig 7 done
從 v1.10 開始,部分 kubelet 參數需在配置文件中配置,建議創建kubelet配置文件。
1 [root@k8smaster01 ~]# cd /opt/k8s/work 2 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh 3 [root@k8smaster01 work]# cat > kubelet-config.yaml.template <<EOF 4 kind: KubeletConfiguration 5 apiVersion: kubelet.config.k8s.io/v1beta1 6 address: "##ALL_IP##" 7 staticPodPath: "" 8 syncFrequency: 1m 9 fileCheckFrequency: 20s 10 httpCheckFrequency: 20s 11 staticPodURL: "" 12 port: 10250 13 readOnlyPort: 0 14 rotateCertificates: true 15 serverTLSBootstrap: true 16 authentication: 17 anonymous: 18 enabled: false 19 webhook: 20 enabled: true 21 x509: 22 clientCAFile: "/etc/kubernetes/cert/ca.pem" 23 authorization: 24 mode: Webhook 25 registryPullQPS: 0 26 registryBurst: 20 27 eventRecordQPS: 0 28 eventBurst: 20 29 enableDebuggingHandlers: true 30 enableContentionProfiling: true 31 healthzPort: 10248 32 healthzBindAddress: "##ALL_IP##" 33 clusterDomain: "${CLUSTER_DNS_DOMAIN}" 34 clusterDNS: 35 - "${CLUSTER_DNS_SVC_IP}" 36 nodeStatusUpdateFrequency: 10s 37 nodeStatusReportFrequency: 1m 38 imageMinimumGCAge: 2m 39 imageGCHighThresholdPercent: 85 40 imageGCLowThresholdPercent: 80 41 volumeStatsAggPeriod: 1m 42 kubeletCgroups: "" 43 systemCgroups: "" 44 cgroupRoot: "" 45 cgroupsPerQOS: true 46 cgroupDriver: cgroupfs 47 runtimeRequestTimeout: 10m 48 hairpinMode: promiscuous-bridge 49 maxPods: 220 50 podCIDR: "${CLUSTER_CIDR}" 51 podPidsLimit: -1 52 resolvConf: /etc/resolv.conf 53 maxOpenFiles: 1000000 54 kubeAPIQPS: 1000 55 kubeAPIBurst: 2000 56 serializeImagePulls: false 57 evictionHard: 58 memory.available: "100Mi" 59 nodefs.available: "10%" 60 nodefs.inodesFree: "5%" 61 imagefs.available: "15%" 62 evictionSoft: {} 63 enableControllerAttachDetach: true 64 failSwapOn: true 65 containerLogMaxSize: 20Mi 66 containerLogMaxFiles: 10 67 systemReserved: {} 68 kubeReserved: {} 69 systemReservedCgroup: "" 70 kubeReservedCgroup: "" 71 enforceNodeAllocatable: ["pods"] 72 EOF
1 [root@k8smaster01 ~]# cd /opt/k8s/work 2 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh 3 [root@k8smaster01 work]# for all_ip in ${ALL_IPS[@]} 4 do 5 echo ">>> ${all_ip}" 6 sed -e "s/##ALL_IP##/${all_ip}/" kubelet-config.yaml.template > kubelet-config-${all_ip}.yaml.template 7 scp kubelet-config-${all_ip}.yaml.template root@${all_ip}:/etc/kubernetes/kubelet-config.yaml 8 done
1 [root@k8smaster01 ~]# cd /opt/k8s/work 2 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh 3 [root@k8smaster01 work]# cat > kubelet.service.template <<EOF 4 [Unit] 5 Description=Kubernetes Kubelet 6 Documentation=https://github.com/GoogleCloudPlatform/kubernetes 7 After=docker.service 8 Requires=docker.service 9 10 [Service] 11 WorkingDirectory=${K8S_DIR}/kubelet 12 ExecStart=/opt/k8s/bin/kubelet \\ 13 --allow-privileged=true \\ 14 --bootstrap-kubeconfig=/etc/kubernetes/kubelet-bootstrap.kubeconfig \\ 15 --cert-dir=/etc/kubernetes/cert \\ 16 --cni-conf-dir=/etc/cni/net.d \\ 17 --container-runtime=docker \\ 18 --container-runtime-endpoint=unix:///var/run/dockershim.sock \\ 19 --root-dir=${K8S_DIR}/kubelet \\ 20 --kubeconfig=/etc/kubernetes/kubelet.kubeconfig \\ 21 --config=/etc/kubernetes/kubelet-config.yaml \\ 22 --hostname-override=##ALL_NAME## \\ 23 --pod-infra-container-image=registry.cn-beijing.aliyuncs.com/k8s_images/pause-amd64:3.1 \\ 24 --image-pull-progress-deadline=15m \\ 25 --volume-plugin-dir=${K8S_DIR}/kubelet/kubelet-plugins/volume/exec/ \\ 26 --logtostderr=true \\ 27 --v=2 28 Restart=always 29 RestartSec=5 30 StartLimitInterval=0 31 32 [Install] 33 WantedBy=multi-user.target 34 EOF
解釋:
1 [root@k8smaster01 ~]# cd /opt/k8s/work 2 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh 3 [root@k8smaster01 work]# for all_name in ${ALL_NAMES[@]} 4 do 5 echo ">>> ${all_name}" 6 sed -e "s/##ALL_NAME##/${all_name}/" kubelet.service.template > kubelet-${all_name}.service 7 scp kubelet-${all_name}.service root@${all_name}:/etc/systemd/system/kubelet.service 8 done
kubelet 啟動時查找 –kubeletconfig 參數對應的文件是否存在,如果不存在則使用 –bootstrap-kubeconfig 指定的 kubeconfig 文件向 kube-apiserver 發送證書籤名請求 (CSR)。
kube-apiserver 收到 CSR 請求后,對其中的 Token 進行認證,認證通過後將請求的 user 設置為 system:bootstrap:<Token ID>,group 設置為 system:bootstrappers,這一過程稱為 Bootstrap Token Auth。
默認情況下,這個 user 和 group 沒有創建 CSR 的權限,因此kubelet 會啟動失敗,可通過如下方式創建一個 clusterrolebinding,將 group system:bootstrappers 和 clusterrole system:node-bootstrapper 綁定。
1 [root@k8smaster01 ~]# kubectl create clusterrolebinding kubelet-bootstrap --clusterrole=system:node-bootstrapper --group=system:bootstrappers
1 [root@k8smaster01 ~]# source /opt/k8s/bin/environment.sh 2 [root@k8smaster01 ~]# for all_name in ${ALL_NAMES[@]} 3 do 4 echo ">>> ${all_name}" 5 ssh root@${all_name} "mkdir -p ${K8S_DIR}/kubelet/kubelet-plugins/volume/exec/" 6 ssh root@${all_name} "/usr/sbin/swapoff -a" 7 ssh root@${all_name} "systemctl daemon-reload && systemctl enable kubelet && systemctl restart kubelet" 8 done
kubelet 啟動后使用 –bootstrap-kubeconfig 向 kube-apiserver 發送 CSR 請求,當這個 CSR 被 approve 后,kube-controller-manager 為 kubelet 創建 TLS 客戶端證書、私鑰和 –kubeletconfig 文件。
注意:kube-controller-manager 需要配置 –cluster-signing-cert-file 和 –cluster-signing-key-file 參數,才會為 TLS Bootstrap 創建證書和私鑰。
提示:
啟動服務前必須先創建工作目錄;
關閉 swap 分區,否則 kubelet 會啟動失敗。
1 [root@k8smaster01 ~]# source /opt/k8s/bin/environment.sh 2 [root@k8smaster01 ~]# for all_name in ${ALL_NAMES[@]} 3 do 4 echo ">>> ${all_name}" 5 ssh root@${all_name} "systemctl status kubelet" 6 done 7 [root@k8snode01 ~]# kubectl get csr 8 [root@k8snode01 ~]# kubectl get nodes
創建三個 ClusterRoleBinding,分別用於自動 approve client、renew client、renew server 證書。
1 [root@k8snode01 ~]# cd /opt/k8s/work 2 [root@k8snode01 work]# cat > csr-crb.yaml <<EOF 3 # Approve all CSRs for the group "system:bootstrappers" 4 kind: ClusterRoleBinding 5 apiVersion: rbac.authorization.k8s.io/v1 6 metadata: 7 name: auto-approve-csrs-for-group 8 subjects: 9 - kind: Group 10 name: system:bootstrappers 11 apiGroup: rbac.authorization.k8s.io 12 roleRef: 13 kind: ClusterRole 14 name: system:certificates.k8s.io:certificatesigningrequests:nodeclient 15 apiGroup: rbac.authorization.k8s.io 16 --- 17 # To let a node of the group "system:nodes" renew its own credentials 18 kind: ClusterRoleBinding 19 apiVersion: rbac.authorization.k8s.io/v1 20 metadata: 21 name: node-client-cert-renewal 22 subjects: 23 - kind: Group 24 name: system:nodes 25 apiGroup: rbac.authorization.k8s.io 26 roleRef: 27 kind: ClusterRole 28 name: system:certificates.k8s.io:certificatesigningrequests:selfnodeclient 29 apiGroup: rbac.authorization.k8s.io 30 --- 31 # A ClusterRole which instructs the CSR approver to approve a node requesting a 32 # serving cert matching its client cert. 33 kind: ClusterRole 34 apiVersion: rbac.authorization.k8s.io/v1 35 metadata: 36 name: approve-node-server-renewal-csr 37 rules: 38 - apiGroups: ["certificates.k8s.io"] 39 resources: ["certificatesigningrequests/selfnodeserver"] 40 verbs: ["create"] 41 --- 42 # To let a node of the group "system:nodes" renew its own server credentials 43 kind: ClusterRoleBinding 44 apiVersion: rbac.authorization.k8s.io/v1 45 metadata: 46 name: node-server-cert-renewal 47 subjects: 48 - kind: Group 49 name: system:nodes 50 apiGroup: rbac.authorization.k8s.io 51 roleRef: 52 kind: ClusterRole 53 name: approve-node-server-renewal-csr 54 apiGroup: rbac.authorization.k8s.io 55 EOF 56 [root@k8snode01 work]# kubectl apply -f csr-crb.yaml
解釋:
auto-approve-csrs-for-group:自動 approve node 的第一次 CSR; 注意第一次 CSR 時,請求的 Group 為 system:bootstrappers;
node-client-cert-renewal:自動 approve node 後續過期的 client 證書,自動生成的證書 Group 為 system:nodes;
node-server-cert-renewal:自動 approve node 後續過期的 server 證書,自動生成的證書 Group 為 system:nodes。
1 [root@k8snode01 ~]# kubectl get csr | grep boot #等待一段時間(1-10 分鐘),三個節點的 CSR 都被自動 approved 2 [root@k8snode01 ~]# kubectl get nodes #所有節點均 ready 3 [root@k8snode01 ~]# ls -l /etc/kubernetes/kubelet.kubeconfig 4 [root@k8snode01 ~]# ls -l /etc/kubernetes/cert/|grep kubelet
基於安全性考慮,CSR approving controllers 不會自動 approve kubelet server 證書籤名請求,需要手動 approve。
1 [root@k8smaster01 ~]# kubectl get csr 2 [root@k8smaster01 ~]# kubectl certificate approve csr-2kmtj 3
1 [root@k8smaster01 ~]# ls -l /etc/kubernetes/cert/kubelet-*
1 [root@k8smaster01 ~]# sudo netstat -lnpt|grep kubelet #查看kubelet監聽端口
解釋:
kubelet 配置了如下認證參數:
同時配置了如下授權參數:
authroization.mode=Webhook:開啟 RBAC 授權。
kubelet 收到請求后,使用 clientCAFile 對證書籤名進行認證,或者查詢 bearer token 是否有效。如果兩者都沒通過,則拒絕請求,提示 Unauthorized。
1 [root@k8smaster01 ~]# curl -s --cacert /etc/kubernetes/cert/ca.pem https://172.24.8.71:10250/metrics 2 Unauthorized[root@k8smaster01 ~]# 3 [root@k8smaster01 ~]# curl -s --cacert /etc/kubernetes/cert/ca.pem -H "Authorization: Bearer 123456" https://172.24.8.71:10250/metrics 4 Unauthorized
若通過認證后,kubelet 使用 SubjectAccessReview API 向 kube-apiserver 發送請求,查詢證書或 token 對應的 user、group 是否有操作資源的權限(RBAC)。
1 [root@k8smaster01 ~]# curl -s --cacert /etc/kubernetes/cert/ca.pem --cert /etc/kubernetes/cert/kube-controller-manager.pem --key /etc/kubernetes/cert/kube-controller-manager-key.pem https://172.24.8.71:10250/metrics #默認權限不足 2 Forbidden (user=system:kube-controller-manager, verb=get, resource=nodes, subresource=metrics) 3 curl -s --cacert /etc/kubernetes/cert/ca.pem --cert /opt/k8s/work/admin.pem --key /opt/k8s/work/admin-key.pem https://172.24.8.71:10250/metrics|head #使用最高權限的admin
解釋:
–cacert、–cert、–key 的參數值必須是文件路徑,如上面的 ./admin.pem 不能省略 ./,否則返回 401 Unauthorized。
1 [root@k8smaster01 ~]# kubectl create sa kubelet-api-test 2 [root@k8smaster01 ~]# kubectl create clusterrolebinding kubelet-api-test --clusterrole=system:kubelet-api-admin --serviceaccount=default:kubelet-api-test 3 [root@k8smaster01 ~]# SECRET=$(kubectl get secrets | grep kubelet-api-test | awk '{print $1}') 4 [root@k8smaster01 ~]# TOKEN=$(kubectl describe secret ${SECRET} | grep -E '^token' | awk '{print $2}') 5 [root@k8smaster01 ~]# echo ${TOKEN}
1 [root@k8smaster01 ~]# curl -s --cacert /etc/kubernetes/cert/ca.pem -H "Authorization: Bearer ${TOKEN}" https://172.24.8.71:10250/metrics|head
cadvisor 是內嵌在 kubelet 二進制中的,統計所在節點各容器的資源(CPU、內存、磁盤、網卡)使用情況的服務。
瀏覽器訪問 https://172.24.8.71:10250/metrics 和 https://172.24.8.71:10250/metrics/cadvisor 分別返回 kubelet 和 cadvisor 的 metrics。
注意:
kubelet.config.json 設置 authentication.anonymous.enabled 為 false,不允許匿名證書訪問 10250 的 https 服務;
參考https://github.com/opsnull/follow-me-install-kubernetes-cluster/blob/master/A.%E6%B5%8F%E8%A7%88%E5%99%A8%E8%AE%BF%E9%97%AEkube-apiserver%E5%AE%89%E5%85%A8%E7%AB%AF%E5%8F%A3.md,創建和導入相關證書,然後訪問上面的 10250 端口。本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※為什麼 USB CONNECTOR 是電子產業重要的元件?
※網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!
※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光
※想知道最厲害的台北網頁設計公司推薦、台中網頁設計公司推薦專業設計師”嚨底家”!!
※專營大陸快遞台灣服務
※台灣快遞大陸的貨運公司有哪些呢?
為深化EVCARD專案合作,中國國網上海是電力公司與上海國際汽車城集團簽屬戰略協議,將在上海新增4,000組電動車充電設備,促進上海市的電動車租賃業發展。
《新民晚報》報導,國往上海電力公司與上海國際汽車城將在上海三處建置新的充電設備,位置在漕溪電動車示範充電站(華石路38號)、浦東新區政府三角地、上海火車站北廣場下沉廣場。3日啟用後,用戶可透過手機EVCARD APP連線使用。未來,充電網將逐步提供EVCARD租賃車輛熱點租還車服務,將提高電動車租賃的便利性。
國網上海市電力公司表示,今年該公司規劃在上海市建設229座城市快充站、22做城際高速快充站、14做公共充電站,共將新增4,000做充電樁。2016年間,目標覆蓋轄區內所有高速公路充電服務。
本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※為什麼 USB CONNECTOR 是電子產業重要的元件?
※網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!
※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光
※想知道最厲害的台北網頁設計公司推薦、台中網頁設計公司推薦專業設計師”嚨底家”!!
※專營大陸快遞台灣服務
※台灣快遞大陸的貨運公司有哪些呢?
近日,國家發改委正式批復同意《關於要求核准萬向集團公司年產5000輛新能源客車項目申請報告的請示》,這意味著萬向集團公司年產5000輛新能源客車項目正式通過。
該專案建設規模為年產5000輛新能源客車,分步實施。一是改建北塘路廠區,利用原有純電動專用車生產廠房和生產、研發、檢測設備,新增部分工藝、公用設備及工位元器具,實現研發和小批量生產能力。二是在建設一路廠區新建焊裝、塗裝、總裝、底盤等車間及研發大樓和試車跑道,新增各類生產和研發設備。
專案建設地點為浙江省杭州市蕭山經濟技術開發區萬向集團公司現有北塘路廠區和建設一路廠區。
據悉,項目總投資20.6億元,其中新增投資17.6億元,利用原有固定資產投資3億元。新增投資包括固定資產投資12.7億元,鋪底流動資金4.8億元。新增投資資金來源為企業自籌10.4億元,申請銀行貸款7.1億元。
本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!
※網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!
※想知道最厲害的台北網頁設計公司推薦、台中網頁設計公司推薦專業設計師”嚨底家”!!
※大陸寄台灣空運注意事項
※大陸海運台灣交貨時間多久?
上一篇文章寫了一個簡易版的螞蟻莊園登山賽,有小夥伴留言說想要看星星球的,那麼就寫起來吧!
配置環境 cocos creator 3d 1.0.0
3d里節點無法用 cc.Node.EventType.TOUCH_START 監聽。最終在論壇上找到一個 raycast 解決方法。參考代碼如下。
start() {
systemEvent.on(SystemEventType.TOUCH_START, this.onClickBall, this);
}
private _ray = new geometry.ray();
private onClickBall(touch: Touch, event: EventTouch) {
const pos = touch.getLocation();
this.camera.screenPointToRay(pos.x, pos.y, this._ray);
const result: { node: Node }[] = this.node_ball_click.scene.renderScene['raycast'](this._ray);
if (result.some((i) => {
if (i.node === this.node_ball_click) {
return true;
}
})) {
//點擊到小球處理邏輯
}
}
其中 result 返回的是一個包含node節點的結果數組。獲取后需要判斷一下是否為小球節點。
據說這個方案消耗性能比較大,後續應該會有更好的解決方案。
採用了編輯器的動畫編輯器,對需要部分增加動畫效果。由於我的資源是網上找的,那隻雞有些身體部分切割的不好,所以小雞的動畫比較差一些。
需要注意的是動畫編輯器里的rotation屬性,與節點里的屬性面板的rotation對應不上,而應該採用eulerAngles的屬性。
據說後續版本會處理?
採用tween控制小球坐標數值,先移動到最高點,然後再移動到最低點。
在運動軌跡中加入一些隨機值,就可以達到不同位置的效果啦。
tweenUtil(this._node_balll_pos)
.stop()
.to(time, new math.Vec3((this.node_ball.position.x + BALL_INIT_X) / 2, BALL_MAX_Y * (0.8 + 0.2 * Math.random()), targetZ / 2))
.to(time, new math.Vec3(BALL_GAMEOVER_X, BALL_MIN_Y, targetZ))
.start();
完成這個小功能主要遇到的問題是3d節點點擊事件,和動畫系統的rotation的問題。不過這些都在論壇里找到了相應的解決方法。
以上就是我最新的學習成果!如有問題或新的想法歡迎留言!我有了好想法會第一時間分享給大家的!
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※為什麼 USB CONNECTOR 是電子產業重要的元件?
※網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!
※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光
※想知道最厲害的台北網頁設計公司推薦、台中網頁設計公司推薦專業設計師”嚨底家”!!
※專營大陸快遞台灣服務
※台灣快遞大陸的貨運公司有哪些呢?
grunt插件,是一種npm環境下的自動化工具。對於需要反覆重複的任務,例如壓縮、編譯、單元測試、linting等,自動化工具可以減輕你的勞動,簡化你的工作。grunt模塊根據Gruntfile.js文件中的配置進行任務。
如果在package.json中定義如下命令:
"scripts": {
"build": "npm install && grunt"
}
因為運行npm run build會先安裝devDependencies中定義的一些模塊,則運行npm run build這個命令相當於做如下操作:
每一份 Gruntfile.js(和grunt插件)都遵循同樣的格式,你所書寫的Grunt代碼必須放在此函數內:
module.exports = function(grunt){
//do grunt-related things in here
}
大部分的Grunt任務都依賴某些配置數據,我們通過grunt.initConfig 方法來配置Grunt任務的參數。
grunt.initConfig 方法的參數是一個JSON對象,你可以在這個配置對象中存儲任意的數據。此外,由於這本身就是JavaScript,你還可以在這裏使用任意的有效的JS代碼。甚至你可以用<% %>模板字符串來引用已經配置過的屬性,例如:
// 項目和任務配置
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'), //獲取 package.json 中的元數據(js代碼)
proj:{
name:'hello',
description:'a hello demo'
},
hello: {
options: {
name: '<%= proj.name %>' //用<% %>模板字符串匹配hello
},
srcs: ['1.txt', '2.txt']
}
});
在grunt.initConfig 方法中配置的屬性,在任務模塊中,可用grunt.config方法進行訪問,例如:
grunt.config("proj.name");
另外,grunt任務模塊會自動根據任務名來提取配置對象中和任務名對應的屬性,比如定義任務hello,則在配置對象對應的屬性”hello”中配置任務執行函數中所需用到的配置和數據。
為了減少重複勞動,我們可以加載已有的插件任務。
可將自己定義的一些task腳本放在同一個目錄下,通過grunt.loadTasks方法從指定目錄加載該目錄下所有的grunt任務腳本。
像 grunt-contrib-copy和grunt-contrib-uglify這些常用的任務都已經以grunt插件的形式被開發出來了,且被發布在npm公開庫中,只要在 package.json 文件中將需要使用的插件列在dependency中,並通過npm install安裝之後,就可以直接加載該任務。
// 加載能夠提供"copy"任務的插件。
grunt.loadNpmTasks('grunt-contrib-copy');
npm上有個load-grunt-tasks插件可以用來加載dependency列表中所有以”grunt-“打頭的插件。
將需要使用的”grunt-“打頭的插件列在dependency中,然後在Gruntfile.js中進行調用。
//Load grunt tasks from NPM packages
load-grunt-tasks
grunt.registerTask('hello', 'Show some msg', function() {
console.log(this.options().name); //輸出hello
});
可以將一個任務定義為一系列任務的組合,這一系列任務將按照順序執行。
grunt.registerTask('dothings', 'copy and Show some msg', ['copy','hello']);
通過定義 default 任務,可以讓Grunt默認執行一個或多個任務。執行 grunt 命令時如果不指定一個任務的話,將會執行默認任務。如進行下面定義的話執行grunt 相當於執行grunt hello。
grunt.registerTask('default', ['hello']);
registerMultiTask方法可以定義一個複合任務,複合任務將會對grunt.initConfig 方法中配置的相應屬性中除了options外定義的屬性依次作為target:data對進行處理。
module.exports = function(grunt) {
grunt.initConfig({
Log: {
options: {
sep: ';'
},
srcs: ['1.txt', '2.txt'],
dests: ['d1.txt', 'd2.txt']
}
});
grunt.registerMultiTask("Log", function() {
var options = this.options({ sep: '&' });
console.log(this.target);
console.log(this.data.join(options.sep));
});
};
執行grunt Log將會輸出:
Running “Log:srcs” (Log) task
srcs
1.txt;2.txt
Running “Log:dests” (Log) task
dests
d1.txt;d2.txt
grunt.registerTask('mytask', function() {
grunt.task.run('task1', 'task2');
// Or:
grunt.task.run(['task1', 'task2']);
});
grunt.registerTask('mytask', function() {
var done = this.async();
//do something
done();
});
在任務中,當執行失敗,可以return false來表明當前任務執行失敗,一般,多個任務按順序執行,如果有任務失敗時,所有後續任務都將終止。可以通過在命令行后加上–force來使有任務失敗時,後續任務能繼續進行。
有些任務可以依賴於其他任務的成功執行。通過grunt.task.requires方法來檢查其前置任務是否已經執行,並且沒有失敗。
可以用方法grunt.task.requiresConfig指定一個或者多個字符串或者數組的配置屬性為必需的。如果一個或多個必需的配置屬性缺失,就通知系統當前任務失敗。
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!
※網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!
※想知道最厲害的台北網頁設計公司推薦、台中網頁設計公司推薦專業設計師”嚨底家”!!
※大陸寄台灣空運注意事項
※大陸海運台灣交貨時間多久?
https://git-scm.com/book/zh/v2/Git-%E5%B7%A5%E5%85%B7-%E9%87%8D%E7%BD%AE%E6%8F%AD%E5%AF%86
https://git-scm.com/book/en/v2/Git-Tools-Reset-Demystified
https://git-scm.com/docs/git-reset
https://www.liaoxuefeng.com/wiki/896043488029600/897013573512192
從歷史記錄中刪除 參考 https://www.cnblogs.com/studywithallofyou/p/11772684.html https://www.cnblogs.com/studywithallofyou/p/11772844.html
在使用git的時候,我們一般提倡是不允許回滾。對於問題的追蹤和項目的發展歷程而言歷史記錄都是有用的。並且為了節省一點存儲空間而丟失寶貴的代碼信息是不值當的。但是我們開發中,肯定會遇到特殊情況需要回退。比如確實操作錯了一步歷史提交,導致倉庫混亂污染或是內容丟失,我們需要回退到乾淨的一次提交,重新操作。
在git等所有的版本管理軟件中,刪除操作,只是增加一次記錄,內容並不會被刪掉,我們可以大膽的操作,這也符合版本倉庫的邏輯。只有特殊情況才需要真正的刪除,做這種操作的時候需要特別注意,因為一旦失誤,無法挽回。
要想理解git reset,那麼就要搞清楚git倉庫管理流程:
我們修改完內容后,這些記錄單單是保存在我們本地目錄下,也就是工作目錄。如果丟了,就是丟了,無法找回。與普通磁盤上的文件一樣,除非到回收站找回。
這時,如果運行了add,那麼內容就被記錄到本地的暫存倉庫,也就是index。這時如果刪除文件,在暫存區的內容還存在,並沒有丟失。
如下圖,我們創建了一個文件4,這時文件4在本地。我們add到暫存倉庫,然後刪掉文件4,我們發現又多了一條記錄刪除記錄,原來的4還存在,我們可以commit。原來的文件4並沒有丟失。
運行commit之後,修改的內容就被保存到了倉庫,修改了HEAD(HEAD就是指向當前倉庫在哪一個提交歷史,不特殊修改,都指向最新的一次提交)。這時運行git status發現目錄是乾淨的。git裏面需要提交倉庫,暫存倉庫和本地目錄內容都完全一致,才是乾淨的,任何一個不一致都會有不同的提示。
比如上面的圖,提交的倉庫中沒有文件4,暫存倉庫中增加了4,所以显示綠色的提示內容,add了文件但是還沒有commit。但是本次又刪除了文件,本地目錄中的內容與暫存倉庫也不一樣,所以提示紅色的內容,因為算是警告,沒有add的內容是會丟失的。
修改
add
commit
理解了上面的流程,就可以理解git reset了。git reset就是分別逆向操作,也就是把HEAD(提交的倉庫)回退到一個指定歷史,把HEAD、index(暫存倉庫)都回退到指定歷史,把HEAD、index和本地目錄的內容都回退到一個指定歷史。
git reset –soft就是把HEAD回退到指定歷史。運行后結果如下
也就相當於我們add了修改的文件,本地目錄和暫存倉庫都已經一致了,就等待commit。如果我們commit,可以再次填寫commit記錄,也就實現了git commit –amend的功能。
git reset –mixed就是把HEAD和index都回退到指定的歷史。這個也是運行git reset不加參數時的默認規則。運行結果如下
相當於我們僅僅修改了文件,還沒做任何處理。
git reset –hard這個是把HEAD、index和本地目錄的內容都回退到指定的歷史記錄。做這一步操作的時候一定要小心,最好把所有的內容都提交,並且push到遠程或是拷貝一份。因為這個操作會重置本地的內容到一個指定歷史。
我們先運行git log,看到有三個提交歷史
運行git reset –hard HEAD~
我們發現倉庫是乾淨的,並且原來的3文件沒了,運行git log參看
提交的歷史記錄也沒有了。
這次是真的回退到了指定歷史,所有的記錄都不見了,我們可以開心的(真的嗎?)在原來一個乾淨的分支上繼續寫代碼了。
世上有沒有後悔葯我不知道,但是git作為一個先進的分佈式管理器,卻有無限可能。如果你一不小心,腦袋發熱,運行了git reset –hard,但是發現不是你想要的,原來的記錄也沒了。怎麼辦?大腦瞬間充血,一片空白。不要慌,首先冷靜下來,然後運行git reflog,這個命令是告訴你你的每一次對倉庫操作的歷史記錄,如下
看一下最上面的幾條,第一條告訴你當前在e86d948這個提交記錄,通過reset切換過來的。對照上面的git log,可以發現這個是第二個提交歷史,也就是我們git reset –hard HEAD~的時候回退的分支。第二條記錄告訴我們當前是fc9fbc6分支,通過reset回退到這個分支的,我們可以參考上面的git log記錄,這個就是我們想要回去的分支。好了,有了記錄的哈希值,我們只需再運行一次git reset –hard,如下
查看一下git log
回來了。
git不會刪除任何已經提交到版本庫的內容,除非你非要專門特殊這樣做,並且為了再給你一次機會,像git reset這樣,就算你明確說明不要了,它也不會立馬刪除,除非超過一定時間或是你主動運行git gc等操作,把無用的,沒有關聯的內容刪掉。不然,那條記錄還是在本地倉庫,只不過它沒有被載入歷史的長河中。
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※為什麼 USB CONNECTOR 是電子產業重要的元件?
※網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!
※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光
※想知道最厲害的台北網頁設計公司推薦、台中網頁設計公司推薦專業設計師”嚨底家”!!
※專營大陸快遞台灣服務
※台灣快遞大陸的貨運公司有哪些呢?
MySQL 原理篇
數據準備:
/* SQLyog Ultimate v12.09 (64 bit) MySQL - 5.6.17 : Database - test ********************************************************************* */ /*!40101 SET NAMES utf8 */; /*!40101 SET SQL_MODE=''*/; /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; CREATE DATABASE /*!32312 IF NOT EXISTS*/`test` /*!40100 DEFAULT CHARACTER SET utf8 */; USE `test`; /*Table structure for table `t2` */ DROP TABLE IF EXISTS `t2`; CREATE TABLE `t2` ( `id` int(11) NOT NULL, `name` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*Data for the table `t2` */ insert into `t2`(`id`,`name`) values (1,'1'),(4,'4'),(7,'7'),(10,'10'); /*Table structure for table `teacher` */ DROP TABLE IF EXISTS `teacher`; CREATE TABLE `teacher` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(32) NOT NULL, `age` int(11) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4; /*Data for the table `teacher` */ insert into `teacher`(`id`,`name`,`age`) values (1,'seven11124',18),(2,'qingshan',18); /*Table structure for table `user_account` */ DROP TABLE IF EXISTS `user_account`; CREATE TABLE `user_account` ( `id` int(11) NOT NULL DEFAULT '0', `balance` int(11) NOT NULL, `lastUpdate` datetime NOT NULL, `userID` int(11) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; /*Data for the table `user_account` */ insert into `user_account`(`id`,`balance`,`lastUpdate`,`userID`) values (1,3200,'2018-12-06 13:27:57',1),(2,50,'2018-12-06 13:28:08',2),(3,1000,'2018-12-06 13:28:22',3); /*Table structure for table `users` */ DROP TABLE IF EXISTS `users`; CREATE TABLE `users` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(32) NOT NULL, `age` int(11) NOT NULL, `phoneNum` varchar(32) NOT NULL, `lastUpdate` datetime NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `idx_eq_name` (`name`) ) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8mb4; /*Data for the table `users` */ insert into `users`(`id`,`name`,`age`,`phoneNum`,`lastUpdate`) values (1,'seven',26,'13666666666','2018-12-07 19:22:51'),(2,'qingshan',19,'13777777777','2018-12-08 21:01:12'),(3,'james',20,'13888888888','2018-12-08 20:59:39'),(4,'tom',99,'13444444444','2018-12-06 20:34:10'),(6,'jack',91,'13444444544','2018-12-06 20:35:07'),(11,'jack1',33,'13441444544','2018-12-06 20:36:19'),(15,'tom2',30,'1344444444','2018-12-08 15:08:24'),(19,'iiii',30,'1344444444','2018-12-08 21:21:47'); /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
在運行下面的演示案例之前,先把表和數據準備好。
鎖是用於管理不同事務對共享資源的併發訪問。
表鎖與行鎖的區別:
InnoDB 存儲引擎支持行鎖和表鎖(另類的行鎖),InnoDB 的表鎖是通過對所有行加行鎖實現的。
行鎖的算法
官網文檔:
共享鎖:又稱為讀鎖,簡稱 S 鎖,顧名思義,共享鎖就是多個事務對於同一數據可以共享一把鎖,都能訪問到數據,但是只能讀不能修改。
通過如下代碼,加鎖和釋放鎖:
-- 加鎖 select * from users WHERE id=1 LOCK IN SHARE MODE; -- 釋放鎖:提交事務 or 回滾事務 commit; rollback;
-- 共享鎖 -- 事務A執行 BEGIN; SELECT * FROM users WHERE id=1 LOCK IN SHARE MODE; ROLLBACK; COMMIT; -- 事務B執行 SELECT * FROM users WHERE id=1; UPDATE users SET age=19 WHERE id=1;
結論:UPDATE 語句被鎖住了,不能執行。在事務A獲得共享鎖的情況下,事務B可以執行查詢操作,但是不能執行更新操作。
排它鎖:又稱為寫鎖,簡稱 X 鎖,排他鎖不能與其他鎖並存,如一個事務獲取了一個數據行的排他鎖,其他事務就不能再獲取該行的鎖(共享鎖、排他鎖),只有該獲取了排他鎖的事務是可以對數據行進行讀取和修改。(其他事務要讀取數據可來自於快照)
通過如下代碼,加鎖和釋放鎖:
-- 加鎖 -- delete / update / insert 默認加上X鎖 -- SELECT * FROM table_name WHERE ... FOR UPDATE -- 釋放鎖:提交事務 or 回滾事務 commit; rollback;
-- 排它鎖 -- 事務A執行 BEGIN; UPDATE users SET age=23 WHERE id=1; COMMIT; ROLLBACK; -- 事務B執行 SELECT * FROM users WHERE id=1 LOCK IN SHARE MODE; SELECT * FROM users WHERE id=1 FOR UPDATE; -- SELECT 可以執行,數據來自於快照 SELECT * FROM users WHERE id=1;
結論:事務B的第一條 SQL 和第二條 SQL 語句都不能執行,都已經被鎖住了,第三條 SQL 可以執行,數據來自於快照,關於這點後面會講到。
InnoDB 的行鎖是通過給索引上的索引項加鎖來實現的。
只有通過索引條件進行數據檢索,InnoDB 才使用行級鎖,否則,InnoDB 將使用表鎖(鎖住索引的所有記錄)
通過普通索引進行數據檢索,比如通過下面例子中 UPDATE users SET lastUpdate=NOW() WHERE `name`='seven';該 SQL 會在 name 字段的唯一索引上面加一把行鎖,同時會在該唯一索引對應的主鍵索引上面也會加上一把行鎖,總共會加兩把行鎖。
演示之前,先看一下 users 表的結構和數據內容。
-- 案例1 -- 事務A執行 BEGIN; UPDATE users SET lastUpdate=NOW() WHERE phoneNum='13666666666'; ROLLBACK; -- 事務B執行 UPDATE users SET lastUpdate=NOW() WHERE id=2; UPDATE users SET lastUpdate=NOW() WHERE id=1; -- 案例2 -- 事務A執行 BEGIN; UPDATE users SET lastUpdate=NOW() WHERE id=1; ROLLBACK; -- 事務B執行 UPDATE users SET lastUpdate=NOW() WHERE id=2; UPDATE users SET lastUpdate=NOW() WHERE id=1; -- 案例3 -- 事務A執行 BEGIN; UPDATE users SET lastUpdate=NOW() WHERE `name`='seven'; ROLLBACK; -- 事務B執行 UPDATE users SET lastUpdate=NOW() WHERE `name`='seven'; UPDATE users SET lastUpdate=NOW() WHERE id=1; UPDATE users SET lastUpdate=NOW() WHERE `name`='qingshan'; UPDATE users SET lastUpdate=NOW() WHERE id=2;
注意:這裏演示的案例都是在事務A沒有提交之前,執行事務B的語句。
案例1執行結果如下圖所示:
案例2執行結果如下圖所示:
案例3執行結果如下圖所示:
表示事務準備給數據行加入共享鎖,即一個數據行加共享鎖前必須先取得該表的 IS 鎖,意向共享鎖之間是可以相互兼容的。
表示事務準備給數據行加入排他鎖,即一個數據行加排他鎖前必須先取得該表的 IX 鎖,意向排它鎖之間是可以相互兼容的
意向鎖(IS 、IX)是 InnoDB 數據操作之前自動加的,不需要用戶干預。
意義:當事務想去進行鎖表時,可以先判斷意向鎖是否存在,存在時則可快速返回該表不能啟用表鎖。
-- IS鎖的意義 -- 事務A執行 BEGIN; UPDATE users SET lastUpdate=NOW() WHERE id=1; ROLLBACK; -- 事務B執行 -- 因為沒有通過索引條件進行數據檢索,所以這裏加的是表鎖 UPDATE users SET lastUpdate=NOW() WHERE phoneNum='13777777777';
結論:事務B的 SQL 因為沒有通過索引條件進行數據檢索,所以這裏加的是表鎖,在對錶加鎖之前會查看該表是否已經存在了意向鎖,因為事務A已經獲得了該表的意向鎖了,所以事務B不需要判斷每一行數據是否已經加鎖,可以快速通過意向鎖阻塞當前 SQL 的更新操作。
針對自增列自增長的一個特殊的表級別鎖。
通過如下命令查看自增鎖的默認等級:
SHOW VARIABLES LIKE 'innodb_autoinc_lock_mode';
默認取值1,代表連續,事務未提交 ID 永久丟失。
-- 事務A執行 BEGIN; INSERT INTO users(NAME , age ,phoneNum ,lastUpdate ) VALUES ('tom2',30,'1344444444',NOW()); ROLLBACK; BEGIN; INSERT INTO users(NAME , age ,phoneNum ,lastUpdate ) VALUES ('xxx',30,'13444444444',NOW()); ROLLBACK; -- 事務B執行 INSERT INTO users(NAME , age ,phoneNum ,lastUpdate ) VALUES ('yyy',30,'13444444444',NOW());
事務A執行完后,在執行事務B的語句,發現插入的 ID 數據不再連續,因為事務A獲取的 ID 數據在 ROLLBACK 之後被丟棄了。
當 SQL 執行按照索引進行數據的檢索時,查詢條件為範圍查找(between and、<、>等)並有數據命中,則此時 SQL 語句加上的鎖為 Next-key locks,鎖住索引的記錄 + 區間(左開右閉)。
演示之前,先看一下 t2 表的結構和數據內容。
臨鍵鎖(Next-key Locks):InnoDB 默認的行鎖算法。
t2 表中的數據行有4條數據:1,4,7,10,InnoDB 引擎會將表中的數據劃分為:(-∞, 1] (1, 4] (4, 7] (7, 10] (10, +∞),執行如下 SQL 語句:
-- 臨鍵鎖 -- 事務A執行 BEGIN; SELECT * FROM t2 WHERE id>5 AND id<9 FOR UPDATE; ROLLBACK -- 事務B執行 BEGIN; SELECT * FROM t2 WHERE id=4 FOR UPDATE; -- 可以執行 SELECT * FROM t2 WHERE id=7 FOR UPDATE; -- 鎖住 SELECT * FROM t2 WHERE id=10 FOR UPDATE; -- 鎖住 INSERT INTO `t2` (`id`, `name`) VALUES (9, '9'); -- 鎖住
SELECT * FROM t2 WHERE id>5 AND id<9 FOR UPDATE; 這條查詢語句命中了7這條數據,它會鎖住 (4, 7] 這個區間,同時還會鎖住下一個區間 (7, 10]。
防止幻讀。當我們把下一個區間也鎖住的時候,這個時候我們要新增數據,就會被鎖住,這樣就可以防止幻讀。
當 SQL 執行按照索引進行數據的檢索時,查詢條件的數據不存在,這時 SQL 語句加上的鎖即為 Gap locks,鎖住數據不存在的區間(左開右開)
Gap 只在 RR 事務隔離級別存在。因為幻讀問題是在 RR 事務通過臨鍵鎖和 MVCC 解決的,而臨鍵鎖=間隙鎖+記錄鎖,所以間隙鎖只在 RR 事務隔離級別存在。
-- 間隙鎖 -- 事務A執行 BEGIN; SELECT * FROM t2 WHERE id>4 AND id <6 FOR UPDATE; -- 或者 SELECT * FROM t2 WHERE id=6 FOR UPDATE; ROLLBACK; -- 事務B執行 INSERT INTO `t2` (`id`, `name`) VALUES (5, '5'); INSERT INTO `t2` (`id`, `name`) VALUES (6, '6');
SELECT * FROM t2 WHERE id>4 AND id <6 FOR UPDATE; 這條查詢語句不能命中數據,它會鎖住 (4, 7] 這個區間。
當 SQL 執行按照唯一性(Primary key、Unique key)索引進行數據的檢索時,查詢條件等值匹配且查詢的數據是存在,這時 SQL 語句加上的鎖即為記錄鎖 Record Locks,鎖住具體的索引項。
-- 記錄鎖 -- 事務A執行 BEGIN; SELECT * FROM t2 WHERE id=4 FOR UPDATE; ROLLBACK; -- 事務B執行 SELECT * FROM t2 WHERE id=7 FOR UPDATE; SELECT * FROM t2 WHERE id=4 FOR UPDATE;
事務A執行 SELECT * FROM t2 WHERE id=4 FOR UPDATE; 把 id=4 的數據行鎖住。
當 SQL 執行按照普通索引進行數據的檢索時,查詢條件等值匹配且查詢的數據是存在,這時 SQL 語句鎖住數據存在區間(左開右開)
InnoDB 真正處理事務併發帶來的問題不僅僅是依賴鎖,還有其他的機制,下篇文章會講到,所以這裏只是演示利用鎖是如何解決事務併發帶來的問題,並不是 InnoDB 真實的處理方式。
在事務B的更新語句上面加上一把 X 鎖,這樣就可以有效的解決臟讀問題。
在事務A的查詢語句上面加上一把 S 鎖,事務B的更新操作將會被阻塞,這樣就可以有效的解決不可重複讀的問題。
在事務A的查詢語句上面加上一把 Next-key 鎖,通過臨鍵鎖的定義,可以知道這個時候,事務A會把 (-∞,+∞) 的區間數據都鎖住,事務B的新增操作將會被阻塞,這樣就可以有效的解決幻讀的問題。
-- 事務A執行 BEGIN; UPDATE users SET lastUpdate = NOW() WHERE id =1; UPDATE t2 SET `name`='test' WHERE id =1; ROLLBACK; -- 事務B執行 BEGIN; UPDATE t2 SET `name`='test' WHERE id =1; UPDATE users SET lastUpdate = NOW() WHERE id =1; ROLLBACK;
事務A和事務B按照上面的執行步驟,最後因為存在相互等待的情況,所以 MySQL 判斷出現死鎖了。
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!
※網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!
※想知道最厲害的台北網頁設計公司推薦、台中網頁設計公司推薦專業設計師”嚨底家”!!
※大陸寄台灣空運注意事項
※大陸海運台灣交貨時間多久?
最近在把自己的一個老項目從Framework遷移到.Net Core 3.0,數據訪問這塊選擇的是EFCore+Mysql。使用EF的話不可避免要和DbContext打交道,在Core中的常規用法一般是:創建一個XXXContext類繼承自DbContext,實現一個擁有DbContextOptions參數的構造器,在啟動類StartUp中的ConfigureServices方法里調用IServiceCollection的擴展方法AddDbContext,把上下文注入到DI容器中,然後在使用的地方通過構造函數的參數獲取實例。OK,沒任何毛病,官方示例也都是這麼來用的。但是,通過構造函數這種方式來獲取上下文實例其實很不方便,比如在Attribute或者靜態類中,又或者是系統啟動時初始化一些數據,更多的是如下一種場景:
public class BaseController : Controller { public BloggingContext _dbContext; public BaseController(BloggingContext dbContext) { _dbContext = dbContext; } public bool BlogExist(int id) { return _dbContext.Blogs.Any(x => x.BlogId == id); } } public class BlogsController : BaseController { public BlogsController(BloggingContext dbContext) : base(dbContext) { } }
從上面的代碼可以看到,任何要繼承BaseController的類都要寫一個“多餘”的構造函數,如果參數再多幾個,這將是無法忍受的(就算只有一個參數我也忍受不了)。那麼怎樣才能更優雅的獲取數據庫上下文實例呢,我想到以下幾種辦法。
1、 直接開溜new
回歸原始,既然要創建實例,沒有比直接new一個更好的辦法了,在Framework中沒有DI的時候也差不多都這麼干。但在EFCore中不同的是,DbContext不再提供無參構造函數,取而代之的是必須傳入一個DbContextOptions類型的參數,這個參數通常是做一些上下文選項配置例如使用什麼類型數據庫連接字符串是多少。
public BloggingContext(DbContextOptions<BloggingContext> options) : base(options) { }
默認情況下,我們已經在StartUp中註冊上下文的時候做了配置,DI容器會自動幫我們把options傳進來。如果要手動new一個上下文,那豈不是每次都要自己傳?不行,這太痛苦了。那有沒有辦法不傳這個參數?肯定也是有的。我們可以去掉有參構造函數,然後重寫DbContext中的OnConfiguring方法,在這個方法中做數據庫配置:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlite("Filename=./efcoredemo.db"); }
即使是這樣,依然有不夠優雅的地方,那就是連接字符串被硬編碼在代碼中,不能做到從配置文件讀取。反正我忍受不了,只能再尋找其他方案。
2、 從DI容器手動獲取
既然前面已經在啟動類中註冊了上下文,那麼從DI容器中獲取實例肯定是沒問題的。於是我寫了這樣一句測試代碼用來驗證猜想:
var context = app.ApplicationServices.GetService<BloggingContext>();
不過很遺憾拋出了異常:
報錯信息說的很明確,不能從root provider中獲取這個服務。我從G站下載了DI框架的源碼(地址是),拿報錯信息進行反向追溯,發現異常來自於CallSiteValidator類的ValidateResolution方法:
public void ValidateResolution(Type serviceType, IServiceScope scope, IServiceScope rootScope) { if (ReferenceEquals(scope, rootScope) && _scopedServices.TryGetValue(serviceType, out var scopedService)) { if (serviceType == scopedService) { throw new InvalidOperationException( Resources.FormatDirectScopedResolvedFromRootException(serviceType, nameof(ServiceLifetime.Scoped).ToLowerInvariant())); } throw new InvalidOperationException( Resources.FormatScopedResolvedFromRootException( serviceType, scopedService, nameof(ServiceLifetime.Scoped).ToLowerInvariant())); } }
View Code
繼續往上,看到了GetService方法的實現:
internal object GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope) { if (_disposed) { ThrowHelper.ThrowObjectDisposedException(); } var realizedService = RealizedServices.GetOrAdd(serviceType, _createServiceAccessor); _callback?.OnResolve(serviceType, serviceProviderEngineScope); DependencyInjectionEventSource.Log.ServiceResolved(serviceType); return realizedService.Invoke(serviceProviderEngineScope); }
View Code
可以看到,_callback在為空的情況下是不會做驗證的,於是猜想有參數能對它進行配置。把追溯對象換成_callback繼續往上翻,在DI框架的核心類ServiceProvider中找到如下方法:
internal ServiceProvider(IEnumerable<ServiceDescriptor> serviceDescriptors, ServiceProviderOptions options) { IServiceProviderEngineCallback callback = null; if (options.ValidateScopes) { callback = this; _callSiteValidator = new CallSiteValidator(); } //省略.... }
說明我的猜想沒錯,驗證是受ValidateScopes控制的。這樣來看,把ValidateScopes設置成False就可以解決了,這也是網上普遍的解決方案:
.UseDefaultServiceProvider(options => { options.ValidateScopes = false; })
但這樣做是極其危險的。
為什麼危險?到底什麼是root provider?那就要從原生DI的生命周期說起。我們知道,DI容器被封裝成一個IServiceProvider對象,服務都是從這裏來獲取。不過這並不是一個單一對象,它是具有層級結構的,最頂層的即前面提到的root provider,可以理解為僅屬於系統層面的DI控制中心。在Asp.Net Core中,內置的DI有3種服務模式,分別是Singleton、Transient、Scoped,Singleton服務實例是保存在root provider中的,所以它才能做到全局單例。相對應的Scoped,是保存在某一個provider中的,它能保證在這個provider中是單例的,而Transient服務則是隨時需要隨時創建,用完就丟棄。由此可知,除非是在root provider中獲取一個單例服務,否則必須要指定一個服務範圍(Scope),這個驗證是通過ServiceProviderOptions的ValidateScopes來控制的。默認情況下,Asp.Net Core框架在創建HostBuilder的時候會判定當前是否開發環境,在開發環境下會開啟這個驗證:
所以前面那種關閉驗證的方式是錯誤的。這是因為,root provider只有一個,如果恰好有某個singleton服務引用了一個scope服務,這會導致這個scope服務也變成singleton,仔細看一下註冊DbContext的擴展方法,它實際上提供的是scope服務:
如果發生這種情況,數據庫連接會一直得不到釋放,至於有什麼後果大家應該都明白。
所以前面的測試代碼應該這樣寫:
using (var serviceScope = app.ApplicationServices.CreateScope()) { var context = serviceScope.ServiceProvider.GetService<BloggingContext>(); }
與之相關的還有一個ValidateOnBuild屬性,也就是說在構建IServiceProvider的時候就會做驗證,從源碼中也能體現出來:
if (options.ValidateOnBuild) { List<Exception> exceptions = null; foreach (var serviceDescriptor in serviceDescriptors) { try { _engine.ValidateService(serviceDescriptor); } catch (Exception e) { exceptions = exceptions ?? new List<Exception>(); exceptions.Add(e); } } if (exceptions != null) { throw new AggregateException("Some services are not able to be constructed", exceptions.ToArray()); } }
View Code
正因為如此,Asp.Net Core在設計的時候為每個請求創建獨立的Scope,這個Scope的provider被封裝在HttpContext.RequestServices中。
[小插曲]
通過代碼提示可以看到,IServiceProvider提供了2種獲取service的方式:
這2個有什麼區別呢?分別查看各自的方法摘要可以看到,通過GetService獲取一個沒有註冊的服務時會返回null,而GetRequiredService會拋出一個InvalidOperationException,僅此而已。
// 返回結果: // A service object of type T or null if there is no such service. public static T GetService<T>(this IServiceProvider provider); // 返回結果: // A service object of type T. // // 異常: // T:System.InvalidOperationException: // There is no service of type T. public static T GetRequiredService<T>(this IServiceProvider provider);
到現在為止,儘管找到了一種看起來合理的方案,但還是不夠優雅,使用過其他第三方DI框架的朋友應該知道,屬性注入的快感無可比擬。那原生DI有沒有實現這個功能呢,我滿心歡喜上G站搜Issue,看到這樣一個回復():
官方明確表示沒有開發屬性注入的計劃,沒辦法,只能靠自己了。
我的思路大概是:創建一個自定義標籤(Attribute),用來給需要注入的屬性打標籤,然後寫一個服務激活類,用來解析給定實例需要注入的屬性並賦值,在某個類型被創建實例的時候也就是構造函數中調用這個激活方法實現屬性注入。這裡有個核心點要注意的是,從DI容器獲取實例的時候一定要保證是和當前請求是同一個Scope,也就是說,必須要從當前的HttpContext中拿到這個IServiceProvider。
先創建一個自定義標籤:
[AttributeUsage(AttributeTargets.Property)] public class AutowiredAttribute : Attribute { }
解析屬性的方法:
public void PropertyActivate(object service, IServiceProvider provider) { var serviceType = service.GetType(); var properties = serviceType.GetProperties().AsEnumerable().Where(x => x.Name.StartsWith("_")); foreach (PropertyInfo property in properties) { var autowiredAttr = property.GetCustomAttribute<AutowiredAttribute>(); if (autowiredAttr != null) { //從DI容器獲取實例 var innerService = provider.GetService(property.PropertyType); if (innerService != null) { //遞歸解決服務嵌套問題 PropertyActivate(innerService, provider); //屬性賦值 property.SetValue(service, innerService); } } } }
然後在控制器中激活屬性:
[Autowired] public IAccountService _accountService { get; set; } public LoginController(IHttpContextAccessor httpContextAccessor) { var pro = new AutowiredServiceProvider(); pro.PropertyActivate(this, httpContextAccessor.HttpContext.RequestServices); }
這樣子下來,雖然功能實現了,但是裏面存着幾個問題。第一個是由於控制器的構造函數中不能直接使用ControllerBase的HttpContext屬性,所以必須要通過注入IHttpContextAccessor對象來獲取,貌似問題又回到原點。第二個是每個構造函數中都要寫這麼一堆代碼,不能忍。於是想有沒有辦法在控制器被激活的時候做一些操作?沒考慮引入AOP框架,感覺為了這一個功能引入AOP有點重。經過網上搜索,發現Asp.Net Core框架激活控制器是通過IControllerActivator接口實現的,它的默認實現是DefaultControllerActivator():
/// <inheritdoc /> public object Create(ControllerContext controllerContext) { if (controllerContext == null) { throw new ArgumentNullException(nameof(controllerContext)); } if (controllerContext.ActionDescriptor == null) { throw new ArgumentException(Resources.FormatPropertyOfTypeCannotBeNull( nameof(ControllerContext.ActionDescriptor), nameof(ControllerContext))); } var controllerTypeInfo = controllerContext.ActionDescriptor.ControllerTypeInfo; if (controllerTypeInfo == null) { throw new ArgumentException(Resources.FormatPropertyOfTypeCannotBeNull( nameof(controllerContext.ActionDescriptor.ControllerTypeInfo), nameof(ControllerContext.ActionDescriptor))); } var serviceProvider = controllerContext.HttpContext.RequestServices; return _typeActivatorCache.CreateInstance<object>(serviceProvider, controllerTypeInfo.AsType()); }
View Code
這樣一來,我自己實現一個Controller激活器不就可以接管控制器激活了,於是有如下這個類:
public class HosControllerActivator : IControllerActivator { public object Create(ControllerContext actionContext) { var controllerType = actionContext.ActionDescriptor.ControllerTypeInfo.AsType(); var instance = actionContext.HttpContext.RequestServices.GetRequiredService(controllerType); PropertyActivate(instance, actionContext.HttpContext.RequestServices); return instance; } public virtual void Release(ControllerContext context, object controller) { if (context == null) { throw new ArgumentNullException(nameof(context)); } if (controller == null) { throw new ArgumentNullException(nameof(controller)); } if (controller is IDisposable disposable) { disposable.Dispose(); } } private void PropertyActivate(object service, IServiceProvider provider) { var serviceType = service.GetType(); var properties = serviceType.GetProperties().AsEnumerable().Where(x => x.Name.StartsWith("_")); foreach (PropertyInfo property in properties) { var autowiredAttr = property.GetCustomAttribute<AutowiredAttribute>(); if (autowiredAttr != null) { //從DI容器獲取實例 var innerService = provider.GetService(property.PropertyType); if (innerService != null) { //遞歸解決服務嵌套問題 PropertyActivate(innerService, provider); //屬性賦值 property.SetValue(service, innerService); } } } } }
View Code
需要注意的是,DefaultControllerActivator中的控制器實例是從TypeActivatorCache獲取的,而自己的激活器是從DI獲取的,所以必須額外把系統所有控制器註冊到DI中,封裝成如下的擴展方法:
/// <summary> /// 自定義控制器激活,並手動註冊所有控制器 /// </summary> /// <param name="services"></param> /// <param name="obj"></param> public static void AddHosControllers(this IServiceCollection services, object obj) { services.Replace(ServiceDescriptor.Transient<IControllerActivator, HosControllerActivator>()); var assembly = obj.GetType().GetTypeInfo().Assembly; var manager = new ApplicationPartManager(); manager.ApplicationParts.Add(new AssemblyPart(assembly)); manager.FeatureProviders.Add(new ControllerFeatureProvider()); var feature = new ControllerFeature(); manager.PopulateFeature(feature); feature.Controllers.Select(ti => ti.AsType()).ToList().ForEach(t => { services.AddTransient(t); }); }
View Code
在ConfigureServices中調用:
services.AddHosControllers(this);
到此,大功告成!可以愉快的繼續CRUD了。
市面上好用的DI框架一堆一堆的,集成到Core裏面也很簡單,為啥還要這麼折騰?沒辦法,這不就是造輪子的樂趣嘛。上面這些東西從頭到尾也折騰了不少時間,屬性注入那裡也還有優化的空間,歡迎探討。
推薦閱讀:
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※為什麼 USB CONNECTOR 是電子產業重要的元件?
※網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!
※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光
※想知道最厲害的台北網頁設計公司推薦、台中網頁設計公司推薦專業設計師”嚨底家”!!
※專營大陸快遞台灣服務
※台灣快遞大陸的貨運公司有哪些呢?