使用SQL計算寶寶每次吃奶的時間間隔(數據保障篇)

  • 2019 年 12 月 30 日
  • 筆記

目前程式從功能上其實已經完全滿足客戶(當然我這裡的客戶都是指媳婦兒^_^)需求,具體可參考:

那麼本篇 使用SQL計算寶寶每次吃奶的時間間隔(數據保障篇) 存在的意義在哪呢? 原因很簡單,就是因為我們作為技術人,實際需要考慮的要更多。比如本篇從數據保障層面,我們必須要考慮數據的一致性和安全性等。而且我們要很清楚,這些需求並不是客戶不關注不需要,很可能只是因為客戶並不知道可能會出現什麼問題,不知道如果一旦出現硬體損壞、各類故障導致數據損壞或出現訛誤時,我們目前的情況是恢復不了的,到時就只能被說成是技術差,甚至連帶背各種鍋。在這樣的場景下,技術人再去狡辯說客戶開始也壓根沒提這些安全性的數據保障需求啊,是沒有人會站在技術這一邊的,因為這些客戶考慮不到的,恰恰是需要你來主動去提出去建議去實施的,這也是體現一位技術人專業性的關鍵所在。 下面以幾個維度來展開說明:

1.程式備份

場景:一旦程式所在主機故障,需要在新環境下重新部署程式時,程式備份的作用就體現出來了。 其實我這裡程式所連接的底層資料庫是Oracle RAC架構,可直接在RAC另一個節點部署一套程式。因為之前程式使用的文件默認在/home/oracle下,該目錄還有很多其他與程式無關的文件,比較混亂,現考慮將程式整理到統一目錄下整體打包,便於備份,遇到故障也可以方便快速重新部署。 我這裡統一放置目錄:/home/oracle/baby,並將程式按照當前版本號進行打包備份,最後拷貝備份的程式包到NAS留存。

1.1 統一放置目錄:/home/oracle/baby

[oracle@jystdrac2 baby]$ pwd  /home/oracle/baby  [oracle@jystdrac2 baby]$ ls -lrth  total 76K  -rw-r--r-- 1 oracle oinstall  36 Dec 22 09:47 d1.sql  -rw-r--r-- 1 oracle oinstall  71 Dec 22 09:47 i1.sql  -rw-r--r-- 1 oracle oinstall  91 Dec 22 09:47 i2.sql  -rw-r--r-- 1 oracle oinstall  59 Dec 22 09:47 u1.sql  -rw-r--r-- 1 oracle oinstall 199 Dec 22 09:47 v1.sql  -rw-r--r-- 1 oracle oinstall 218 Dec 22 09:47 v2.sql  -rw-r--r-- 1 oracle oinstall 396 Dec 22 09:47 v3.sql  -rw-r--r-- 1 oracle oinstall 465 Dec 22 09:47 v4.sql  -rw-r--r-- 1 oracle oinstall 132 Dec 22 09:47 v_estimate.sql  -rwxr-xr-x 1 oracle oinstall 302 Dec 22 09:54 baby_delete.sh  -rwxr-xr-x 1 oracle oinstall 296 Dec 22 09:55 baby_insert.sh  -rwxr-xr-x 1 oracle oinstall 335 Dec 22 09:55 baby_insert_diy.sh  -rwxr-xr-x 1 oracle oinstall 545 Dec 22 09:56 baby_help.sh  -rwxr-xr-x 1 oracle oinstall 305 Dec 22 09:57 baby_update.sh  -rwxr-xr-x 1 oracle oinstall 293 Dec 22 09:57 baby_view.sh  -rwxr-xr-x 1 oracle oinstall 252 Dec 22 09:58 baby_view_diy.sh  -rw-r--r-- 1 oracle oinstall 244 Dec 22 13:30 bash_profile  -rw-r--r-- 1 oracle oinstall 273 Dec 26 09:10 backup_exp_t_baby.sh  -rw-r--r-- 1 oracle oinstall 154 Dec 26 09:53 readme  [oracle@jystdrac2 baby]$ cd ..

