文章出處
數據在任何一家公司里面都是最核心的資產,定期備份則是為了保證數據庫出現問題的時候能夠及時回滾到最近的備份點,將損失縮小到最小
這篇文章將會兩部分來說明:1、mysql的定期備份;2、同步到其它服務器
mysql 備份
備份還原某個數據庫
備份還原
# 導出數據庫
/usr/bin/mysqldump -u root -ppwd database > database20160929.sql
# 導入數據庫
mysql -u root -p database < database20160929.sql

備份到壓縮文件從壓縮文件導入
#備份到壓縮文件
/usr/bin/mysqldump -u root -ppwd database | gzip > database20160929.sql.gz
#從壓縮文件導入
gzip < database20160929.sql.gz | mysql -u root -p database

crontab定時備份
1、創建備份目錄
# root 用戶,創建備份目錄
mkdir -p /bak/mysqlbak
cd /bak/mysqldata

2、編寫運行腳本
vi /usr/sbin/bakmysql.sh
腳本代碼:
#!/bin/bash
# Name:bakmysql.sh
# This is a ShellScript For Auto DB Backup and Delete old Backup
#
backupdir=/bak/mysqlbak
time=` date +%Y%m%d%H `
mysql_bin_dir/mysqldump -u root -ppwd database | gzip > $backupdir/database$time.sql.gz
#
find $backupdir -name "name_*.sql.gz" -type f -mtime +7 -exec rm {} ; > /dev/null 2>&1
#

