前景看好,持股投資人:特斯拉將市值將達 1 兆

近日特斯拉股票上揚,許多業界人士覺得股價被吹太高。不過手中有股票的投資者可不這麼認為,而是相當樂觀。投資特斯拉的億萬富翁 Ron Baron 日前斷言,在這 10 年或是下個 10 年時,特斯拉將成為 1 兆市值的公司,超越傳統的汽車廠如日產。

Baron 語出驚人,談到 10 年後電動車廠特斯拉市值達 1 兆,而且超越傳統車廠。過去 3 年半 Baron 的投資機構陸陸續續累積 160 萬股的特斯拉股票,而每股 208 美元。Baron 表示,特斯拉股價上升,他表示只是開始。

目前特斯拉的市值是 451 億美元,Baron 在接受 CNBC 專訪時表示,2020 年時會翻 4 倍,2025 年時會再翻3 倍,而2030 年時則是再翻3 倍。意味著 2020 年這個十年,特斯拉從現在 451 億美元翻4 倍,也就是1.8 兆美元。

Baron 也對 SolarCity 和特斯拉的合作深具信心,去年曾發表讚揚的言論,因為他相當相信 Elon Musk 的遠景,相信他對電力租用服務。

不過 Baron 去年曾說十年之後特斯拉市值將達 7,000 億,這次增加預測幅度,將達到1 兆美元的規模。Baron 以長期持有特斯拉股票而出名,為了手上特斯拉的股票,把電動車的特斯拉公司說得很好,也有利於自己持有的股票,說不定那天價格好才好出手賣出。

(合作媒體:。圖片出處:Pixabay CC0)  

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

※回頭車貨運收費標準

剛果廠2018年重新上線,鋰電池材料鈷價恐直落

車用鋰電池的關鍵材料──鈷在需求增溫、供給短缺的帶動下,年初迄今報價已狂飆135%,但認為鈷價可能漲到外太空的避險基金和投機客要小心了,因為剛果的礦脈2018年將開出大量產能,屆時供需景況驟變、原本飛龍在天的鈷價恐墜回地球。

英國金融時報14日報導,全球最大鈷生產商嘉能可(Glencore Plc)位於剛果共和國的加丹加(Katanga)礦場,在花費4.3億美元整治機台後,明年就可上線產鈷,預計市場將因此增加最多22,000公噸的供應量。目前鈷的全球年產量約在100,000公噸左右。

高盛分析師指出,加丹加礦場重新上線,將明顯改變供需狀態、終結短缺,預計鈷的供給量到了2019年底,都能充分滿足需求。

鈷是銅和鎳的副產品。原物料價格於2015年底慘崩之際,數個銅礦和鎳礦都被迫縮減產能,而嘉能可也在市況最為嚴峻的時候決定暫時封閉加丹加礦場。巴西礦商Votorantim Metais則跟著在2016年初暫停了鎳、鈷的生產線。

不過,中國預定要在2020年讓500萬輛電動車上路,特斯拉(Tesla Motors)首款平價電動車接單接到手軟,卻讓車用電池的需求水漲船高。根據倫敦原物料顧問機構CRU分析師Edward Spencer的數據,高級鈷的報價已拉高至每磅27美元。

看準這個趨勢,業界開始有消息傳出,括瑞士專門關注採礦業的創投機構Pala Investments,以及中國大型原物料基金上海混沌投資(Shanghai Chaos Investment)在內的六家機構,因看好電動車業者的需求,至今已囤積了約6,000公噸的鈷,價值多達2.8億美元,相當於去年全球總產量的17%。(註:混沌投資的控股股東是被稱為「中國索羅斯」的葛衛東。)()

路透社2月14日報導,根據電池用鈷鹽製造商eCobalt Solutions的預估,到了2020年,75%的鋰電池都將含有鈷,因為鈷能增加電動車每一次充電的里程數。

 

不過,由於98%的鈷都是銅、鎳礦的副產品,因此投資人很難買到純粹的鈷。歐洲一名交易員透露,私募股權基金業者雖然考慮過倫敦金屬交易所(LME)的鈷合約,但流動性卻不足,難以滿足投資機構的龐大需求。因此,許多基金公司決定直接囤積鈷、靜待價格上漲,設定的目標價則是每磅25美元,甚至更多。

(本文內容由授權使用。圖片出處:Wikipedia)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

和Tesla抗衡,Volvo平價電動車預計2019上市

瑞典汽車大廠Volvo(富豪汽車)的CEO Lex Kerssemakers在日內瓦汽車展上向記者透漏公司將於2019上市自家電動車,價格約落在$35,000至$40,000美元間。

據媒體Automotive News報導,在這之前,Volvo雖有兩款油電混和車XC90 SUV和V60上市,但不曾真正推出過純電動車。Volvo定出的價格也和電動車龍頭Tesla旗下車款Model 3具競爭力。預定於2017年末上市的Tesla Model 3擁有215英哩的續航,優惠期間可以35,000的價格預購;Chevrolet(雪佛蘭汽車)的電動車Bolt則擁有238英哩的續航里程,優惠價格落在37,500美元。

雖然車體尺寸等具體資訊都還沒有公布,但Kerssemakers表示續航力可達250英哩。外界也推估以這個價位大概是一般轎車而緊湊型轎車(Compact Car)或休旅車。

(首圖來源:Volvo)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※超省錢租車方案

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

※回頭車貨運收費標準

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

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

01 . ELK Stack簡介原理及部署應用

簡介

ELK並不是一款軟件,是一整套解決方案,是由ElasticSearch,Logstash和Kibana三個開源工具組成:通常是配合使用,而且先後歸於Elastic.co公司名下,簡稱ELK協議棧.

日誌的收集和處理

在日常運維工作中,對於系統和業務日誌的處理尤為重要。日誌主要包括系統日誌,應用日誌,應用程序日誌和安全日誌。系統運維和開發人員可以通過日誌了解服務器軟硬件信息,檢查配置過程中的錯誤及錯誤發生的原因。經常分析日誌可以了解服務器的負荷,性能安全性,從而及時採取措施糾正錯誤。

通常,日誌被分散的存儲在不同的設備上,如果你管理上百台服務器,你還在使用依次登錄每台機器的傳統方法查閱日誌是很繁瑣且效率低下的。當務之急是使用集中化的日誌管理,例如: 開源的syslog,將所有服務器上的日誌收集匯總。集中化管理日誌后,日誌的統計和檢索又成為一件比較麻煩的事情.

一個完整的集中式日誌系統,是離不開以下幾個主要特點的

# 1. 收集 - 能夠收集多種來源的日誌數據
# 2. 傳輸 - 能夠穩定的把日誌數據傳輸到中央系統
# 3. 存儲 - 如何存儲日誌數據
# 4. 分析 - 可以支持UI分析
# 5. 警告 - 能夠提供錯誤報告
日誌分析工具

1 . grep,awk,wc,rsyslog,syslog-ng: 對於龐大的機器數量,要達到要求更高的查詢,排序和統計等使用這樣的方法難免有點力不從心.

2 . 商業化的splunk: Splunk作為企業級的分佈式機器數據的平台,擁有強大的分佈式配置,包括跨數據中心的集群配置,Splunk提供兩種集群,indexer集群和Search Head集群.具體情況請看Splunk文章.

3 . 開源的:

# 1 FaceBook公司的Scribe
# 2 Apache的Chukwa
# 3 Linkedin的Kafka
# 4 Cloudera的Fluentd
# 5 ELK
Elasticsearch

ElasticSearch是一個基於Lucene的開源分佈式搜索服務器.是一個實時的分佈式搜索和分析引擎,他可以用於全文搜索,結構化搜索以及分析,他是一個建立在全文搜索引擎Apache lucene基礎上的搜索引擎,使用Java語言編寫,並作為Apache許可條款下的開放源碼發布,是第二流行的企業搜索引擎。設計用於雲計算中,能夠達到實時搜索,穩定,可靠,快速,安裝使用方便,在elasticsearch中,所有節點的數據是均等的.

主要特點

# 1 實時分析
# 2 分佈式實時文件存儲,並將每一個字段都編入索引
# 3 文檔導向,所有的對象全部是文檔
# 4 高可用性,易擴展,支持集群(cluster),分片和複製(hards和Replicas
# 5 接口友好,支持JSON

Logstash

logstash是一個具有實時渠道能力的數據收集引擎,使用JRuby語言編寫,其做着是世界著名的運維工程師喬丹西塞,他是一個完全開源工具,可以對你的日誌進行收集,過濾

# 主要特點:
#        1> 幾乎可以訪問任何數據
#        2> 可以和多種外部應用結合
#        3> 支持彈性擴展

# 它主要由三個主要部分組成,見下圖.
#        1> Shipper - 發送日誌數據
#        2> Broker -  收集數據,缺省內置Redis
#        3> Indexer - 數據寫入

Kibana

Kibana是一款基於Apache開源協議,使用JavaScript語言編寫,為Elasticsearch提供分析和可視化的Web平台,他可以在Elasticsearch的索引中查找,交互數據,並生成各種維度的表圖.

Filebeat

ELK協議棧的新成員,一個輕量級開源日誌數據搜集器,基於Logstash-Forwarder源代碼開發,是對他的替代。是需要在採集日誌數據server上安裝filebeat,並指定日誌目錄或日誌文件后,Filebeat就能讀取數據,迅速發送到Logstash進行解析,亦或直接發送到Elasticsearch進行集中式存儲和分析

ELK協議棧體繫結構

最簡單架構

