2024-09-12

新時代 Web App 設計思路

這邊寫下目前經歷過的 "真" microservice 的設計思路

我們先看一下舊的環境或狀況,假設一個 blog system 為範例,user 需要 login 的狀況,然後每篇 blog 支援 comment,還要記錄每篇的瀏覽量

  1. 不管 DB auth 或 OAuth2 甚至 PKCE,最終發出一個亂碼的 session_id 到 cookie 去
  2. user request 拿該 session_id 來對照 DB / Redis 進行登入換得 user_id,確認後得知已登入
  3. login 後 server return user info 丟到 web site 的 top bar 去
  4. user create 一篇 blog,DB 用 auto increment primary key 來做 pkey 然後 INSERT
  5. another user 使用 pkey 來回覆 comment
  6. 每篇 blog 有人 get 時下一個 redis incrby 的指令
這份設計到這樣,幾乎是所有教科書等級的教學了,所有人保證看到這份應該都不疑有他,但 ... 真的嗎?

這份目前有點問題,下面是疑問的部分
  1. frontend / app 需要戳 server 才知道該 session_id 是否過期,且 DB / Redis 才有這把 session_id 與 user_id 之間的對照
  2. 需要 login 後才能得到 user info 即使每次都是一樣的
  3. blog 本身 pkey 會連號
  4. 每次 read 都會需要 trigger write!?
這其實說明幾件事情,有點麻煩,pkey 本身連號同時也代表了 DB table 需在 INSERT 的時候進行 lock,這將會是一個 globle effect,且通常換得 user_id 後會需要和 DB 重新 query,就幾乎無法製作分散式 DB,最後的在某個 path 進行 GET 時,戳了這世界最快的 redis 一下 ... 但戳這下會造成內網連線過多,且事實上也造成了效能瓶頸,因為戳了也會有 latency

這整票看似無解,但 ... 新時代有新的解法,我所感到漂亮的解法:)

========== 以下是手術過程 ==========
  1. 把 session_id 改為 JWT 並把部分 info 丟到 JWT 內去
    1. 前端可以弄個 JWT reader 就能知道是否 expired 不用戳 server 才知道
    2. user basic info 在 JWT 內有,類似 name & avatar 不用戳 server 才知道
  2. pkey 從 int 改為 rand UUID(包含 user_id)
    1. table(data) relation 重點是 relation 而非 pkey 的 format
    2. 不用 auto increment 不會有 global effect 所以 INSERT 會變快且平行處理
    3. 不用管連號的問題,而你也不該用 pkey 當作 sorting / ordering 的依據,用別的 column 會更好些,類似 nano sec timestamp (NOW) 也行
  3. 瀏覽量使用 background job 來執行,建立類似 5 sec 一次的 batch update
    1. 也可以用 logger parsing 的方式完成,如果有專用 logger server 的話
    2. 任何東西 batch update (batch INSERT) 永遠是最快的,包括 redis 的 mset 系列
    3. 大幅減少 latency 且保持回應的最高速
    4. 這樣的 logger / tracer 才不會影響到正常流程的速度
這些是基本思維了,保證瞬間提升 N 個量級,且 ... 我們可以安全地使用 microservice 架構了

舊時代的做法轉成 microservice 架構的問題會是 session <=> user_id 的解構部分,所以每個 sub service 都需要連 user table ... 因為要檢查 user_id 是否存在,他的 permission 有啥,有的沒的

但新時代的 microservice 只需要拿 JWT 為底時,不用驗證這個 user_id (通常也為 UUID 在 JWT 內) 是否存在,因為這 JWT 當初是自己簽的,只要簽章正確,則這個 user_id 一定存在,且一定信任 JWT 內的 permission list / user info 等等 (寫在 JWT payload fields 內) 所以整個系統就能完全解構,sub service 隨便寫,甚至建議每個 sub service 都有自己獨立的一套 DB 的( microservice 的真義 )

如果還要更安全些,能前置個 auth service 來做 JWT exchange,類似從 external JWT 轉換成 internal JWT,之後所有下級的 sub service 只吃 internal JWT 就能保障所有安全的部分了,這樣 external JWT 也不用流出太多 internal info,internal JWT 可以在 auth service 塞更多東西進去給 sub service 使用,這樣 sub service 也不用額外去 query user info 等等

剩下就是 batch 處理的哲學了,MQ + worker / cron background job / aggregate 基本上都是新時代的標準了,不管如何都用 batch 處理的話,一樣的 latency 減少效能增加,大家都開心,傷心的只有多一點點記憶體,何樂不為呢 ...

anyway 推薦給大家 : )

2024-08-15

2k / 4k kubuntu resolution fix

大概就是 ... 超高解析度配 kubuntu 的解法的紀錄罷了,一般安裝到使用應該沒問題,且 KDE 內可以可以自己條解析度,但其他地方幾乎都有超高解析度的問題,看了很難過很不爽,所以這邊把流程紀錄下來,這邊只寫 KDE 內建設定無法處理的問題

先在 grub menu 內按 c 然後輸入


> videoinfo
# list resolution from driver


抄下自己想要縮放的大小,類似 "1280x960x32" (這段一定要和設定值一模一樣,grub 只吃 driver 
resolution list 的設定,然而可能沒 "x32" 的部份,類似 "1024x768" 這樣也行),然後修正


> sudo vim /etc/default/grub
# add this at tail
GRUB_GFXMODE=1280x960x32
GRUB_GFXPAYLOAD_LINUX=keep
GRUB_TERMINAL=gfxterm
> sudo update-grub


再來是登入的畫面,kubuntu 用的是 sddm,可以先用 xrandr 指令試用,確定沒問題再寫入成 sddm 的 init script,這邊可以不照 driver 給的,我喜歡用 1440x900 因為我主要螢幕為 2880x1800 剛好 1/2 但 driver resolution list 內沒 
1440x900 這項,這邊就可以按照喜歡的改了


> xrandr
# show "eDP connected" & 1440x900 using

# test command (you can use custom resolution and test it)
> xrandr --output eDP --mode 1440x900
# make sure without any err

# update sddm init script
> sudo vim /usr/share/sddm/scripts/Xsetup
# add `xrandr --output eDP --mode 1440x900` at tail


sddm 到這邊結束,其他 dm 要看它自己的寫法了 ...
再來是 tty3~ttyN 也就是 Ctrl+Alt+F3 的那種,目前看來只能改字形大小


> sudo vim /etc/default/console-setup
# change FONTSIZE from "8x16" to "32x64"
> sudo stty --all


這樣應該就全部都好了,reboot 後應該正常順眼了才是,至於 tty 的解析度 ... 應該沒差,畢竟少用的,能正常顯示應該就好了,而如果未來發生需要資料救援的狀況,這邊可能會大噴死,反過來改應該就好了

2021-11-18

OpenWRT + Pi + PPPoE + VPN (static IP)

uncompress the img.gz to img , dd it to TF card

boot it , OpenWRT default IP is 192.168.1.1 from eth port , connect it , if no DHCP to get IP , set NIC to use like static ip 192.168.1.123/24 , and connect to 192.168.1.1

add a wan interface "wan" select PPPoE and device select "br-lan" , "Obtain IPv6 address" set Disable

set "LAN" IPv4 address to like "192.168.200.1" , you may want use different subnet

LED Configuration set led0 "kernel: heartbeat" and led1 "kernel: netdev" + "br-lan" + "Transmit + Receive" , the Pi led will be one heartbeat and one network status

then use this doc , this doc will keep continue upgrade to last version ... ssh to Pi and all copy paste to work haha

https://openwrt.org/docs/guide-user/services/vpn/openvpn/server

and if use Tunnelblick at macOS , import the .ovpn key and connect it , will get like "Unrecognized option or missing parameter(s)" , you need goto Tunnelblick settings , select the imported config , and set the "OpenVPN Version" to lastest version

have fun : )

2021-11-10

How to make FileZilla over 10 Simultaneous Transfers on FTP protocol

short answer : you can open multiple FileZilla ... haha ......