1.2 將程式按照當前版本號進行打包備份

[oracle@jystdrac2 ~]$ tar -zcvf baby_v2.02.tar.gz baby/  baby/  baby/readme  baby/u1.sql  baby/v4.sql  baby/baby_view_diy.sh  baby/d1.sql  baby/v3.sql  baby/baby_update.sh  baby/v2.sql  baby/v_estimate.sql  baby/i1.sql  baby/bash_profile  baby/baby_insert_diy.sh  baby/baby_insert.sh  baby/i2.sql  baby/v1.sql  baby/baby_help.sh  baby/baby_view.sh  baby/baby_delete.sh  baby/backup_exp_t_baby.sh  [oracle@jystdrac2 ~]$ ls -lrth baby_v2.02.tar.gz  -rw-r--r-- 1 501 1000 1.9K Dec 26 11:46 baby_v2.02.tar.gz

1.3 最後拷貝備份的程式包到NAS留存

[oracle@jystdrac2 ~]$ cp baby_v2.02.tar.gz /public/backup/

2.數據備份

場景:上面已經做了程式備份,但出現故障時我們只恢復程式是不夠的,還需要之前產生的業務數據。所以我們還需要業務數據的備份。 可以採用exp/expdp定時邏輯備份,因為我這裡數據量很小,所以直接採用更簡單的exp備份。 比如每天12點使用exp備份出當前表t_baby的數據: 設置crontab定時任務:

[oracle@jystdrac2 ~]$ crontab -l  0 12 * * * /bin/sh /home/oracle/baby/backup_exp_t_baby.sh

exp備份腳本:

[oracle@jystdrac2 ~]$ cat /home/oracle/baby/backup_exp_t_baby.sh  backupdate=`date +%Y%m%d`    export ORACLE_SID=demo2  export ORACLE_BASE=/opt/app/oracle  export ORACLE_HOME=/opt/app/oracle/product/11.2.0/dbhome_1  export PATH=$PATH:$ORACLE_HOME/bin    exp test/test tables=t_baby file=/public/backup/t_baby_$backupdate.dmp log=/public/backup/t_baby_$backupdate.log

備份出的文件類似這樣:

[oracle@jystdrac2 backup]$ ls -lrth t_baby*  -rw-rw-rw- 1 501 1000 626 Dec 26 12:00 t_baby_20191226.log  -rw-rw-rw- 1 501 1000 16K Dec 26 12:00 t_baby_20191226.dmp

3.數據實時同步

場景:如果只有上面步驟的定時邏輯備份,其實還是無法滿足完全的數據恢復的。 比如今天中午12點做了備份,晚上18點出現了故障,數據丟失。通過邏輯備份只能恢復到今天中午12點的數據,而12點到18點之間的數據將會丟失。 如果採用物理RMAN備份呢?其實也同樣存在這樣的問題,因為日誌歸檔並不是實時的,如果故障不可恢復,聯機重做日誌也丟失,RMAN也是不完全恢復到最近的歸檔日誌,也同樣會有丟失部分數據的風險。

那怎麼辦呢?如何進行數據實時同步到另外的環境呢?目前可以想到兩種主流的解決方案:

  • 1)資料庫DG實時同步
  • 2)數據表OGG同步

資料庫DG實時同步是物理的方式,數據表OGG同步是邏輯的方式。 一般情況下,如果兩個方案只能選擇其一時,我們會強烈推薦客戶選用物理方式的實時同步,因為邏輯方式按經驗來看遇到的問題遠比物理方式要高。 而在我這個場景下,數據量很小,其實完全可以二者都選擇。 至於DG和OGG環境搭建的部分我這裡不再詳細展開,如有問題,可參考之前的文章:

4.已知問題解決

在這個計算餵奶間隔的程式投入使用了一段時間後,還發現一些問題亟待解決: 4.1 系統時間不準確 系統運行幾天後,作業系統的時間會和真實時間相差幾分鐘,這個暫時通過定時同步阿里雲的NTP伺服器來解決。