在這種架構中,只有一個Logstash,Elasticsearch和Kibana實例。Logstash通過輸入插件從多種數據源(比如日誌文件,標準輸入Stdin等)獲取數據,再經過過濾插件加工數據,然後經過Elasticsearch輸出插件輸出到Elasticsearch,通過Kibana展示.

Logstash作為日誌收集器

這種架構是對上面架構的擴展,把一個Logstash數據搜集節點擴展到多個,分佈於多台機器,將解析好的數據發送到Elasticsearch server進行存儲,最後在Kibana查閱,生成日誌報表等.

這種結構因為需要在各個服務器上部署Logstash,而它比較消耗CPU和內存資源,所以比較適合資源豐富的服務器,否則容易造成服務器性能下降,甚至可能導致無法正常工作.

Beats作為日誌搜集器

這種架構引入Beats作為日誌搜集器。目前Beats包括四種

# 1> Packetbeat (搜集網絡流量數據)
# 2> Topbeat (搜集系統,進程和文件系統級別的CPU和內存使用情況等數據)
# 3> Filebeat (搜集文件數據)
# 4> Winlogbeat (搜集Windows事件日誌數據)

Beats將搜集到的數據發送到Logstash,經Logstash解析,過濾后,將其發送到Elasticsearch存儲,並由Kibana呈現給用戶.

這種架構解決了Logstash在各服務器節點上佔用系統資源高的問題,相比Logstash,Beats所佔系統的CPU和內存幾乎可以省略不計,另外,Beats和Logstash之間保持SSL/TLS加密傳輸,客戶端和服務器雙向認證,保證了通信安全

因此這種架構適合對數據安全性要求較高,同時各服務器性能比較敏感的場景.

基於Filebeat架構的配置部署詳解

前面提到Filebeat已經完全替代了Logstash-Forwarder 成為新一代的日誌採集器,同時鑒於它輕量、安全等特點,越來越多人開始使用它。這個章節將詳細講解如何部署基於 Filebeat 的 ELK 集中式日誌解決方案,具體架構見下圖.

引入消息隊列機制的架構

Beats 還不支持輸出到消息隊列,所以在消息隊列前後兩端只能是 Logstash 實例。這種架構使用 Logstash 從各個數據源搜集數據,然後經消息隊列輸出插件輸出到消息隊列中。目前 Logstash 支持 Kafka、Redis、RabbitMQ 等常見消息隊列。然後 Logstash 通過消息隊列輸入插件從隊列中獲取數據,分析過濾后經輸出插件發送到 Elasticsearch,最後通過 Kibana 展示。見下圖

這種架構適合於日誌規模比較龐大的情況,但由於Logstash日誌解析節點和Elasticsearch的符合比較重,可將他們配置為集群模式,以分擔負荷,引入消息隊列,均衡了網絡傳輸,從而降低了網絡閉塞,尤其丟失數據的可能性,但依然存在Logstash佔用系統資源過多的問題.

部署

架構圖

999

List
Package:
# elasticsearch-7.2.0-linux-x86_64.tar.gz
# filebeat-7.2.0-x86_64.rpm
# jdk-8u121-linux-x64.rpm
# kibana-7.2.0-linux-x86_64.tar.gz
# logstash-7.2.0.tar.gz
IP hostname 軟件 配置要求 網絡 備註
192.168.144.131 ES/數據存儲 elasticsearch-7.2 內存最低2GB/硬盤40GB Nat,內網
192.168.144.128 Kibana/UI展示 kibana-7.2****logstash-7.2 內存最低2GB/硬盤40GB Nat,內網
192.168.144.135 Filebeat/數據採集 Filebeat-7.2/nginx 內存最低2GB/硬盤40GB Nat,內網
注意事項
# 1.一定要對時,時間校正,不然日誌出不來
# 2.啟動Elasticsearch必須切換成所創建的ELK用戶啟動,不然ES處於安全目的,會啟動報錯.
# 3.日誌從Filebeat到Logstash再到ES檢索到Kibana的讀取速度取決於機器配置
Elasticsearch安裝
# 1.初始化
setenforce 0
sed -i '/^SELINUX=/ s/enforcing/disabled/'  /etc/selinux/config
systemctl stop firewalld
systemctl disable firewalld
sed -i '/^GSSAPIAu/ s/yes/no/' /etc/ssh/sshd_config
sed -i '/^#UseDNS/ {s/^#//;s/yes/no/}' /etc/ssh/sshd_config
curl -o /etc/yum.repos.d/163.repo http://mirrors.163.com/.help/CentOS7-Base-163.repo &>/dev/null
curl  -o /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo
    yum -y install ntpdate
    ntpdate -b  ntp1.aliyun.com

# 2.設置Hostname解析
hostnamectl set-hostname elk-1
        ## 修改/etc/hosts 增加如下內容
        192.168.144.131     elk-1

# 4.java安裝
# rpm -ivh jdk-8u121-linux-x64.rpm
# 如果使用rpm請到/etc/profile加上
# JAVA_HOME=/usr/java/jdk1.8.0_121
# export PATH=$PATH:$JAVA_HOME/bin
yum -y install java-1.8.0-openjdk.x86_64

# 5.創建用戶和組並準備相關目錄並授權
groupadd  elk
useradd  elk  -g  elk
mkdir  -pv  /data/elk/{data,logs}
chown  -R  elk:elk  /data/elk/

# 6.軟件包解壓、授權
# 上傳軟件包
            通過scp 或者FTP方式上傳到/opt下
# 解壓軟件包到/opt目錄
tar xvf elasticsearch-7.2.0-linux-x86_64.tar.gz -C  /opt/
# 授權
chown  -R  elk:elk  /opt/elasticsearch-7.2.0/  # 軟件包名

# 7.修改elk配置文件
[root@elk-1 ~]# vim /opt/elasticsearch-7.2.0/config/elasticsearch.yml
        # 集群名
        cluster.name:  elk
        # 節點名
        node.name: node-1
        # 存儲數據
        path.data:  /data/elk/data
        # 存放日誌
        path.logs:  /data/elk/logs
        # 鎖內存,盡量不使用交換內存
        bootstrap.memory_lock:  false
        # 網絡地址
        network.host: 0.0.0.0
        http.port: 9200
        # 發現集群hosts
        discovery.seed_hosts: ["elk-1"]
        # 設置集群master節點
        cluster.initial_master_nodes: ["node-1"]

# 8.修改/etc/security/limits.conf
        # *號不是註釋
        * soft nproc 65535
        * hard nproc 65535
        * soft nofile 65535
        * hard nofile 65535

ulimit -n 65535
ulimit -u 20480

# 9.修改/etc/sysctl.conf
        echo "vm.max_map_count=262144" >> /etc/sysctl.conf
        sysctl -p

# 10.啟動ES並檢查集群健康狀態
nohup runuser -l elk -c '/bin/bash /opt/elasticsearch-7.2.0/bin/elasticsearch' &
    # 這裏為了省事直接用的nohup 可以使用supervisord 對進程進行管理
[root@elk-1 ~]# curl -XGET 'elk-1:9200/_cluster/health?pretty'
{
  "cluster_name" : "elk",
  "status" : "green",
  "timed_out" : false,
  "number_of_nodes" : 1,
  "number_of_data_nodes" : 1,
  "active_primary_shards" : 0,
  "active_shards" : 0,
  "relocating_shards" : 0,
  "initializing_shards" : 0,
  "unassigned_shards" : 0,
  "delayed_unassigned_shards" : 0,
  "number_of_pending_tasks" : 0,
  "number_of_in_flight_fetch" : 0,
  "task_max_waiting_in_queue_millis" : 0,
  "active_shards_percent_as_number" : 100.0
}
Kibana安裝
# 1.解壓Kibana安裝包
tar xvf kibana-7.2.0-linux-x86_64.tar.gz  -C /opt/

# 2.修改Kibana配置文件
server.port:  5601   # Port
server.host:  0.0.0.0   # 訪問限制
elasticsearch.hosts: ["http://192.168.144.131:9200"]        # ES主機IP:Port

# 3.啟動命令
[root@kibana ~]# nohup /opt/kibana-7.2.0-linux-x86_64/bin/kibana --allow-root &
[1] 14650
    # tailf nohup.out 即可實時查看日誌
Logstash安裝
[root@kibana ~]# rpm -ivh jdk-8u121-linux-x64.rpm
[root@kibana ~]# tar xvf logstash-7.2.0.tar.gz  -C /opt/
[root@kibana opt]# vim /opt/nginx.yml
# Sample Logstash configuration for creating a simple
# # Beats -> Logstash -> Elasticsearch pipeline.
#
input {
    beats {
        port => 5044
        }
}

filter {
    grok {
        match => ["message","%{DATA:log_date} %{WORD:method} %{TIME:log_localtime} %{HOSTNAME:host_name} %{WORD:workd}\[%{WORD:ls}\]\: %{DATA:log_date} %{TIME:log_localtime2} %{WORD:year_tmp}\: %{WORD:name_2}\: %{WORD:request_leixin} %{WORD:request_num}\, %{WORD}\: %{WORD:app_id}\, %{WORD}\: %{IP:ip}\, %{WORD}\: %{INT}\, %{WORD}\: %{USERNAME:device_id}"]
        }
}

output {
    elasticsearch {
       hosts => ["http://192.168.144.131:9200"]
       index => "app_log-%{[@metadata][beat]}-%{[@metadata][version]}-%{+YYYY.MM.dd}"
     }
}




# 啟動 (如果起不來或者報錯一般都是配置文件語法錯誤之類的)
    /opt/logstash-7.2.0/bin/logstash -f /opt/配置文件名.yml
    ## 後台運行
    nohup  /opt/logstash-7.2.0/bin/logstash -f /opt/配置文件名.yml  &