long answer : you may be BAN from ISP , because FileZilla will send 10+ connection per time , it will be very like DoS attack , so you need test how many connections is allow , and wait like 60 sec to open next FileZilla process , and set the retry connections after 60 sec (like "Delay between failed login attempts") , finally enable the "Send FTP keep-alive commands" , you don't want reconnect when it has limits

have fun : )

2021-11-05

event sourcing / MQ(websocket) 配 rest API 難題

這篇單純寫給有緣人,其實是自己的 memo 就是了

client => ws(pub) => MQ => process(sub => pub) => MQ => ws(sub) => client

這是很簡單的異步處理結構,但如果配上 rest 會 block 的特性,則會很尷尬,類似

client => rest(pub) => MQ => process(sub => pub) ??

如果 rest 端得不到回傳到底是有沒有完成了?且照這結構 rest 端要弄個 block timeout 才行,MQ server 如果不穩時或掉封包,會得不到 response 還後 timeout 到底,最終交易是否有完成其實也是未知

所以這邊有兩種解法才是,或是 1 + 1 解法

client => rest(blocked) => process(sub + rest)

也就是 API 需求直接戳到 process 去,該 process 要開 rest + MQ sub,然後在記憶體內 merge jobs,當然需要過濾只有 CRUD 中的 CUD 且過了所有檢查才能戳著,這樣 process 噴了至少還是會 return result

然後 process 可以不回正確的 response package,可以再由另外一個回覆,類似

client => rest(blocked) => proxy process(job[s]) => process(return uuid[s]) => proxy process(package result[s]) => rest => client

這樣子就算漂亮了,然後還可以繼續擴增處理,類似 ... client 可能只需要知道 uuid 則 process 可以做隔離進行,成為兩條完整的平行線

client => ... => insert process(return uuid , pub uuid need trade) => MQ

MQ => trade process(got inserted jobs , then process)

這樣做隔離的話,就能回 client 完整的 pk 且不會掉才是,當然凡事是有缺點的,類似 insert 的為空的 job 可能會有 balance 的問題,能夠不真實扣帳,而 trade 可以取消 job 的狀況下 ... 應該都還能接受就是,單純要進行取捨而已

2021-07-28

bcache

這邊單純的列出所有 bcache 指令

假設 /dev/sda3 為 ssd,/dev/sdb1 為 hdd

重建:

sudo apt-get install bcache-tools

wipefs -a /dev/sda3

wipefs -a /dev/sdb1

make-bcache -C /dev/sda3 -B /dev/sdb1

sudo mkdir /media/bcache

sudo mount /dev/bcache0 /media/bcache

觀察狀況:

sudo bcache-super-show -f /dev/sda3

sudo bcache-super-show -f /dev/sdb1

取得 ssd 的 uuid 塞到 sdb1 內去做 cache,最終兩個 cset.uuid 必須相同

echo 86cd72eb-3b75-4a67-866a-cdfc48e498ac > /sys/block/bcache0/bcache/attach

# 86cd72eb-3b75-4a67-866a-cdfc48e498ac 為 /dev/sda3 的 cset.uuid

取得目前是何種模式:

cat /sys/block/bcache0/bcache/cache_mode

模式有三種:

    writeback:先寫入 ssd 再寫入 hdd,適用於 hdd 過慢狀況

    writethrough:兩者 ssd 和 hdd 同時寫入

    writearound:直接寫入 hdd

修改模式:

echo "writeback" > /sys/block/bcache0/bcache/cache_mode

cache 狀況:

cat /sys/block/bcache0/bcache/state

    如果回 dirty 反而正常,有 cache 尚未回寫的意思

強制回寫內容:

echo 0 > /sys/block/bcache0/bcache/writeback_percent

回復強制回寫(預設為 10):

echo 10 > /sys/block/bcache0/bcache/writeback_percent

最終 /dev/fstab 增加

/dev/bcache0 /media/bcache ext4 rw 0 0

    請使用 mount -a 做測試

2020-11-22

M5Stack Core2 build from PlatformIO with VScode (tried official default code)

hmm ... first , you may need install USB driver (CP210x) from official site (test at macOS)
https://docs.m5stack.com/#/en/arduino/arduino_core2_development