--使用ntpdate命令與阿里雲時間伺服器(ntp2.aliyun.com)同步  [root@jystdrac1 ~]# date  Sun Dec 22 08:48:51 CST 2019  [root@jystdrac1 ~]# ntpdate ntp2.aliyun.com  22 Dec 08:52:31 ntpdate[24481]: step time server 203.107.6.88 offset 206.232030 sec  [root@jystdrac1 ~]# date  Sun Dec 22 08:52:35 CST 2019    --使用crontab定時,每小時與阿里雲時間伺服器同步一次,同步日誌追加到/tmp/ntpdate.log日誌文件  crontab -l  0 * * * * ntpdate ntp2.aliyun.com >> /tmp/ntpdate.log

當然,這裡其實還可以設置NTP微調(-x)模式,保證RAC穩定性不受其調整的影響。

4.2 數據一致性問題 這個也可以說是程式設計時的bug。 現象:當前程式連接的資料庫底層是單實例,或始終在RAC的同一個節點上運行,就不會有任何問題;但如果在RAC的兩個節點交叉運行插入數據,序列就會出現問題導致計算結果產生訛誤。 先稱之為是RAC環境下sequence的問題解決:

比如:在節點1插入記錄,ID為235,再到節點2插入記錄,ID卻為192.

[oracle@jystdrac2 ~]$ i  Insert a row using current time:    1 row created.      Commit complete.    View Today's Result:            ID FEED_TIME   L   LAG(min)     LAG(h)  ---------- ----------- - ---------- ----------         192 12-26 18:21 N       5689      94.82         227 12-26 02:22 N        225       3.75         228 12-26 04:48 N        146       2.43         229 12-26 07:31 N        164       2.73         230 12-26 10:02 N        151       2.51         231 12-26 11:49 N        107       1.79         232 12-26 14:10 N        141       2.34         233 12-26 17:38 N        208       3.47         234 12-26 18:18 N         41        .68         235 12-26 18:19 N          0        .01    10 rows selected.

可以看到在節點2後插入的記錄ID值反而小,導致程式本身間隔計算也出現了訛誤,明顯這樣是有問題的。 其實問題也非常明顯,實例1和實例2獲取s1的sequence是不連續的,分別在兩個實例上查詢:

--實例1:  test@DEMO> select s1.nextval from dual;       NEXTVAL  ----------         239    --實例2:  test@DEMO> select s1.nextval from dual;       NEXTVAL  ----------         193

查詢下sequence的創建語句:

test@DEMO> select dbms_metadata.get_ddl('SEQUENCE','S1') from dual;    DBMS_METADATA.GET_DDL('SEQUENCE','S1')  --------------------------------------------------------------------------------       CREATE SEQUENCE  "TEST"."S1"  MINVALUE 1 MAXVALUE 9999999999999999999999999999 INCREMENT BY 1 STA  RT WITH 241 CACHE 20 NOORDER  NOCYCLE

可以看到序列默認是NOORDER,如果設為ORDER,測試反覆在兩個實例上交叉讀序列的nextval,都能保證序列值是順序的,就不會再出現最初的情況。 所以解決方案就是重建sequence s1,修改為ORDER。

drop SEQUENCE s1;  CREATE SEQUENCE s1 MINVALUE 1 MAXVALUE 9999999999999999999999999999 INCREMENT BY 1 START WITH 261 CACHE 20 ORDER  NOCYCLE;

再次驗證(select s1.nextval from dual;),確認此時序列是有序的:

--實例1:  test@DEMO> select s1.nextval from dual;       NEXTVAL  ----------         261    --實例2:  test@DEMO> select s1.nextval from dual;       NEXTVAL  ----------         262

但還需要注意如果將序列改為ORDER,在實際業務壓力大時很可能會造成嚴重性能問題,這估計也是不加任何參數創建的sequence默認就是NOORDER的原因。