Filebeat安裝
# 安裝filebeat RPM包
[root@filebeat ~]# rpm -ivh filebeat-7.2.0-x86_64.rpm

# 修改配置文件:
Filebeat到Logstash
(vim /etc/filebeat/filebeat.yml) shift + : 輸入set nu 显示行號
24:   enabled:  true                         # 更改為true以啟用輸入配置
28:   - /data/*.log                   # 替換為要抓取的日誌文件路徑,如果抓取nginx日誌,修改為/var/log/nginx/access.log,如果是日誌服務器,在下面再加上多行其他地方的日誌目錄即可
73:  reload.enabled:  true                  # 啟動Filebeat模塊
148: output.elasticsearch:                  # 加上註釋;
150:  hosts: ["localhost:9200"]              # 加上註釋;
158: output.logstash:                       # 去掉註釋;
160: hosts: ["192.168.144.128<logstash>:5044"]       # 去掉註釋,並修改localhost為logstash機器IP及對應端口號;


# 測試配置文件並啟動
filebeat  test  config  -e
systemctl  start filebeat && systemctl  enable filebeat

[root@filebeat ~]# cat /data/nginx.log        # 創建該目錄及文件,加入兩行日誌.
Sep  2 16:00:18 cc-prd-3-tk-13-pcs1 pcs[16118]: Mon Sep  2 16:00:18 2019: PCS: recv request, app_id: app_1w5B6O4R2o1k881k12177, ip: 223.104.145.136, os: 0, device_id: 02c3864050502d43dc514905133bcc9c
Sep  2 16:00:18 cc-prd-3-tk-13-pcs1 pcs[16118]: Mon Sep  2 16:00:18 2019: PCS: recv request, app_id: app_1w5B6O4R2o1k881k12177, ip: 223.104.145.136, os: 0, device_id: 02c3864050502d43dc514905133bcc9c

收集的日誌需要把有用的信息做切割,讓相應字段成為獨立的個一個字段,而不是一整條日誌是一個字段,那樣就沒法做分析,而做數據源切割很重要,否則日誌會過不來,或者日誌做不了分析,統計.

可以把源日誌拿到kibana上的grok上做切割,直到Structured Data能出現你想要的字段就可以放到logstash的配置文件中

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

【其他文章推薦】

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

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

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

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

※回頭車貨運收費標準

設計模式系列之組合模式(Composite Pattern)——樹形結構的處理

說明:設計模式系列文章是讀劉偉所著《設計模式的藝術之道(軟件開發人員內功修鍊之道)》一書的閱讀筆記。個人感覺這本書講的不錯,有興趣推薦讀一讀。詳細內容也可以看看此書作者的博客https://blog.csdn.net/LoveLion/article/details/17517213

模式概述

樹形結構在軟件中隨處可見,例如操作系統中的目錄結構、應用軟件中的菜單、辦公系統中的公司組織結構等等,如何運用面向對象的方式來處理這種樹形結構是組合模式需要解決的問題。組合模式通過一種巧妙的設計方案使得用戶可以一致性地處理整個樹形結構或者樹形結構的一部分,也可以一致性地處理樹形結構中的恭弘=叶 恭弘子節點(不包含子節點的節點)和容器節點(包含子節點的節點)。

模式定義

組合模式(Composite Pattern):組合多個對象形成樹形結構以表示具有“整體—部分”關係的層次結構。組合模式對單個對象(即恭弘=叶 恭弘子對象)和組合對象(即容器對象)的使用具有一致性,組合模式又可以稱為“整體—部分”(Part-Whole)模式,它是一種對象結構型模式。

模式結構圖

組合模式結構圖如下所示:

在組合模式結構圖中包含如下幾個角色:

  • Component(抽象構件):它可以是接口或抽象類,為恭弘=叶 恭弘子構件和容器構件對象聲明接口,在該角色中可以包含所有子類共有行為的聲明和實現。在抽象構件中定義了訪問及管理它的子構件的方法,如增加子構件、刪除子構件、獲取子構件等。

  • Leaf(恭弘=叶 恭弘子構件):它在組合結構中表示恭弘=叶 恭弘子節點對象,恭弘=叶 恭弘子節點沒有子節點,它實現了在抽象構件中定義的行為。對於那些訪問及管理子構件的方法,可以通過異常等方式進行處理。

  • Composite(容器構件):它在組合結構中表示容器節點對象,容器節點包含子節點,其子節點可以是恭弘=叶 恭弘子節點,也可以是容器節點,它提供一個集合用於存儲子節點,實現了在抽象構件中定義的行為,包括那些訪問及管理子構件的方法,在其業務方法中可以遞歸調用其子節點的業務方法。

組合模式的關鍵是定義了一個抽象構件類,它既可以代表恭弘=叶 恭弘子,又可以代表容器,而客戶端針對該抽象構件類進行編程,無須知道它到底表示的是恭弘=叶 恭弘子還是容器,可以對其進行統一處理。同時容器對象與抽象構件類之間還建立一個聚合關聯關係,在容器對象中既可以包含恭弘=叶 恭弘子,也可以包含容器,以此實現遞歸組合,形成一個樹形結構。

模式偽代碼

對於客戶端而言,一般針對抽象構件編程,而無須關心其具體子類是容器構件還是恭弘=叶 恭弘子構件。抽象構建類典型代碼如下:

public abstract class Component {
    public abstract void add(Component c); //增加成員

    public abstract void remove(Component c); //刪除成員

    public abstract Component getChild(int i); //獲取成員

    public abstract void operation();  //業務方法
}

如果繼承抽象構件的是恭弘=叶 恭弘子構件,則其典型代碼如下所示:

public class Leaf extends Component {
    @Override
    public void add(Component c) {
        //異常處理或錯誤提示 
    }

    @Override
    public void remove(Component c) {
        //異常處理或錯誤提示 
    }

    @Override
    public Component getChild(int i) {
        //異常處理或錯誤提示 
        return null;
    }

    @Override
    public void operation() {
        //恭弘=叶 恭弘子構件具體業務方法的實現
    }
}

如果繼承抽象構件的是容器構件,則其典型代碼如下所示:

public class Composite extends Component {

    private List<Component> list = new ArrayList<>();

    @Override
    public void add(Component c) {
        list.add(c);
    }

    @Override
    public void remove(Component c) {
        list.remove(c);
    }

    @Override
    public Component getChild(int i) {
        return (Component) list.get(i);
    }

    @Override
    public void operation() {
        //容器構件具體業務方法的實現
        //遞歸調用成員構件的業務方法
        for (Object obj : list) {
            ((Component) obj).operation();
        }
    }
}

客戶端對抽象構件類進行編程

public class Client {
    public static void main(String[] args) {
        Component component;
        component = new Leaf();
        //component = new Composite();

        // 無須知道到底是恭弘=叶 恭弘子還是容器
        // 可以對其進行統一處理
        component.operation();
    }
}

模式簡化

透明組合模式

透明組合模式中,抽象構件Component中聲明了所有用於管理成員對象的方法,包括add()、remove()以及getChild()等方法,這樣做的好處是確保所有的構件類都有相同的接口。在客戶端看來,恭弘=叶 恭弘子對象與容器對象所提供的方法是一致的,客戶端可以相同地對待所有的對象。透明組合模式也是組合模式的標準形式。

透明組合模式的完整結構圖如下:

也可以將恭弘=叶 恭弘子構件的add()remove()等方法的實現代碼移至Component中,由Component提供統一的默認實現,這樣子類就不必強制去實現管理子Component。代碼如下所示:

public abstract class Component {
    public void add(Component c) {
        throw new RuntimeException("不支持的操作");
    }

    public void remove(Component c) {
        throw new RuntimeException("不支持的操作");
    }

    public Component getChild(int i) {
        throw new RuntimeException("不支持的操作");
    }

    public abstract void operation();  //業務方法
}

透明組合模式的缺點是不夠安全,因為恭弘=叶 恭弘子對象和容器對象在本質上是有區別的。恭弘=叶 恭弘子對象不可能有下一個層次的對象,即不可能包含成員對象,因此為其提供add()、remove()以及getChild()等方法是沒有意義的,這在編譯階段不會出錯,但在運行階段如果調用這些方法可能會出錯(如果沒有提供相應的錯誤處理代碼)。

安全組合模式

安全組合模式中,在抽象構件Component中沒有聲明任何用於管理成員對象的方法,而是在Composite類中聲明並實現這些方法。

安全組合模式的完整結構圖如下:

此時Component就應該這樣定義了

public abstract class Component {
    // 業務方法
    public abstract void operation();
}

安全組合模式的缺點是不夠透明,因為恭弘=叶 恭弘子構件和容器構件具有不同的方法,且容器構件中那些用於管理成員對象的方法沒有在抽象構件類中定義,因此客戶端不能完全針對抽象編程,必須有區別地對待恭弘=叶 恭弘子構件和容器構件。在實際應用中,安全組合模式的使用頻率也非常高,在Java AWT中使用的組合模式就是安全組合模式。

模式應用

模式在JDK中的應用

Java SE中的AWTSwing包的設計就基於組合模式,在這些界麵包中為用戶提供了大量的容器構件(如Container)和成員構件(如CheckboxButtonTextComponent等),其結構如下圖所示

Component類是抽象構件,CheckboxButtonTextComponent是恭弘=叶 恭弘子構件,而Container是容器構件,在AWT中包含的恭弘=叶 恭弘子構件還有很多。在一個容器構件中可以包含恭弘=叶 恭弘子構件,也可以繼續包含容器構件,這些恭弘=叶 恭弘子構件和容器構件一起組成了複雜的GUI界面。除此以外,在XML解析組織結構樹處理文件系統設計等領域,組合模式都得到了廣泛應用。

模式在開源項目中的應用

Springorg.springframework.web.method.support.HandlerMethodArgumentResolver使用了安全組合模式。提取關鍵代碼如下:

public interface HandlerMethodArgumentResolver {
    
    boolean supportsParameter(MethodParameter parameter);

    Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
                           NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;

}

再看下它的一個實現類org.springframework.web.method.support.HandlerMethodArgumentResolverComposite

public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgumentResolver {


    private final List<HandlerMethodArgumentResolver> argumentResolvers = new LinkedList<>();

    /**
     * Add the given {@link HandlerMethodArgumentResolver}.
     */
    public HandlerMethodArgumentResolverComposite addResolver(HandlerMethodArgumentResolver resolver) {
        this.argumentResolvers.add(resolver);
        return this;
    }

    /**
     * Add the given {@link HandlerMethodArgumentResolver HandlerMethodArgumentResolvers}.
     */
    public HandlerMethodArgumentResolverComposite addResolvers(
            @Nullable HandlerMethodArgumentResolver... resolvers) {

        if (resolvers != null) {
            Collections.addAll(this.argumentResolvers, resolvers);
        }
        return this;
    }

    /**
     * Clear the list of configured resolvers.
     */
    public void clear() {
        this.argumentResolvers.clear();
    }


    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return getArgumentResolver(parameter) != null;
    }


    @Override
    public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
                                  NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

        HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
        if (resolver == null) {
            throw new IllegalArgumentException("Unsupported parameter type [" +
                    parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
        }
        return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
    }
}