create {new PlatformIO project} , choice board "M5Stack-Core"(no Core2) , then fix the [[ platformio.ini ]] like this
[env:m5stack-core-esp32]
platform = espressif32
board = m5stack-core-esp32
framework = arduino
; [[[for macos]]]
;upload_port = /dev/cu.SLAB_USBtoUART
; [[[fix PSRAM size and you won't need have this file]]]
board_build.partitions = default_16MB.csv
build_flags =
    -DBOARD_HAS_PSRAM
    -mfix-esp32-psram-cache-issue

{M5Stack Arduino libs}
https://github.com/m5stack/M5Core2

{M5Stack Core2 default factory code}
https://github.com/m5stack/M5-ProductExampleCodes/blob/master/Core/M5Core2/Arduino/Core2_Factory_test/Core2_Factory_test.ino

to build this , you need clone it and put [[ Core2_Factory_test/* ]] to [[{new PlatformIO project}/src/ ]] , rename(replace) [[ Core2_Factory_test.ino ]] to [[ main.cpp ]] and add
#include <Arduino.h>
at first line , then copy [[ {M5Stack Arduino libs}/src/* ]] to same folder (or some way to include it)

and some ref here :

{some examples}
https://github.com/m5stack/M5-ProductExampleCodes

{PlatformIO custom settings doc for ESP32 , include custom partition way}
https://docs.platformio.org/en/latest/platforms/espressif32.html

{ESP32 for Arduino partitions settings}
https://github.com/espressif/arduino-esp32/blob/master/tools/partitions/default_16MB.csv

have fun :)

2020-10-28

金流與帳務系統開發心法

首先,這篇一定是對的,最多有缺漏些東西而已,掌握此心法來做系統開發,不管程式設計師,系統安全,會計等等,基本上都會滿足需求

以下名詞盡量通用,所以使用"資料集",你開心可以替換成 Table / Collection 之類的單位



心法零:四個人打麻將,打了三天三夜,總額不變

任何交易系統都是,從最小的購物車到交易所的撮合系統,全部都是



心法一:使用"搬移"的方式來做帳,所有金流都有來源與對象,如果沒有,就虛擬出來

假設一個情境:『ATM 入帳給使用者,則建立一筆單,然後使用者的餘額增加』

所以通常用很簡單的直覺來規劃一個系統,通常會有兩個資料集,"入帳紀錄"與"使用者餘額",但這邊就缺了一個對象,"銀行"

銀行>入帳紀錄>使用者餘額

如果當下完成並直接入帳 100 元,則銀行餘額變動為 -100 使用者餘額變動為 +100,這邊有個好處,就是你不用去統整所有入帳紀錄,就能得知全站所有使用者餘額絕對不會超過銀行總扣款,所以你可以從使用者餘額總數出發,從入帳紀錄出發,從銀行餘額出發,三方驗證來檢查這件事情是對的,要簡單就用頭尾即可,那麼繼續往下推

假設『銀行入帳需要人工審核,才能進行使用者加值』的狀況,然後發生了些 ... race condtiion 的問題,幫使用者重複入帳

銀行>入帳紀錄>入帳紀錄(帳務變更狀態)>使用者餘額

所以銀行匯入後,該把 100 元金額從"銀行"移至"銀行_{UserID}_未實現"(或是把UserID換成InvoiceID),等帳務驗證後,再從"銀行_{UserID}_未實現"移至"使用者餘額"內,最後正常結果會與上個 case 相同,但這樣有什麼好處?

如果後台寫爛了,連點兩次幫使用者加了兩次款,則其實是『"銀行_{UserID}_未實現"移至"使用者餘額"』這個過程重複執行兩次,則"銀行_UserID_未實現"這個項目的數值會是負的 ... 這樣應該瞬間能稽核出異常的部分才是,因為假設因為系統問題,使用者重複儲值成了 200 元,全以搬移的方式進行,一定會在某個中介狀態或來源為負值,所以"中介狀態"或是"未實現"或是會計原則中的"在途"的數值就變得異常重要,而非只有單純的搬移概念而已

以這心法應該能推到非常多的實作,類似把任何的餘額變動全部存起來,有來源有對象,則整理出任何一個報表,任何時間點切入應該都變得沒問題才是,且多個面向可以彼此稽核,也會滿足會計與安全的角度才是,稽核的速度也會變得超級快



心法二:自我稽核,串鏈與自省

餘額修正時可以留下一個簡單的 crc32 做檢核碼(請加上固定或變動的 private / salt 字串),類似 "twd" 與 "twd_sum",當然如果多幣別則可以把 checksum 統整為同一個欄位,這樣的好處能防止任何的 injection 攻擊,下個要變更餘額的動作時,必須要先驗證 checksum 後才能進行交易,而歷史資料內上下筆內文,可以如同區塊鏈般取出特徵值後,把目前的結果放在下一筆資料內,保持串鏈連結不被打破,類似 checksum("{ID}_{UserID}_{Amount}_{上一筆的checksum}") 放在某個欄位,這樣就不怕某些資料順序被互換,且順序可由 checksum 得知,以上兩者都完成時,你就不用怕有任何的 injection 和做帳狀態被修正才是



心法三:用新的資料(insert / new item)來解決所有問題

在人工審核的 case 內,假設一個訂單 id 1234 修正狀態從"未處理"改成"完成",但其實你無法保證"完成"前面是否曾經是另外一個"未處理"和另外一個"完成"(race condition>重複入帳),所以這個的重點應該會類似於,新增完成後的任何資料應該都必須是鎖定的狀態,中間如果有變更的行為,會有個虛無飄渺的感覺存在那邊,所以建議使用子資料集來做變更,類似 id 1234 變動時,都必須在另外一個子資料集內有另外一個紀錄,則該子資料集就有所有的變動順序等等,或是全部使用新增另一筆資料來覆蓋原本的資料,類似 parent_id (原始資料是誰) , is_masked (是否已遭到覆蓋) 的方式來進行完成



心法四:給自己留後路,每個值變動前存入上次的結果

在某個使用者一千萬筆資料集面前,已知其中有一筆資料的誤差值為 1,請找出該資料,基本上以上面所有實作還不夠,你還缺了一個帳本的概念,就像銀行的存款簿,都會寫上次餘額(上一行的結餘)、此次修正(存提)、此次結餘,所以如果要完成這個條件,你勢必在任何一張單,有做任何餘額變動時,都必須存下它的上次或此次的最終結餘為多少(這邊包含任何的"在途",因為它們也是餘額的一種),這樣用頭尾兩張單還有自我比對就能比對出那筆誤差的單才是




心法五:分散資料集,排序很重要(使用時間,或是序列完成)

系統一定會擴張到某個很恐怖的程度,系統會分散,而會變動使用者餘額的帳也會到處跑到處長,而這如何收斂?甚至還要歸出整個順序才行?重複怎麼辦?

"時間"很重要,分散式系統中每台主機都不準,所以這邊一定要留存中心伺服器給的時間,通常是 DB 時間,類似 MySQL 可以用 "CURTIME(4)" 取到小數點下四位的時間,其他 DB 甚至可以留存下到 nanosecond 等級,任何交易變動的時間都必須要留存下來(留存不下來的代表你違反"心法三"的規則,請用別的 column 或新的資料來完成)這樣就能很方便的以時序拼回來才是,當然假設你們家系統很小,或是這類交易事務不夠頻繁,則集中用同一個資料集,應該就能完成整個排序的維持這樣就最好了



目前的心得大概就是這些唄,有額外的心得會在進行補充,你不用完成所有的項目,可以抽出認為 ok 的來完成即可,或是使用此心法來完成一道道的內外部防火牆來提前告警哩



可能比內文還重要的補充:(有任何問題都能提出,我會用補充的方式繼續補下去哈哈)



1. 心法一的例子中 "移至"銀行_{UserID}_未實現" 這個欄位勢必會一直修正,因為訂單會 merge 此在途餘額,且以本文規則而言,每次修正都必須存下上次修正的過程與結果,所以可以改為 "銀行_{InvoiceID}_未實現",甚至是

"銀行_{InvoiceType}_{InvoiceKind}_{InvoiceID}_{OwnerType}_{OwnerID}_未實現"

就能大大的減少這事情,這問題的根本原因其實是在途金額的唯一性對唄?因為一張訂單的完成,通常只有成功和失敗,至於需要補單或是部分退款,則可以是先成功後,再打退款紀錄,老實說應該沒有所謂入帳一半的甚至需要修正之類的(同一訂單部分商品取消,其實也能使用先退原單後,再新增部分單完成,然後註記 ID 為原始 ID 就能完成需求),單純在途餘額欄位會變成 N 個,但我認為這對 DB schema 設計來說並不會是困難點才是



2. 心法五的例子中,時間其實只是排序方式,假設有其他欄位,類似 pkey / uuid / serial ... 能完成的話,就拿那個就好了,然而如果沒這前提,繼續假設 DB 為分散式系統,勢必每一台 DB 時間都不準,然而這話題需要分割兩個部分,首先要從 transaction 角度為起點,如果該 DB 設計是所有 transaction 都從同一台來執行的話 ( RDBMS 系列,還有部分 noSQL 實作) 則你應該不用擔心這問題才是,然而如果還是會發生的話,就要自己寫 sort 演算法了,所有的帳應該都可以 merge 在一起在任何一個時間點,則可以找個資料集用 merge 的方式一直算下去,如果發覺結果完全正確單純順序錯帳的話(配合心法四能自省找到錯帳),應該只有 sort 有問題,通常 sort 有問題的深度應該不會超過 10 筆才是,10 筆的前提會是同一使用者在同一幣別同時操作超過了 10 次,然而這問題真正的主因應該會類似:大量批次處理新增資料時順序塞錯了,或是某台主機時間根本就沒有 sync 到 ... 而用遞迴或是一些演算法應該就能重新 sort 完成才是



以上

2020-06-10

來源不明的 image 的 dd 後處理

首先拿到來源不明的 image 通常都先 dd 到對象目錄去
下次應該都會類似 fdisk / resize2fs 連續技

fdisk 可以用 x / i / r 的連續技來改 PARTUUID,輸入類似 0xaaaaaaaa 

之後就糟糕了 ...

通常來源不明的 image 都是吃到 99% 所以掛進去後 root 保留空間預設為 5%
則會變成 readonly 的模式,所以先

tune2fs -r $((10*1024*1024/4096)) /dev/sda1

4096 是 block size & 10*1024*1024 = 10MiB,結果就是能解放真實可用空間


最後務必還要要跑 fsck / e2fsck,這樣能解決很多問題才是

2020-02-24

dd image 重建



sudo -sH
losetup -Pf disk.img

fdisk -l #可以看到硬碟了,目標在 loop4p

mkdir /mnt/loop4p2
mount /dev/loop4p2 loop4p2

# 先清一些垃圾
cd /mnt/loop4p2
rm var/log/*.gz var/log/*.1 var/cache/apt/archives/*.deb

dd if=/dev/zero of=/mnt/loop4p2/empty
# df 可以看到硬碟慢慢減少

rm /mnt/loop4p2/empty

# 記下 df 有多少,類似
# /dev/loop4p2   122851588 113626940   6090084  95% /mnt/loop4p2
# 則目標為 113626940,這次應該希望 120000000 就好,丟過過去再擴容

umount /mnt/loop4p2/empty

e2fsck -f /dev/loop4p2
resize2fs /dev/loop4p2 120000000s

dd if=/loop4 of=/dev/sdc

# fdisk 刪除後新增 = 擴容,可以從 table 層 (DOS / GPT) 開始重建

# e2fsck -f /dev/sdc1
# resize2fs /dev/sdc1

losetup -vd /dev/loop4p2

2019-05-17

BTC node 的 block 倒退手法 ...

工作上需要,單純列出隱藏指令

invalidateblock
reconsiderblock

一個後退一個前進,應該可以解決大部分末尾 block 錯誤的問題,然後重新 sync 即可

2018-05-21

Ethereum smart contract / ERC20 補遺

大概就是工作的需要 ... 此處單純解釋

首先 ... 如果全世界有個程式只能寫一次,之後無法再修改,只能發佈新的程式時,會引發怎樣的災難和困擾? ... 單純這邊列下買保險的做法

A.所有 transaction 都必須確認 eip658Transition 上的 status (使用 eth_getTransactionReceipt => status 確認 0x1(完成) or 0x0(失敗) [下簡寫為 status])做為是否有完成 contract 的首要條件,否則沒有任何地方可供判斷,因為 transaction 入 chain 後就不會刪除,不同於 bitcoin 會刪除或是隱藏有問題的交易,且即使使用了 EVM 也無法追蹤實際運作細項(類似誰多了錢誰少了錢),多重交易時無法判斷哪個 transaction 是否完成,因此只能使用 status 來做判斷(案例 ... 對手商 ...)

B.不管如何上 SafeMath 就對了,不用判斷大小,而是直接上 SafeMath 後直接運算,簡單來說實際運作的 contract code 內不應該有比較運算(大於小於之類的運算),而是直接用 SafeMath 中的類似 .add / .sub / .div 之類的運算 function 做包裹,而比較運算只能在 SafeMath 中出現,因為當出事時,SafeMath 內直接會 require(false) 而直接噴走,並自動把 status 改成 0x0(案例 ... BatchOverFlow ...)

C.contract code 內的 return false 並不會把 status 改成 0x0,事實上只有 require(false) 或是 gas 不夠之類的才會改,其餘全部都是 0x1,所以請勿使用 return false (舊的寫法是 throw,新的寫法是 require(false),status 真實代表只是程式是否有"正確完成"而已,但並不代表交易是有效的,因此把交易是否有效反向綁定在程式是否由正確完成上,即可互相觀察)

大概是這些,有想到再繼續補下去 ......

2018-04-30

RDBMS 的 lock 更新前置計算或然率

首先,自己一般的寫法

SELECT ... FROM items WHERE id = 123
#前置檢查 status 或其他欄位,可免除 lock 成本,甚至可以快取還有額外運算
SELECT ... FROM items WHERE id = 123 FOR UPDATE
# row base lock 開始
# a = a + 1
# 很慢的 function 要處理,處理完後會塞回原值或其他欄位
UPDATE items SET ... WHERE id = 123


但其實可以改成

SELECT ... FROM items WHERE id = 123
# 前置檢查 status 或其他欄位,可免除 lock 成本,甚至可以快取還有額外運算
# ori_a = a
# new_a = ori_a + 1
# 很慢的 function 要處理,並暫存於其他欄位
SELECT ... FROM items WHERE id = 123 FOR UPDATE
# row base lock 開始
# if ori_a == a #額外判斷成本
#   a = new_a
#   直接把暫存欄位也直接塞入,免除計算
#   #賺到的部分
# else
#   重新處理:很慢的 function 要處理,處理完後會塞回原值或其他欄位
#   #虧的部分
# end
UPDATE items SET ... WHERE id = 123


原因是 lock 有其餘 service 的交互影響,這樣做就可以把 lock 在一定機率內最小化,且命中率 50% 時應該全都是賺的,而虧的時候只賠了一個 if 的判斷(或多個?)重新計算則幾乎等於原本成本 ... 原本 DB 的 lock 成本要等待所有單一主機流水式的算完,而省略成碰撞後才需要之類的

anyway 這思維還滿酷炫的哈哈,或許可以考慮實作?(主要節省「很慢的 function 要處理」可以盡量不在 lock 內完成 ......)且還要測試 if 的成本是否高於該成本才是 ......

2017-11-12

convert image to Arduino OLED ( Adafruit GFX Library & black white )

大概就是在做奇怪的東西哈哈,首先 imagemagick 轉圖很好用,很多詭異的格式可以轉,其中一個是 xbm (X BitMap),其實就是 xwindow 內跑 C 語言然後載到記憶體內的鬼,格式大概長這樣

#define filename_width 24
#define filename_height 24
static char filename_bits[] = {
  0xFF, 0x3F, 0xFF, 0xFF, 0xDF, 0xFE, 0xFF, 0xEF, 0xFD, 0xFF, 0xF7, 0xFB,
  0xFF, 0xF7, 0xFB, 0xFF, 0xFB, 0xFD, 0xFF, 0xFD, 0xFE, 0xFF, 0xFE, 0xFE,
  0xFF, 0xFD, 0xFD, 0xFF, 0xF3, 0xFD, 0xFF, 0xFB, 0xFE, 0xFF, 0x7B, 0xFF,
  0xFF, 0xBD, 0xFF, 0xFF, 0xDE, 0xFF, 0x7F, 0xDF, 0xFF, 0xFF, 0xBE, 0xFF,
  0xFF, 0xDE, 0xFF, 0xFF, 0xEE, 0xFF, 0x7F, 0xF7, 0xFF, 0x7F, 0xFB, 0xFF,
  0xBF, 0xFD, 0xFF, 0xBF, 0xFE, 0xFF, 0x5F, 0xFF, 0xFF, 0x9F, 0xFF, 0xFF,
  };

因為是記憶體直接對照,所以就是 memory buffer 的格式了,其中的 filename 處為檔案名稱,會直接換成變數名稱,但如果直接出 stdout 所以該處會變成 -

這 code 其實 Arduino 中的 Adafruit GFX Library 可以直接吃了,不過需要加工一下

其中 char 要轉成 uint8_t 型態,而黑白顛倒(對 OLED 而言亮的是黑色),所以要 xor 255 也就是把整個 binary 整個反轉過,而 char 轉 uint8_t 時每個 set 都是顛倒的,要換回去才行

完成的程式長這樣,有需要的人可以拿去用哩

# [[Ruby code]]
# need imagemagick , convert all images(gif) & merge to output.txt

target = File.open('output.txt' , 'w')

filenames = Dir["*.gif"]
filenames.each do |filename|
  var_name = filename.split(/\./)[0..-2].join('_')
  content = `convert #{__dir__}/#{filename} xbm:-`

  #re-define var name & change type to uint8_t
  content.gsub!(/define \-/ , "define #{var_name}")
  content.gsub!(/static char -_bits/ , "static uint8_t #{var_name}_bits")

  #inverse color && swap bitmap buffer
  content.gsub!(/0x[\dA-F]{2}/) do |source|
    source = (source.to_i(16).to_s(2).rjust(8 , '0').reverse.to_i(2) ^ 255).to_s(16).upcase
    "0x#{"0" if source.length < 2}#{source}"
  end
  target.puts(content)
end

target.close

puts "finished , total #{filenames.length} files"

之後把 output.txt 的內容複製貼上到 IDE 內就可以直接使用了呦,超方便的哈哈

2017-06-13

童話鎮的勇者

很久很久以前,有個童話鎮,然而距離童話鎮幾十公里外有個魔王城

童話鎮長期以來被魔王城跑出來的魔物侵擾,破壞莊稼,調戲民女,但缺乏領導人物,村民也不想抵抗,想辦法討好魔物們,雖然有損失但彼此相安無事

有一天童話鎮來了一個外人,穿著非常破爛,看似經歷過非常多的戰鬥,身上充滿著殺意與鬥氣,來到鎮上後看到鎮上的狀況,把所有的魔物都趕跑了,並佇立在鎮門口,魔物看到都嚇跑了

鎮長知道這一切後就邀請那個外人,住進了鎮內,去除破爛的外表,穿著光鮮亮麗有威嚴的服裝,並稱他為勇者,而鎮內因為沒有魔物的侵擾,從童話鎮變成了童話市,最後發展變成了童話城,鎮長也變成了城主

童話城其實不時的還是有魔物的侵擾,所以城主命令勇者建立了護衛軍,希望勇者把技術都傳給手下們,然而城主也希望勇者能褪去殺意與鬥氣,和善的對待村民,城主也希望能有定期報告,得知近況

勇者盡量的符合城主的期待,護衛軍雖然人數少,但盡忠職守不眠不休地防止侵擾,護衛軍內人人都想變成勇者,完成相同偉大的任務,勇者抑制了自己的殺意和鬥氣,學會如何談笑風聲,變得健談,定期彙報狀況給城主,安定了好長一段時間

但勇者漸漸感覺自己已經不再是當初的那個自己,沒有了鬥氣和殺意,長期過著和平的日子,好像少了些什麼 ......

城內過慣了和平,當民眾遠遠的看到了魔物,責怪護衛軍為何不去處理,護衛軍去追魔物時,魔物一溜煙就跑了,平常的訓練常常就這樣空缺了荒廢了,民眾看到多次魔物後,回報給城主,城主問勇者說:『護衛軍不是你在帶的嗎?為何有這種事情發生呢?』

勇者為了滿足城主的需求與旁邊大臣的建議,建立了多個哨點,把所有護衛軍都派到哨點去,連勇者都去哨點了,而城內一個護衛軍也沒有,當飛行系的魔物,如獨眼蝙蝠、獨角翼龍等等的魔物飛到城內作亂時,勇者與護衛軍會接到飛鴿傳書,指示要回城內平亂,但哨點與童話城相隔半天的路程,所以來回一次一天就什麼事情都沒做到了,更別說疏於平常的練習等等

有一天,前線的哨兵帶來一個非常不好的消息,魔王城的等級提升了

城主知道後找來勇者問說該怎麼辦,勇者說:『我們可以把護衛軍全部從哨點叫回來,組成勇者騎士團,去攻打魔王城』,城主說不行,因為週邊還是要顧,勇者說:『那我自己去吧,不過我需要練習和準備的時間』,城主勉強的點頭同意

勇者在找回過去的自我的期間,升級過的魔王城果然不是蓋的,一次就是十多隻的飛行魔物,護衛軍因為都還很遠在當哨兵,所以城主直接找人去勇者家中找勇者來,勇者大劍一輝,沒事了,回家繼續修煉,民眾看到了很崇拜勇者,但卻也養成習慣,看到大隻一點的鳥就會跑到勇者家嚷嚷,勇者到現場定眼一看,品種不對,還要放下剛剛在家的殺意和鬥氣,拿出過往的笑容對民眾解釋該品種不是魔物,不會有任何干擾民眾的行為

城很大,民眾很多,所以勇者多少次忘記自己是正在醞釀屠城的氣魄,反而都在服務著民眾,逐漸地喪失自我 ... 護衛軍每一個人也都想成為另一個勇者,但卻也都在哨塔站哨 ... 不時的還會被怪罪:為何你不會打魔物和攻打魔王城?

有一天,勇者出發了,前往魔王城,出發前要去拿武器,發覺兵器庫沒兵器了,原來是長久以來城內維運用光了,勇者心裡想:『罷了,這次前去,完成這一切,武器應該都用不到了』,一路走經過哨點,哨兵給予著勇者莫大的祝福,勇者信心滿滿,當攻破了第三個魔物的要塞後,準備觸碰到魔王城門,勇者回頭 ... 回到了城內 ......

原來是剛剛勇者收到了飛鴿傳書,上面用紅色的紙,上面寫著三個"急"字,勇者怕出事所以回城一趟,結果是旁邊的大臣說想知道最近的抗魔物花費多少,想知道最近的資料,不管怎樣都要第一個回報,因為要抓到最近的量來做準確評估,另外一方面魔王城再遠一點的地方還有另外兩個魔王城,請『順便打一打』,對你來說很容易的不是嗎?城主聽了很有道理,點頭同意

勇者知道後又花費了一段時間,將裝備和心態升級,重新打了第一個魔王城的三個魔物的要塞,摸到魔王城門時又收到飛鴿傳書,又是紅色的紙,又是三個"急"字,勇者擔心又回頭了,結果是旁邊的大臣提議要增加錄影機來增加城內的安全,防止財庫被偷,要勇者同意才能進行,城主聽了很有道理,點頭同意,並質疑勇者如果沒有辦法攻下魔王城時該怎麼辦的問題

此時,勇者站在一個人生的十字路口,分歧世界分歧劇情

A:勇者選擇了離開這一切,離開童話城,離開前城主說:『你忍心拋下這一切嗎?當初為了你建立這整套制度,如果你離開我會解散護衛軍!!!』,勇者最終離開了,童話城內聚力不足,崩解四散

B:勇者裝傻,拋棄掉殺意與鬥氣,單純的服務民眾,結果幾年後三個魔王城合併成一個超大的魔王國,以武力把童話城納為己有

C1:勇者就此消失匿跡,來到鄉間山上的一間房,不理會任何飛鴿傳書與凡間的侵擾,拋下所有過去,但尚未沉著心就出發了,只消滅兩個魔王城,回到城內,獲得城主和大臣的賞識,不過一旁的大臣小聲碎碎念:『不是說好消滅三個嗎?為何只有兩個?』

C2:勇者就此消失匿跡,來到鄉間山上的一間房,不理會任何飛鴿傳書與凡間的侵擾,拋下所有過去,修得最終無劍的技能,一舉成功消滅三個魔王城,回到城內,獲得城主和大臣的賞識,但城主和大臣心中感覺:『這不是應該的嗎?這就是勇者該做的事情啊,花了那麼多時間不是理所當然的嗎?』

D:勇者就此消失匿跡,另外一個外人來到童話城內,在勇者修煉期間滅了三個魔王城,勇者發覺沒城可滅,遠走他鄉

==========

我不是勇者,但我正做著勇者的事情,沒人可以幫助到勇者,而人人卻都可以是勇者 ... 或是幫助勇者的人 ...... 如果你懂 MIS 和 RD 的分別,請你拆開而非混用,不然再強的勇者都會離開 ...

然後,順便聽個音樂唄:

https://www.youtube.com/watch?v=ijRH_84lIQQ

https://www.youtube.com/watch?v=GlFIfw7kadY

2017-02-10

phantomjs , web crawler 的最終解

大概就是另外一群團隊需要,一直在找尋 web crawler 的最佳解,尤其面對未來 react / angular / vue 之類的 AJAX 的 web content 時,傳統人工光是分析 AJAX 行為模式就花費太多時間,更別說還要處理別的事情之類的

最完美的方式莫過於類似 wkhtmltopdf / wkhtmltoimage,開一個 webkit 的 browser 將網頁完整下載,並執行 CSS / JS / AJAX 後所剩下的 dom tree 轉為 source code,該 HTML 就是真正顯示到使用者螢幕上的資訊,也才是最終想要的成果

之後才看到 phantomjs,稍微練了一下,效果不錯,缺點後述

/*
  指令使用
  ./phantomjs --web-security=false --ignore-ssl-errors=true --load-images=false hello.js
  你可以增加 --debug=true --max-disk-cache-size=1000 來做 debug
*/

console.log('[[initialize]]');

//lib,等待 ajax call,判定 dom 的產生時機
function waitFor(testFx, onReady, timeOutMillis){
  var maxtimeOutMillis = timeOutMillis ? timeOutMillis : 10000, //最長 timeout 時間
    start = new Date().getTime(),
    condition = false,
    interval = setInterval(function(){
      if((new Date().getTime() - start < maxtimeOutMillis) && !condition){
        condition = testFx();
      }else if(!condition){
        console.log("...下載太久timeout或判定來源失誤");
        phantom.exit(1);
      }else{
        //console.log("'waitFor()' finished in " + (new Date().getTime() - start) + "ms.");
        onReady(condition);
        clearInterval(interval);
      }
    }, 250); //0.25一次巡迴
};

//這個來源其實可以從別頁來才是
var source = [
  'http://24h.pchome.com.tw/store/DBAC9R','http://24h.pchome.com.tw/store/DBAC9S',
  'http://24h.pchome.com.tw/store/DBAC9T','http://24h.pchome.com.tw/store/DBAC9U',
  'http://24h.pchome.com.tw/store/DBACA0','http://24h.pchome.com.tw/store/DBACA1',
  'http://24h.pchome.com.tw/store/DBACA2','http://24h.pchome.com.tw/store/DBACG7',
  'http://24h.pchome.com.tw/store/DBACEA','http://24h.pchome.com.tw/store/DBACFP'
];

var page = require('webpage').create();
var start_at = (new Date()).getTime() / 1000;

console.log(start_at);

var process_me = function(url){
  console.log(url);
  page.open(url , function(status){
    //下面這行顯示整頁的HTML
    //console.log(page.content);
    if(status !== "success"){
      console.log("斷線了??或是該網頁連線錯誤");
    }else{
      waitFor(function(){
        return page.evaluate(function(){
          var temp = []
          jQuery('#ProdGridContainer dd').each(function(index,dom){var t = jQuery(dom) ; temp.push([t.find('.prod_img img').attr('alt') , t.find('.price .value').html()])});
          return temp.length > 0 ? temp : false;
        });
      },function(ans){
        for(var i = 0 ; i < ans.length ; i++){
          console.log(ans[i][0] + " : " + ans[i][1]);
        }
        console.log('總數 : ' + ans.length + ', cost(sec) : ' + ((new Date()).getTime() / 1000 - start_at))
        
        start_at = (new Date()).getTime() / 1000;
        
        if(source.length == 0){
          console.log('結束惹')'
          phantom.exit();
        }else{
          process_me(source.pop());
        }
      });
    }
  });
}

process_me(source.pop());


裡面有執行方式,phantomjs 本身包含了 webkit 瀏覽器,所以要去官方網站下載各平台的執行檔才行,好處是相依性都包在裡面了不用另外處理,其餘請看註解

phantomjs 因為是 JS 控制瀏覽器,所以外面大部分的 code 都是針對 webkit 瀏覽器的操作,而 page.evaluate 這個 function 會在 webkit 的網頁內執行,很像是開了 chrome dev tool 然後把 code 貼上的感覺就是,所以腦袋裡面要分開操作瀏覽器的 JS 和在網頁裡面執行的 JS 兩個角色,而對方網頁有載入 jQuery,當然在 page.evaluate 內就可以用 jQuery,但其他地方無法就是(也不需要),然而記得從裡面傳出資訊到外面的 JS 時,一定只能用基本型別(上面的 return temp),否則 phantomjs 會把 prototype 之類的打光光包光光,會非常非常緩慢且肥大,這邊要注意就是

裡面建了一個 waitFor 的 listener,功用是等待 AJAX 後 dom 的生成,所以輸入兩個值,第一個 func 會 return temp || false,當不為 false 時則判定 dom 生成,該 temp 會傳到第二個 func 變成 ans,也就是取得後要做啥事情的意思而已,概念很簡單就是

所以以上,完美的機器人最終解法整套超漂亮的,然而缺點是所有的 JS / CSS 應該都會被下載回來,所以和傳統爬蟲會多 x N 隻的 JS 下載,和執行的等待時間,這花費的基數其實有點高,所以...看似完美解,但其實能不用就不用唄

事實上還有高級做法,類似把對方網址所有的需求先過一層 proxy,把對方的 JS / CSS 先 cache 起來,則可以大幅減少下載和等待的時間才是,不過這邊等待 dom 生成的時間還是省不了,所以保守估計還是會慢一般傳統做法的 20 倍左右唄

okay anyway 這邊大概就這樣而已,也當作自己的 memo 就是了 ...... 然而實際上不用 phantomjs 的話,還有非常多種"邪惡"的爬法,不過我怕我公開後被抓去關,就先略過唄 X"D

2016-09-12

GnuPG 簽署

大概就是最近開始實作加密系統,有一個特殊需求,類似 HSM ( Hardware security module ) 中的多把 key 的實作,舉例來說 master key 總共有 3 把,每一把都能解密,而線上機用其中 1 把,而另外 2 把備存的方式

看了很多演算法,類似 AES ( 對稱加密 ) / RSA ( 非對稱加密 ) 都無法滿足實作需求,最後轉到了 OpenPGP 身上,而 Linux 已經有實作叫做 GnuPG,或稱為 GPG(PGP vs GPG ?? 這一定是某種玩笑 X"D), GPG 本身是非對稱式加密

以下貼一些之前整理的使用方式

  #JokerCatz = key_name
  #產生公+私金鑰
gpg --expert --gen-key
  #匯出公私鑰
gpg --armor --export JokerCatz > public.asc
gpg --armor --export-secret-keys JokerCatz > secret.asc
  #匯入公私鑰
gpg --import secret.asc
  #編輯#裡面很囉唆
gpg --edit-key JokerCatz
  #公私鑰列表
gpg --list-keys
gpg --list-secret-key
  #移除公私鑰(自產金鑰 = 公 + 私,要移除的話要分別移除)
gpg --delete-key JokerCatz
gpg --delete-secret-key JokerCatz
  #加密
  #--trust-model : 信任非第三方公正者發的key,自產大概都要加這個
  #--yes : 同意覆蓋檔案
  #多個 --recipient KEYNAME 則用多把 public key 來 sign,且任何一把 private 都能解)
gpg --trust-model always --yes --encrypt --recipient MEOWs --output temp.txt.gpg temp.txt
  #解密,使用private key(不用指定key)
gpg --decrypt --output temp.txt temp.txt.gpg

實際就大概這樣用而已唄,而不像 AES / RSA 本身是 key string or key file,GPG弄了之後很類似 global key pool 所以使用時都直接指定名稱即可,而 key 轉移到其他主機,則用匯入匯出即可

再來,其實 gpg 支援 pipeline,所以可以用在備份即時壓縮上,類似這邊所說的

https://lists.gnupg.org/pipermail/gnupg-users/2008-December/035168.html

my-pipeline-that-streams-data | gpg -o output-file.gpg --encrypt

嗯,超帥氣,這樣連中介檔案都不用留的 所以 mysql 備份也可以這樣來玩,類似
mysqldump --opt --all-databases | gpg --trust-model always --yes --encrypt --recipient JokerCatz --output mysqldump.sql.gpg
而其實 GPG 在簽章的同時應該有同時使用 gz 壓縮就是,所以不用二次壓縮,而這邊和 HSM 相比只差沒辦法把 private key 丟在破壞即失效的硬體內唄,以上

2016-08-29

Mi Note (Android 6.x) Root from OSX

簡單解釋,首先安裝 ADB 工具,內容包括 adb 指令和 fastboot 指令
類似用 brew 安裝

http://stackoverflow.com/questions/31374085/installing-adb-on-mac-os-x

或是用指令安裝

http://wccftech.com/set-android-adb-fastboot-mac-os/

再來把手機切到 fastboot 模式,然後

$ fastboot devices

會列出連結的手機

之後就可以使用這篇的教學

https://xiaomi.eu/community/threads/how-to-root-the-mi-note-virgo-on-marshmallow-6-2-18-6-2-4.29952/

用 TWRP 開機後使用 TWRP 刷入 SuperSU(建議先下載到手機內)

============

如果是要重刷整隻手機,一樣在 fastboot 內看到該手機後,把線刷包解壓縮,直接執行裡面有的 sh 檔即可

flash_all.sh 全清空,包含所有的內容
flash_all_except_storage.bat 清空 User 但不會清空虛擬 SD 卡的樣子
flash_all_except_data_storage.sh 保留 User 和虛擬 SD 卡

這邊選擇使用,就這樣而已,這邊單純自己用的 memo,以上

2016-08-05

Raspberry Pi as OpenVPN 暫存

此處待整理


新版使用

https://github.com/StarshipEngineer/OpenVPN-Setup


Pi3

#/etc/modprobe.d/raspi-blacklist.conf
#wifi
blacklist brcmfmac
blacklist brcmutil
#bt
blacklist btbcm
blacklist hci_uart

Pi3 remove wlan

sudo apt-get purge wireless* wpasupplicant bluez*
sudo apt-get autoremove

Pi3 rotate screen and remove colorful square

#/boot/config.txt
avoid_warnings=1
display_rotate=2

Pi3 disable screen blank

#/etc/kbd/config
POWERDOWN_TIME=0
BLANK_TIME=0

addon up / down / auth script


2016-06-20

sleep sort...

just code

[[sleep_sort.rb]]
ARGV.each{|e|fork{sleep(e.to_f/10);puts e}}

use

ruby sleep_sort.rb 1 9 2 8 3 7 4 6 5 0
A...... 這不會出事咪X"DDD

來源:http://m.blog.csdn.net/article/details?id=8514088

2016-05-27

Ruby : 有趣的 卍用 trace obj

just...a funny code

module W卍
  def self.method_missing(method_name , *argv , &block)
    puts "======vvvvvv======"
    puts "name : #{method_name}"
    puts "argv : #{argv}"
    puts "block : #{block}"
    puts "======^^^^^^======"
    return W卍
  end
end

# W卍[123].new.map{|i|i}.uniq.join(W卍).each do |i| puts i ; end
#=> ======vvvvvv======
#=> name : []
#=> argv : [123]
#=> block :
#=> ======^^^^^^======
#=> ======vvvvvv======
#=> name : new
#=> argv : []
#=> block :
#=> ======^^^^^^======
#=> ======vvvvvv======
#=> name : map
#=> argv : []
#=> block : #
#=> ======^^^^^^======
#=> ======vvvvvv======
#=> name : uniq
#=> argv : []
#=> block :
#=> ======^^^^^^======
#=> ======vvvvvv======
#=> name : join
#=> argv : [W卍]
#=> block :
#=> ======^^^^^^======
#=> ======vvvvvv======
#=> name : each
#=> argv : []
#=> block : #
#=> ======^^^^^^======
#=>  W卍

2016-05-11

captcha 之必要性與修正

這邊只寫下開發歷經的過程,沒實際 code

在開發時遇到的需求,類似太簡單的 captcha 會被破解( ... 自己破解自己成功 ... ),太困難的 captcha 很好用但其實很擾人,然而這就是辯思之旅,captcha 存在必要的與否

captcha 的存在必要性其實是針對機器人的防止,防止的是對『下個動作的操作』,類似登入或發文系統通常有 captcha,原因是防止登入測試與機器人發廣告信

而目前系統的問題發生在登入時,所以是怕被機器人 try 帳號密碼

分析『 try 帳號密碼 』這件事情,所得到的影響的其實是『頻率』與『對象』這兩個因素,所以只需針對這兩個因素來完成即可

中間過程略過,其實將 IP 與帳號來視為危險來源與保護對象即可,目前所想得到的最完美解為類似
  1. 該 IP 期間內是否登入錯誤太多次,否則直接顯示
  2. 使用者輸入帳號後,Ajax 到後端檢查該帳號期間內是否登入錯誤太多次,而需要顯示 captcha,如果需要則回傳 captcha 資訊並顯示
然後定義『期間』與『峰值』,就可以解決『頻率』與『對象』這回事,然而還有新的問題,類似如果遭受到 DDOS 且無差別攻擊時(無特定對象的帳號)該怎麼辦?也就是在一個來源 IP 無限且對象不定的狀況之下

這個問題其實是總量管制的意思,類似高速公路塞滿了車,則實施高承載管制來提高效率,所以再訂定了一個新的規則,類似
  1. 設定時間區間為 segment counter,每次登入錯誤時就把目前的 counter + 1,目前這個 segment 的初始值是上個 segment x 0.8 來做退火,判斷使用目前和上個 segment 的錯誤總數是否到達峰值
這樣遭到 DDOS 來破解帳號密碼時,也會自動啟動全站防護機制,而退火和判斷有依據上個 segment,原因是當此 segment 退火後,該時段攻擊機器人觀察到後又馬上攻擊,會出現很糟糕的鋸齒狀曲線的攻擊次數,雖然避免不了,但是至少可以平滑很多就是


目前分析至此,希望這樣就夠了,還有變更再另外更新

2016-05-04

產生有向性圖狀關連( MySQL + Graphviz )

DB schema

user_id , from_user_id , kind

過程:先定義 user_id < from_user_id + way ( 1 = 正向 , 2 = 反向 ),進行 GROUP 來做 clear 重複關連的動作,外面的 SELECT 再 sum 一次,如果 1 & 2 都有時變成 3 雙向,即為所求

SELECT kind , user_id , from_user_id , SUM(way) AS way FROM (
  SELECT
    kind ,
    IF(user_id < from_user_id , user_id , from_user_id) AS user_id ,
    IF(user_id < from_user_id , from_user_id , user_id) AS from_user_id ,
    IF(user_id < from_user_id , 1 , 2) AS way
  FROM tracks
  WHERE user_id IS NOT NULL AND from_user_id IS NOT NULL
  GROUP BY
    kind ,
    IF(user_id < from_user_id , user_id , from_user_id) ,
    IF(user_id < from_user_id , from_user_id , user_id) ,
    IF(user_id < from_user_id , 1 , 2)
) AS t GROUP BY kind , user_id , from_user_id

之後 Graphviz 就很簡單,圖要用 digraph(有箭頭)

way(1) : A -> B
way(2) : B -> A
way(3) : A -> B [dir="both"]

最後,類似這種解法一定要 1+ 開頭,因為 0 + 1 = 1 ([0,1] = 2 status) , 1 + 2 = 3 ([1,2,3] = 3 status)

2016-03-28

Ruby method object...

just code ...

class A
  def say(name = "yoo")
    puts name
  end
end
a = A.new
temp = a.method(:say) #get Method obj

class B
  def do_something(me)
    me.call('WTF')
  end
end
B.new.do_something(temp)

#=> WTF

2016-03-16

log trace ...

 gunzip *.gz #decompress log files
 grep -B 10 '\(aaa\|bbb\|ccc\)' * > target.txt #keywords and include 10 back lines and save to target.txt
 #vim's work...

2016-03-13

Google AdWords API + test account 申請流程......

在這邊記下來,因為他喵的這真的是一個超級大坑,看官方 doc 會遺漏一堆動作之類的 

Google AdWords 的帳號分成下面幾種:
  1. 一般 AdWords 帳號:申請頁
  2. AdWords Manager 帳號:申請頁
  3. AdWords Manager test 帳號:申請頁
其中一般的 AdWords 是沒 API 可以用的,而 AdWords Manager 帳號有 API 可用,但不能直接用,它會規定你一定要申請 test 帳號或是申請簽核後才能直接用,而 test 帳號內沒 developer_token,一定要配 AdWords Manager 帳號才能用,而一個 Google Account 同時只能有一種身份,所以......WTF...((請了一票帳號來配這個鬼,害我帳號名稱的梗都快用光了

anyway 如果你要玩的是 API 的話,請用下面的方式來申請,略過一般 AdWords 帳號你最少需要有兩個 Google Account,反正現在申請很快且很方便的切換之類的
  1. 請先去申請 AdWords Manager 帳號,登入後在灰色齒輪項目內可以找到『AdWords API 中心』,然後拿到格式很亂的亂碼『開發人員權杖(developer_token)』(這個項目在其他類型的 AdWords 帳號內"一定不會有")
  2. 申請 AdWords Manager test 帳號,登入後在灰色齒輪左邊可以找到數字中間用 "-" 連接的『客戶編號(client_customer_id)』
  3. 用 Adword Manager test 的帳號開啟 Google Developers Console 開一個新的專案,專案名稱隨便,新增後憑證那邊要先建立,開『OAuth 用戶端 ID』,名稱隨便,Logo 隱私權之類的都可略過,之後是建立用戶端 ID,選其他就好,名稱隨便,之後取得『用戶端 ID(oauth2_client_id)』,還有『用戶端密鑰(oauth2_client_secret)』,之後在總覽內啟用 API,廣告類項目都可以開
okay,所以以上已經取得『oauth2_client_id , oauth2_client_secret , developer_token , client_customer_id』之後打 OAuth2 去換 key,記得在換 key 的 URL 內 login 的是 AdWords Manager test account,之後應該可以換得『oauth2_token 的 access_token & refresh_token』

上面 OAuth2 登入部分可以使用類似官方的 tool,下面付的是 Ruby 的 OAuth2 Setup,其他語言類似 Perl / Python / Java / PHP / .NET / Android SDK / iOS SDK / Windows Phone 8 在同一個作者內的 GitHub 專案內都有

https://github.com/googleads/google-api-ads-ruby/blob/master/adwords_api/examples/v201601/misc/setup_oauth2.rb

這東西可以很方便的來做輔助設定和換 key 的動作,這個第零關打過之後你終於可以進行 API 的開發和測試惹 ......((乾,這票好囉唆的啊啊啊啊啊啊,這是我碰過最囉唆的 API 申請和測試了 Orz"...




繼續後記:

client_customer_id 分成主帳號 test 和次要帳號 sub account,類似 create_account 必須用 test 的 client_customer_id,而類似 add_campaigns 因為有內容,則需要用 sub account 的 client_customer_id

test 主帳號的 ID 在頁面的右上方,而次要帳號 sub account 在帳號列表,每個帳號的下方的數字的部分

用錯主帳號的話會得到類似『OperationAccessDenied.ADD_OPERATION_NOT_PERMITTED』
用錯子帳號的話會得到類似『NOT_AUTHORIZED』

anyway 換一下 client_customer_id 切換一下說不定就可以解掉這些問題




繼續後記:


AdWords Manager 可以樹狀,可以再連結另外一個 Manager 帳號,而 API 打出來做列表時,可列出所有 Manager 帳號和一般帳號(所有子代都一次列出)並且做操作之類的

2016-03-09

mysqld_multi + master / slave replication

這邊在製作一台 MySQL 在 mysqldump 的時候沒有 lock,所以需建立 Master / Slave 然後對 Slave 來做 dump,而希望在同一台這樣速度最快,當然 dump 下來的 sql file 需放在別台機器上來做異地備份,以下是順序

  #增加設定值(/etc/mysql/my.cnf)
[mysqld]
server_id = 1
log_bin = /var/log/mysql/mysql-bin.log # if replication

[mysqld2]
server_id = 1002
pid-file  = /var/run/mysqld/mysqld2.pid
socket    = /var/run/mysqld/mysqld2.sock
port  = 3307
datadir   = /var/lib/mysql2
log_bin = /var/log/mysql/mysql-bin2.log # if replication

  #要建立額外的檔案,包含pid和sock(這邊沒做會開不起來...)
sudo -u mysql touch /var/run/mysqld/mysqld2.pid
sudo -u mysql touch /var/run/mysqld/mysqld2.sock

  #建立空白資料集
sudo mysql_install_db --user=mysql --datadir=/var/lib/mysql2

  #之後就可以測試mysqld_multi
mysqld_multi report   #check status
mysqld_multi start 2  #open 2
mysqld_multi report   #recheck
#mysqld_multi stop 2  #if need stop

  #建立連線2改用
mysql -u root -p -S /var/run/mysqld/mysqld2.sock

#==========[[master]]

sudo /etc/init.d/mysql restart #重開 master server (因為有設定 log_bin)

  #next need 2 session , mysql client + bash

  #設定帳號密碼
GRANT REPLICATION SLAVE ON *.* TO 'repl'@'localhost' IDENTIFIED BY 'PASSWORD_HERE';
  #鎖定整個資料庫
FLUSH TABLES WITH READ LOCK;
  # remember 顯示資訊要記好,類似以下要記下來
  #+------------------+----------+--------------+------------------+ 
  #| File             | Position | Binlog_Do_DB | Binlog_Ignore_DB |
  #+------------------+----------+--------------+------------------+
  #| mysql-bin.000002 |      267 |              |                  |
  #+------------------+----------+--------------+------------------+
SHOW MASTER STATUS;
  #dump所有的資料庫,需加master-data(這邊要另外的session來做,原來的client不能中斷)
mysqldump -u root -p --master-data --all-databases > all_mysql_db.sql
  #解除寫入
UNLOCK TABLES;

#==========[[slave]]

  #dump回去
mysql -u root -p -S /var/run/mysqld/mysqld2.sock < all_mysql_db.sql
  #設定繫結,這邊要填入 File => MASTER_LOG_FILE & Position => MASTER_LOG_POS
CHANGE MASTER TO MASTER_HOST='localhost',MASTER_USER='repl',MASTER_PASSWORD='PASSWORD_HERE',MASTER_LOG_FILE='mysql-bin.000002',MASTER_LOG_POS=267;
  #開始
START SLAVE;
  #check master & slave
SHOW MASTER STATUS;
SHOW SLAVE STATUS;
  #如果是 "Waiting for master to send event" 就ok
打完收工 (rock)

2016-01-16

Rails memcache lock (dalli)

最近在實作 cache & lock,不過這邊不寫一般的作法,單純希望有個方便的lock可以在記憶體中,要防止使用者的重複交易之類的

我可以用DB,但怕有效能影響,所以只能選擇 memory 系列,而我有 Redis & memcache(dalli) 可以選

用 Redis 的話缺點很大,因為 hash 沒有 expire 可選,只有一般的 key 有,所以會多一票垃圾 key 在列表內,非常 dirty ...,所以只剩下 memcache

而 memcache 其實無 lock 選項,而...只有一種作法之類的,叫做 increment,也就是某個變數 + 1 的意思,當 return > 1 時,就代表這個 lock 已經失效,這就非常方便了

最後實作會類似這樣的 code :

  def action_lock(kind , timer: 600 , token: nil , user: current_user)
    return false if !timer || timer < 1
    #key的格式請自己編
    return Rails.cache.increment("ACTION_LOCK_#{user.id}_#{kind}_#{token}" , 1 , :expires_in => timer) == 1
  end

有這個基本後,就可以再製作很多的東西,也就是專案的需求

  1. 封包重送問題
  2. 同BTC wallet和金額的重複交易問題
  3. 發送頻率問題

(1):封包重送問題,可以使用 random post token 來完成,類似每次 post from 都產生一個 random key 來當 token,然後 post 出去後就記錄已使用,下次再接收時則阻擋,這樣可以防止類似手機或筆電的連線中斷而封包重送且重複執行的問題,類似 "#{kind}_#{user_id}_#{random_token}"

(2):重複交易其實就很簡單,想辦法把KEY編寫一樣即可,類似 "#{kind}_#{user_id}_#{wallet}_#{amount}" 的方式,並設定過期時間,就可以輕易完成

(3):發送頻率則更簡單,因為是限制單一使用者,則直接把 user_id 當作 key即可,類似 "#{kind}_#{user_id}"

okay打完收工(rock)

2015-10-19

Ruby : Exception bug??

......just code
require 'thread'

$temp = Thread.new do
  loop do
    puts 'loop me'
    begin
      puts "try thread"
      raise Exception.new('QwQ') if rand > 0.5
      puts "skip try"
    rescue
      puts "QwQ"
    end
    sleep(0.5)
  end
  puts '...WTF'
end

loop do
  puts "runner #{Thread.list.length} #{$temp.status}"
  sleep(2)
end
loop 會 fail loop ... 然後把所有的 rescue 改成 rescue Exception => e 就會成功,like
require 'thread'

$temp = Thread.new do
  loop do
    puts 'loop me'
    begin
      puts "try thread"
      raise Exception.new('QwQ') if rand > 0.5
      puts "skip try"
    rescue Exception => e
      puts "QwQ"
    end
    sleep(0.5)
  end
  puts '...WTF'
end

loop do
  puts "runner #{Thread.list.length} #{$temp.status}"
  sleep(2)
end
......兩個檔案diff只有一行......Orz"......成功... 原因: rescue 預設是 StandardError 而非 Exception ...... 來源: https://robots.thoughtbot.com/rescue-standarderror-not-exception

2015-09-15

Pebble time CJK language pack

just link: http://blog.kuro.ro/pebble-time-chinese-japanese-language-pack/