腳本說明:
  • backupdir mysql備份地址

  • root mysql用戶名

  • pwd mysql密碼

  • database 數據庫名

  • mysql_bin_dir mysql的bin路徑;

  • time=` date +%Y%m%d%H `也可以寫為time="$(date +"%Y%m%d$H")"其中`符號是TAB鍵上面的符號,不是ENTER左邊的'符號,還有date后要有一個空格。

  • type f 表示查找普通類型的文件,f表示普通文件。

  • mtime +7 按照文件的更改時間來查找文件,+5表示文件更改時間距現在7天以前;如果是 -mmin +5 表示文件更改時間距現在5分鐘以前。

  • exec rm {} \ 表示執行一段shell命令,exec選項后面跟隨著所要執行的命令或腳本,然后是一對兒{},一個空格和一個\,最后是一個分號。

  • /dev/null 2>&1 把標準出錯重定向到標準輸出,然后扔到/DEV/NULL下面去。通俗的說,就是把所有標準輸出和標準出錯都扔到垃圾桶里面;其中的& 表示讓該命令在后臺執行。

  • 3、為腳本添加執行權限
    # chmod +x /usr/sbin/bakmysql.sh
    4、設置crontab定時執行
    vi /etc/crontab
    #在最后一行中加入:
    00 3 * * * root /usr/sbin/bakmysql.sh
    #表示每天3點00分執行備份

    注:crontab配置文件格式如下:
    分 時 日 月 周  命令


    5、重啟crontab
    /etc/rc.d/init.d/crond restart
    這樣就完了定時備份并清理前7天的備份數據
    同步到其它服務器
    這里使用Linux同步文件工具rsync+inotify來進行文件的同步
    rsync
    rsync是類unix系統下的數據鏡像備份工具——remote sync。一款快速增量備份工具 Remote Sync,遠程同步 支持本地復制,或者與其他SSH、rsync主機同步
    用法
    rsync src dest
    這是最簡單的用法,表示同步src,dest文件。(即,執行之后,dest的文件與src的相同,以src的為準)
    常用選項

    • -a: 等價于-rlptgoD,歸檔式

    • -r: 遞歸

    • -l: 復制軟件鏈接

    • -p: 保留權限信息

    • -t: 將src的修改時間,同步到dest

    • -g: 同步組信息(group)

    • -o: 同步擁有者信息(own)

    • -D: 保持字符與塊設備文件

    • -z: 啟用壓縮傳輸

    • -–delete:如果src沒有此文件,那么dest也不能有,即在dest刪除src里沒有的文件。(如果你使用這個選項,就必須搭配-r選項一起)


    ## 將本地/bak/mysqlbak/文件同步到 遠程服務器 /bak/mysql/bak 目錄下面 排除 mysqlbak/index目錄 通過ssh端口
    rsync -vzacu /bak/mysqlbak/ root@192.168.53.86:/bak/mysqlbak --exclude "mysqlbak/index" -e "ssh -p 22"
    # 將遠程目錄 /bak/mysqlbak下的文件同步到本地 /bak/mysqlbak/目錄下
    rsync -vzrtopg --progress --delete root@192.168.53.85:/bak/mysqlbak /bak

    啟用rsync服務器端同步遠程文件
    rsycn的服務端為服務器的文件接收端,rsycn的客戶端為服務器的文件推動端。
    rsycn的服務端/文件接收端配置
    服務端需要開啟rsyncd服務
    添加配置文件rsyncd.conf
    vi /etc/rsyncd.conf
    #以下是全局配置
    log file = /var/log/rsyncd.log
    pid file = /var/run/rsyncd.pid
    lock file = /var/lock/rsyncd
    [mysqlbak] #模塊名,在源服務器指定這個名字
    comment = sync rsync/home #描述信息
    path = /bak/mysqlbak #備份目錄
    use chroot=no #不使用chroot,不用root權限
    read only = no #設置本地備份目錄為讀寫權限
    uid=root
    gid=root
    max connections=10 #客戶端最大連接數
    auth users = root #指定數據同步用戶
    secrets file = /etc/rsyncd.pass #指定數據同步用戶信息文件
    hosts allow=192.168.53.0/85 #允許連接的客戶端
    ignore errors = yes #忽略出現I/O錯誤
    timeout = 600

    創建認證文件
    vi /etc/rsyncd.pass
    ##代碼
    root:root #格式是用戶名:密碼
    #屬主要有權限讀這個文件,否則會報沒權限
    chmod 600 /etc/rsyncd.pass

    修改/etc/xinetd.d/rsync文件,disable 改為 no
    service rsync
    {
    disable = no
    socket_type = stream
    wait = no
    user = root
    server = /usr/bin/rsync
    server_args = --daemon
    log_on_failure += USERID
    }

    啟動服務端
    rsync --daemon --config=/etc/rsyncd.conf
    rsycn的客戶端/文件發送端配置
    客戶端配置簡單 只需要配置密碼既可
    vi /etc/rsync_client.pwd
    ##代碼
    root #只需要填寫rsync服務的密碼
    #屬主要有權限讀這個文件,否則會報沒權限
    chmod 600 /etc/rsync_client.pwd

    客戶端同步測試
    /usr/bin/rsync -auvrtzopgP --progress --password-file=/etc/rsync_client.pwd /bak/mysqlbak/ root@192.168.53.86::mysqlbak

    rsync只是一次性同步,如果需要實時同步就需要引入另一個工具了


    inotify
    Inotify 是一種強大的、細粒度的、異步的文件系統事件監控機制,linux內核從2.6.13起,加入了Inotify支持,通過Inotify可以監控文件系統中添加、刪除,修改、移動等各種細微事件,利用這個內核接口,第三方軟件就可以監控文件系統下文件的各種變化情況,而inotify-tools就是這樣的一個第三方軟件。

    Inotify只需要要按照部署在同步的客戶端,當監控的文件有變化觸動 rsync腳本來同步


    安裝
    yum install inotify-tools
    配置監控的文件路徑
    vi /etc/inotify_exclude.lst
    #代碼
    /bak/mysqlbak #監控目錄
    @/bak/log #排除監控目錄

    rsync排除監控文件目錄
    vi /etc/rsyncd.d/rsync_exclude.lst
    #代碼
    src/*.html*
    src/js/
    src/2014/20140[1-9]/

    客戶端同步到遠程的腳本rsync.sh
    #rsync auto sync script with inotify
    #variables
    current_date=$(date +%Y%m%d_%H%M%S)
    source_path=/bak/mysqlbak/
    log_file=/var/log/rsync_client.log
    #rsync
    rsync_server=192.168.53.86
    rsync_user=root
    rsync_pwd=/etc/rsync_client.pwd
    rsync_module=mysqlbak
    INOTIFY_EXCLUDE='(.*/*\.log|.*/*\.swp)$|^/tmp/src/mail/(2014|20.*/.*che.*)'
    RSYNC_EXCLUDE='/bak/rsync_exclude.lst'
    #rsync client pwd check
    if [ ! -e ${rsync_pwd} ];then
    echo -e "rsync client passwod file ${rsync_pwd} does not exist!"
    exit 0
    fi
    #inotify_function
    inotify_fun(){
    /usr/bin/inotifywait -mrq --timefmt '%Y/%m/%d-%H:%M:%S' --format '%T %w %f' \
    --exclude ${INOTIFY_EXCLUDE} -e modify,delete,create,move,attrib ${source_path} \
    | while read file
    do
    /usr/bin/rsync -auvrtzopgP --exclude-from=${RSYNC_EXCLUDE} --progress --bwlimit=200 --password-file=${rsync_pwd} ${source_path} ${rsync_user}@${rsync_server}::${rsync_module}
    done
    }
    #inotify log
    inotify_fun >> ${log_file} 2>&1 &

    給腳本執行權限,執行后就可以了
    chmod 777 rsync.sh
    ./rsync.sh

    參考:
    Linux下同步工具inotify+rsync使用詳解


    作者:純潔的微笑
    出處:http://www.ityouknow.com/
    版權所有,歡迎保留原文鏈接進行轉載 :)

    AutoPoster 發表在 痞客邦 留言(0) 人氣()


    文章出處
    其實一直想寫一篇文章名字都想好了,叫做“程序員該不該理財?”。后來想了想,該不該這個就不用想了,必須要理財!那么市面上那么多理財的方式對于我們屌絲的程序員該如何選擇呢?其實我也是那種土的掉咋的那種類型,以前幾乎沒有想過神馬理財的,一來呢畢業的時候工資全都不夠花的還理個毛線,二來總是感覺理財好像都是有錢人搞的東西;后來偶然進入了互聯網金融行業,呆了幾年,慢慢也接觸了很多理財方式,但也還是一個門外漢,此文就是和大家一起聊聊我們程序員該如何去理財?算是拋磚引玉,歡迎拍磚。
    純潔的微笑原創 轉載請注明出處 http://www.cnblogs.com/ityouknow/p/5772248.html
    我的理財經歷
    先聊聊我的理財經歷,我的理財大概也是從畢業三年之后才慢慢開始的,對了也就是從余額寶開始,后來在第三方支付、互聯網金融公司工作,對理財的種類了解慢慢多了起來,互聯網金融也就是P2P或者金融公司產品各種變種(其實也是債權類產品的進一步封裝),有活期產品、封閉期的固定收益產品(日、月、年類的固定收益);買了股票(先是A股,后來美股),在了解了基金(指數基金、債券基金、股票基金等等),后來也看了黃金和期貨(了解較少)、保險接觸最少。
    一路慢慢走來,理財的這種想法和認識也是在不斷的發展變化,不認識、不了解、不認同的一些偏見也在慢慢的消失,可以以更理性的角度去看待投資理財的這件事情,理財真的是我們每一個人都應該好好的學習的事情,如何讓你辛辛苦苦的積蓄可以跑贏通貨膨脹,不隨著時間推移我們的資產不斷縮水;但是在工作中常常發現我們的程序員對于理財目前來講大多都還是比較保守,常常是全部都銀行或者全部寶寶類產品,更有激進者全都投入股市,有一年損失十幾萬的。下面分幾個階段來聊聊我對理財的幾個認識:
    寶寶時代
    說起了理財或者第三方支付,總是不能避開一家偉大的公司-支付寶,支付寶一直在引領、教育著我們這一代人對于理財的認識;我的理財起點就是從余額寶開始的,清晰的記得13年6月余額寶出來的時候,大家都感覺到稀奇或者不了解(當時仍然有很多人認為還是把錢放到銀行保險,哪怕余額寶也是!),記得當初剛開始余額寶轉進去了100元,每天看著它給帶來的幾分錢收益都很滿足,然后就一發不扣收拾,幾乎是前腳收到發工資的短信,下一分鐘錢就同步到了余額寶里面,曾經有一段時間中國鬧錢荒,導致余額寶的最高收益高達6.76;
    不過后來由于這塊的蛋糕臺大了,各大銀行也都著急出了很多的政策來限制:第一步,開限額,導致大額資金無法進入;第二步,自己干,很多銀行自己也推出了資金的貨幣基金理財產品和余額寶來競爭,理財通也橫空出世,理財通搞了一個同卡進出的理念比較好,很大程度上對資金安全起了很大的作用。
    互聯網金融
    2014年算是互聯網金融的元年,其實互聯網金融在很早的時候就被引入了國內,之前一直是不溫不火的發展著,突然到了2014年隨著資本的大量進入催化了互聯網金融的大發展。現在的互聯網金融主要分為大的兩個部分:p2p和眾籌,p2p在中國發展進行非常多的變種,現在主要的產品有活期債權、封裝成月或者年的理財產品,然后后面去打包,從債權方面又分為,企業和個人。我也是在2014年由于工作原因接觸到了p2p,小做嘗試了之后,發現完爆余額寶,幾乎又是全部積蓄投入到p2p里面,當初算了一筆賬,按照年化12的標的來計算的話,投入10萬,一年的收益就是1.2萬,投資1000萬,那么每年不用工作就是120萬!,輕輕松松的年收入破百萬!對了沒有一點稅!!
    那時候各家平臺又是送蘋果手機、又是送電腦的各種活動玩的不亦樂乎,后來到了15年跑路了很多平臺,慢慢的才把很大的一部分撤了出來。眾籌也是在中國玩出了很多的花樣,主要還是分為產品眾籌和股權眾籌,產品眾籌主要是以高科技產品為主,但是現在很多新品發布會也搞上去了;股權眾籌也就是拿出自己的錢去投資一些比較看好的小公司來占一部分原始股權,一般股權眾籌都有一定的門檻,沒有資金百萬級別以上基本上不用參與了,風險也非常大不是咱這小市民可以玩的。
    股票
    我是在第一次股災快結束的時候進入的,15年股市正火爆的時候公司有很多的同事整天都在關注著股票,什么過了3000點了,這回進去就是炮灰了,然后大盤到了4000點,有同事又說,人民日報都刊登了文章,4000點只是一個起點,好吧,他們最后的結局都是什么,我就不說了大家也猜的到,那時候我們部門幾乎有一半的程序員多多少少都在在A股開了戶,活動的時候也是在討論著各種的股票。
    到了6月份以后我突然有了點時間,也就特別好奇股市為啥有這么大的吸引力,也試探性的在網上開了戶,在網上找了費率最便宜的那種,好多同事都是在模擬炒股的軟件上面模擬的先玩上一段時候再進入市場,但是我覺得模擬因為沒有真正的資金在上面,感覺不到任何的刺激。于是乎剛開始搞了1000元的資金去試探。清楚的記得買的第一只股票是“蘇寧云商”,然后迫不及待的每天看著它漲了還是跌了樂此不疲,買了沒有多久“蘇寧云商”就停牌了,發公告說阿里投資,重大利好!好多人都說股市新手的運氣都會比較好,先給你一點甜頭,讓你先陷進去,然后慢慢在和你玩,后來“蘇寧云商”開盤后連續三個漲停,賺了幾百塊錢。
    我立刻對股市產生了極大的興趣,大家是不是又準備看一個A股小散被收割的故事:) 于是乎,追漲殺跌、專門看股票技術指標(成交量、KDJ、MACD ...)、請教一些玩股票的朋友推薦股票,分析股票。專門組建了一個討論組,把喜歡炒股的幾個IT的朋友都拉進去,大家討論每天的行情,那是一個熱鬧!慢慢的發現很多科技股票買不了才發現創業板是需要專門去線下開通,好吧,迫不及待就去開通了創業板。短短的6個月買了很多股票,樂視、掌趣科技、東方財富、衛寧健康、華裔兄弟、萬達院線、信息發展。。。。。多到我也不清楚到底買了多少,全部短線交易!資金也從最初的1000一直增加到最高50000,經歷了股災2.0、3.0,也經歷了創業板最低的1800點、10月份的超級大反彈、今年的熔斷。認識的兩個朋友中,有一個在牛市的時候曾經一周每天盈利1萬(這樣還有什么心情上班),有一個在股災中還么賠多少但是熔斷沒有躲過去,搭進去近一年的工資。
    作為一個IT男,中國最優秀的互聯網幾乎都在美國上市,所以就一直了解如何去買美股,那時候“宜人貸”剛上納斯達克破發了,"lending club 簡稱LC" 也跌到12美元左右,覺得是個機會。剛好積木出了一款產品叫做積木股票,就迫不及待的注冊、開通海外匯款。進去之后就買了LC ,結果LC沒多久就跌到8美元、7、6 ...。失望之極,剛退出沒多久LC又出黑天鵝時間 ,直接從7左右跌到最低3.44美元!美股由于沒有漲跌幅度的限制比A股要刺激很多,經常看到某一個股票一天漲40%、50%,但跌的時候也一樣經常一下子一半市值就沒了,我在美股的虧損幾乎都來自LC。宜人貸是讓我非常震撼的一只股票,因為從開盤一直跌到3美元左右,然后開始上升到現在達30美元左右,沒有抓住非常可惜。現在手里拿的都是一些中概念股,京東、58、獵豹等。
    基金
    一個人的精力總是有限,整天看股票真的很累還影響工作、有時候甚至影響心情還不一定賺錢。如何想讓牛逼的人來替我們理財,這時候也就慢慢的了解到了基金,特別是在關注了微信公共賬號的“股社區”學了很多的知識,目前也是剛剛了解一些。基金我了解主要是這幾種:債卷基金,主要是買國家的債卷或者國企類型的公司債卷為主,風險度比較低,收益率也偏低一些,我目前就是定投的這類;股票基金,也就是找一個牛逼的基金經理幫我們炒股,盈利了大家分錢這種模式,風險也很高,也比較依賴于基金經理的個人水平。大盤類的基金,比如ETF300 之類,就是買主板最牛逼的股票各買一點,只要大盤漲,我們收益就會漲,大盤跌,我們就會虧損這種,短期看收益很難確定,長期來看肯定收益為正;也有一部分混合基金就是股票和債卷都買一些。
    黃金
    2016年初的時候,各種報道黃金跌破1000美元就是底部,那時候是1100美元左右吧,感覺差不多了,就買了交通銀行黃金延期(T+D)買了不到一萬元,沒幾天虧損了一點就出來了沒有堅持,如果堅持到現在那也賺了一些;有時候你想到和做到完全是兩種情形。現在如果想投資黃金的話,直接買螞蟻聚寶的“存金寶”,關鍵方便快捷。
    我的投資經驗與教訓
    結合我這三年多的投資理財教訓,談一些對投資的看法。
    銀行存款
    一直流行著一句話,窮人往銀行里面存錢,富人從銀行中貸款,可以很大程度上反應中國情況。如果你把積蓄都放到銀行里面躺著,結合通貨膨脹來講,你就是在虧錢!而且銀行的服務超級爛,辦業務超級慢,因此建議銀行卡里面一分錢也不要放!
    寶寶類產品
    可以把平時需要緊急用的錢和零花的錢都可以放到這里面,既可以享受一定的收益性,也可以靈活的存取、消費,建議以大平臺余額寶、理財通為主,其它類的謹慎選擇。
    互聯網金融
    平臺真的重要! 平臺真的重要!! 平臺真的重要!!! 重要的事情說三遍,如果選擇不好平臺就是0和1的事情,投資的平臺跑路了就一分錢都沒有了,筆者就投資了一個跑路的公司e租寶和一個在跑路之前投資過的公司。我們經常說,你貪戀的是人家的利息 ,人家貪戀的是你的本金。 但回過頭來看其實大多數跑路的公司都是騙子公司,幾乎沒有什么正經的互聯網金融公司,只是打著互聯網金融的旗號來行騙而已。P2P真的風險很高嗎,其實筆者看來只要選擇好平臺,應該還是非常好的一種理財方式。那怎么選擇平臺呢,盡量選擇評級靠前、國企或者上市公司背景的企業。
    股市和基金
    股市是一個學問很深的東西,有人窮盡一生也不能領悟,主要看性格、價值觀、人性的把握。大家要以我為戒,禁忌追漲殺跌、頻繁買賣,要堅持價值投資,持續持用,追求長遠的收益。股市也是一個風險度很高的投資,大家需要根據自己的風險偏好來選擇投資,也可以適當的選擇合適的基金產品來投資,一般股票配置比率應該和年齡成反比,越年輕就可以多占比你投資的一部分,最重要的是不要讓股票影響了我們正常的生活和工作!一直想找一個既可以買A股、港股和美股的產品,一直沒有發現很可惜。對于股市,要敬畏它、尊重它、理解它。
    比如程序員來講最酷的方法就是用 Python 寫一些程序進行量化交易或者分析股票相關內容,我這里也給大家推薦一個:股票分析(一):常見的技術指標雪球
    黃金和保險
    和平時期買股票,戰亂期間買黃金,這個要看每個的理解了,我了解也不是很多;保險是我了解最少的一部分了,總感覺我們這么年輕考慮保險干啥呢,但是這種保險的意識也需要慢慢的養成。
    買房
    好多人評論說沒有提到買房,是因為對于這個買房還真不太懂,但我感覺拐點也快到了,一線城市的房價很難說,但是三四線投資房產的必要性真的不太大了,而且買房建議一定買核心地段或者位置環境偏好的小區,貴一點不要緊,方便以后轉手。
    資產配置
    我目前的資金配置是,寶寶類產品1/4、p2p投資1/4、a股+美股3/8、基金1/8;大家可以根據各自風險承受度來選擇自己的資產配比。
    最后
    理財是我們每一個人必須要關注的事情,那怕我們現在并沒有多少錢,經常看到一個廣告說,投資是你人生最后的一個事業,隨著年齡的增長投資理財對我們越來越重要。投資也是一件謹慎、嚴肅的事情畢竟關系著我們辛苦賺的血汗錢。
    最后的最后
    對于我們,投資自己更重要!
    投資有風險,理財需謹慎 !
    好多人私信想一起交流,我就建了一個QQ群,有興趣的入群交流:308719523 暗號:程序員

    AutoPoster 發表在 痞客邦 留言(0) 人氣()


    文章出處
    在上篇文章springboot(二):web綜合開發中簡單介紹了一下thymeleaf,這篇文章將更加全面詳細的介紹thymeleaf的使用。thymeleaf 是新一代的模板引擎,在spring4.0中推薦使用thymeleaf來做前端模版引擎。
    thymeleaf介紹
    簡單說, Thymeleaf 是一個跟 Velocity、FreeMarker 類似的模板引擎,它可以完全替代 JSP 。相較與其他的模板引擎,它有如下三個極吸引人的特點:
  • 1.Thymeleaf 在有網絡和無網絡的環境下皆可運行,即它可以讓美工在瀏覽器查看頁面的靜態效果,也可以讓程序員在服務器查看帶數據的動態頁面效果。這是由于它支持 html 原型,然后在 html 標簽里增加額外的屬性來達到模板+數據的展示方式。瀏覽器解釋 html 時會忽略未定義的標簽屬性,所以 thymeleaf 的模板可以靜態地運行;當有數據返回到頁面時,Thymeleaf 標簽會動態地替換掉靜態內容,使頁面動態顯示。


  • 2.Thymeleaf 開箱即用的特性。它提供標準和spring標準兩種方言,可以直接套用模板實現JSTL、 OGNL表達式效果,避免每天套模板、該jstl、改標簽的困擾。同時開發人員也可以擴展和創建自定義的方言。


  • 3.Thymeleaf 提供spring標準方言和一個與 SpringMVC 完美集成的可選模塊,可以快速的實現表單綁定、屬性編輯器、國際化等功能。


  • 標準表達式語法
    它們分為四類:
  • 1.變量表達式

  • 2.選擇或星號表達式

  • 3.文字國際化表達式

  • 4.URL表達式

  • 變量表達式
    變量表達式即OGNL表達式或Spring EL表達式(在Spring術語中也叫model attributes)。如下所示:
    ${session.user.name}
    它們將以HTML標簽的一個屬性來表示:
    <span th:text="${book.author.name}"> 
    <li th:each="book : ${books}">

    選擇(星號)表達式
    選擇表達式很像變量表達式,不過它們用一個預先選擇的對象來代替上下文變量容器(map)來執行,如下:
    *{customer.name}
    被指定的object由th:object屬性定義:
     <div th:object="${book}"> 
    ...
    <span th:text="*{title}">...</span>
    ...
    </div>

    文字國際化表達式
    文字國際化表達式允許我們從一個外部文件獲取區域文字信息(.properties),用Key索引Value,還可以提供一組參數(可選).
     #{main.title} 
    #{message.entrycreated(${entryId})}

    可以在模板文件中找到這樣的表達式代碼:
     <table> 
    ...
    <th th:text="#{header.address.city}">...</th>
    <th th:text="#{header.address.country}">...</th>
    ...
    </table>

    URL表達式
    URL表達式指的是把一個有用的上下文或回話信息添加到URL,這個過程經常被叫做URL重寫。
    @{/order/list}
    URL還可以設置參數:
    @{/order/details(id=${orderId})}
    相對路徑:
    @{../documents/report}
    讓我們看這些表達式:
     <form th:action="@{/createOrder}"> 
    <a href="main.html" th:href="@{/main}">

    變量表達式和星號表達有什么區別嗎?
    如果不考慮上下文的情況下,兩者沒有區別;星號語法評估在選定對象上表達,而不是整個上下文
    什么是選定對象?就是父標簽的值,如下:
     <div th:object="${session.user}">
    <p>Name: <span th:text="*{firstName}">Sebastian</span>.</p>
    <p>Surname: <span th:text="*{lastName}">Pepper</span>.</p>
    <p>Nationality: <span th:text="*{nationality}">Saturn</span>.</p>
    </div>

    這是完全等價于:
     <div th:object="${session.user}">
    <p>Name: <span th:text="${session.user.firstName}">Sebastian</span>.</p>
    <p>Surname: <span th:text="${session.user.lastName}">Pepper</span>.</p>
    <p>Nationality: <span th:text="${session.user.nationality}">Saturn</span>.</p>
    </div>

    當然,美元符號和星號語法可以混合使用:
     <div th:object="${session.user}">
    <p>Name: <span th:text="*{firstName}">Sebastian</span>.</p>
    <p>Surname: <span th:text="${session.user.lastName}">Pepper</span>.</p>
    <p>Nationality: <span th:text="*{nationality}">Saturn</span>.</p>
    </div>

    表達式支持的語法
    字面(Literals)
  • 文本文字(Text literals): 'one text', 'Another one!',…

  • 數字文本(Number literals): 0, 34, 3.0, 12.3,…

  • 布爾文本(Boolean literals): true, false

  • 空(Null literal): null

  • 文字標記(Literal tokens): one, sometext, main,…

  • 文本操作(Text operations)
  • 字符串連接(String concatenation): +

  • 文本替換(Literal substitutions): |The name is ${name}|

  • 算術運算(Arithmetic operations)
  • 二元運算符(Binary operators): +, -, *, /, %

  • 減號(單目運算符)Minus sign (unary operator): -

  • 布爾操作(Boolean operations)
  • 二元運算符(Binary operators):and, or

  • 布爾否定(一元運算符)Boolean negation (unary operator):!, not

  • 比較和等價(Comparisons and equality)
  • 比較(Comparators): >, <, >=, <= (gt, lt, ge, le)

  • 等值運算符(Equality operators):==, != (eq, ne)

  • 條件運算符(Conditional operators)
  • If-then: (if) ? (then)

  • If-then-else: (if) ? (then) : (else)

  • Default: (value) ?: (defaultvalue)

  • 所有這些特征可以被組合并嵌套:
    'User is of type ' + (${user.isAdmin()} ? 'Administrator' : (${user.type} ?: 'Unknown'))

    常用th標簽都有那些?


    關鍵字
    功能介紹
    案例




    th:id
    替換id
    <input th:id="'xxx' + ${collect.id}"/>


    th:text
    文本替換
    <p th:text="${collect.description}">description</p>


    th:utext
    支持html的文本替換
    <p th:utext="${htmlcontent}">conten</p>


    th:object
    替換對象
    <div th:object="${session.user}">


    th:value
    屬性賦值
    <input th:value="${user.name}" />


    th:with
    變量賦值運算
    <div th:with="isEven=${prodStat.count}%2==0"></div>


    th:style
    設置樣式
    th:style="'display:' + @{(${sitrue} ? 'none' : 'inline-block')} + ''"


    th:onclick
    點擊事件
    th:onclick="'getCollect()'"


    th:each
    屬性賦值
    tr th:each="user,userStat:${users}">


    th:if
    判斷條件
    <a th:if="${userId == collect.userId}" >


    th:unless
    和th:if判斷相反
    <a th:href="@{/login}" th:unless=${session.user != null}>Login</a>


    th:href
    鏈接地址
    <a th:href="@{/login}" th:unless=${session.user != null}>Login</a> />


    th:switch
    多路選擇 配合th:case 使用
    <div th:switch="${user.role}">


    th:case
    th:switch的一個分支
    <p th:case="'admin'">User is an administrator</p>


    th:fragment
    布局標簽,定義一個代碼片段,方便其它地方引用
    <div th:fragment="alert">


    th:include
    布局標簽,替換內容到引入的文件
    <head th:include="layout :: htmlhead" th:with="title='xx'"></head> />


    th:replace
    布局標簽,替換整個標簽到引入的文件
    <div th:replace="fragments/header :: title"></div>


    th:selected
    selected選擇框 選中
    th:selected="(${xxx.id} == ${configObj.dd})"


    th:src
    圖片類地址引入
    <img class="img-responsive" alt="App Logo" th:src="@{/img/logo.png}" />


    th:inline
    定義js腳本可以使用變量
    <script type="text/javascript" th:inline="javascript">


    th:action
    表單提交的地址
    <form action="subscribe.html" th:action="@{/subscribe}">


    th:remove
    刪除某個屬性
    <tr th:remove="all"> 1.all:刪除包含標簽和所有的孩子。2.body:不包含標記刪除,但刪除其所有的孩子。3.tag:包含標記的刪除,但不刪除它的孩子。4.all-but-first:刪除所有包含標簽的孩子,除了第一個。5.none:什么也不做。這個值是有用的動態評估。


    th:attr
    設置標簽屬性,多個屬性可以用逗號分隔
    比如 th:attr="src=@{/image/aa.jpg},title=#{logo}",此標簽不太優雅,一般用的比較少。


    還有非常多的標簽,這里只列出最常用的幾個,由于一個標簽內可以包含多個th:x屬性,其生效的優先級順序為:
    include,each,if/unless/switch/case,with,attr/attrprepend/attrappend,value/href,src ,etc,text/utext,fragment,remove。
    幾種常用的使用方法
    1、賦值、字符串拼接
     <p th:text="${collect.description}">description</p>
    <span th:text="'Welcome to our application, ' + ${user.name} + '!'">

    字符串拼接還有另外一種簡潔的寫法
    <span th:text="|Welcome to our application, ${user.name}!|">

    2、條件判斷 If/Unless
    Thymeleaf中使用th:if和th:unless屬性進行條件判斷,下面的例子中,<a>標簽只有在th:if中條件成立時才顯示:
    <a th:if="${myself=='yes'}" > </i> </a>
    <a th:unless=${session.user != null} th:href="@{/login}" >Login</a>

    th:unless于th:if恰好相反,只有表達式中的條件不成立,才會顯示其內容。
    也可以使用 (if) ? (then) : (else) 這種語法來判斷顯示的內容
    3、for 循環
     <tr th:each="collect,iterStat : ${collects}"> 
    <th scope="row" th:text="${collect.id}">1</th>
    <td >
    <img th:src="${collect.webLogo}"/>
    </td>
    <td th:text="${collect.url}">Mark</td>
    <td th:text="${collect.title}">Otto</td>
    <td th:text="${collect.description}">@mdo</td>
    <td th:text="${terStat.index}">index</td>
    </tr>

    iterStat稱作狀態變量,屬性有:
  • index:當前迭代對象的index(從0開始計算)

  • count: 當前迭代對象的index(從1開始計算)

  • size:被迭代對象的大小

  • current:當前迭代變量

  • even/odd:布爾值,當前循環是否是偶數/奇數(從0開始計算)

  • first:布爾值,當前循環是否是第一個

  • last:布爾值,當前循環是否是最后一個

  • 4、URL
    URL在Web應用模板中占據著十分重要的地位,需要特別注意的是Thymeleaf對于URL的處理是通過語法@{...}來處理的。
    如果需要Thymeleaf對URL進行渲染,那么務必使用th:href,th:src等屬性,下面是一個例子
    <!-- Will produce 'http://localhost:8080/standard/unread' (plus rewriting) -->
    <a th:href="@{/standard/{type}(type=${type})}">view</a>
    <!-- Will produce '/gtvg/order/3/details' (plus rewriting) -->
    <a href="details.html" th:href="@{/order/{orderId}/details(orderId=${o.id})}">view</a>

    設置背景
    <div th:style="'background:url(' + @{/<path-to-image>} + ');'"></div>

    根據屬性值改變背景
     <div class="media-object resource-card-image" th:style="'background:url(' + @{(${collect.webLogo}=='' ? 'img/favicon.png' : ${collect.webLogo})} + ')'" ></div>

    幾點說明:
  • 上例中URL最后的(orderId=${o.id}) 表示將括號內的內容作為URL參數處理,該語法避免使用字符串拼接,大大提高了可讀性

  • @{...}表達式中可以通過{orderId}訪問Context中的orderId變量

  • @{/order}是Context相關的相對路徑,在渲染時會自動添加上當前Web應用的Context名字,假設context名字為app,那么結果應該是/app/order

  • 5、內聯js
    內聯文本:[[...]]內聯文本的表示方式,使用時,必須先用th:inline="text/javascript/none"激活,th:inline可以在父級標簽內使用,甚至作為body的標簽。內聯文本盡管比th:text的代碼少,不利于原型顯示。
    <script th:inline="javascript">
    /*<![CDATA[*/
    ...
    var username = /*[[${sesion.user.name}]]*/ 'Sebastian';
    var size = /*[[${size}]]*/ 0;
    ...
    /*]]>*/
    </script>

    js附加代碼:
    /*[+
    var msg = 'This is a working application';
    +]*/

    js移除代碼:
    /*[- */
    var msg = 'This is a non-working template';
    /* -]*/

    6、內嵌變量
    為了模板更加易用,Thymeleaf還提供了一系列Utility對象(內置于Context中),可以通過#直接訪問:
  • dates : java.util.Date的功能方法類。

  • calendars : 類似#dates,面向java.util.Calendar

  • numbers : 格式化數字的功能方法類

  • strings : 字符串對象的功能類,contains,startWiths,prepending/appending等等。

  • objects: 對objects的功能類操作。

  • bools: 對布爾值求值的功能方法。

  • arrays:對數組的功能類方法。

  • lists: 對lists功能類方法

  • sets

  • maps
    ...

  • 下面用一段代碼來舉例一些常用的方法:
    dates
    /*
    * Format date with the specified pattern
    * Also works with arrays, lists or sets
    */
    ${#dates.format(date, 'dd/MMM/yyyy HH:mm')}
    ${#dates.arrayFormat(datesArray, 'dd/MMM/yyyy HH:mm')}
    ${#dates.listFormat(datesList, 'dd/MMM/yyyy HH:mm')}
    ${#dates.setFormat(datesSet, 'dd/MMM/yyyy HH:mm')}
    /*
    * Create a date (java.util.Date) object for the current date and time
    */
    ${#dates.createNow()}
    /*
    * Create a date (java.util.Date) object for the current date (time set to 00:00)
    */
    ${#dates.createToday()}

    strings
    /*
    * Check whether a String is empty (or null). Performs a trim() operation before check
    * Also works with arrays, lists or sets
    */
    ${#strings.isEmpty(name)}
    ${#strings.arrayIsEmpty(nameArr)}
    ${#strings.listIsEmpty(nameList)}
    ${#strings.setIsEmpty(nameSet)}
    /*
    * Check whether a String starts or ends with a fragment
    * Also works with arrays, lists or sets
    */
    ${#strings.startsWith(name,'Don')} // also array*, list* and set*
    ${#strings.endsWith(name,endingFragment)} // also array*, list* and set*
    /*
    * Compute length
    * Also works with arrays, lists or sets
    */
    ${#strings.length(str)}
    /*
    * Null-safe comparison and concatenation
    */
    ${#strings.equals(str)}
    ${#strings.equalsIgnoreCase(str)}
    ${#strings.concat(str)}
    ${#strings.concatReplaceNulls(str)}
    /*
    * Random
    */
    ${#strings.randomAlphanumeric(count)}

    使用thymeleaf布局
    使用thymeleaf布局非常的方便
    定義代碼片段
    <footer th:fragment="copy"> 
    &copy; 2016
    </footer>

    在頁面任何地方引入:
    <body> 
    <div th:include="footer :: copy"></div>
    <div th:replace="footer :: copy"></div>
    </body>

    th:include 和 th:replace區別,include只是加載,replace是替換
    返回的HTML如下:
    <body> 
    <div> &copy; 2016 </div>
    <footer>&copy; 2016 </footer>
    </body>

    下面是一個常用的后臺頁面布局,將整個頁面分為頭部,尾部、菜單欄、隱藏欄,點擊菜單只改變content區域的頁面
    <body class="layout-fixed">
    <div th:fragment="navbar" class="wrapper" role="navigation">
    <div th:replace="fragments/header :: header">Header</div>
    <div th:replace="fragments/left :: left">left</div>
    <div th:replace="fragments/sidebar :: sidebar">sidebar</div>
    <div layout:fragment="content" id="content" ></div>
    <div th:replace="fragments/footer :: footer">footer</div>
    </div>
    </body>

    任何頁面想使用這樣的布局值只需要替換中見的 content模塊即可
     <html xmlns:th="http://www.thymeleaf.org" layout:decorator="layout">
    <body>
    <section layout:fragment="content">
    ...

    也可以在引用模版的時候傳參
    <head th:include="layout :: htmlhead" th:with="title='Hello'"></head>

    layout 是文件地址,如果有文件夾可以這樣寫 fileName/layout:htmlhead
    htmlhead 是指定義的代碼片段 如 th:fragment="copy"
    源碼案例
    這里有一個開源項目幾乎使用了這里介紹的所有標簽和布局,大家可以參考:
    cloudfavorites
    參考
    thymeleaf官方指南
    新一代Java模板引擎Thymeleaf
    Thymeleaf基本知識
    thymeleaf總結文章
    Thymeleaf 模板的使用
    thymeleaf 學習筆記


    作者:純潔的微笑
    出處:http://www.ityouknow.com/
    版權所有,歡迎保留原文鏈接進行轉載:)

    AutoPoster 發表在 痞客邦 留言(0) 人氣()


    文章出處
    我們本身是一家互聯網金融公司,公司的主流業務就是p2p,因為各種原因吧,15年底啟動建設眾籌平臺。考慮到前期開發過程中的一些弊端和架構經驗,本次架構引用了dubbo做soa服務的治理,web容器nginx+tomcat,后端語言采用java,框架選擇spring+mybaits,前端模板引擎使用的是btl,app采用原生+h5的模式。這個架構可以參考我之前寫的文章從零到百億互聯網金融架構發展史中的第三代系統架構,之前的文章主要介紹了架構的變遷,本篇文章主要介紹在第三代平臺中遇到的問題以及解決方法。
    首先介紹一下眾籌系統的部署架構(如下圖),網站和app請求都是首先到最前端的nginx,如果只是靜態內容的訪問nginx直接處理后返回;動態請求分別轉發到后端的tomcat前端服務層,前端服務層只關注頁面端業務邏輯不涉及數據庫的操作,如果只是頁面框架渲染以及不涉及數據庫的請求,在前端服務層直接處理返回;如果涉及到數據庫操作或者核心業務邏輯,前端服務層通過dubbo調用后端的接入層服務或者核心層服務。
    上線在生產測試期間,發現tomcat過一段時間就會莫名奇妙的down掉,特別是后端的tomcat down掉的頻率比較高。后端的tomcat down掉之后對前端的頁面展示沒有影響,會影響后端的交易。
    jvm參數配置
    查看tomcat業務日志,報錯如下:
    2016-04-14 12:01:55,025 - org.jboss.netty.channel.DefaultChannelPipeline -59679839 [New I/O worker #29] WARN null - [DUBBO] An exception was thrown by a user handler while handling an exception event ([id: 0x5f980c11, /xxx:55386 => /xxx:6666] EXCEPTION: com.alibaba.dubbo.remoting.ExecutionException: class com.alibaba.dubbo.remoting.transport.dispatcher.all.AllChannelHandler error when process received event .), dubbo version: 2.8.4, current host: xxx
    com.alibaba.dubbo.remoting.ExecutionException: class com.alibaba.dubbo.remoting.transport.dispatcher.all.AllChannelHandler error when process caught event .
    at com.alibaba.dubbo.remoting.transport.dispatcher.all.AllChannelHandler.caught(AllChannelHandler.java:67)
    at com.alibaba.dubbo.remoting.transport.AbstractChannelHandlerDelegate.caught(AbstractChannelHandlerDelegate.java:44)
    at com.alibaba.dubbo.remoting.transport.AbstractChannelHandlerDelegate.caught(AbstractChannelHandlerDelegate.java:44)
    at com.alibaba.dubbo.remoting.transport.AbstractPeer.caught(AbstractPeer.java:127)
    at com.alibaba.dubbo.remoting.transport.netty.NettyHandler.exceptionCaught(NettyHandler.java:112)
    at com.alibaba.dubbo.remoting.transport.netty.NettyCodecAdapter$InternalDecoder.exceptionCaught(NettyCodecAdapter.java:165)
    at org.jboss.netty.channel.Channels.fireExceptionCaught(Channels.java:525)
    at org.jboss.netty.channel.AbstractChannelSink.exceptionCaught(AbstractChannelSink.java:48)
    at org.jboss.netty.channel.Channels.fireMessageReceived(Channels.java:296)
    at com.alibaba.dubbo.remoting.transport.netty.NettyCodecAdapter$InternalDecoder.messageReceived(NettyCodecAdapter.java:148)
    at org.jboss.netty.channel.Channels.fireMessageReceived(Channels.java:268)
    at org.jboss.netty.channel.Channels.fireMessageReceived(Channels.java:255)
    at org.jboss.netty.channel.socket.nio.NioWorker.read(NioWorker.java:88)
    at org.jboss.netty.channel.socket.nio.AbstractNioWorker.process(AbstractNioWorker.java:109)
    at org.jboss.netty.channel.socket.nio.AbstractNioSelector.run(AbstractNioSelector.java:312)
    at org.jboss.netty.channel.socket.nio.AbstractNioWorker.run(AbstractNioWorker.java:90)
    at org.jboss.netty.channel.socket.nio.NioWorker.run(NioWorker.java:178)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
    at java.lang.Thread.run(Thread.java:745)
    Caused by: java.lang.OutOfMemoryError: unable to create new native thread
    at java.lang.Thread.start0(Native Method)
    at java.lang.Thread.start(Thread.java:714)
    at java.util.concurrent.ThreadPoolExecutor.addWorker(ThreadPoolExecutor.java:949)
    at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1360)
    at com.alibaba.dubbo.remoting.transport.dispatcher.all.AllChannelHandler.caught(AllChannelHandler.java:65)
    ... 19 more

    查看output日志,發現其中有這么一句。
    SEVERE: The web application [/xxx] appears to have started a thread named [DubboResponseTimeoutScanTimer] but has failed to stop it. This is very likely to create a memory leak.
    根據日志提示貌似有內存泄露,以前確實還沒有碰到過這個錯誤,一片迷茫。重新啟動后,先用命令jstat -gc xxx 1000 30查看java 進程的gc情況,發現在30秒的世界內minor gc了n次,隨懷疑年輕代內存配置少了,查看個區域內存的配置參數如下:
    -Xms10g -Xmx10g -XX:PermSize=1g -XX:MaxPermSize=2g -Xshare:off -Xmn1024m
    按照年輕代為堆內存為百分之三的原則修改為-Xmn4g,重新啟動觀察之后mimor gc的頻率確實有所下降,測試大約過了3小時候之后又反饋tomcat down掉了,繼續分析啟動參數配置的時候發現了這么一句-XX:-+DisableExplicitGC,顯示的禁止了System.gc(),但是使用了java.nio的大量框架中使用System.gc()來執行gc期通過full gc來強迫已經無用的DirectByteBuffer對象釋放掉它們關聯的native memory,如果禁用會導致OOM,隨即懷疑是否是這個參數引發的問題,在啟動參數中去掉它。
    為了防止再次出現異常的時候能更加詳細的分析堆內存的使用情況,在啟動參數中添加了-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/local/logs/java/,當tomcat down的時候讓輸出堆內存文件,一邊也啟動jvisualvm工具來實時的監控內存各個線程的使用情況。
    數據庫連接池
    繼續使用壓測工具來壓測,在壓測的過程中發現名為com.mchange.v2.resourcepool.ssync.ThreadPoolAsynchronousRunner$PoolThred-#xxx的線程不斷的增長,并且后臺tomcat報錯如下:
    2016-04-13 16:55:15,175 - com.alibaba.dubbo.common.threadpool.support.AbortPolicyWithReport -83649035 [New I/O worker #27] WARN - [DUBBO] Thread pool is EXHAUSTED! Thread Name: DubboServerHandler-xxx:6666, Pool Size: 200 (active: 200, core: 200, max: 200, largest: 200), Task: 692 (completed: 492), Executor status:(isShutdown:false, isTerminated:false, isTerminating:false), in dubbo://xxx:6666!, dubbo version: 2.8.4, current host: xxx
    2016-04-13 16:55:15,176 - com.alibaba.dubbo.common.threadpool.support.AbortPolicyWithReport -83649036 [New I/O worker #27] WARN - [DUBBO] Thread pool is EXHAUSTED! Thread Name: DubboServerHandler-xxx:6666, Pool Size: 200 (active: 200, core: 200, max: 200, largest: 200), Task: 692 (completed: 492), Executor status:(isShutdown:false, isTerminated:false, isTerminating:false), in dubbo://xxx:6666!, dubbo version: 2.8.4, current host: xxx
    2016-04-13 16:55:15,177 - org.jboss.netty.channel.DefaultChannelPipeline -83649037 [New I/O worker #27] WARN - [DUBBO] An exception was thrown by a user handler while handling an exception event ([id: 0x2f345d45, /192.168.57.20:36475 => /xxx:6666] EXCEPTION: com.alibaba.dubbo.remoting.ExecutionException: class com.alibaba.dubbo.remoting.transport.dispatcher.all.AllChannelHandler error when process received event .), dubbo version: 2.8.4, current host: xxx
    com.alibaba.dubbo.remoting.ExecutionException: class com.alibaba.dubbo.remoting.transport.dispatcher.all.AllChannelHandler error when process caught event .
    at com.alibaba.dubbo.remoting.transport.dispatcher.all.AllChannelHandler.caught(AllChannelHandler.java:67)
    at com.alibaba.dubbo.remoting.transport.AbstractChannelHandlerDelegate.caught(AbstractChannelHandlerDelegate.java:44)
    at com.alibaba.dubbo.remoting.transport.AbstractChannelHandlerDelegate.caught(AbstractChannelHandlerDelegate.java:44)
    at com.alibaba.dubbo.remoting.transport.AbstractPeer.caught(AbstractPeer.java:127)
    at com.alibaba.dubbo.remoting.transport.netty.NettyHandler.exceptionCaught(NettyHandler.java:112)
    at com.alibaba.dubbo.remoting.transport.netty.NettyCodecAdapter$InternalDecoder.exceptionCaught(NettyCodecAdapter.java:165)
    at org.jboss.netty.channel.Channels.fireExceptionCaught(Channels.java:525)
    at org.jboss.netty.channel.AbstractChannelSink.exceptionCaught(AbstractChannelSink.java:48)
    at org.jboss.netty.channel.Channels.fireMessageReceived(Channels.java:296)
    at com.alibaba.dubbo.remoting.transport.netty.NettyCodecAdapter$InternalDecoder.messageReceived(NettyCodecAdapter.java:148)
    at org.jboss.netty.channel.Channels.fireMessageReceived(Channels.java:268)
    at org.jboss.netty.channel.Channels.fireMessageReceived(Channels.java:255)
    at org.jboss.netty.channel.socket.nio.NioWorker.read(NioWorker.java:88)
    at org.jboss.netty.channel.socket.nio.AbstractNioWorker.process(AbstractNioWorker.java:109)
    at org.jboss.netty.channel.socket.nio.AbstractNioSelector.run(AbstractNioSelector.java:312)
    at org.jboss.netty.channel.socket.nio.AbstractNioWorker.run(AbstractNioWorker.java:90)
    at org.jboss.netty.channel.socket.nio.NioWorker.run(NioWorker.java:178)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
    at java.lang.Thread.run(Thread.java:745)
    Caused by: java.util.concurrent.RejectedExecutionException: Thread pool is EXHAUSTED! Thread Name: DubboServerHandler-xxx:6666, Pool Size: 200 (active: 200, core: 200, max: 200, largest: 200), Task: 692 (completed: 492), Executor status:(isShutdown:false, isTerminated:false, isTerminating:false), in dubbo://xxx:6666!
    at com.alibaba.dubbo.common.threadpool.support.AbortPolicyWithReport.rejectedExecution(AbortPolicyWithReport.java:53)
    at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:821)
    at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1372)
    at com.alibaba.dubbo.remoting.transport.dispatcher.all.AllChannelHandler.caught(AllChannelHandler.java:65)
    ... 19 more

    根據這些信息隨懷疑數據庫連接池有問題,為了更好的監控連接池的使用,因此前期使用c3p0也會出現的一些問題,所以我們決定將數據庫連接池替換成druid,已經在別的項目中使用測試過,因此非常快速的更換投產。投產后繼續用壓測工具來測試,根據druid的后臺監控頁面發現(項目地址/druid/index.html),每次前端掉用一次數據庫連接就加一次,執行完成之后數據庫連接并沒有釋放。如下圖紅色區域,我們將數據庫連接池調整成1000,不一會就占滿了。
    根據這些信息判斷出,數據庫執行sql后肯定沒有釋放數據庫連接,導致數據庫連接池用滿后,后續的線程無法執行,檢查代碼之后發現果然有問題,請看下方代碼,我們最先使用的是SqlSessionFactory,如果使用SqlSessionFactory,在執行完sql后必須要執行session.close()來關閉連接,才會被連接池重新回收。
    public class SessionFactory {
    @Resource
    private SqlSessionFactory coreSqlSessionFactory;
    protected SqlSession getSession() {
    return coreSqlSessionFactory.openSession();
    }
    }

    public class BaseDao extends SessionFactory{
    public void add(Entity entity) {
    this.getSession().update(entity.getClass().getSimpleName()+"."+Thread.currentThread().getStackTrace()[2].getMethodName(), entity);
    }
    }

    但是使用SqlSessionTemplate卻不用手動執行代碼來關閉session,因此我們把上面SessionFactory類中的代碼改成SqlSessionTemplate(如下),此問題便解決了。
    public class SessionFactory {
    @Resource
    public SqlSessionTemplate coreSqlSession;
    protected SqlSessionTemplate getSession() {
    return coreSqlSession;
    }
    }

    詭異的腳本
    做完上面的優化之后,我們感覺問題應該解決了,但過了一段時間后tomcat又詭異的掛了,繼續分析gc情況,分階段使用jmap -dump:live,format=b,file=dump.hprof xxx命令生成堆轉儲快照來對比堆內存使用情況,監控線程使用情況,均發現沒有問題。這個問題困擾了我們好幾天,每天都監控這端口,一但發現tomcat down之后馬上通知運營人員重啟。一方面我們也查閱了各種資料,到網上查找各種tomcat自動down的原因,一一在我們服務器進行了測試、修復均不起作用。
    終于在google各種tomcat down原因的時候發現了這么一篇文章Tomcat進程意外退出的問題分析,立刻想起了我們最近使用的一個腳本來,因為我們的tomcat禁止了通過bat文件來關閉,因此為了啟動方便我們寫了一個腳本文件,方便通過腳本來啟動、停止、重啟tomcat文件,這是這個腳本導致tomcat down的原因,不不,不叫原因叫元兇!腳本內容如下:
    #!/bin/sh
    # eg: tomcat.sh start xxx
    #
    proc_dir="/usr/local/xxx/tomcat-zc-web/bin"
    proc_name=$2
    if [ x$proc_name != x ]
    then
    proc_dir=${proc_dir//xxx/$proc_name}
    fi
    #echo $proc_dir
    function stop () {
    kill -9 `ps -ef |grep $proc_dir |grep -v grep|awk '{print $2}'`
    }
    function start () {
    cd $proc_dir
    ./startup.sh
    tail -300f /usr/local/logs/tomcat-business/$proc_name.log
    }
    case $1 in
    start)
    start;;
    stop)
    stop;;
    restart)
    stop
    start;;
    esac

    就是因為tail -300f /usr/local/logs/tomcat-business/$proc_name.log這一句導致的問題,在別的項目使用的時候其實是沒有這一句的,一般在使用的步驟是:
  • 1 執行tomcat.sh start xxx啟動tomcat,

  • 2 執行tail -300f /usr/local/logs/tomcat-business/xxx.log 查看啟動日志是否成功。

  • 在這次投產的時候為了省一步操作,就將執行查看日志的命令,直接加在了啟動命令的后面,當執行tomcat.sh start xxx這個命令的時候,即啟動的tomcat,也自動會打印出tomcat的日志,那時候的想法非常好。
    原因是,使用腳本命令啟動后因為使用了tail -300f xxx 命令,tomcat的進程會成為shell腳本的子進程,這樣的話,如過shell腳本停止的話,系統會自動殺掉tomcat進程導致tomcat down掉,在我們的腳本中去掉這條命令tomcat就正常了,更深層次的原因參考Tomcat進程意外退出的問題分析這篇文章,文章的內容還是分析的比較透徹,最后感覺阿里的技術真的很牛X,這篇文章也是出自于阿里的員工。

    經歷這么些波折,后續的tomcat服務終于穩定了下來




    作者:純潔的微笑
    出處:http://www.ityouknow.com/
    版權歸作者所有,轉載請注明出處

    AutoPoster 發表在 痞客邦 留言(0) 人氣()

    list
    文章出處
    這兩年python特別火,火到博客園現在也是隔三差五的出現一些python的文章。各種開源軟件、各種爬蟲算法紛紛開路,作為互聯網行業的IT狗自然看的我也是心癢癢,于是趁著這個霧霾橫行的周末瞅了兩眼,作為一名老司機覺得還是應該以練帶學,1024在程序員界這么流行的網站,當然拿來先練一練。
    python自稱是以自然語言的視角來編程,特點是開發快,語言簡潔,沒那么多技巧,大名鼎鼎的豆瓣、youtube都是使用python開發的網站,看來python在大規模使用這個方面來講應該沒有啥子問題;python也不是沒有缺點在性能方面就Java、C++等老前輩還是沒得比的,另外python和nodejs一樣只能使用CPU單核,也是性能方面影響是因素之一。但python在特定領域表現突出,特別是腳本、爬蟲、科學算法等。

    好了,還是說正事如何爬取1024網站的圖片


    分析
    列表頁面
    首先進入1024的導航網站,隨便點擊一個地址進入選擇圖片區或者在網站地址后面添加thread0806.php?fid=16&search=&page=,這就是1024網站的圖片區,這個爬蟲就是主要抓取這個區域的所有圖片,使用瀏覽器debug分析一下這個頁面發現基本都是列表頁,格式如下:
    在地址欄http://xxxxxx.biz/thread0806.php?fid=16&search=&page=后面拼1、2、3等于就是訪問圖片區第一頁、第二頁、第三頁的列表頁。根據這些列表頁就可以爬出具體的每一個圖片頁的地址,類似上圖的地址:htm_data/16/1611/2114702.html 在地址的前面拼接上主站地址就是具體的圖片頁了。所以根據以上的分析:通過循環地址欄找到不同的列表頁在根據列表頁找到具體的圖片頁

    地址欄->圖片列表->圖片頁地址


    獲取列表頁圖片地址代碼如下:
    import urllib.request,socket,re,sys,os
    baseUrl='http://xxxx.biz/'
    def getContant(Weburl):
    Webheader= {'Upgrade-Insecure-Requests':'1',
    'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.103 Safari/537.36',}
    req = urllib.request.Request(url = Weburl,headers=Webheader)
    respose = urllib.request.urlopen(req)
    _contant = respose.read()
    respose.close()
    return str(_contant)
    def getUrl(URL):
    pageIndex = 1
    for i in range(1,int(pageIndex)+1):
    Weburl = URL + str(i)
    contant = getContant(Weburl)
    comp = re.compile(r'<a href="htm_data.{0,30}html" target="_blank" id=""><font color=g')
    urlList1 = comp.findall(contant)
    comp = re.compile(r'a href="(.*?)"')
    urlList2 = comp.findall(str(urlList1))
    urlList = []
    for url1 in urlList2:
    url2 = baseUrl+url1
    urlList.append(url2)
    return urlList
    URL = baseUrl+'thread0806.php?fid=16&search=&page='
    UrlList = getUrl(URL)
    print(UrlList)

    在這個地址后面拼接1到N就是不同的列表頁


    圖片頁面
    利用瀏覽器debug一下頁面,圖片基本上都是外鏈地址,以http或者https開頭以jpg、png、gif結尾,寫個正則表達式匹配這些地址,然后交給程序下載就OK了。
    頁面代碼如下:
    在下載過程中遇到了幾個問題,就是有的頁面會報403禁止訪問等,應該是網站加了一些防止爬蟲的手段,網上找了下加上header參數來模擬瀏覽器訪問就解決了;
    下載單個頁面代碼如下:
    import urllib.request,socket,re,sys,os
    #定義文件保存路徑
    targetPath = "D:\\temp\\1024\\1"
    def openUrl(url):
    headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) ' 'Chrome/51.0.2704.63 Safari/537.36'
    }
    req = urllib.request.Request(url=url, headers=headers)
    res = urllib.request.urlopen(req)
    data = res.read()
    downImg(data)
    def downImg(data):
    for link,t in set(re.findall(r'([http|https]:[^\s]*?(jpg|png|gif))', str(data))):
    if link.startswith('s'):
    link='http'+link
    else:
    link='htt'+link
    print(link)
    try:
    opener=urllib.request.build_opener()
    opener.addheaders=[('User-Agent','Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1941.0 Safari/537.36')]
    urllib.request.install_opener(opener)
    urllib.request.urlretrieve(link,saveFile(link))
    except:
    print('失敗')
    def saveFile(path):
    #檢測當前路徑的有效性
    if not os.path.isdir(targetPath):
    os.mkdir(targetPath)
    #設置每個圖片的路徑
    pos = path.rindex('/')
    t = os.path.join(targetPath,path[pos+1:])
    return t
    url = "http://xxxx.biz/htm_data/16/1611/2115193.html"
    openUrl(url)

    批量爬取
    批量爬取有兩個工作要做,第一for循環目標內的所有列表頁,第二為了避免重復爬取,需要給每個頁面建立唯一的文件夾,下次爬取的時候如果存在直接跳過。最后在理一下所有的爬取步驟:

    循環地址欄->找出圖片頁列表->圖片頁分析找出圖片地址->為圖片頁建立唯一的文件夾->開始下載頁面圖片


    完整的代碼如下:
    import urllib.request,socket,re,sys,os
    baseUrl='http://xxxx.biz/'
    targetPath = "D:\\temp\\1024\\"
    def getContant(Weburl):
    Webheader= {'Upgrade-Insecure-Requests':'1',
    'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.103 Safari/537.36',}
    req = urllib.request.Request(url = Weburl,headers=Webheader)
    respose = urllib.request.urlopen(req)
    _contant = respose.read()
    respose.close()
    return str(_contant)
    def getUrl(URL):
    pageIndex = 1
    for i in range(1,int(pageIndex)+1):
    Weburl = URL + str(i)
    contant = getContant(Weburl)
    comp = re.compile(r'<a href="htm_data.{0,30}html" target="_blank" id=""><font color=g')
    urlList1 = comp.findall(contant)
    comp = re.compile(r'a href="(.*?)"')
    urlList2 = comp.findall(str(urlList1))
    urlList = []
    for url1 in urlList2:
    url2 = baseUrl+url1
    urlList.append(url2)
    return urlList
    def openUrl(url):
    headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) ' 'Chrome/51.0.2704.63 Safari/537.36'
    }
    filePath=targetPath+url[-12:-5]
    #檢測當前路徑的有效性
    if not os.path.isdir(filePath):
    os.mkdir(filePath)
    req = urllib.request.Request(url=url, headers=headers)
    res = urllib.request.urlopen(req)
    data = res.read()
    downImg(data,filePath)
    else:
    print("已經下載過的地址跳過:"+url)
    print("filePath "+filePath)
    def downImg(data,filePath):
    for link,t in set(re.findall(r'([http|https]:[^\s]*?(jpg|png|gif))', str(data))):
    if link.startswith('s'):
    link='http'+link
    else:
    link='htt'+link
    print(link)
    try:
    opener=urllib.request.build_opener()
    opener.addheaders=[('User-Agent','Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1941.0 Safari/537.36')]
    urllib.request.install_opener(opener)
    urllib.request.urlretrieve(link,saveFile(link,filePath))
    except:
    print('失敗')
    def saveFile(path,filePath):
    #設置每個圖片的路徑
    pos = path.rindex('/')
    t = os.path.join(filePath,path[pos+1:])
    return t
    def openPage(UrlList):
    for pageUlr in UrlList:
    try:
    print('正在下載地址:'+pageUlr)
    openUrl(pageUlr)
    except:
    print('地址:'+pageUlr+'下載失敗')
    URL = baseUrl+'thread0806.php?fid=16&search=&page='
    for num in range(0,20):#0-20頁
    print("#######################################")
    print("##########總目錄下載地址###############")
    print(URL+str(num))
    print("#######################################")
    print("#######################################")
    UrlList = getUrl(URL+str(num))
    openPage(UrlList)

    最后的爬取結果:

    源代碼地址:python-crawler
    具體地址和源代碼在一起


    其它
    關于python2和python3的爭論,網站爭論比較大python3不兼容pyhton2,很多第三方的類庫暫時還沒有支持python3等等,但是對于我們新手來說,肯定是往前看果斷python3.
    代碼比較冗余幾個地方還沒有寫好,還在慢慢學習中,目前只是搞的可以跑起來。還有幾個問題沒有解決,下載一段時間后會莫名其妙的斷掉目前還么找到原因,后期看是否可以加上多線程來爬取可能會快一點,大家有什么更好的建議也可以提出來。
    參考:
    爬取豆瓣首頁圖片
    使用Python爬取1024上的圖片


    作者:純潔的微笑
    出處:http://www.ityouknow.com/
    版權所有,歡迎保留原文鏈接進行轉載:)

    AutoPoster 發表在 痞客邦 留言(0) 人氣()


    文章出處
    spring boot對常用的數據庫支持外,對nosql 數據庫也進行了封裝自動化。
    redis介紹
    Redis是目前業界使用最廣泛的內存數據存儲。相比memcached,Redis支持更豐富的數據結構,例如hashes, lists, sets等,同時支持數據持久化。除此之外,Redis還提供一些類數據庫的特性,比如事務,HA,主從庫。可以說Redis兼具了緩存系統和數據庫的一些特性,因此有著豐富的應用場景。本文介紹Redis在Spring Boot中兩個典型的應用場景。
    如何使用
    1、引入 spring-boot-starter-redis
    <dependency> 
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-redis</artifactId>
    </dependency>

    2、添加配置文件
    # REDIS (RedisProperties)
    # Redis數據庫索引(默認為0)
    spring.redis.database=0
    # Redis服務器地址
    spring.redis.host=192.168.0.58
    # Redis服務器連接端口
    spring.redis.port=6379
    # Redis服務器連接密碼(默認為空)
    spring.redis.password=
    # 連接池最大連接數(使用負值表示沒有限制)
    spring.redis.pool.max-active=8
    # 連接池最大阻塞等待時間(使用負值表示沒有限制)
    spring.redis.pool.max-wait=-1
    # 連接池中的最大空閑連接
    spring.redis.pool.max-idle=8
    # 連接池中的最小空閑連接
    spring.redis.pool.min-idle=0
    # 連接超時時間(毫秒)
    spring.redis.timeout=0

    3、添加cache的配置類
    @Configuration
    @EnableCaching
    public class RedisConfig extends CachingConfigurerSupport{
    @Bean
    public KeyGenerator keyGenerator() {
    return new KeyGenerator() {
    @Override
    public Object generate(Object target, Method method, Object... params) {
    StringBuilder sb = new StringBuilder();
    sb.append(target.getClass().getName());
    sb.append(method.getName());
    for (Object obj : params) {
    sb.append(obj.toString());
    }
    return sb.toString();
    }
    };
    }
    @SuppressWarnings("rawtypes")
    @Bean
    public CacheManager cacheManager(RedisTemplate redisTemplate) {
    RedisCacheManager rcm = new RedisCacheManager(redisTemplate);
    //設置緩存過期時間
    //rcm.setDefaultExpiration(60);//秒
    return rcm;
    }
    @Bean
    public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {
    StringRedisTemplate template = new StringRedisTemplate(factory);
    Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
    ObjectMapper om = new ObjectMapper();
    om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
    om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
    jackson2JsonRedisSerializer.setObjectMapper(om);
    template.setValueSerializer(jackson2JsonRedisSerializer);
    template.afterPropertiesSet();
    return template;
    }
    }

    3、好了,接下來就可以直接使用了
    @RunWith(SpringJUnit4ClassRunner.class)
    @SpringApplicationConfiguration(Application.class)
    public class TestRedis {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private RedisTemplate redisTemplate;
    @Test
    public void test() throws Exception {
    stringRedisTemplate.opsForValue().set("aaa", "111");
    Assert.assertEquals("111", stringRedisTemplate.opsForValue().get("aaa"));
    }
    @Test
    public void testObj() throws Exception {
    User user=new User("aa@126.com", "aa", "aa123456", "aa","123");
    ValueOperations<String, User> operations=redisTemplate.opsForValue();
    operations.set("com.neox", user);
    operations.set("com.neo.f", user,1,TimeUnit.SECONDS);
    Thread.sleep(1000);
    //redisTemplate.delete("com.neo.f");
    boolean exists=redisTemplate.hasKey("com.neo.f");
    if(exists){
    System.out.println("exists is true");
    }else{
    System.out.println("exists is false");
    }
    // Assert.assertEquals("aa", operations.get("com.neo.f").getUserName());
    }
    }

    以上都是手動使用的方式,如何在查找數據庫的時候自動使用緩存呢,看下面;
    4、自動根據方法生成緩存
    @RequestMapping("/getUser")
    @Cacheable(value="user-key")
    public User getUser() {
    User user=userRepository.findByUserName("aa");
    System.out.println("若下面沒出現“無緩存的時候調用”字樣且能打印出數據表示測試成功");
    return user;
    }

    其中value的值就是緩存到redis中的key
    共享Session-spring-session-data-redis
    分布式系統中,sessiong共享有很多的解決方案,其中托管到緩存中應該是最常用的方案之一,
    Spring Session官方說明
    Spring Session provides an API and implementations for managing a user’s session information.
    如何使用
    1、引入依賴
    <dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session-data-redis</artifactId>
    </dependency>

    2、Session配置:
    @Configuration
    @EnableRedisHttpSession(maxInactiveIntervalInSeconds = 86400*30)
    public class SessionConfig {
    }

    maxInactiveIntervalInSeconds: 設置Session失效時間,使用Redis Session之后,原Boot的server.session.timeout屬性不再生效


    好了,這樣就配置好了,我們來測試一下
    3、測試
    添加測試方法獲取sessionid
    @RequestMapping("/uid")
    String uid(HttpSession session) {
    UUID uid = (UUID) session.getAttribute("uid");
    if (uid == null) {
    uid = UUID.randomUUID();
    }
    session.setAttribute("uid", uid);
    return session.getId();
    }

    登錄redis 輸入 keys '*sessions*'
    t<spring:session:sessions:db031986-8ecc-48d6-b471-b137a3ed6bc4
    t(spring:session:expirations:1472976480000

    其中 1472976480000為失效時間,意思是這個時間后session失效,db031986-8ecc-48d6-b471-b137a3ed6bc4 為sessionId,登錄http://localhost:8080/uid 發現會一致,就說明session 已經在redis里面進行有效的管理了。
    如何在兩臺或者多臺中共享session
    其實就是按照上面的步驟在另一個項目中再次配置一次,啟動后自動就進行了session共享。
    示例代碼地址
    參考
    Redis的兩個典型應用場景
    pringBoot應用之分布式會話


    作者:純潔的微笑
    出處:www.ityouknow.com
    版權所有,歡迎保留原文鏈接進行轉載:)

    AutoPoster 發表在 痞客邦 留言(0) 人氣()


    文章出處
    近期文章
  • 百億互金平臺救火故事

  • jvm系列(八):jvm知識點總覽-高級Java工程師面試必備

  • jvm系列(七):jvm調優-工具篇

  • 實戰
  • 一次生產事故的優化經歷

  • 一次dns緩存引發的慘案

  • 一個腳本引發的血案

  • 百億互金平臺救火故事

  • 從零到百億互聯網金融架構發展史

  • 生活
  • 六年程序生涯

  • 程序員該用哪種姿勢來理財

  • 2016顛倒夢想,2017靜心前行

  • 發現另外一個世界-網盤關閉背后

  • spring boot
  • springboot(一):入門篇

  • springboot(二):web綜合開發

  • springboot(三):Spring boot中Redis的使用

  • springboot(四):thymeleaf使用詳解

  • springboot(五):spring data jpa的使用

  • springboot(六):如何優雅的使用mybatis

  • springboot(七):springboot+mybatis多數據源最簡解決方案

  • springboot(八):RabbitMQ詳解

  • springboot(九):定時任務

  • springboot實戰:我們的第一款開源軟件

  • JVM
  • jvm系列(一):java類的加載機制

  • jvm系列(二):JVM內存結構

  • jvm系列(三):GC算法 垃圾收集器

  • jvm系列(四):jvm調優-命令篇

  • jvm系列(五):tomcat性能調優和性能監控(visualvm)

  • jvm系列(六):jvm調優-從eclipse開始

  • jvm系列(七):jvm調優-工具篇

  • jvm系列(八):jvm知識點總覽-高級Java工程師面試必備

  • 爬蟲
  • python3爬取1024圖片

  • 網貸之家的爬蟲之旅

  • 抓取某一個網站整站的記錄

  • 運維
  • linux定時備份mysql并同步到其它服務器

  • Elasticsearch、Logstash、Kibana搭建統一日志分析平臺

  • 大數據實踐-數據同步篇tungsten-relicator(mysql->mongo)

  • 網站文件系統發展&&分布式文件系統fastDFS

  • spring
  • spring aop

  • spring ioc

  • spring 多數據源一致性事務方案

  • AutoPoster 發表在 痞客邦 留言(0) 人氣()


    文章出處
    16年的時候花了一些時間整理了一些關于jvm的介紹文章,到現在回顧起來還是一些還沒有補充全面,其中就包括如何利用工具來監控調優前后的性能變化。工具做為圖形化界面來展示更能直觀的發現問題,另一方面一些耗費性能的分析(dump文件分析)一般也不會在生產直接分析,往往dump下來的文件達1G左右,人工分析效率較低,因此利用工具來分析jvm相關問題,長長可以到達事半功倍的效果來。
    jvm監控分析工具一般分為兩類,一種是jdk自帶的工具,一種是第三方的分析工具。jdk自帶工具一般在jdk bin目錄下面,以exe的形式直接點擊就可以使用,其中包含分析工具已經很強大,幾乎涉及了方方面面,但是我們最常使用的只有兩款:jconsole.exe和jvisualvm.exe;第三方的分析工具有很多,各自的側重點不同,比較有代表性的:MAT(Memory Analyzer Tool)、GChisto等。
    對于大型 JAVA 應用程序來說,再精細的測試也難以堵住所有的漏洞,即便我們在測試階段進行了大量卓有成效的工作,很多問題還是會在生產環境下暴露出來,并且很難在測試環境中進行重現。JVM 能夠記錄下問題發生時系統的部分運行狀態,并將其存儲在堆轉儲 (Heap Dump) 文件中,從而為我們分析和診斷問題提供了重要的依據。其中VisualVM和MAT是dump文件的分析利器。
    jdk自帶的工具
    jconsole
    Jconsole(Java Monitoring and Management Console)是從java5開始,在JDK中自帶的java監控和管理控制臺,用于對JVM中內存,線程和類等的監控,是一個基于JMX(java management extensions)的GUI性能監測工具。jconsole使用jvm的擴展機制獲取并展示虛擬機中運行的應用程序的性能和資源消耗等信息。
    直接在jdk/bin目錄下點擊jconsole.exe即可啟動,界面如下:
    在彈出的框中可以選擇本機的監控本機的java應用,也可以選擇遠程的java服務來監控,如果監控遠程服務需要在tomcat啟動腳本中添加如下代碼:
     -Dcom.sun.management.jmxremote.port=6969 
    -Dcom.sun.management.jmxremote.ssl=false
    -Dcom.sun.management.jmxremote.authenticate=false

    連接進去之后,就可以看到jconsole概覽圖和主要的功能:概述、內存、線程、類、VM、MBeans
  • 概述,以圖表的方式顯示出堆內存使用量,活動線程數,已加載的類,CUP占用率的折線圖,可以非常清晰的觀察在程序執行過程中的變動情況。

  • 內存,主要展示了內存的使用情況,同時可以查看堆和非堆內存的變化值對比,也可以點擊執行GC來處罰GC的執行

  • 線程,主界面展示線程數的活動數和峰值,同時點擊左下方線程可以查看線程的詳細信息,比如線程的狀態是什么,堆棧內容等,同時也可以點擊“檢測死鎖”來檢查線程之間是否有死鎖的情況。

  • 類,主要展示已加載類的相關信息。

  • VM 概要,展示JVM所有信息總覽,包括基本信息、線程相關、堆相關、操作系統、VM參數等。

  • Mbean,查看Mbean的屬性,方法等。

  • VisualVM
    簡介
    VisualVM 是一個工具,它提供了一個可視界面,用于查看 Java 虛擬機 (Java Virtual Machine, JVM) 上運行的基于 Java 技術的應用程序(Java 應用程序)的詳細信息。VisualVM 對 Java Development Kit (JDK) 工具所檢索的 JVM 軟件相關數據進行組織,并通過一種使您可以快速查看有關多個 Java 應用程序的數據的方式提供該信息。您可以查看本地應用程序以及遠程主機上運行的應用程序的相關數據。此外,還可以捕獲有關 JVM 軟件實例的數據,并將該數據保存到本地系統,以供后期查看或與其他用戶共享。
    VisualVM 是javajdk自帶的最牛逼的調優工具了吧,也是我平時使用最多調優工具,幾乎涉及了jvm調優的方方面面。同樣是在jdk/bin目錄下面雙擊jvisualvm.exe既可使用,啟動起來后和jconsole 一樣同樣可以選擇本地和遠程,如果需要監控遠程同樣需要配置相關參數,主界面如下;
    VisualVM可以根據需要安裝不同的插件,每個插件的關注點都不同,有的主要監控GC,有的主要監控內存,有的監控線程等。
    如何安裝:

    1、從主菜單中選擇“工具”>“插件”。
    2、在“可用插件”標簽中,選中該插件的“安裝”復選框。單擊“安裝”。
    3、逐步完成插件安裝程序。


    我這里以 Eclipse(pid 22296)為例,雙擊后直接展開,主界面展示了系統和jvm兩大塊內容,點擊右下方jvm參數和系統屬性可以參考詳細的參數信息.
    因為VisualVM的插件太多,我這里主要介紹三個我主要使用幾個:監控、線程、Visual GC
    監控的主頁其實也就是,cpu、內存、類、線程的圖表
    線程和jconsole功能沒有太大的區別
    Visual GC 是常常使用的一個功能,可以明顯的看到年輕代、老年代的內存變化,以及gc頻率、gc的時間等。
    以上的功能其實jconsole幾乎也有,VisualVM更全面更直觀一些,另外VisualVM非常多的其它功能,可以分析dump的內存快照,dump出來的線程快照并且進行分析等,還有其它很多的插件大家可以去探索
    第三方調優工具
    MAT
    MAT是什么?
    MAT(Memory Analyzer Tool),一個基于Eclipse的內存分析工具,是一個快速、功能豐富的Java heap分析工具,它可以幫助我們查找內存泄漏和減少內存消耗。使用內存分析工具從眾多的對象中進行分析,快速的計算出在內存中對象的占用大小,看看是誰阻止了垃圾收集器的回收工作,并可以通過報表直觀的查看到可能造成這種結果的對象。
    通常內存泄露分析被認為是一件很有難度的工作,一般由團隊中的資深人士進行。不過要介紹的 MAT(Eclipse Memory Analyzer)被認為是一個“傻瓜式“的堆轉儲文件分析工具,你只需要輕輕點擊一下鼠標就可以生成一個專業的分析報告。和其他內存泄露分析工具相比,MAT 的使用非常容易,基本可以實現一鍵到位,即使是新手也能夠很快上手使用。
    MAT以eclipse 插件的形式來安裝,具體的安裝過程就不在描述了,可以利用visualvm或者是 jmap命令生產堆文件,導入eclipse mat中生成分析報告:
    生產這會報表的同時也會在dump文件的同級目錄下生成三份(dump_Top_Consumers.zip、dump_Leak_Suspects.zip、dump_Top_Components.zip)分析結果的html文件,方便發送給相關同事來查看。
    需要關注的是下面的Actions、Reports、Step by Step區域:
  • Histogram:列出內存中的對象,對象的個數以及大小,支持正則表達式查找,也可以計算出該類所有對象的retained size

  • Dominator Tree:列出最大的對象以及其依賴存活的Object (大小是以Retained Heap為標準排序的)

  • Top Consumers : 通過圖形列出最大的object

  • duplicate classes :檢測由多個類裝載器加載的類


  • Leak Suspects :內存泄漏分析


  • Top Components: 列出大于總堆數的百分之1的報表。

  • Component Report:分析對象屬于同一個包或者被同一個類加載器加載

  • 以上只是一個初級的介紹,mat還有更強大的使用,比如對比堆內存,在生產環境中往往為了定位問題,每隔幾分鐘dump出一下內存快照,隨后在對比不同時間的堆內存的變化來發現問題。
    GChisto
    GChisto是一款專業分析gc日志的工具,可以通過gc日志來分析:Minor GC、full gc的時間、頻率等等,通過列表、報表、圖表等不同的形式來反應gc的情況。雖然界面略顯粗糙,但是功能還是不錯的。
    配置好本地的jdk環境之后,雙擊GChisto.jar,在彈出的輸入框中點擊 add 選擇gc.log日志
  • GC Pause Stats:可以查看GC 的次數、GC的時間、GC的開銷、最大GC時間和最小GC時間等,以及相應的柱狀圖

  • GC Pause Distribution:查看GC停頓的詳細分布,x軸表示垃圾收集停頓時間,y軸表示是停頓次數。


  • GC Timeline:顯示整個時間線上的垃圾收集


  • 不過這款工具已經不再維護,不能識別最新jdk的日志文件。
    gcviewer
    GCViewer也是一款分析小工具,用于可視化查看由Sun / Oracle, IBM, HP 和 BEA Java 虛擬機產生的垃圾收集器的日志,gcviewer個人感覺顯示 的界面比較亂沒有GChisto更專業一些。
    以上的兩款gc分析日志,一個不太維護了,一個不太專業,求推薦更好的gc分析工具
    前期jvm系類文章回顧:
  • jvm系列(一):java類的加載機制

  • jvm系列(二):JVM內存結構

  • jvm系列(三):GC算法 垃圾收集器

  • jvm系列(四):jvm調優-命令篇

  • jvm系列(五):tomcat性能調優和性能監控(visualvm)

  • jvm系列(六):jvm調優-從eclipse開始



  • 作者:純潔的微笑
    出處:http://www.ityouknow.com/
    版權歸作者所有,轉載請注明出處

    AutoPoster 發表在 痞客邦 留言(0) 人氣()


    文章出處



    這兩天啟動了一個新項目因為項目組成員一直都使用的是mybatis,雖然個人比較喜歡jpa這種極簡的模式,但是為了項目保持統一性技術選型還是定了 mybatis。到網上找了一下關于spring boot和mybatis組合的相關資料,各種各樣的形式都有,看的人心累,結合了mybatis的官方demo和文檔終于找到了最簡的兩種模式,花了一天時間總結后分享出來。


    orm框架的本質是簡化編程中操作數據庫的編碼,發展到現在基本上就剩兩家了,一個是宣稱可以不用寫一句SQL的hibernate,一個是可以靈活調試動態sql的mybatis,兩者各有特點,在企業級系統開發中可以根據需求靈活使用。發現一個有趣的現象:傳統企業大都喜歡使用hibernate,互聯網行業通常使用mybatis。


    hibernate特點就是所有的sql都用Java代碼來生成,不用跳出程序去寫(看)sql,有著編程的完整性,發展到最頂端就是spring data jpa這種模式了,基本上根據方法名就可以生成對應的sql了,有不太了解的可以看我的上篇文章springboot(五):spring data jpa的使用


    mybatis初期使用比較麻煩,需要各種配置文件、實體類、dao層映射關聯、還有一大推其它配置。當然mybatis也發現了這種弊端,初期開發了generator可以根據表結果自動生產實體類、配置文件和dao層代碼,可以減輕一部分開發量;后期也進行了大量的優化可以使用注解了,自動管理dao層和配置文件等,發展到最頂端就是今天要講的這種模式了,mybatis-spring-boot-starter就是springboot+mybatis可以完全注解不用配置文件,也可以簡單配置輕松上手。



    現在想想spring boot 就是牛逼呀,任何東西只要關聯到spring boot都是化繁為簡。



    mybatis-spring-boot-starter


    官方說明:MyBatis Spring-Boot-Starter will help you use MyBatis with Spring Boot
    其實就是myBatis看spring boot這么火熱也開發出一套解決方案來湊湊熱鬧,但這一湊確實解決了很多問題,使用起來確實順暢了許多。mybatis-spring-boot-starter主要有兩種解決方案,一種是使用注解解決一切問題,一種是簡化后的老傳統。


    當然任何模式都需要首先引入mybatis-spring-boot-starter的pom文件,現在最新版本是1.1.1(剛好快到雙11了 :)



    <dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>1.1.1</version>
    </dependency>


    好了下來分別介紹兩種開發模式


    無配置文件注解版


    就是一切使用注解搞定。


    1 添加相關maven文件



    <dependencies>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>1.1.1</version>
    </dependency>
    <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <optional>true</optional>
    </dependency>
    </dependencies>


    完整的pom包這里就不貼了,大家直接看源碼


    2、application.properties 添加相關配置



    mybatis.type-aliases-package=com.neo.entity
    spring.datasource.driverClassName = com.mysql.jdbc.Driver
    spring.datasource.url = jdbc:mysql://localhost:3306/test1?useUnicode=true&characterEncoding=utf-8
    spring.datasource.username = root
    spring.datasource.password = root


    springboot會自動加載spring.datasource.*相關配置,數據源就會自動注入到sqlSessionFactory中,sqlSessionFactory會自動注入到Mapper中,對了你一切都不用管了,直接拿起來使用就行了。


    在啟動類中添加對mapper包掃描@MapperScan



    @SpringBootApplication
    @MapperScan("com.neo.mapper")
    public class Application {
    public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
    }
    }


    或者直接在Mapper類上面添加注解@Mapper,建議使用上面那種,不然每個mapper加個注解也挺麻煩的


    3、開發Mapper


    第三步是最關鍵的一塊,sql生產都在這里



    public interface UserMapper {
    @Select("SELECT * FROM users")
    @Results({
    @Result(property = "userSex", column = "user_sex", javaType = UserSexEnum.class),
    @Result(property = "nickName", column = "nick_name")
    })
    List<UserEntity> getAll();
    @Select("SELECT * FROM users WHERE id = #{id}")
    @Results({
    @Result(property = "userSex", column = "user_sex", javaType = UserSexEnum.class),
    @Result(property = "nickName", column = "nick_name")
    })
    UserEntity getOne(Long id);
    @Insert("INSERT INTO users(userName,passWord,user_sex) VALUES(#{userName}, #{passWord}, #{userSex})")
    void insert(UserEntity user);
    @Update("UPDATE users SET userName=#{userName},nick_name=#{nickName} WHERE id =#{id}")
    void update(UserEntity user);
    @Delete("DELETE FROM users WHERE id =#{id}")
    void delete(Long id);
    }


    為了更接近生產我特地將user_sex、nick_name兩個屬性在數據庫加了下劃線和實體類屬性名不一致,另外user_sex使用了枚舉




    • @Select 是查詢類的注解,所有的查詢均使用這個

    • @Result 修飾返回的結果集,關聯實體類屬性和數據庫字段一一對應,如果實體類屬性和數據庫屬性名保持一致,就不需要這個屬性來修飾。

    • @Insert 插入數據庫使用,直接傳入實體類會自動解析屬性到對應的值

    • @Update 負責修改,也可以直接傳入對象

    • @delete 負責刪除



    了解更多屬性參考這里



    注意,使用#符號和$符號的不同:




    // This example creates a prepared statement, something like select * from teacher where name = ?;
    @Select("Select * from teacher where name = #{name}")
    Teacher selectTeachForGivenName(@Param("name") String name);
    // This example creates n inlined statement, something like select * from teacher where name = 'someName';
    @Select("Select * from teacher where name = '${name}'")
    Teacher selectTeachForGivenName(@Param("name") String name);


    4、使用


    上面三步就基本完成了相關dao層開發,使用的時候當作普通的類注入進入就可以了



    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class UserMapperTest {
    @Autowired
    private UserMapper UserMapper;
    @Test
    public void testInsert() throws Exception {
    UserMapper.insert(new UserEntity("aa", "a123456", UserSexEnum.MAN));
    UserMapper.insert(new UserEntity("bb", "b123456", UserSexEnum.WOMAN));
    UserMapper.insert(new UserEntity("cc", "b123456", UserSexEnum.WOMAN));
    Assert.assertEquals(3, UserMapper.getAll().size());
    }
    @Test
    public void testQuery() throws Exception {
    List<UserEntity> users = UserMapper.getAll();
    System.out.println(users.toString());
    }
    @Test
    public void testUpdate() throws Exception {
    UserEntity user = UserMapper.getOne(3l);
    System.out.println(user.toString());
    user.setNickName("neo");
    UserMapper.update(user);
    Assert.assertTrue(("neo".equals(UserMapper.getOne(3l).getNickName())));
    }
    }


    源碼中controler層有完整的增刪改查,這里就不貼了
    源碼在這里spring-boot-mybatis-annotation


    極簡xml版本


    極簡xml版本保持映射文件的老傳統,優化主要體現在不需要實現dao的是實現層,系統會自動根據方法名在映射文件中找對應的sql.


    1、配置


    pom文件和上個版本一樣,只是application.properties新增以下配置



    mybatis.config-locations=classpath:mybatis/mybatis-config.xml
    mybatis.mapper-locations=classpath:mybatis/mapper/*.xml


    指定了mybatis基礎配置文件和實體類映射文件的地址


    mybatis-config.xml 配置



    <configuration>
    <typeAliases>
    <typeAlias alias="Integer" type="java.lang.Integer" />
    <typeAlias alias="Long" type="java.lang.Long" />
    <typeAlias alias="HashMap" type="java.util.HashMap" />
    <typeAlias alias="LinkedHashMap" type="java.util.LinkedHashMap" />
    <typeAlias alias="ArrayList" type="java.util.ArrayList" />
    <typeAlias alias="LinkedList" type="java.util.LinkedList" />
    </typeAliases>
    </configuration>


    這里也可以添加一些mybatis基礎的配置


    2、添加User的映射文件



    <mapper namespace="com.neo.mapper.UserMapper" >
    <resultMap id="BaseResultMap" type="com.neo.entity.UserEntity" >
    <id column="id" property="id" jdbcType="BIGINT" />
    <result column="userName" property="userName" jdbcType="VARCHAR" />
    <result column="passWord" property="passWord" jdbcType="VARCHAR" />
    <result column="user_sex" property="userSex" javaType="com.neo.enums.UserSexEnum"/>
    <result column="nick_name" property="nickName" jdbcType="VARCHAR" />
    </resultMap>
    <sql id="Base_Column_List" >
    id, userName, passWord, user_sex, nick_name
    </sql>
    <select id="getAll" resultMap="BaseResultMap" >
    SELECT
    <include refid="Base_Column_List" />
    FROM users
    </select>
    <select id="getOne" parameterType="java.lang.Long" resultMap="BaseResultMap" >
    SELECT
    <include refid="Base_Column_List" />
    FROM users
    WHERE id = #{id}
    </select>
    <insert id="insert" parameterType="com.neo.entity.UserEntity" >
    INSERT INTO
    users
    (userName,passWord,user_sex)
    VALUES
    (#{userName}, #{passWord}, #{userSex})
    </insert>
    <update id="update" parameterType="com.neo.entity.UserEntity" >
    UPDATE
    users
    SET
    <if test="userName != null">userName = #{userName},</if>
    <if test="passWord != null">passWord = #{passWord},</if>
    nick_name = #{nickName}
    WHERE
    id = #{id}
    </update>
    <delete id="delete" parameterType="java.lang.Long" >
    DELETE FROM
    users
    WHERE
    id =#{id}
    </delete>
    </mapper>


    其實就是把上個版本中mapper的sql搬到了這里的xml中了


    3、編寫Dao層的代碼



    public interface UserMapper {
    List<UserEntity> getAll();
    UserEntity getOne(Long id);
    void insert(UserEntity user);
    void update(UserEntity user);
    void delete(Long id);
    }


    對比上一步這里全部只剩了接口方法


    4、使用


    使用和上個版本沒有任何區別,大家就看代碼吧


    xml配置版本


    如何選擇


    兩種模式各有特點,注解版適合簡單快速的模式,其實像現在流行的這種微服務模式,一個微服務就會對應一個自已的數據庫,多表連接查詢的需求會大大的降低,會越來越適合這種模式。


    老傳統模式比適合大型項目,可以靈活的動態生成SQL,方便調整SQL,也有痛痛快快,洋洋灑灑的寫SQL的感覺。


    完整代碼地址




    作者:純潔的微笑
    出處:http://www.ityouknow.com/
    版權所有,歡迎保留原文鏈接進行轉載:)



    AutoPoster 發表在 痞客邦 留言(0) 人氣()


    文章出處
    多年前,又是周六客服打電話過來,平臺官網不能訪問,app完全無法打開,客戶在QQ群和微信群中各種反饋,說平臺是不是跑路了?客服的多條400熱線完全被打爆,電話已經接不過來...
    前言
    一直以來總是想以什么方式去記錄下自己在互金行業的這段經歷,趁著自己還記得清楚,還能找到一些資料原型,一方面可以分享出來供大家參考,但是更重要就是多年以后我可以根據這些文章回憶起來自己的那段激情歲月。
    想了很久但一直沒有實施,后來覺得應該從架構的角度來梳理一篇文章,就寫了從零到百億互聯網金融架構發展史這篇文章;最后認為只有實戰出來的東西以及解決問題的過程,才是工作中最寶貴的經驗,應該把它分享出來,在梳理的過程中覺得有三起事故比較有代表性就整理出了下面這三篇文章,本篇文章從整體來回憶一下一路走過來所經歷過的救火故事。
  • 一次生產事故的優化經歷

  • 一次dns緩存引發的慘案

  • 一個腳本引發的血案

  • 作為一個互聯網金融平臺,涉及到用戶資金,任何的服務(資金)差錯用戶都是不可容忍的,用戶不懂什么是數據庫,不知道什么網絡不通,就是一會看不到錢在app里面展示都會覺得不安。在已經有很多P2P公司跑路的前提下,用戶個個已經被鍛煉成為福爾摩斯偵探,每天打開app查看收益,監控著平臺一切,甚至半夜升級斷網十分鐘,也會被用戶察覺,直接就發到群里面,更有甚者直接在QQ群或者微信群中你們的技術行不行!
    我們常說的互聯網工作經驗,一方面是開發經驗,但其實更重要的是處理問題的能力。那么處理問題的能力怎么來呢,就是不斷的去解決問題,不斷的去總結經驗,其中處理生產環境中問題的經驗更甚,因為在處理生產環境中對個人的壓力和臨危應變的能力要求最高,你不但需要面臨千萬個用戶反饋,客服不時得催促而且旁邊可能就站了N個領導在看著你,一副你行不行的樣子要求立刻馬上解決問題!這個時候你的操作就非常重要,稍有不慎便會引發二次生產事故。
    說了這么多,只是想說明,生產事故對技術綜合能力要求頗高,更是鍛煉處理問題能力最佳時機!下面給大家介紹我們從零開發到現在百億交易量所遇到的幾次關鍵事故,有大有小挑出一些比較有代表性的事件來分享。
    并發滿標
    公司系統剛上線的時候,其實沒有經歷過什么大量用戶并發的考驗,結果公司做了一個大的推廣,涌入了一批用戶來搶標,共1000萬的標的幾乎都在10秒之內搞定,大概會有上萬左右的用戶會同時去搶標,平均每秒大概有千人左右的并發,滿標控制這塊沒有經過大的并發測試,上來之后就被打垮了,導致得結果是什么呢,1000萬的標的,有可能到一千零幾萬滿標,也有可能會九百多萬就滿標,也就說要不就是多了一些,要不就是少了一些,就滿標了。
    這就會很尷尬,因為借款用戶就借款一千萬整,那么多出來的錢既不能給用戶退回了,因為用戶好不容易才搶上了,無端退了用戶也鬧;少了也是問題,用戶借款一千萬,少了幾十萬也不行,如果短得少了可以想辦法找一些有錢的客戶直接給買了,多了就必須重新放出來讓用戶投資,非常影響士氣,這個問題困擾了我們有一段時間。
    購買標的流程圖,不知道大家是否能根據此圖發現問題呢?
    超募
    為何會產生超募?在最早前的版本中沒有使用樂觀鎖來控制,如果在最后購買的用戶一單出現并發,就會出現超募,比如最后剩余30000份的購買份額,因為并發量特別大,可能同時會有十幾個用戶拿到了剩余30000份余額的可購買額度,有的買1000份、有的買上3000份、有的買上20000份都會驅動滿標,所以最后導致了超募。
    針對這個問題,主要是引入了memcached樂觀鎖的概念(底層主要是casgets兩個命令),在發標的時候存入標的總份額,當用戶購買的時候首先去鎖定用戶購買的份額,因為樂觀鎖的原因,如果同時有兩個用戶拿到份額的時候保證只有一個最后可以更新成功(鎖定份額),(鎖定份額)失敗直接返回,這樣就保證了在入口的時候就直接屏蔽了部分并發的請求。
    少募
    為何產生少募?少募是可能1000萬的標的突然到980萬就給滿標了,這是因為在超募情況下我們完善了代碼,用戶一進來首先就是鎖定購買份額,只有鎖定購買份額才能進行下面的流程,如果鎖定購買份額失敗直接返回,這樣雖然保證了在1000萬份額在購買初期必須每一個用戶只能鎖定一份,但是在高并發的情況下,因為購買流程中有十幾個分支,每一個分支失敗就會退回鎖定的份額,這樣就會導致這樣的現象,就是可能是并發一上來,馬上就滿標了,過了一會進度就回退回來了。
    少募主要是因為分支失敗回退導致的,一方面我們分析了容易導致回退熱點,因為在用戶搶標的時候會給用戶實時的展示標的進度,在很早的版本中直接就是存入到一個標的進度表里面,并且采用了樂觀鎖,如果并發一高就頻繁的更新失敗導致回退,因此優化了標的進度這塊,直接去掉了標的進度表,實時根據查詢來展示標的進度(可以有延遲,有緩存);另一方面在回退份額的時候在次判斷試下memcached的份額和標的的狀態,如果份額不為零并且標的狀態是滿標,馬上自動更新狀態保證后續用戶可以立即購買再次驅動滿標。
    做了以上的兩種優化后,我們還遇到了其它的一些小問題,在不斷的優化過程中,終于穩定下來;在后期版本中將考慮使用MQ隊列或者redis隊列來處理搶標更合理對用戶也更公平一些。
    黑客攻擊
    2015年應該是互聯網行業受黑客攻擊最多的一年吧,各互金公司都深受其害,其中我就記得網貸之家有一段時間被黑客攻擊的太厲害,連續幾天網站都無法打開。當然了我們也未能幸免,什么DDOS攻擊、SQL注入、尋找系統漏洞等都幾乎都經歷過了,有的黑客還比較好,應該是出于善意或者展示自己,將漏洞放到烏云上面或者漏洞盒子里面讓廠商來修復。但更多的是一些黑產完全就是威脅、敲詐想撈一筆錢,先看看下面這位吧:
    這個家伙潛伏到我們公司的客戶群里面,冒充我們的客戶代表將頭像和資料替換成一樣,然后給群里所有的客服讓他們發送我們內部的后臺地址,想通過這種方式來尋找突破口,當然了這個算是里面的小菜鳥吧。
    DDOS攻擊
    DDOS攻擊我們也是遇到了很多次,確實也沒有比較好辦法,最后都是通過一些笨辦法來盡量的避免,先說說我們的經歷吧。有一次我正在敲代碼,客服QQ又閃爍了起來,還沒來得及打開查看信息,客服的經理電話就直接打了過來,我立刻就有一種不祥的預感,說官網打不開了,后臺也登錄不了。
    掛了電話,我在本機進行了測試果然不行,立刻準備登錄VPN查看服務器各項指標,結果登錄不上去,馬上上樓找運維經理,他也登錄不上,剛準備給機房打電話的時候,機房來電話了,說我們的一個IP正經歷著1G多的流量訪問,問我們是否正在做什么活動,剛話沒有說完就說流量已經到5G,不到一分鐘之后流量已經到達18G之多。因為我們的機房和集團公用了一個寬帶入口,結果陸續的集團上面反饋他們的網站、服務也都出現了問題,機房方面害怕引起更大的沖擊,直接把我們官網對外的IP封掉,集團的其它業務也才慢慢都恢復了過來,我們也緊急的更換了外網IP,重新切換了域名解析才恢復。
    事后我們根據apache分析了日志,流量來自N多個不同的IP地址根本無法應對,也正式因為這次攻擊也才讓我們領導重視了起來,將我們公司的機房網絡層和公司集團徹底分離,這樣的話不管那方受到大流量攻擊都不會相互影響,我們也想了一些笨辦法,因為上次我們更換了外網IP之后攻擊也就停止了,那么我們認為肯定是針對我們外網來攻擊的,所有我們就多準備了6個外網IP,當監控到對某一個外網進行攻擊的時候馬上切換到另一個外網地址,就這樣跟他們玩,可以起到非常有限的一點作用,如果黑客真的想跟我們玩,這個辦法就像是小孩子捉迷藏。
    周年慶的DDOS攻擊
    還有一次我們正在做周年慶活動,突然有人在QQ群里面給我們客服說了一句,叫你們的技術負責人來找我,然后我們的網站就掛了,我還保留了當時的一個截圖如下:
    完了之后客服就來找我,然后按照往常的策略處理完之后,我根據客服給我的QQ號碼加上了那個人,開口就來嚇我,我依稀記當年的對話如下:

    黑客:你是平臺的技術負責人嗎?
    我:算是吧
    黑客:你信不信我可以讓你們官網在5秒之內掛掉?
    我:...(沉默,還真害怕又把官網搞掛了)
    黑客:你們的官網漏洞很大
    我:如果有好的建議請您賜教
    黑客:你們的服務器是不是什么防護軟件都沒有裝?
    我:...(繼續沉默,這會在想不會是那個安全廠商來推廣產品的吧,當然我們基礎的防護肯定有)
    黑客:我們有非常多的肉雞,想攻擊誰,幾秒之內肯定搞定
    我:...
    黑客:我們已經給很多互聯網金融行業做了滲透測試,花點錢幫買你們平安,保證以后不會在出事情
    我:...
    黑客:免費的策略也有很多,比如360、百度云的安全產品可以免費低檔10G左右的流量


    ......(中間省略)


    黑客:我說了這多,你們也是不是給包煙錢,表示表示。


    ......


    后來也和領導進行了商議,堅決不能給他們錢,不能助漲這種囂張氣焰,實在不行就報警!
    曝光一下當年使用的假QQ號,剛查了下變了個頭像和描述,如下:
    后來我一直在想為什么DDOS攻擊總是喜歡根據外網IP來攻擊呢,慢慢好像是理解了如果針對域名來攻擊的話,那不就是攻擊到域名商的服務器了嗎,一般域名商比較強大,黑客不太搞的定,也確實沒有必要。當然記的前一段時間,某著名域名服務商被攻擊,導致國外twitter等著名的互聯網公司訪問不斷到達半天以上,還是很嚴重的。但是對于我們這些小公司,倒不至于搞這么大的動作。
    到底如何正確的防止DDOS攻擊:

    • 第一種方案,隱藏服務器外網地址,服務器前端加CDN中轉,免費的有百度云加速、360網站衛士、加速樂、安全寶等,如果資金充裕的話,可以購買高防的盾機,用于隱藏服務器真實IP,域名解析使用CDN的IP,所有解析的子域名都使用CDN的IP地址。此外,服務器上部署的其他域名也不能使用真實IP解析,全部都使用CDN來解析。



    • 第二種方案,買一些安全產品來進行流量清洗,主要是阿里云、騰訊云這種大廠商提供的一種服務。



    • 第三種方案,有很多的防火墻產品聲稱可以防止Ddos攻擊,但是我個人使用感覺效果非常有限。


    SQL注入
    我們的官網使用的是PHP開發,因為框架比較老舊的原因,存在著一些SQL注入的點,我們發現了一些進行了修補,沒想到還是被一些黑客找到了突破點,這塊還是比較感謝這些黑客的在漏洞盒子上面提交了bug(如下圖),最后我們根據提示進行了緊急修復,后來我們也在WAF防火墻配置了一些攔截SQL注入的策略,起到雙保險的作用。
    我一直在想為什么PHP一般比較容易出現SQL注入呢,而Java較少的暴漏出來SQL漏洞的情況,我估摸著有兩方面的原因:第一,PHP一般會在前端使用的較多,受攻擊的機會更多一些,Java一般做為后端服務攻擊的可能性會比較少;第二,PHP框架較多而且很多早期的框架并沒有特別考慮SQL注入的情況,Java大量普及了mybaits\hibernate這種orm框架,框架本身對常見的SQL注入有防止的功能,但不是說mybaits/hibernate框架就沒有被sql注入的可能,大部分場景下是OK的。另外參數化查詢可以有效的避免SQL注入。
    通過一段時間的學習,我發黑客一般先使用工具對網站做整體的掃描類似Acunetix,在根據掃描出來的漏洞做個大概的分析,但是比較深入的漏洞都需要根據網站的業務在進行調整,比如sql注入會根據頁面的查詢使用sqlmap等工具來進一步的滲透。當然我對這方面還是外行,描述的可能不夠清晰。
    其它攻擊
    其它方面的攻擊,主要是在業務方面,比如我們當初有一個很小的失誤,有一個程序員在H5的小網頁中將發送短信驗證碼返回了前端,最后被haker發現了,利用這個漏洞可以給任意的用戶重置登錄密碼;短信攻擊,現在的網站幾乎都有發送短信或者短信驗證碼的功能,如果前端不做校驗,haker會隨便寫一個for循環來發短信,一般系統的短信會進行全方位的防控,比如:1、前端加驗證(字符驗證碼,有的是拖拽的動畫);2、后端根據用戶或者IP加限制,比如用戶一分鐘只可以發送一條短信,忘記密碼的短信一天只能發送10條、一個IP地址限制每天只能發送100條短信等。
    BUG
    重復派息
    15年的某一天看到一個新聞說是陸金所的一個用戶發現自己銀行里面突然多了很多錢,沒過多久又被扣走了,然后收到陸金所那邊的解釋,說是給用戶還本派息的時候程序出現了問題導致還本派息兩次,當他們程序員發現了此問題后緊急進行了處理,用戶當然鬧了呀,就上了新聞,當然陸金所通道能力確實比較強可以直接從用戶卡里面扣,當大家都興致勃勃的談論這個話題的時候,我卻有一股淡淡的憂傷,為什么呢?因為這個錯誤我們也犯過,具體說就是我搞的,大家可不知道當時的心里壓力有多大!
    事情是這樣子的,我們使用的第三方支付的扣款接口不是特別的穩定,于是我們前期就對接了兩種不通的扣款接口,平時前端投資的時候走一個接口,后端派息或者還本的時候走另外的一個接口,在初期的時候扣款接口不穩定,因此在給用戶跑批的時候經常會有個別用戶失敗,需要手動給失敗的用戶二次派息。做為一個有志向的程序員當然覺得這種方式是低效的,于是將程序改造了一下,在后端派息的時候當第一種扣款失敗的時候,自動再次調用第二種扣款接口進行扣款,當時想著這種方式挺好的,各個環境測試也沒有問題,上線之后監控過一段時間也運行穩定。
    當我感覺一切都很美妙的時候,事故就來了,突然有一天客服反饋說有的用戶說自己收到的利息感覺不對,好像是多了(真的是太感謝這個用戶了),我登錄后臺看了一下派息的流水復核了一遍,果然利息被重復派了,一股冷水從頭而下,把當天所有的用戶派息記錄和到期記錄都進行了檢查,影響了70多個用戶,導致多派息了6萬多元,幸虧只是派息出了問題,如果是到期的話金額會翻N倍,其中70多個人里面有幾個進行了體現、幾個進行了再次投資,絕大部分用戶在我們發現的時候還不知情,金額也沒有動。
    怎么處理呢,當然不能直接就動用戶的錢了,給每個重復派息的用戶打電話,說明原因贈送小禮物,請求諒解后我們把重復派過的利息在次調回來。大部分用戶進行了核對之后都還是比較配合的,但是肯定有一些用戶不干了,當然也不能怪客戶,都是我的原因,有的客戶需要上門賠禮道歉,有的客戶需要公司出具證明材料,我們的老板親自給客戶打了N個電話被客戶罵了N遍,我心里壓力可想而知,其中有一個客戶特別難纏,各種威脅說既然到了我的賬戶里面肯定是我的,你們的失誤不應該讓他來承擔,折騰了很久,還是不能怪客戶。可能會說有的互聯網公司經常出現這種問題后就送給客戶了,哎,我們是小公司呀!這個噱頭玩不起。
    到底是什么原因呢,事后進行了復盤也給領導做了匯報,平時都是首先進行派息的定時任務,過一個小時之后進行到期的定時任務,當天的派息標的比較多,跑了一個半小時,就導致了派息和到期的兩個定時任務同時進行,轉賬有了并發,第三方支付的接口不穩定給我們返回的失敗,其實有的是成功的,就導致了我們進行了二次的扣款嘗試引發了此問題。這個事情給我帶來了非常大的教訓,對于金融扣款的這種事情一定需要謹慎,哪怕付款引發報警之后在人工處理,也不能盲目重試可能引發雪崩效應。
    雜七雜八
    還有就是其它一些零碎的問題了,記的有一次對用戶的登錄過程進行優化,導致有一塊判斷少了一個括號結果用戶在那兩個小時內,只要輸入賬戶,任意密碼就可以登錄了,幸好及時發現這個問題,正是這個問題才導致了我們正式確立了規范的上線流程,為以后的上線制度建定了基礎。
    還有一次我們在模擬用戶投資一種標的時候,留了一個入口通過http就可以調用,測試也沒有問題,有一天正好給領導演示呢,就在次用http請求的方式在瀏覽器執行了一下,前端就會看到自動投標的過程,因為生產的數據有點多,投標的過程有點長,我們為了加快進度,找了好幾個人同時來執行這http請求,導致最后出現了問題,最后發現寫測試腳本的這個同事根本就沒有考慮并發的情況,才導致出現了問題。
    也做了很多的活動,記得做一個網貸之家的一個活動的時候,活動上線比較緊張,我們團隊曾經連續工作超過30個小時(一天一夜再一天),當天晚上我2點左右寫完程序,測試從2兩點測試到早上9點,最終確認沒有任何問題,才進行投產。半夜公司沒有暖氣,我們實在凍的不行了,就在辦公室跑步,從這頭跑到那頭,第二天上線之后,又害怕出現問題,監控了一天,確認沒有任何問題,才到下午正常下班回家,那時候真是激情滿滿呀。
    說到做活動肯定少了羊毛黨,說哪一家互金公司沒有遇到過羊毛黨那很少見,而且現在的羊毛黨規模簡直逆天了,我們用戶里面就有一個羊毛黨在兩三天之內邀請了六七千位用戶,如果說邀請一個用戶送1元,那這個用戶就可以搞幾千塊一次,而且有很多專業的網站、QQ群、微信公共賬號都是他們的聚集地,那天那個平臺有活動門清,他們寫的淘羊毛操作手冊有時候比我們官網的幫助文檔還清晰,所以做活動的時候要考慮特別周全,各種限制,有封定、有預案、講誠信,只要是符合我們活動規則的堅決按照流程走。
    還有一個有趣的事情,app推送,一次我在公交車上就看到xx盒子app彈出hhhhh的推送,這個事情我們也搞過,因為在調試的時候生產和測試就差了一個參數,有時候開發人員不注意就把生產參數部署到uat環境了,測試一發送就跑到生產了,這方面只能嚴格流產管理來防止了。
    其實還很多問題:mongodb集群和mysql的同步出現的一些狀況、后臺大量數據查詢下的sql優化、golang使用mapreduce碰到的問題... 限于篇幅這里就不一一清晰的描述了。
    其實每次的出現問題都是對團隊一次非常好的鍛煉機會,通過發現問題,定位問題,解決問題,再次回過頭來反思這些問題;重新梳理整個環節,
    舉一反三避免下次再次出現類似的問題。正是因為經歷這些種種的困難、考驗才讓團隊變的更強大更穩定,也更體現了流程的重要性,更是避免再次發生類似問題。
    總結
    古代對將軍的要求是,心有萬馬奔騰而過,而面平靜如湖水可照鏡,在互聯網行業對大牛的要求也同如此,特別是技術的負責人,在面對生產事故的時候,一定首先是安撫同事,靜下下心來找到問題本質在去解決,而不是不斷去施加壓力催促解決,重壓之下很多心里承受能力稍弱的隊友,更加慌亂而不利于解決問題或者引發二次事故。
    在看淘寶雙十一視頻中,有一段特別受到感觸,在雙十一初期,雖然技術團隊做了很多的準備,但是在零點過后流量瞬間涌入,服務被打垮,部分用戶投訴刷新不出網頁,緊接著隔壁同事也都反饋網站打不開,在大家都在慌亂中,xx一拍桌子大喊一聲,大家都別動,三分鐘之后再說,過了幾分鐘之后服務慢慢部分恢復了正常。后來回憶說,當時雖然服務癱瘓,但是監控還是有部分得業務成功,說明系統并沒有被壓垮,而此時的任何操作都有可能引發更大的問題,從此之后此人一戰成名,成為阿里大將。
    互聯網平臺發展大抵都會經歷三個階段:
  • 1、上線初期,此階段問題最為繁多,生產事故不斷,系統快速迭代優化。有人說為什么不測試到完全沒有問題在投產嗎?說實話在互聯網行業這個很難,第一小公司很難做到生產環境和測試環境一致,成本太高;時間緊迫,一般都是很短的時間內要求上線,上線之后在快速迭代。另外互聯網本就是一個快速試錯的行業,錯過半年時間可能風口早過;


  • 2、發展期,此階段主要業務模式已經得到驗證,系統出現問題的頻繁度較少,低級錯誤減少,但此時是用戶量和交易量不斷爆發的時候,對系統性能、高并發的要求又上來了,所以此時出現的問題大多都是性能的問題;


  • 3、成熟期,發展期過后系統相對比較平穩,用戶量和交易量都已經慢慢穩定下來,生產問題越來越少,出現問題幾乎都是細小的bug,這個階段也是公司最忽略技術得階段,恰好我們公司就處于這個階段,在這個階段就需要靜下心里,組織架構升級,補齊在初期和發展起所欠的技術債務,做好公司在升下一個量級的技術準備。


  • 所有的這些問題幾乎都集中在14年底到15年初的這個階段,15年后半年開始到現在平臺慢慢穩定了下來,到現在幾乎沒有再出現過類似的問題,也因為幾乎都是兩年前的事情,有很多記的不是特別清楚了,寫的比較粗糙望見諒。




    作者:純潔的微笑
    出處:http://www.ityouknow.com/
    版權歸作者所有,轉載請注明出處

    AutoPoster 發表在 痞客邦 留言(0) 人氣()


    文章出處
    說起多數據源,一般都來解決那些問題呢,主從模式或者業務比較復雜需要連接不同的分庫來支持業務。我們項目是后者的模式,網上找了很多,大都是根據jpa來做多數據源解決方案,要不就是老的spring多數據源解決方案,還有的是利用aop動態切換,感覺有點小復雜,其實我只是想找一個簡單的多數據支持而已,折騰了兩個小時整理出來,供大家參考。

    廢話不多說直接上代碼吧


    配置文件
    pom包就不貼了比較簡單該依賴的就依賴,主要是數據庫這邊的配置:
    mybatis.config-locations=classpath:mybatis/mybatis-config.xml
    spring.datasource.test1.driverClassName = com.mysql.jdbc.Driver
    spring.datasource.test1.url = jdbc:mysql://localhost:3306/test1?useUnicode=true&characterEncoding=utf-8
    spring.datasource.test1.username = root
    spring.datasource.test1.password = root
    spring.datasource.test2.driverClassName = com.mysql.jdbc.Driver
    spring.datasource.test2.url = jdbc:mysql://localhost:3306/test2?useUnicode=true&characterEncoding=utf-8
    spring.datasource.test2.username = root
    spring.datasource.test2.password = root

    一個test1庫和一個test2庫,其中test1位主庫,在使用的過程中必須制定主庫,不然會報錯。
    數據源配置
    @Configuration
    @MapperScan(basePackages = "com.neo.mapper.test1", sqlSessionTemplateRef = "test1SqlSessionTemplate")
    public class DataSource1Config {
    @Bean(name = "test1DataSource")
    @ConfigurationProperties(prefix = "spring.datasource.test1")
    @Primary
    public DataSource testDataSource() {
    return DataSourceBuilder.create().build();
    }
    @Bean(name = "test1SqlSessionFactory")
    @Primary
    public SqlSessionFactory testSqlSessionFactory(@Qualifier("test1DataSource") DataSource dataSource) throws Exception {
    SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
    bean.setDataSource(dataSource);
    bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mybatis/mapper/test1/*.xml"));
    return bean.getObject();
    }
    @Bean(name = "test1TransactionManager")
    @Primary
    public DataSourceTransactionManager testTransactionManager(@Qualifier("test1DataSource") DataSource dataSource) {
    return new DataSourceTransactionManager(dataSource);
    }
    @Bean(name = "test1SqlSessionTemplate")
    @Primary
    public SqlSessionTemplate testSqlSessionTemplate(@Qualifier("test1SqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
    return new SqlSessionTemplate(sqlSessionFactory);
    }
    }

    最關鍵的地方就是這塊了,一層一層注入,先創建DataSource,在創建SqlSessionFactory在創建事務,最后包裝到SqlSessionTemplate中。其中需要制定分庫的mapper文件地址,以及分庫到層代碼
    @MapperScan(basePackages = "com.neo.mapper.test1", sqlSessionTemplateRef = "test1SqlSessionTemplate")

    這塊的注解就是指明了掃描dao層,并且給dao層注入指定的SqlSessionTemplate。所有@Bean都需要按照命名指定正確。
    dao層和xml層
    dao層和xml需要按照庫來分在不同的目錄,比如:test1庫dao層在com.neo.mapper.test1包下,test2庫在com.neo.mapper.test1
    public interface User1Mapper {
    List<UserEntity> getAll();
    UserEntity getOne(Long id);
    void insert(UserEntity user);
    void update(UserEntity user);
    void delete(Long id);
    }

    xml層
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
    <mapper namespace="com.neo.mapper.test1.User1Mapper" >
    <resultMap id="BaseResultMap" type="com.neo.entity.UserEntity" >
    <id column="id" property="id" jdbcType="BIGINT" />
    <result column="userName" property="userName" jdbcType="VARCHAR" />
    <result column="passWord" property="passWord" jdbcType="VARCHAR" />
    <result column="user_sex" property="userSex" javaType="com.neo.enums.UserSexEnum"/>
    <result column="nick_name" property="nickName" jdbcType="VARCHAR" />
    </resultMap>
    <sql id="Base_Column_List" >
    id, userName, passWord, user_sex, nick_name
    </sql>
    <select id="getAll" resultMap="BaseResultMap" >
    SELECT
    <include refid="Base_Column_List" />
    FROM users
    </select>
    <select id="getOne" parameterType="java.lang.Long" resultMap="BaseResultMap" >
    SELECT
    <include refid="Base_Column_List" />
    FROM users
    WHERE id = #{id}
    </select>
    <insert id="insert" parameterType="com.neo.entity.UserEntity" >
    INSERT INTO
    users
    (userName,passWord,user_sex)
    VALUES
    (#{userName}, #{passWord}, #{userSex})
    </insert>
    <update id="update" parameterType="com.neo.entity.UserEntity" >
    UPDATE
    users
    SET
    <if test="userName != null">userName = #{userName},</if>
    <if test="passWord != null">passWord = #{passWord},</if>
    nick_name = #{nickName}
    WHERE
    id = #{id}
    </update>
    <delete id="delete" parameterType="java.lang.Long" >
    DELETE FROM
    users
    WHERE
    id =#{id}
    </delete>
    </mapper>

    測試
    測試可以使用SpringBootTest,也可以放到Controller中,這里只貼Controller層的使用
    @RestController
    public class UserController {
    @Autowired
    private User1Mapper user1Mapper;
    @Autowired
    private User2Mapper user2Mapper;
    @RequestMapping("/getUsers")
    public List<UserEntity> getUsers() {
    List<UserEntity> users=user1Mapper.getAll();
    return users;
    }
    @RequestMapping("/getUser")
    public UserEntity getUser(Long id) {
    UserEntity user=user2Mapper.getOne(id);
    return user;
    }
    @RequestMapping("/add")
    public void save(UserEntity user) {
    user2Mapper.insert(user);
    }
    @RequestMapping(value="update")
    public void update(UserEntity user) {
    user2Mapper.update(user);
    }
    @RequestMapping(value="/delete/{id}")
    public void delete(@PathVariable("id") Long id) {
    user1Mapper.delete(id);
    }
    }

    最后源碼地址在這里spring-boot-mybatis-mulidatasource


    作者:純潔的微笑
    出處:http://www.ityouknow.com/
    版權所有,歡迎保留原文鏈接進行轉載:)

    AutoPoster 發表在 痞客邦 留言(0) 人氣()

    文章出處



    工作六年對一個程序員意味什么?在職位上:高級開發工程師?架構師?技術經理?or ... ?在能力上:各種編碼無壓力?核心代碼無壓力?平臺架構無壓力? or ... fuck?看著這些問號都心累。那么,六年你迷惘了嗎?又走到了那個十字路口?


    六對我來講總是一個特殊的數字,六年中一直想對自己的程序員生涯做一個回顧,總是有各種的借口飄然而過就到了幾天。畢業六年,大學同學們基本上都走在了不同的路線,也走進了完全不同的生活,能在六年沖出來的現在也都小有了名氣,為什么相同的學校相同的專業卻走向了不同的方向呢,且聽我慢慢道來。



    每個人程序員的經歷都是一個故事



     


    如何入坑


    在XX的培訓班上有一次我對大家這樣介紹:我來自一個二流的本科院校中的一個三流專業,學校本來就是師范類的院校,自然不是特別受歡迎,我們是師范學校里面的非師范專業,而且是學校剛開的專業掛在數學系更加非主流,專業就是:信息與計算科學。我聽說有的學校這個專業是計算機系的,不知準確信息。但基本上都是學數學的,帶著學習一點計算機,當初報考這個專業也是因為這個名字,看起來很有科技含量,多少農村孩子都是這樣報考專業的!!!


     


    大學生活


    一般大家回顧都要說說大學生活,我的大學一年一句話來總結,大一基本上都是在網吧度過的,大二基本上都是在籃球場度過的,大三基本上都和女朋友一起過的,大四基本上都在找工作中度過,導致我走向編程這條道路的經歷基本上都在大四了。放一張當時大學的圖片,當然現在都已經發生了很大的變化。


    dx


    有幾個原因導致了我最后選擇去培訓機構培訓Java編碼,第一、對計算機比較感興趣,大學數學課程基本沒聽過,都是考試應付,但對相關計算機課程很感興趣,但學的太淺了,我不討厭數學,但是讓我想到學這么多微積分、線性代數...畢業后有個鳥用,就泄氣了,沒有一點動力。第二、真的不好找工作,專業幾乎沒有對口,同學干啥都有,有的走向了培訓、當了教師,有的做了文員、公務員、銀行職員,有的做了交警、還有公安,但最讓我驚奇的是有一個當了律師,太驚訝了,我感覺比編程可困難多了,基本上都是各自找自己的出路。第三、大三暑假那會參加了數學建模競賽,在小組中我負責編程的部分,那時候用的MATLAB和C語言,隨著不斷的練習和使用更加驗證了對軟件的熱情和理解,也只是覺得軟件應該是一個朝陽行業,慢慢的去了解了入行的標準,找工作的過程中慢慢試著去接觸了一些培訓機構,但是看到1w左右的培訓費用,我猶豫了,那時候的1w對我來講太重了。


    大四那年冬天,印象很深刻,跑遍了省會城市大小招聘會場,不是簡歷都過不了,就是么消息;最后到應聘上幾家公司,但是和自己想象中差距太遠,一個是培訓機構當老師,沒去;一個是做管培生,去了,我靠基本上跟傳銷一樣,什么管培生就是賣軟件,記得應該叫“紅利軟件”,就是跑到各個交易所里面去找大爺大媽,聊天要電話,讓聽講座最后引導買軟件,一套軟件大概是3000左右,可以提成10%;早上7點上班,各種活動游戲,8:30出發,9點左右到交易所各種找人,看著眼睛發光的大媽大爺就是目標客戶。下午5點左右回來,各種培訓,然后開始根據話術打電話晚上10點回家,我們學校一共去了8個人吧,最后留下了一個我們都想到不到一個人,我們班一個文文弱弱的小女生,學習很好的那種,一干就是兩年,真是人不可貌相呀,堅持了兩周我就撤了。


     


    南下深圳


    為什么了去了深圳?幾個原因,我老大(初中很好的朋友,初中畢業后當兵,然后南下深圳)在這里,萬一不行還有一個投奔的地方。深圳應該是當時印象中南方比較發達的城市,希望可以見見世面,找找工作,對了那年還是非典,疫苗剛出來,只給大四的學生先用,那天還發著低燒,也沒管直接就打了。小馬是我們班的一個同學,關系比較好,聽我說要去深圳,特別激動說,強哥咱倆一起去闖天下去 :),因為小馬家境還不錯,沒有吃過太多的苦,從小都沒出過省,還是有點擔心,但是看著小馬這么激動,恰好我也有一個伴,就欣然接受了。后來我先去的,看了情況還行,就打了電話叫了小馬一起過來。那時候從西安到深圳為了省錢買了硬座,應該是做了將近30個小時左右吧,吃了N多筒的泡面終于到了深圳,南方人的普通話真是聽不懂呀。對了,小馬最終做了一名人民警察,這就是另外的故事了。


    因為我先去的深圳,就先去了深圳人才大市場,當然了各種受挫。等小馬來了以后我們就先進了龍崗的比亞迪工廠,我們分在了不同的車間,小馬比較幸運去了電池車間,就是流水線,我分到了紙箱車間,大家可能平時覺得紙箱子沒有啥,但是在沒有成品之前,邊緣幾乎都跟刀子一樣,沒幾天從手到胳膊全部是傷,但最重要的是我和我們車間的老大搞不到一起,整天吵架,有一次差點干了起來,那個車間老大后面瞬間站了兩個人,那次之后起我就出來了。


    聽了老大的建議,我又去了龍華的富士康,有一個朋友接我,對我說那邊大學生應聘一人還發一個電腦而且是做辦公室的(車間里面的人都羨慕做辦公室的,畢竟不用賣苦力;在廠里面一般工服有三種顏色:藍領,就是普工或者技工身份進廠的;紅領,就是質檢,專門檢查質量的,大部分從藍領升上去;白領,一般就是大學生了做文職或者管理),我沒去,我還是以普工的身份進的富士康,我不信我干不下去,最后又分到了觀瀾的一個小分廠里面做物流,對了就是富士康當時有名的第一跳,就在那個廠區。廠里面不像大家現在洋氣的說996什么的,幾乎沒有假期,什么周末更別提了,早8點晚8點,半月白班,半月夜班。富士康在深圳還是福利比較好的一個工廠,包吃住用藥等。


    這個圖網上找的,有點像當初那個屌絲的年華 dx


    在工廠的這段經歷讓我明白了,我必須要依靠一個技能來養活自己。這個技能就是搞軟件!


     


    培訓班


    根據之前的了解我大概選了兩家培訓機構,一家就是野馬XXX,另外一家就是XX內培訓機構,價格都差不多,但野馬有教師,X內是視頻教學,當時感覺不能接受,不太靠譜。本來都拿著錢去野馬那邊交錢了,在付款的時候,財務說可以減免了一百元,跟我一直溝通的那位說政策搞錯了,肯定不是這個。就因為這個原因,我感覺他們不夠嚴謹,我說那我就先不報名了,然后就去了隔壁的X內看看,結果他們正在上課就讓我試聽了一下,里面全是大四的學生,我坐在后排聽了一上午,雖然感覺還是跟不上,但是有那種建模學習的那種氛圍,我就定了X內。后來我在X內都上課兩周了,野馬打電話說給我特殊減免3千元讓我過去,最后沒去,但是感覺水分真大。


    其實X內當在西安的培訓還挺扎實的,還考了sun的證書,其實也么啥用。開始從跟不上,到處請教別人,到慢慢的理解,寫各種小游戲,到最后也有學員來問我問題。那時候周六、周天可以免費在培訓機構學習,我幾乎周末都在哪里,畢業答辯的前兩天才回去,然后又過來,畢業典禮沒有去,畢業體檢舍友這個幫我測血壓,那個幫忙測體重,畢業證書、學位證書舍友幫忙領。我的大四幾乎和學校都沒有關系。在X內認識了好幾個朋友,工作到現在都聯系著,有的去了華為,有的在外包,還有的搞了小公司。


    培訓機構承諾免費介紹工作,那時候的培訓好像也沒有讓大家偽造什么工作經驗,學校什么的,一般情況下只要你不是很爛,基本上都能找到工作。X內推薦了幾家公司,自己也投了簡歷記得最后收到了2個或者3個offer,也記不得都有什么公司了,最后選擇了去李嘉誠兒子的一家公司就是電訊盈科,這個公司那時候剛來西安,還算不錯的公司,主要是電信方面的研發,可惜我再這公司也才呆了不到一個月。


     


    工作西安


    剛入這行還是比較周折,也差點進去了另一個方向,所幸最后走向了正軌。程序員都是第一年的工作不是特別好找,過了第一年后,后面就比較輕松了。剛進電訊盈科的時候我的心里狀態還是沒有調整過來,感覺還是在大學的那種狀態,進去之后是淘汰制,一個月內培訓oracle,兩周淘汰一次人。其實我感覺自己太不會表現了或者其它吧,最終一個月底的時候我也被淘汰了,打電話給我姐說的時候,我姐說,關鍵人家一共20多個人就淘汰了2個人!其中就有你!對了培訓的錢,借我姐夫的。我感覺很憋屈,但我還是不認可,我是最差的。但最終我還是需要面對再找工作的問題。


    網上海投了N份簡歷,電話不多,面試了幾家公司后,也收到了幾份Offer一個小公司不交社保,1800;有一家外包華為2100正規繳納社保,我去了華為外包。關于薪資我給大家說兩個笑話:1、我當初培訓的時候一個學員給我說,有一個朋友從這里出來后,第一份工作2000,跳槽后4000,再跳槽后6000,我們一群人心里都默默的崇拜著,想著這肯定都是大牛級別的人物;2、我畢業第一份工作預期是2000左右,然后我就幻想著以后每年能漲1000元我就滿足了,到了30歲我就能拿快8000了,現在真到30了才發現現在的畢業生起步價也是這個數。


     


    第一份工作


    那時候我也在網上看了很多外包公司的種種不是,但我的選擇不是很多,不管怎么樣畢業了就不能再往家里要錢了,總得先掙錢吧。華為外包有一個變態的特點為了保密,不允許帶U盤、手機等各種存儲、通訊設備,上班后基本就和外界失聯了。那時候是做無線上網卡的客戶端,就是那時候往電腦一插就可以上網那種。每個人進來會分配一個師傅來帶,比較幸運我來的時候分給了一個比較好的師傅,性格、態度,以及他工作的方式其實最后也都影響了我。


    我們應該在研發二部,大概分了三個部門,大巴組,小巴組和定制組。定制組:就是不用寫代碼的那種,華為開發了一些工具通過工具可以改變客戶端軟件的logo,模塊功能等,華為的軟件真是遍布全球到處的客戶都有,阿拉伯、非洲各種語言大部分的需求都是基本可以工具搞定;小巴組,就是需要改一些代碼,但是工作量又不是特別多的那種;大巴組就是需要改動需要1月以上的需求。我當時分在了小巴組,大概有十幾個人,其中也有很多碩士畢業的也被忽悠進來。最原始的代碼都是印度阿三寫的,我們都是在上面做二次開發,剛進去看了一個類代碼有上萬多行驚呆了,但是代碼確實寫的非常正規。沒有什么架構文檔給我們,但改動基本都是外層的皮膚了或者小按鈕之類的。


    加班非常多,但比較開心的是加班有工資,而且是按照國家標準來的,平時加班1.5倍,周六天2倍,假期三倍。這是我工作到現在最正規的加班制度了,工資只有2100,但通過加班可以拿到3000左右,加到2點都是很正常的事情,特別喜歡假期加班可以拿三倍工資,華為在西安包了N多大樓,當時在軟件園三期,班車上百輛開出去還是挺壯觀的。華為的中午休息文化,確實好,中午吃完飯熄燈大家都睡覺,中午趟在哪里睡一個小時,下午工作質量明顯提高N倍。


    我剛進去的時候客戶端有兩種一種是Java寫的,一種是QT(C++的封裝),慢慢的Java版本的都淘汰了,全部上線了QT。我從小巴慢慢開發了大巴需求,但到最后沒有Java版本的需求了,全部用QT。Java組的大家都各種轉型,有的去了另外一個js控制的項目組,我選擇了留下來搞QT,開始學習C++,因為有C語言的底子,倒也不難慢慢的可以開始搞QT版本的小巴需求,但是最后我就糾結了,我以后到底是往Java方向發展呢,還是C++呢。后來終于想通了,我花了那么多錢培訓java這樣太虧了 ,于是選擇了離職。那時候華為方的領導其實對我也特別好,還專門給了我兩周時間不用上班去參加華為Java的培訓,大家都帶著華為的白牌子,我帶著外包的黃色牌子,培訓老師問了我好幾次我是那個部門的,但最后我還是撤了。


    放一張我們小巴組出游的照片
    dx



    第一份工作促成我從學生到職場的轉變。



     


    第二份工作


    當時面試有意向的公司大概是兩家,一家是做GIS系統關于地理信息的公司,另外一家是XX系統,主要是做思科代理,給思科做各種軟件或者給思科的硬件去做集成方案的各種軟件。我選擇了后者,沒有別的原因,后面這家工資給的高,我就這么實在。


    剛進公司其實比較緊張,因為半年沒有做Java了,每天各種學習,各種加班最后發現其實還可以,雖然半年沒有搞了但問題也不大,公司用的是hibernate和Struts基本上都是以前用過的框架。第一個項目是,smart meeting智能會議,就是大公司預定會議的一套系統,大部分的工作都在前端,那段時間讓我對js有很了很大的了解,因為預定會議的系統界面都是各種拖拽。第二個項目大概就是vozimate,就是給思科的IP話機上面做應用,就是通過電話可以查詢股票了、天氣預報了等等,我們的這些信息都是通過爬蟲抓取第三方的,過一段時間就需要調整一下爬蟲策略。第一次讓我對硬件和軟件交互有了理解,思科的IP話機當時還是蠻先進的視頻通話,各種會議都是沒有問題。


    最重要的一個項目也是我幾乎入職一直在搞的項目就是UC manager,就是通過思科的電話打完短途、長途、漫游、國際漫游、轉接、會議等等,凡是和打電話相關都會有,其實就是相當于聯通或者移動公司話費的計費系統,當然還有路線最優路由,統計等各種功能非常多,剛開始做一些小功能,到負責一個模塊,到最后整個系統都是我來負責,直到我離職的最后一天,我都在做這個項目的最后一版計費優化。正因為這個項目到北京面試的時候得到了一個高薪的機會,這個下面再講。


    其實在這個公司里面,項目中規中矩,代碼也是主流的框架和技術,一年多的時間穩扎穩打讓我對大項項目框架和設計有了很多的認識,特別是爬蟲什么的讓我非常興奮。那時候中午我經常看博客園的新聞,整天都是互聯網公司怎么怎么了,但是西安基本沒有一家正正經經的互聯網公司,于是就有了去北京想法,剛好jerry也有這樣的想法13年過年后,大家紛紛提離職,準備去帝都呼吸新鮮的霧霾。


    記得是11年十一過后入職的,當天一起入職了四名同事,我什么要強調這個呢,因為這四個同事到現在為止,都成了我職場后關系最好的四個好基友,其中有一個女孩,但我們仍然這樣認為。jerry、波仔和鴿子,我們四個各有特點,jerry就是那種極客精神,喜歡各種硬件、軟件,做了好幾個網站,創業幾次,目前創業中;波仔,天生搞笑天王,唱歌天王,在生活中帶來無限的樂趣,跟他在一起永遠是歡笑不斷;鴿子,是女漢子或者是逍遙著,天生喜歡流浪、喝酒,拉薩、云南、日本、臺灣、英國各處流浪,永遠給人一種激情滿滿的感覺。


    我們甚至創建了自己的戶外組織Flyever,有自己的官網,甚至印了自己的隊旗,logo和口號:自由 夢想!每月組織去排山,腐敗、各種活動。發工資了說今天活動一下吧,十幾個人就去吃飯喝酒,晚上通宵唱歌;世界末日了說去慶祝下吧,這兩天心情不好,去活動一下吧!找各種借口去腐敗,爬了很多山,喝了數不清多少瓶的9度。這是曾經的官網www.flyever.cn


    放一張我們當時一個活動策劃的截圖
    fly


    在放幾張我們去過的地方


    青海湖的太陽
    fly


    蘆葦蕩
    fly


     


    北上帝都


    來到北京的時候,我身上只剩了3000快錢,1000多在分鐘寺(現在已經拆遷)租了個公寓,買了些日常用品后就剩1千多了,我專門挑互聯網公司來投簡歷,大概頭了100份左右,找了10家去面試,一周內面試完拿到了5份offer,其中有一家給的特別高就是因為我以前做了UCmanager這個項目,他們公司剛好給愛立信做項目,缺少這樣項目經驗的人,我猶豫了很久,畢竟工資給的很高,但最后還是放棄了,進了一家第三方支付公司。


     


    第三方支付公司


    選擇第三方支付公司的時候,其實我還不是特別了解這個行業,只是覺得支付應該是比較不錯的。這家公司也是我現在公司的母公司,剛入職的時候感覺周圍一大片全都是大牛!公司還管飯,感覺特別好,沒過了幾天,就被同事拉進了一個XX山炮群,又開啟了胡吃海喝的時代,經常私下我們幾個組織著去AA聚餐,各種吹牛,各種燒烤啤酒也別有一番風味,混熟了之后,才發現和我一樣大家都是屌絲,有一次部門聚餐的時候,部門經理說,大家都舉下手看看大家都是那個省的,結果幾乎每個省都是一兩個這種,看著來自五湖四海的同事在一起工作,也是一番景象。


    剛來公司做的是,第三方支付的前置接入系統,當時公司每天交易額剛剛上億,服務壓力非常大,每天各種報警,我們就輔助從前置開始跟蹤,慢慢的對業務有了了解,后來也寫了專門的壓測程序來跑。再后來開始負責公司官網的改版,收銀臺改版,到后來開始了解J8583,銀行接入平臺 慢慢的才對第三方支付有了一個整體的了解,13年底的時候慢慢的興起了很多p2p,很多公司在做對接平臺,那時候大家都不懂什么是p2p,我們也不懂,也是一邊學習一邊搞,項目持續做了很久,我帶了兩三個人來做后端。最后這個項目也沒有做起來,因為我們還是不了解p2p公司到底需要什么,自己琢磨的東西還是不太靠譜。


    14初的時候總監,偷偷告訴我要封閉去做一個關于金融的項目,想讓我參加,沒想到正是這個項目對我的職場有如此大的影響,所以說關鍵時候的選擇非常的重要。緊接著沒過幾天,就開了幾個車拉著我們去了四星級酒店開始封閉開發,后來方案定了使用PHP開發之后,我又撤了回來做平臺接口層的開發。大約過了一個月項目基本完成了,大家回來后項目組因為一個老總出走,帶走了一個團隊,剩下的開發幾乎都走光了,那時候其實我也找好了工作,新的公司待遇和環境都不錯,領導找我談話,想讓我負責這個項目。我對領導說我先考慮一下,等我休假回來后給回復。那段時間太累了加了很多班,請了好幾天假去了青海湖大玩了幾天,朋友都建議我去新的公司,后來我考慮一下還是選擇了留下。


    hkrt


     


    互聯網金融


    那個封閉的項目就是互聯網金融,那時候互聯網金融已經慢慢熱了起來,14年我們上線的時候應該是最后的一波熱潮了。直到今天我們公司在行業的排名都在20-60之間來回。


    14年初的時候大家搞的p2p都是網頁版,app端有幾個公司有,但都很基礎,當時公司人力有限,就面臨一個選擇先做APP還是網站的問題,其實APP的問題主要是通道的問題,當時快捷支付應用到P2P公司還是非常難的。最后領導還是拍板先上APP,大家就集中人力先做APP,最后證明這是個正確的選擇,現在監控我們公司的交易,幾乎百分之80來自APP,第一次感覺移動互聯網的浪潮就是這樣來的。


    系統剛投產的那段時間,交易量火爆,最夸張的時候1000萬的標的幾秒鐘就滿了,雖然現在平臺也是這樣。但當時對我們來講還是蠻震驚的。在交易量火爆的情況下,系統出現了各種問題,首先是秒殺的時候控制不住并發,有時候會出現超賣的現象,最后各種優化,通過memcahed鎖解決了這個問題,緊接著服務器又因為流量太大扛不住了,于是又上線lvs做負載。期間各種問題不斷,那段時間我幾乎晚上11點之前沒有回過家,每次我走的時候老婆還在睡覺,回來的時候她又睡著了,周末也是各種加班,總感覺自己見不到太陽,持續了很久,但是成長也是非常的大。


    因為我們的前端是PHP寫的我又逼著自己學習了PHP,從開始能看懂,到最后可以寫一點。公司慢慢上線了官網,又增加了小網頁(H5),各種分布式系統改造。做各種活動,和滴滴打車做活動、和河貍家做活動、和攜程做活動等等,有一次一天注冊了X千個用戶,驚呆了,發現羊毛黨來了,又是各種限制。在后來慢慢的有黑客盯上了我們,各種騷擾,DDOS攻擊,SQL注入等等。反正是能遇到的問題我們基本都遇到了,每一次問題之后,我們系統就又健壯了一些。


    再后來要做大數據分析,我們又開始啟動golang+monggodb這套方案來做大數據,剛開始也很困難,但是大家對新技術的這種渴望戰勝了一切;再后來上線了dubbo做SOA服務治理,到現在啟動spring boot+cloud。我們的系統也從第一代平臺開始到現在第四代平臺更換中,對這四代平臺做一個簡單的介紹: 第一代平臺,主要是集中式,以快速上線為目的;第二代平臺主要是分布式改造,緩解各服務壓力;第三代平臺主要做服務端SOA治理,后臺統一賬戶中心;第四代微服務化改造,已達到灰度上線、動態部署集中管理的目的。


    我也從負責Java端,到負責整個技術團隊,慢慢的在領導的信任下測試交給了我,再后來分公司獨立后將運維也交給了我,于是成了整個分公司的技術負責人。這就是我的故事。未來仍然有更多的挑戰,感謝我們團隊的兄弟姐妹,感謝工作中遇到的所有同事和領導。


    我特別喜歡一句話在這里分享給大家:



    我的代碼曾運行在幾千萬用戶的機器上,作為一個程序員,還有什么比這更讓人滿足的呢?如果有,那就是讓這個用戶數量再擴大 10 倍。



    zxjr


    路漫漫,歡迎大家在博文下面回復自己個人的經歷,以共勉!




    作者:純潔的微笑
    出處:http://www.ityouknow.com/
    碼字很辛苦,轉載請注明出處 :)



    AutoPoster 發表在 痞客邦 留言(0) 人氣()

    Blog Stats
    ⚠️

    成人內容提醒

    本部落格內容僅限年滿十八歲者瀏覽。
    若您未滿十八歲,請立即離開。

    已滿十八歲者,亦請勿將內容提供給未成年人士。