模式總結

主要優點

  1. 組合模式可以清楚地定義分層次的複雜對象,表示對象的全部或部分層次,它讓客戶端忽略了層次的差異,方便對整個層次結構進行控制。

  2. 客戶端可以一致地使用一個組合結構或其中單個對象,不必關心處理的是單個對象還是整個組合結構,簡化了客戶端代碼。

  3. 組合模式為樹形結構的面向對象實現提供了一種靈活的解決方案,通過恭弘=叶 恭弘子對象和容器對象的遞歸組合,可以形成複雜的樹形結構,但對樹形結構的控制卻非常簡單。

適用場景

(1) 在具有整體和部分的層次結構中,希望通過一種方式忽略整體與部分的差異,客戶端可以一致地對待它們。

(2) 在一個使用面向對象語言開發的系統中需要處理一個樹形結構。

(3) 在一個系統中能夠分離出恭弘=叶 恭弘子對象和容器對象,而且它們的類型不固定,需要增加一些新的類型。

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

Python的多繼承問題-MRO和C3算法

Python 中的方法解析順序(Method Resolution Order, MRO)定義了多繼承存在時 Python 解釋器查找函數解析的正確方式。當 Python 版本從 2.2 發展到 2.3 再到現在的 Python 3,MRO算法也隨之發生了相應的變化。這種變化在很多時候影響了我們使用不同版本 Python 編程的過程。

大部分內容轉載自C3 線性化算法與 MRO 理解Python中的多繼承

什麼是 MRO

MRO 全稱方法解析順序(Method Resolution Order)。它定義了 Python 中多繼承存在的情況下,解釋器查找函數解析的具體順序。什麼是函數解析順序?我們首先用一個簡單的例子來說明。請仔細看下面代碼:

class A():
    def who_am_i(self):
        print("I am A")
        
class B(A):
    pass
        
class C(A):
    def who_am_i(self):
        print("I am C")

class D(B,C):
    pass
    
d = D()

如果我問在 Python 2 中使用 D 的實例調用 d.who_am_i(),究竟執行的是 A 中的 who_am_i() 還是 C 中的 who_am_i(),我想百分之九十以上的人都會不假思索地回答:肯定是 C 中的 who_am_i(),因為 C 是 D 的直接父類。然而,如果你把代碼用 Python 2 運行一下就可以看到 d.who_am_i() 打印的是 I am A

是不是覺得很混亂很奇怪?感到奇怪就對了!!!

這個例子充分展示了 MRO 的作用:決定基類中的函數到底應該以什麼樣的順序調用父類中的函數。可以明確地說,Python 發展到現在,MRO 算法已經不是一個憑藉著執行結果就能猜出來的算法了。如果沒有深入到 MRO 算法的細節,稍微複雜一點的繼承關係和方法調用都能徹底繞暈你。

New-style Class vs. Old-style Class

在介紹不同版本的 MRO 算法之前,我們有必要簡單地回顧一下 Python 中類定義方式的發展歷史。儘管在 Python 3 中已經廢除了老式的類定義方式和 MRO 算法,但對於仍然廣泛使用的 Python 2 來說,不同的類定義方式與 MRO 算法之間具有緊密的聯繫。了解這一點將幫助我們從 Python 2 向 Python 3 遷移時不會出現莫名其妙的錯誤。

在 Python 2.1 及以前,我們定義一個類的時候往往是這個樣子(我們把這種類稱為 old-style class):

class A:
    def __init__(self):
        pass

Python 2.2 引入了新的模型對象(new-style class),其建議新的類型通過如下方式定義:

class A(object):
    def __init__(self):
        pass

注意后一種定義方式显示註明類 A 繼承自 object。Python 2.3 及後續版本為了保持向下兼容,同時提供以上兩種類定義用以區分 old-style class 和 new-style class。Python 3 則完全廢棄了 old-style class 的概念,不論你通過以上哪種方式書寫代碼,Python 3 都將明確認為類 A 繼承自 object。這裏我們只是引入 old-style 和 new-style 的概念,如果你對他們的區別感興趣,可以自行看 stackoverflow 上有關該問題的解釋。

理解 old-style class 的 MRO

我們使用前文中的類繼承關係來介紹 Python 2 中針對 old-style class 的 MRO 算法。如果你在前面執行過那段代碼,你可以看到調用 d.who_am_i() 打印的應該是 I am A。為什麼 Python 2 的解釋器在確定 D 中的函數調用時要先搜索 A 而不是先搜索 D 的直接父類 C 呢?

這是由於 Python 2 對於 old-style class 使用了非常簡單的基於深度優先遍歷的 MRO 算法(關於深度優先遍歷,我想大家肯定都不陌生)。當一個類繼承自多個類時,Python 2 按照從左到右的順序深度遍歷類的繼承圖,從而確定類中函數的調用順序。這個過程具體如下:

  1. 檢查當前的類裏面是否有該函數,如果有則直接調用。
  2. 檢查當前類的第一個父類裏面是否有該函數,如果沒有則檢查父類的第一個父類是否有該函數,以此遞歸深度遍歷。
  3. 如果沒有則回溯一層,檢查下一個父類裏面是否有該函數並按照 2 中的方式遞歸。

上面的過程與標準的深度優先遍歷只有一點細微的差別:步驟 2 總是按照繼承列表中類的先後順序來選擇分支的遍歷順序。具體來說,類 D 的繼承列表中類順序為 B, C,因此,類 D 按照先遍歷 B 分支再遍歷 C 分支的順序來確定 MRO。

我們繼續用第一個例子中的函數繼承圖來說明這個過程:

按照上述深度遞歸的方式,函數 d.who_am_i() 調用的搜索順序是 D, B, A, C, A。由於一個類不能兩次出現,因此在搜索路徑中去除掉重複出現的 A,得到最終的方法解析順序是 D, B, A, C。這樣一來你就明白了為什麼 d.who_am_i() 打印的是 I am A 了。

在 Python 2 中,我們可以通過如下方式來查看 old-style class 的 MRO:

>>> import inspect
>>> inspect.getmro(D)

理解 new-style class 的 MRO

從上面的結果可以看到,使用深度優先遍歷的查找算法並不合理。因此,Python 3 以及 Python 2 針對 new-style class 採用了新的 MRO 算法。如果你使用 Python 3 重新運行一遍上述腳本,你就可以看到函數 d.who_am_i() 的打印結果是 I am C

>>> d.who_am_i()
I am C
>>> D.__mro__
(<class 'test.D'>, <class 'test.B'>, <class 'test.C'>, <class 'test.A'>, <class 'object'>)

新算法與基於深度遍歷的算法類似,但是不同在於新算法會對深度優先遍歷得到的搜索路徑進行額外的檢查。其從左到右掃描得到的搜索路徑,對於每一個節點解釋器都會判斷該節點是不是好的節點。如果不是好的節點,那麼將其從當前的搜索路徑中移除。

那麼問題在於,什麼是一個好的節點?我們說 N 是一個好的節點當且僅當搜索路徑中 N 之後的節點都不繼承自 N。我們還以上述的類繼承圖為例,按照深度優先遍歷得到類 D 中函數的搜索路徑 D, B, A, C, A。之後 Python 解釋器從左向右檢查時發現第三個節點 A 不是一個好的節點,因為 A 之後的節點 C 繼承自 A。因此其將 A 從搜索路徑中移除,然後得到最後的調用順序 D, B, C, A。

採用上述算法,D 中的函數調用將優先查找其直接父類 B 和 C 中的相應函數。

C3線性化算法

上一小結我們從直觀上概述了針對 new-style class 的 MRO 算法過程。事實上這個算法有一個明確的名字 C3 linearization。下面我們給出其形式化的計算過程。

上面的過程看起來好像很複雜,我們用一個例子來具體執行一下,你就會覺得其實還是挺簡單的。假設我們有如下的一個類繼承關係:

