標籤:call func close 下標越界 model hash import keepalive version
3個月沒寫PHP了,這是我的第一個中小型go的websocket微服務。那麼問題來了,github上那麼多輪子,我為什麼要自己造輪子呢?
Why 造輪子?
因為這樣不僅能鍛煉自己的技術能力,而且能協助深入瞭解其中的實現原理。
直接上流程圖:
其實其中有些痛點並沒有反映出來,比如曆史訊息資料的儲存結構、病發時遇到的一些坑等。
曆史訊息的儲存結構 :
即廣播、組播可拆解成單播,那麼代碼就可以變得簡單。
但是,但是,但是,有看到 "ref"? ref表示,使用者的曆史訊息,是否是一個引用, 類似於c/cpp的指標、地址。想一想,如果廣播給1w使用者,那麼是不是要把一個msg push到每一個使用者呢?
答案至少有2:
其一:push msg給everyone,優點:讀取資料時很方便, 缺點:資料大量冗餘,且push一瞬間io量過大,效率低;
其二:push msg時,分別儲存:廣播表、組播表、單播表, 優點:分別查詢效能高,無冗餘 , 缺點:綜合查詢使用者的所有曆史訊息時,效能差,而且redis的網路io次數較多,還有時間等排序的問題。
綜合考慮,選用第1種方案。
問題又來了, 這個項目開發順利不,遇到坑沒?
廢話,技術的活,哪有不帶坑的!
坑1:panic中斷既出 ,真tmd不是我想要的, 解決方式是:recovery ( : P
坑2:環境變數向內包的傳遞,試了幾種辦法不行,最後用一個包作代理,封裝工廠和單例, 最好的解決了。
var instance *envfunc NewEnv()*env {env := env{}env.init()env.parameters = make(map[string]interface{})return &env}func SingleEnv()*env{if nil == instance {instance = NewEnv()}return instance}//...
坑3:websocket跨域問題,解決方案至少有2:可以修改預設設定
// 臨時忽略websocket跨域ws := websocket.Upgrader{}if model.SingleConfig().Http.EnableCrossSite {ws.CheckOrigin = func(r *http.Request) bool { //mock and stubreturn true}}
或者是在nginx上加這些,相當於在同一個域,推薦用這:
nginx conf:upstream push {ip_hash;server 127.0.0.1:9999 ;keepalive 60;}map $http_upgrade $connection_upgrade {default upgrade;‘‘ close;}server { listen 80; server_name dev.push.pub.sina.com.cn; location /push { proxy_http_version 1.1; proxy_redirect off; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; client_max_body_size 10m; client_body_buffer_size 128k; proxy_connect_timeout 300; proxy_send_timeout 300; proxy_read_timeout 300; proxy_pass http://push; fastcgi_keep_conn on; include fastcgi_params; }}
坑4:go map不內建支援並發安全,這是最大的問題。解決稍有點麻煩,需要用到RWMutex鎖。 我參考beego寫的:
package libimport "sync"type RWLocker struct {mtx sync.RWMutex}func NewRWLock()*RWLocker{return &RWLocker{}}func (l *RWLocker)RLockDo(callback func()){l.mtx.RLock()defer l.mtx.RUnlock()callback()}func (l *RWLocker)RWLockDo(callback func()){l.mtx.Lock()defer l.mtx.Unlock()callback()}type Locker struct {mtx sync.Mutex}func NewLock()*Locker{return &Locker{}}func (l *Locker)LockDo(callback func()){l.mtx.Lock()defer l.mtx.Unlock()callback()}type MutexMap struct{m map[interface{}]interface{}lock *sync.RWMutex}func NewMutexMap() *MutexMap {return &MutexMap{lock: new(sync.RWMutex),m: make(map[interface{}]interface{}),}}func (m *MutexMap) Size() int{return len(m.m)}func (m *MutexMap) Raw() map[interface{}]interface{} {return m.m}//Get from maps return the k‘s valuefunc (m *MutexMap) Get(k interface{}) interface{} {m.lock.RLock()defer m.lock.RUnlock()if val, ok := m.m[k]; ok {return val}return nil}// Maps the given key and value. Returns false// if the key is already in the map and changes nothing.func (m *MutexMap) Set(k interface{}, v interface{}) bool {m.lock.Lock()defer m.lock.Unlock()if val, ok := m.m[k]; !ok {m.m[k] = v} else if val != v {m.m[k] = v} else {return false}return true}// Returns true if k is exist in the map.func (m *MutexMap) Check(k interface{}) bool {m.lock.RLock()defer m.lock.RUnlock()if _, ok := m.m[k]; !ok {return false}return true}func (m *MutexMap) Keys(ignoreNil bool, keys ...interface{}) []interface{}{m.lock.RLock()defer m.lock.RUnlock()vals := []interface{}{}for _,k := range keys {if v,ok := m.m[k]; ok {vals = append(vals, v)}else{if !ignoreNil {vals = append(vals, nil)}}}return vals}func (m *MutexMap) Delete(k interface{}) {m.lock.Lock()defer m.lock.Unlock()delete(m.m, k)}
基本的坑就是這些了,上線部署當然是jenkins+salt+rsync:
最後,談下,維護性、調試性。
首先維護性:目前只遇到幾次go會異常崩潰的情況,一般都是不細心或並發安全沒做好,這個根據日誌、race tool、strace/gdb可以搞定。
另外,調試性的話,介於php, cpp之間,和java類似,一般能檢查出問題,並打出日誌,包括數組下標越界等,另外 還有pprof/strace/gdb等神器能用上,還是不錯的。
哈哈,今天就寫這麼多了, 要哄妹子了-----------我閨女。
:P
基於Go的websocketMessage Service