參考來源:Understanding Python MRO – Class search path

class X():
    def who_am_i(self):
        print("I am a X")
        
class Y():
    def who_am_i(self):
        print("I am a Y")
        
class A(X, Y):
    def who_am_i(self):
        print("I am a A")
        
class B(Y, X):
     def who_am_i(self):
         print("I am a B")
         
class F(A, B):
    def who_am_i(self):
        print("I am a F")

Traceback (most recent call last):
  File "test.py", line 17, in <module>
    class F(A, B):
TypeError: Cannot create a consistent method resolution
order (MRO) for bases X, Y

為什麼採用C3算法

上圖中都是使用BFS算法來尋找繼承鏈,但是都會有問題,左邊的繼承模式會違背單調性的原則,右邊的棱形繼承鏈,如果是C重寫了繼承於A的方法,B沒有,但是根據MRO繼承鏈,最終調用的都是A類的方法,C中實現的方法永遠不會被調用,這些都是再python2的問題,python引入C3算法后就解決了這些問題。

C3算法最早被提出是用於Lisp的,應用在Python中是為了解決原來基於深度優先搜索算法不滿足本地優先級,和單調性的問題。
本地優先級:指聲明時父類的順序,比如C(A,B),如果訪問C類對象屬性時,應該根據聲明順序,優先查找A類,然後再查找B類。
單調性:如果在C的解析順序中,A排在B的前面,那麼在C的所有子類里,也必須滿足這個順序。

在Python官網的The Python 2.3 Method Resolution Order中作者舉了例子,說明這一情況

F=type('Food', (), {remember2buy:'spam'})
E=type('Eggs', (F,), {remember2buy:'eggs'})
G=type('GoodFood', (F,E), {})

根據本地優先級在調用G類對象屬性時應該優先查找F類,而在Python2.3之前的算法給出的順序是G E F O,而在心得C3算法中通過阻止類層次不清晰的聲明來解決這一問題,以上聲明在C3算法中就是非法的。

小結

C3算法的核心 :

  1. 遍歷執行merge操作的序列,如果一個序列的第一個元素,在其他序列中也是第一個元素,或不在其他序列出現,則從所有執行merge操作序列中刪除這個元素,合併到當前的mro中。

  2. merge操作后的序列,繼續執行merge操作,直到merge操作的序列為空。

  3. 如果merge操作的序列無法為空,則說明不合法。

參考資料

理解Python中的多繼承-C3 線性化算法

Python的多重繼承問題-MRO和C3算法

Deep Thoughts by Raymond Hettinger

C3 linearization

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

【其他文章推薦】

※超省錢租車方案

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

※回頭車貨運收費標準

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

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

遵守自然法則的吃蟲文化 日本自古就有「追蜂」少年

文:宋瑞文

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

※回頭車貨運收費標準

WEB安全入門:如何防止 CSRF 攻擊?

現在,我們絕大多數人都會在網上購物買東西。但是很多人都不清楚的是,很多電商網站會存在安全漏洞。烏雲就通報過,國內很多家公司的網站都存在 CSRF 漏洞。如果某個網站存在這種安全漏洞的話,那麼我們在購物的過程中,就很可能會被網絡黑客盜刷信用卡。是不是瞬間有點「不寒而栗」 的感覺?

首先,我們需要弄清楚 CSRF 是什麼。它的全稱是 Cross-site request forgery ,翻譯成中文的意思就是「跨站請求偽造」,這是一種對網站的惡意利用。簡單而言,就是某惡意網站在我們不知情的情況下,以我們的身份在你登錄的某網站上胡作非為——發消息、買東西,甚至轉賬……

這種攻擊模式聽起來有點像跨站腳本(XSS),但 CSRF 與 XSS 非常不同,並且攻擊方式幾乎相左。XSS 利用站點內的信任用戶,而 CSRF 則通過偽裝來自受信任用戶的請求來利用受信任的網站。與 XSS 攻擊相比,CSRF 攻擊往往很少見,因此對其進行防範的資源也相當稀少。不過,這種「受信任」的攻擊模式更加難以防範,所以被認為比 XSS 更具危險性。

這個過程到底是怎樣的呢?讓我們看個簡單而鮮活的案例。

銀行網站 A,它以 GET 請求來完成銀行轉賬的操作,如:

http://www.mybank.com/Transfer.php?toBankId=11&money=1000

危險網站 B,它裏面有一段 HTML 的代碼如下:

<img src=http://www.mybank.com/Transfer.php?toBankId=11&money=1000>

可能會發生什麼?你登錄了銀行網站 A,然後訪問危險網站 B 以後,突然發現你的銀行賬戶少了10000塊……

為什麼會這樣呢?原因是在訪問危險網站 B 之前,你已經登錄了銀行網站 A,而 B 中的以 GET 的方式請求第三方資源(這裏的第三方就是指銀行網站了,原本這是一個合法的請求,但這裏被不法分子利用了),所以你的瀏覽器會帶上你的銀行網站 A 的 Cookie 發出 GET 請求,去獲取資源

「http://www.mybank.com/Transfer.php?toBankId=11&money=1000」,

結果銀行網站服務器收到請求后,認為這是一個合理的轉賬操作,就立刻轉賬了……

其實,真實的銀行網站不會如此不加防範,但即使用 POST 替代 GET,也只是讓危險網站多花些力氣而已。危險網站 B 依然可以通過嵌入 Javascript 來嘗試盜取客戶資金,所以我們時不時會聽到客戶資金被盜的案件,其實這並不是很不稀奇。

相信,很多人了解到這兒,會出現一身冷汗,還讓不讓我們在「618」期間能夠愉快地享受網購的快感了?難道沒有什麼辦法防住它嘛?

當然是有的。

在業界目前防禦 CSRF 攻擊主要有三種策略:驗證 HTTP Referer 字段;在請求地址中添加 token 並驗證;在 HTTP 頭中自定義屬性並驗證。下面就分別對這三種策略進行詳細介紹。

驗證 HTTP Referer 字段

根據 HTTP 協議,在 HTTP 頭中有一個字段叫 Referer,它記錄了該 HTTP 請求的來源地址。在通常情況下,訪問一個安全受限頁面的請求來自於同一個網站,比如需要訪問http://bank.example/withdraw?account=bob&amount=1000000&for=Mallory,用戶必須先登陸 bank.example,然後通過點擊頁面上的按鈕來觸發轉賬事件。這時,該轉帳請求的 Referer 值就會是轉賬按鈕所在的頁面的 URL,通常是以 bank.example 域名開頭的地址。而如果黑客要對銀行網站實施 CSRF 攻擊,他只能在他自己的網站構造請求,當用戶通過黑客的網站發送請求到銀行時,該請求的 Referer 是指向黑客自己的網站。因此,要防禦 CSRF 攻擊,銀行網站只需要對於每一個轉賬請求驗證其 Referer 值,如果是以 bank.example 開頭的域名,則說明該請求是來自銀行網站自己的請求,是合法的。如果 Referer 是其他網站的話,則有可能是黑客的 CSRF 攻擊,拒絕該請求。

這種方法的顯而易見的好處就是簡單易行,網站的普通開發人員不需要操心 CSRF 的漏洞,只需要在最後給所有安全敏感的請求統一增加一個攔截器來檢查 Referer 的值就可以。特別是對於當前現有的系統,不需要改變當前系統的任何已有代碼和邏輯,沒有風險,非常便捷。

然而,這種方法並非萬無一失。Referer 的值是由瀏覽器提供的,雖然 HTTP 協議上有明確的要求,但是每個瀏覽器對於 Referer 的具體實現可能有差別,並不能保證瀏覽器自身沒有安全漏洞。使用驗證 Referer 值的方法,就是把安全性都依賴於第三方(即瀏覽器)來保障,從理論上來講,這樣並不安全。事實上,對於某些瀏覽器,比如 IE6 或 FF2,目前已經有一些方法可以篡改 Referer 值。如果 bank.example 網站支持 IE6 瀏覽器,黑客完全可以把用戶瀏覽器的 Referer 值設為以 bank.example 域名開頭的地址,這樣就可以通過驗證,從而進行 CSRF 攻擊。

即便是使用最新的瀏覽器,黑客無法篡改 Referer 值,這種方法仍然有問題。因為 Referer 值會記錄下用戶的訪問來源,有些用戶認為這樣會侵犯到他們自己的隱私權,特別是有些組織擔心 Referer 值會把組織內網中的某些信息泄露到外網中。因此,用戶自己可以設置瀏覽器使其在發送請求時不再提供 Referer。當他們正常訪問銀行網站時,網站會因為請求沒有 Referer 值而認為是 CSRF 攻擊,拒絕合法用戶的訪問。

在請求地址中添加 token 並驗證

CSRF 攻擊之所以能夠成功,是因為黑客可以完全偽造用戶的請求,該請求中所有的用戶驗證信息都是存在於 cookie 中,因此黑客可以在不知道這些驗證信息的情況下直接利用用戶自己的 cookie 來通過安全驗證。要抵禦 CSRF,關鍵在於在請求中放入黑客所不能偽造的信息,並且該信息不存在於 cookie 之中。可以在 HTTP 請求中以參數的形式加入一個隨機產生的 token,並在服務器端建立一個攔截器來驗證這個 token,如果請求中沒有 token 或者 token 內容不正確,則認為可能是 CSRF 攻擊而拒絕該請求。

這種方法要比檢查 Referer 要安全一些,token 可以在用戶登陸后產生並放於 session 之中,然後在每次請求時把 token 從 session 中拿出,與請求中的 token 進行比對,但這種方法的難點在於如何把 token 以參數的形式加入請求。對於 GET 請求,token 將附在請求地址之後,這樣 URL 就變成 http://url?csrftoken=tokenvalue。 而對於 POST 請求來說,要在 form 的最後加上 <input type=”hidden” name=”csrftoken” value=”tokenvalue”/>,這樣就把 token 以參數的形式加入請求了。但是,在一個網站中,可以接受請求的地方非常多,要對於每一個請求都加上 token 是很麻煩的,並且很容易漏掉,通常使用的方法就是在每次頁面加載時,使用 javascript 遍歷整個 dom 樹,對於 dom 中所有的 a 和 form 標籤后加入 token。這樣可以解決大部分的請求,但是對於在頁面加載之後動態生成的 html 代碼,這種方法就沒有作用,還需要程序員在編碼時手動添加 token。

該方法還有一個缺點是難以保證 token 本身的安全。特別是在一些論壇之類支持用戶自己發表內容的網站,黑客可以在上面發布自己個人網站的地址。由於系統也會在這個地址後面加上 token,黑客可以在自己的網站上得到這個 token,並馬上就可以發動 CSRF 攻擊。為了避免這一點,系統可以在添加 token 的時候增加一個判斷,如果這個鏈接是鏈到自己本站的,就在後面添加 token,如果是通向外網則不加。不過,即使這個 csrftoken 不以參數的形式附加在請求之中,黑客的網站也同樣可以通過 Referer 來得到這個 token 值以發動 CSRF 攻擊。這也是一些用戶喜歡手動關閉瀏覽器 Referer 功能的原因。

在 HTTP 頭中自定義屬性並驗證

這種方法也是使用 token 並進行驗證,和上一種方法不同的是,這裏並不是把 token 以參數的形式置於 HTTP 請求之中,而是把它放到 HTTP 頭中自定義的屬性里。通過 XMLHttpRequest 這個類,可以一次性給所有該類請求加上 csrftoken 這個 HTTP 頭屬性,並把 token 值放入其中。這樣解決了上種方法在請求中加入 token 的不便,同時,通過 XMLHttpRequest 請求的地址不會被記錄到瀏覽器的地址欄,也不用擔心 token 會透過 Referer 泄露到其他網站中去。

然而這種方法的局限性非常大。XMLHttpRequest 請求通常用於 Ajax 方法中對於頁面局部的異步刷新,並非所有的請求都適合用這個類來發起,而且通過該類請求得到的頁面不能被瀏覽器所記錄下,從而進行前進,後退,刷新,收藏等操作,給用戶帶來不便。另外,對於沒有進行 CSRF 防護的遺留系統來說,要採用這種方法來進行防護,要把所有請求都改為 XMLHttpRequest 請求,這樣幾乎是要重寫整個網站,這代價無疑是不能接受的。

Java 代碼示例

下文將以 Java 為例,對上述三種方法分別用代碼進行示例。無論使用何種方法,在服務器端的攔截器必不可少,它將負責檢查到來的請求是否符合要求,然後視結果而決定是否繼續請求或者丟棄。在 Java 中,攔截器是由 Filter 來實現的。我們可以編寫一個 Filter,並在 web.xml 中對其進行配置,使其對於訪問所有需要 CSRF 保護的資源的請求進行攔截。

在 filter 中對請求的 Referer 驗證代碼如下

 

清單 1. 在 Filter 中驗證 Referer

// 從 HTTP 頭中取得 Referer 值
String referer=request.getHeader("Referer"); 
// 判斷 Referer 是否以 bank.example 開頭
if((referer!=null) &&(referer.trim().startsWith(“bank.example”))){ 
   chain.doFilter(request, response); 
}else{ 
   request.getRequestDispatcher(“error.jsp”).forward(request,response); 
}

以上代碼先取得 Referer 值,然後進行判斷,當其非空並以 bank.example 開頭時,則繼續請求,否則的話可能是 CSRF 攻擊,轉到 error.jsp 頁面。

如果要進一步驗證請求中的 token 值,代碼如下

清單 2. 在 filter 中驗證請求中的 token

HttpServletRequest req = (HttpServletRequest)request; 
HttpSession s = req.getSession(); 
 
// 從 session 中得到 csrftoken 屬性
String sToken = (String)s.getAttribute(“csrftoken”); 
if(sToken == null){ 
 
   // 產生新的 token 放入 session 中
   sToken = generateToken(); 
   s.setAttribute(“csrftoken”,sToken); 
   chain.doFilter(request, response); 
} else{ 
 
   // 從 HTTP 頭中取得 csrftoken 
   String xhrToken = req.getHeader(“csrftoken”); 
 
   // 從請求參數中取得 csrftoken 
   String pToken = req.getParameter(“csrftoken”); 
   if(sToken != null && xhrToken != null && sToken.equals(xhrToken)){ 
       chain.doFilter(request, response); 
   }else if(sToken != null && pToken != null && sToken.equals(pToken)){ 
       chain.doFilter(request, response); 
   }else{ 
       request.getRequestDispatcher(“error.jsp”).forward(request,response); 
   } 
}

首先判斷 session 中有沒有 csrftoken,如果沒有,則認為是第一次訪問,session 是新建立的,這時生成一個新的 token,放於 session 之中,並繼續執行請求。如果 session 中已經有 csrftoken,則說明用戶已經與服務器之間建立了一個活躍的 session,這時要看這個請求中有沒有同時附帶這個 token,由於請求可能來自於常規的訪問或是 XMLHttpRequest 異步訪問,我們分別嘗試從請求中獲取 csrftoken 參數以及從 HTTP 頭中獲取 csrftoken 自定義屬性並與 session 中的值進行比較,只要有一個地方帶有有效 token,就判定請求合法,可以繼續執行,否則就轉到錯誤頁面。生成 token 有很多種方法,任何的隨機算法都可以使用,Java 的 UUID 類也是一個不錯的選擇。

除了在服務器端利用 filter 來驗證 token 的值以外,我們還需要在客戶端給每個請求附加上這個 token,這是利用 js 來給 html 中的鏈接和表單請求地址附加 csrftoken 代碼,其中已定義 token 為全局變量,其值可以從 session 中得到。

清單 3. 在客戶端對於請求附加 token

function appendToken(){ 
   updateForms(); 
   updateTags(); 
} 
 
function updateForms() { 
   // 得到頁面中所有的 form 元素
   var forms = document.getElementsByTagName('form'); 
   for(i=0; i<forms.length; i++) { 
       var url = forms[i].action; 
 
       // 如果這個 form 的 action 值為空,則不附加 csrftoken 
       if(url == null || url == "" ) continue; 
 
       // 動態生成 input 元素,加入到 form 之後
       var e = document.createElement("input"); 
       e.name = "csrftoken"; 
       e.value = token; 
       e.type="hidden"; 
       forms[i].appendChild(e); 
   } 
} 
 
function updateTags() { 
   var all = document.getElementsByTagName('a'); 
   var len = all.length; 
 
   // 遍歷所有 a 元素
   for(var i=0; i<len; i++) { 
       var e = all[i]; 
       updateTag(e, 'href', token); 
   } 
} 
 
function updateTag(element, attr, token) { 
   var location = element.getAttribute(attr); 
   if(location != null && location != '' '' ) { 
       var fragmentIndex = location.indexOf('#'); 
       var fragment = null; 
       if(fragmentIndex != -1){ 
 
           //url 中含有隻相當頁的錨標記
           fragment = location.substring(fragmentIndex); 
           location = location.substring(0,fragmentIndex); 
       } 
 
       var index = location.indexOf('?'); 
 
       if(index != -1) { 
           //url 中已含有其他參數
           location = location + '&csrftoken=' + token; 
       } else { 
           //url 中沒有其他參數
           location = location + '?csrftoken=' + token; 
       } 
       if(fragment != null){ 
           location += fragment; 
       } 
 
       element.setAttribute(attr, location); 
   } 
}

在客戶端 html 中,主要是有兩個地方需要加上 token,一個是表單 form,另一個就是鏈接 a。這段代碼首先遍歷所有的 form,在 form 最後添加一隱藏字段,把 csrftoken 放入其中。然後,代碼遍歷所有的鏈接標記 a,在其 href 屬性中加入 csrftoken 參數。注意對於 a.href 來說,可能該屬性已經有參數,或者有錨標記。因此需要分情況討論,以不同的格式把 csrftoken 加入其中。

如果你的網站使用 XMLHttpRequest,那麼還需要在 HTTP 頭中自定義 csrftoken 屬性,利用 dojo.xhr 給 XMLHttpRequest 加上自定義屬性代碼如下:

清單 4. 在 HTTP 頭中自定義屬性

var plainXhr = dojo.xhr; 
 
// 重寫 dojo.xhr 方法
dojo.xhr = function(method,args,hasBody) { 
   // 確保 header 對象存在
   args.headers = args.header || {}; 
 
   tokenValue = '<%=request.getSession(false).getAttribute("csrftoken")%>'; 
   var token = dojo.getObject("tokenValue"); 
 
   // 把 csrftoken 屬性放到頭中
   args.headers["csrftoken"] = (token) ? token : "  "; 
   return plainXhr(method,args,hasBody); 
};

這裏改寫了 dojo.xhr 的方法,首先確保 dojo.xhr 中存在 HTTP 頭,然後在 args.headers 中添加 csrftoken 字段,並把 token 值從 session 里拿出放入字段中。

CSRF 防禦方法選擇之道

通過上文討論可知,目前業界應對 CSRF 攻擊有一些克制方法,但是每種方法都有利弊,沒有一種方法是完美的。如何選擇合適的方法非常重要。如果網站是一個現有系統,想要在最短時間內獲得一定程度的 CSRF 的保護,那麼驗證 Referer 的方法是最方便的,要想增加安全性的話,可以選擇不支持低版本瀏覽器,畢竟就目前來說,IE7+, FF3+ 這類高版本瀏覽器的 Referer 值還無法被篡改。

如果系統必須支持 IE6,並且仍然需要高安全性。那麼就要使用 token 來進行驗證,在大部分情況下,使用 XmlHttpRequest 並不合適,token 只能以參數的形式放於請求之中,若你的系統不支持用戶自己發布信息,那這種程度的防護已經足夠,否則的話,你仍然難以防範 token 被黑客竊取並發動攻擊。在這種情況下,你需要小心規劃你網站提供的各種服務,從中間找出那些允許用戶自己發布信息的部分,把它們與其他服務分開,使用不同的 token 進行保護,這樣可以有效抵禦黑客對於你關鍵服務的攻擊,把危害降到最低。畢竟,刪除別人一個帖子比直接從別人賬號中轉走大筆存款嚴重程度要輕的多。

如果是開發一個全新的系統,則抵禦 CSRF 的選擇要大得多。筆者建議對於重要的服務,可以盡量使用 XMLHttpRequest 來訪問,這樣增加 token 要容易很多。另外盡量避免在 js 代碼中使用複雜邏輯來構造常規的同步請求來訪問需要 CSRF 保護的資源,比如 window.location 和 document.createElement(“a”) 之類,這樣也可以減少在附加 token 時產生的不必要的麻煩。

最後,要記住 CSRF 不是黑客唯一的攻擊手段,無論你 CSRF 防範有多麼嚴密,如果你系統有其他安全漏洞,比如跨站域腳本攻擊 XSS,那麼黑客就可以繞過你的安全防護,展開包括 CSRF 在內的各種攻擊,你的防線將如同虛設。我們只有充分重視 CSRF,根據系統的實際情況選擇最合適的策略,這樣才能把 CSRF 的危害降到最低。

 

點擊關注,第一時間了解華為雲新鮮技術~

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

【其他文章推薦】

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

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

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

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

※回頭車貨運收費標準

IDEA自定義類註釋和方法註釋(自定義groovyScript方法實現多行參數註釋)

一、類註釋

1、打開設置面板:file -> setting -> Editor -> file and code Templates

選擇其中的inclues選項卡,並選擇File header,如圖。不要選擇Files選項卡再設置Class,這樣比較麻煩,而且這樣設置以後沒新建一個類都要自己寫一次Date。

2、在右邊編輯面板插入自己想要的註釋即可。其中${}是變量,需要在變量基本都在編輯款下面的Description,往下拉即可看到。
/*
* @Classname ${NAME}
*
* @Date ${DATE}
*
* @userName
*/
3、新建一個類,看是否自動加了註釋

 

二、方法註釋

1、打開設置面板:file -> setting -> Editor -> Live Templates

 

 2、新建一個Template Group…,命名隨意,假設為bokeyuan,然後選擇該組,點擊新建一個模板Live Template

 

3、名稱建議設為*,文本框輸入自己想要設置的註釋格式,右下角要選擇enter(原本是tab)。

 

 4、留意註釋格式,其中參數要直接寫變量$param$,開頭只有一個*號。寫好之後點擊上圖框中的edit variables

 

其中返回值return使用系統自帶的,下拉可以找到methodReturnType()

 

 

 5、自定義多行參數註釋

IDEA自帶的參數函數methodParameters()產出的註釋格式是這樣的:

/**
      * 
      * @param [a,b,c]
      * @return void
      * @throws 
      */

我們可能需要的是多行參數註釋:

/**
      * 
      * @param a
      * @param b
      * @param c
      * @return void
      * @throws 
      */

這個時候就要使用裏面的groovyScript()函數來自定義格式:

groovyScript("def result=''; def params=\"${_1}\".replaceAll('[\\\\[|\\\\]|\\\\s]', '').split(',').toList(); for(i = 0; i < params.size(); i++) {if(i == 0) result += '* @param ' + params[i] + ' ' + ((i < params.size() - 1) ? '\\n' : '');else result+='  * @param ' + params[i] + ' ' + ((i < params.size() - 1) ? '\\n' : '')}; return result", methodParameters())

直接複製在Expression裏面即可。

 

6、選擇語言,點擊Define勾選Java

 

 

有其他問題可以評論問我哦

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

萬級TPS億級流水-中台賬戶系統架構設計

萬級TPS億級流水-中台賬戶系統架構設計

標籤:高併發 萬級TPS 億級流水 賬戶系統

  • 背景
  • 業務模型
  • 應用層設計
  • 數據層設計
  • 日切對賬

背景

我們需要給所有前台業務提供統一的賬戶系統,用來支撐所有前台產品線的用戶資產管理,統一提供支持大併發萬級TPS、億級流水、數據強一致、風控安全、日切對賬、財務核算、審計等能力,在萬級TPS下保證絕對的數據準確性和數據溯源能力。

注:資金類系統只有合格和不合格,哪怕數據出現只有0.01分的差錯也是不合格的,局部數據不準也就意味着全局數據都不可信。

本文只分享系統的核心模型部分的設計,其他常規類的(如壓測驗收、系統保護策略-限流、降級、熔斷等)設計就不做多介紹,如果對其他方面有興趣歡迎進一步交流。

業務模型

基本賬戶管理: 根據交易的不同主體,可以分為個人賬戶機構賬戶
賬戶餘額在使用上沒有任何限制,很純粹的賬戶存儲、轉賬管理,可以滿足90%業務場景。

子賬戶功能: 一個用戶可以開通多個子賬戶,根據餘額屬性不同可以分為基本賬戶、過期賬戶,根據幣種不同可以分為人民幣賬戶、虛擬幣賬戶,根據業務形態不同可以自定義。
(不同賬戶的特定功能是通過賬戶上的賬戶屬性來區分實現。)

過期賬戶管理: 該賬戶中的餘額是會隨着進賬流水到期自動過期。
如:在某平台充值1000元送300元,其中300元是有過期時間的,但是1000元是沒有時間限制的。這裏的1000元存在你的基本賬戶中,300元存在你的過期賬戶中。

注:過期賬戶的每一筆入賬流水都會有一個到期時間。系統根據交易流水的到期時間,自動核銷用戶過期賬戶中的餘額,記為平台的確認收入。

賬戶組合使用:支持多賬戶組合使用,根據配置的優先扣減順序進行扣減餘額。比如:在 基本賬戶過期賬戶 (充值賬戶)中扣錢一般的順序是優先扣減過期賬戶的餘額。

應用層設計

根據上述業務模型,賬戶系統是一個典型的 數據密集型系統 ,業務層的邏輯不複雜。整個系統的設計關鍵點在於如何平衡大併發TPS和數據一致性。

熱點賬戶:前台直播類業務存在熱點賬戶問題,每到各種活動賽事的時候會存在 90%DAU 給少數幾個頭部主播打賞的場景。
DB就會有熱點行問題,由於 行鎖 關係併發一大肯定大量超時、RT突增DB活躍線程 增加等一系列問題,最終DB會被拖掛。

賬戶類系統有一個特點,原賬戶的扣減可以實時處理,目標賬戶可以異步處理,我們可以將轉賬動作拆解為兩個階段進行異步化。(可以參考銀行轉賬業務。)

比如:A給B轉賬100元,原賬戶A的100元餘額扣減可以同步處理,B賬戶的100增加可以異步處理。這樣哪怕10w人給主播打賞,這10w人的賬戶都是分散的,而主播的餘額增加則是異步處理的。

賬戶轉賬扣減A賬戶餘額,記錄A賬戶出賬流水,記錄B賬戶入賬流水,這三個動作可以在一個DBTransaction中處理,可以保證源賬戶進出帳一致性。目標賬戶B的入賬可以異步處理,為了保證萬無一失且滿足一定的實時性,需要兩步結合,程序里通過MQ走異步入賬,同時增加DB的兜底JOB定時掃描 入賬流水記錄未到賬的流水進行入賬。

我們通過異步化緩解熱點行處理,但是如果 收款方 強烈要求收款必須在一定的時間內完成,我們還是需要進一步處理,後面會講到。

過期賬戶: 通常過期賬戶用來管理贈送類賬戶,這類賬戶有一定的時效性,用戶在使用上也是優先扣減此類賬戶餘額。
這類使用需求其實覆蓋面不大,真正用戶賬戶餘額不使用等着被系統過期的很少,畢竟這是一個很傻的行為。

過期賬戶的兩種核銷情況:第一種是用戶使用過期賬戶時的核銷。第二種是某個過期流水到了過期時間,系統自動核銷記為平台的確認收入。

過期賬戶核銷邏輯:用戶充值1000元到基本賬戶,平台贈送300元到贈送賬戶。此時,基本賬戶記錄進賬流水+1000元,贈送賬戶記錄進賬流水+300元並且該筆流水的過期時間2020-12-29 23:59:59 (過期時間由前台業務方設置) 。

系統自動核銷:如果用戶不在此時間之前用完就會被系統自動划進平台的收入,贈送賬戶餘額扣減-300元。

用戶使用核銷:如果用戶在過期時間前陸續在使用贈送賬戶,比如使用100元,那麼我們需要核銷原本進賬的300元的那筆流水,減少-150元。
也就是說,該筆過期流水已經核銷掉150元,帶過期核銷150元,到期后只要核銷150元即可,而不是300元。

過期賬戶每次使用均產生待核銷負向流水,系統自動核銷前必須保證沒有任何負向流水記錄才可以去扣減贈送賬戶餘額。

考慮到極端情況下,剛好過期JOB在進行自動過期核銷,用戶又在此時使用過期賬戶,這點需要注意下。可以簡單通過加DB-X鎖解決,這個場景其實非常稀少。

數據層設計

在應用層設計的時候,我們通過異步化方式來繞開熱點問題。
同樣我們在設計數據層的時候也要考慮單次操作DB的性能,比如控制事務的大小,事務跨網絡的次數等問題。當然還包括金額存儲的精度問題,精度問題處理不好也會影響性能。

浮點數問題: 如果我們用浮點數近似值來存儲金額,那麼就一定會有偏差,隨着金額越大時間越長偏差就會越大。比較好的方式是通過整型來存儲,通過放大金額比例來達到不同的業務場景下對金額比率的要求。

正常的1.12元,存儲比率是1=100元,那麼表裡的存儲值就是112,不同的貨幣比例都可以自由縮放,永遠都可以保持最準確的精度。

分庫分表+讀寫分離: 根據業務特點和未來增量規劃,將DB分為16個邏輯庫,前期使用2個物理庫承載。16個邏輯庫,按照每次2倍擴容,最大擴容上限是16個物理庫。單實例的配置 8c 32g 2t 8000conn 9000iops

按照單次TPS-rt 1ms計算,TPS 1w 需求,每台承載5k TPS,單庫的活躍線程大概在8-10個(考慮網絡延遲)。
最後到達瓶頸的都是iops,因為只要rt足夠短,最終壓力都會在IO上。

分庫按照uid分為16個庫,賬戶表不分表默認16張。每張表按照 1kw*16=1.6 億個賬戶。

單表能存儲多少要綜合考慮,比如查詢類型,單次查詢的RT,冷熱數據佔比( innodb_buffer_pool 利用率)、是否充分發揮了索引,索引是否達到3星級別,索引片中沒有經常變更的字段等。

賬戶流水表按照日期分表365張,流水數據會隨着時間推移逐漸變成冷數據,定期歸檔冷數據。(這裏約定了,流水查詢只能按照uid+日期查詢。如果運營類的需求,要橫跨分片key獲取,走OLAP方案 clickhouse、hive等)

分庫分表採用阿里雲分佈式數據庫產品DRDS,1個主庫集群+2個讀庫集群(讀庫做了讀負載均衡,可以按需擴容)。

讀負載均衡器:https://github.com/Plen-wang/read-loadbalance

既然用了DRDS分佈式數據庫產品,那麼在查詢上需要充分考慮分片鍵的限制,如果存儲和查詢出現分片鍵衝突問題就需要我們手動計算分片路由,直接訪問物理節點。

訪問物理節點需要藉助DRDS專用SQL註釋子句來完成。

先通過 show node 查看物理DB ID、show topology from logic_table_name 查看物理表ID,然後在SQL帶上特定的註釋子句

SELECT /*+TDDL:scan('logic_table_name', real_table=("real_table_name"),node='real_db_node_id')*/ 
count(1) FROM logic_table_name ;

賬戶更新: 對賬戶更新都有一個前提就是賬戶已經開通,但是我們為了最大化賬戶系統在使用上的便利性,讓前台業務方不需要做初始化動作,由賬戶系統惰性初始化,比如發現賬戶不存在就自動初始化賬戶數據。

但是我們怎麼知道賬戶不存在,不可能每次都去查詢一次或者根據執行返回錯誤判斷。而且 update 語句是區分不了錯誤的 賬戶不存在 還是 餘額不足 或者其他原因。

那麼如何巧妙的解決這個問題,只要一次DB往返。

我們可以使用 Mysql INSERT INTO ... ON DUPLICATE KEY UPDATE ... 子句,但是該子句有一個限制就是不支持 where 子句。

-- cut_version 樂觀鎖、account_property 賬戶屬性
insert into tb_account(uid,balance,cut_version,account_property) values("%s",%d,%d,%d) ON DUPLICATE KEY UPDATE balance = balance + %d,cut_version = cut_version+1

其實不完全推薦使用這個方法,因為這個方法也有弊端就是將來 where 子句無法使用,還有一個辦法就是合併 賬戶查詢插入 為一條 sql 提交。

DB操作本身rt可能很短,但是如果跨網絡那麼事務的延遲會帶來DB的串行化增加,降低併發度,整體應用 rt就會增加。所以一個原則就是盡量不要跨網絡開事務,合併sql做一次事務提交,最短的事務周期,減少跨網絡的事務操作,如果我們將單次事務網絡交互減少2-3次,性能的提高可能會增加2-3倍,同樣由於網絡的不穩定抖動丟包對 999rt 線的影響也會減少2-3倍。

平衡好當前系統是業務密集型還是數據密集型
判斷當前系統是否有很強的業務層邏輯,是否要運用DDDRUP等強模型的工程方法。畢竟強模型高性能在落地的時候有些方面是衝突的,需要進一步藉助 CRQSGRASP等工程方法來解決。

單行熱點問題: 單行的TPS都是串行的,事務rt越短TPS就越高,按照1ms計算,差不多TPS就是1000。一般只有機構賬戶類型才會有這個需求。

我們可以將單行變成多行,增加行的并行度,加大賬戶操作的併發度。(這個方案要評估好寫入和查詢兩端需求)

id uid balance slot
1 10101010 1000 1
2 10101010 2000 2
3 10101010 3000 3
4 10101010 400 4
5 10101010 300 5
6 10101010 200 6
7 10101010 200 7
8 10101010 200 8
9 10101010 200 9
10 10101010 200 10
insert into tb_account (uid,balance,slot)
values(10101010, 1000, round(rand()*9)+1) 
on  duplicate key update balance=balance+values(balance)

這裏的 10slot*單個slot 1000TPS,理論上可以跑到1w,如果機構賬戶數據量很大,可以擴展slot個數。

賬戶的總餘額通過sum()匯總,如果業務場景中有餘額的頻繁sum()操作,可以通過增加餘額中間表,定期 insert into tb_account_total select sum(balance) total_balance from tb_account group by uid

通常機構賬戶的結算是有周期的(T+7、T+30等),而且基本是沒有併發,所以在賬戶餘額扣減方面就可以輕鬆處理。
有兩種實現方案:

第一種,賬戶餘額允許單個slot為負數,但是總的sum()是正數。通過子查詢來對餘額進行檢查。

insert into tb_account (uid, balance, slot)
select uid,-1000 as balance,round(rand() *9+ 1)
from(
    select uid, sum(balance) as ss
    from tb_account
    where uid= 10101010
    group by uid having ss>= 1000 for update) as tmp
on duplicate key update balance= balance+ values(balance)

第二種,如果條件允許可以藉助用戶自定義變量來在DB上完成餘額累計掃描,將可以扣減的slot的主鍵id返回給程序,但是只需要一次DB交互就可以獲取出可以扣減的賬戶solt,然後分別開始對slot賬戶進行扣減。

set @f:=0;
select * from tb_account where id in(select id from (select id, @f:=@f+balance from tb_account where @f<1000 order by id) as t);

第二種方案在默認的mysql數據庫上都是支持的,但是有些數據庫雲產品不支持,阿里雲rds是不支持的。

日切對賬

賬戶系統有一個基本的需求,就是每天餘額鏡像,簡單講就是餘額在每天的快照,用來做T+1對賬。
不管財務還是每季度的審計都會需要,最重要的是我們自己也需要對賬戶數據做摸底對賬。

由於每天產生上億的流水,這需要在大數據平台中完成。

日切對賬:昨天賬戶餘額前天賬戶餘額 = 昨天的流水前天的流水

比如,昨天的賬戶餘額是5000w,前台的賬戶餘額是4500w,差值就是500w。同樣道理,昨天的賬戶流水是5000w,前天的賬戶流水是4500w,那麼差值是500w,這就是沒問題的。

賬戶不僅有增加也有減少,可能昨天賬戶餘額比前天賬戶餘額差值是-500w,但是流水也要是-500w才行。

由於每天會產生億級的流水,用傳統的全量抽取不現實,這類數據抽取的速度都會有延遲,而且對賬最重要的是時間點必須非常精準,才能保證餘額和流水是對得上的。

要不然會出現HDFS的分區是2020-06-10號,但是該分區里有2020-06-11的數據,就是因為拉取的時候會延遲到第二天。這個問題也可以通過增加拉取sql的條件限制來解決這個問題,但是無法做到0點瞬間鏡像全部賬戶。

解決方案: 全量餘額+binlog增量更新
1.賬戶表,先做一次全量同步。
2.DB的所有變更通過binlog(默認row複製)進到數倉。(因為 binlog 是基於發生時間的,所以無所謂我們是不是在0點去計算鏡像)
3.T+1跑JOB的時候,獲取前一天的賬戶餘額,然後通過 binlog 來覆蓋前天與昨天的交集部分。

由於數倉的 binlog 數據都是增量的,所以要想取到正確的全量數據需要用到一定的技巧。

select app_id,sub_type,sum(amount) records_amount from (
      select *,row_number()over(partition by id order by updated_at) as rn
      from hive_db_table
      where dt='${YESTERDAY}'
  ) t where t.rn=1
       group by t.sub_type,t.app_id

使用 hive 開窗函數 row_number()over() 對同樣的id進行分組,然後獲取最新的一條數據就是賬戶在T的最後的值。

作者:王清培(趣頭條 Tech Leader)

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

【其他文章推薦】

※超省錢租車方案

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

※回頭車貨運收費標準

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

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