Go實戰專案-Beego的Session、日誌檔案的使用和redis的選擇使用

棋佈發表於2020-10-13

Go實戰專案-Beego的Session、日誌檔案的使用和redis的選擇使用

go標準庫裡面沒有實現這功能,只能靠自己實現了,哦,不,是第三方庫。好在beego就自帶session功能,這個之前就說過了。我們只是簡單使用下,高併發場景估計還得自己來實現,單純的靠這個框架,夠嗆。來看下怎麼使用:
1、在呼叫之前就需要開啟
beego.BConfig.WebConfig.Session.SessionOn = true //開始session
beego目前支援四種session的儲存引擎 memory、file、Redis 和 MySQL
預設就是memory ,但是,你重啟之後就失效了,這除了寫demo可以用之外,就算是保活的程式也是很肉痛,基於之前PHP框架儲存檔案的處理方式,我這邊也是存放檔案中。
2、設定儲存引擎
beego.BConfig.WebConfig.Session.SessionProvider = “file” //指定檔案儲存方式
3、設定儲存路徑
beego.BConfig.WebConfig.Session.SessionProviderConfig = “./.tmp” //指定檔案儲存路徑地址,也可以不指定,有預設的地址。
建議,儲存的資料夾名稱加上“.”,這樣方便git提交的時候直接過濾,但是一般情況下,沒事不要去下載,或者放在專案以外的其他路徑也是可以的。這樣就是永久儲存了,重啟依然有效。

和PHP不同,常駐記憶體的程式碼除錯錯誤或者發現線上問題等等都是需要看日誌記錄的,畢竟控制檯那時候我們也看不到了。靠控制檯發現問題也不太現實。所以很有必要加上日誌,這對於習慣於PHP開發的同學來說是個不順手的習慣,畢竟指令碼除錯太簡單輕鬆了,修改立即生效。beego的啟動日誌也是很簡單的,直接設定就好,支援多檔案,按照規則來分割,預設也會按照日期來進行分割的。
logs.SetLogger(logs.AdapterFile, {"filename":"./logs/callout.log"})
按照這樣設定後,每天也是會有一個單獨的日誌,名稱預設就是callout.2020-10-13.001.log,我們也可以動態更改資料夾,按照日期做,這樣對於排查問題是可以提高效率的,省去了很多麻煩。多檔案設定只需要把logs.AdapterFile改成logs.AdapterMultiFile,後面的基本一致,可以新增分割規則。官方文件有簡單的描述,我們不深究這些,能用就好。程式設計師經典的幾句話:又不是不能用。

redis,很多庫都是自帶連線池的,所以我們沒必要自己親自去造輪子,當然,造一遍自己的提升也是相當明顯的。開始使用的時候,必須是最主流的第三方框架,redigo,引入還是照舊:”github.com/garyburd/redigo/redis”,直接匯入就好。但是由於筆者公司的redis採用的是叢集的方式部署的,所以,考慮到這個的使用,我別無選擇的使用谷歌的親兒子庫go-redis。兩個都用上吧,畢竟都是主流的兩個框架

redigo的使用

直接連線

func ConnectRedis() redis.Conn {
    conn, _ := redis.Dial("tcp", "127.0.0.1:6379")
    return conn
}

連線池連線

func ConnectRedisPool() redis.Conn {
    connPool := &redis.Pool{
        Dial: func() (conn redis.Conn, err error) {
            conn, err = redis.Dial("tcp", "127.0.0.1:6379")
            if err != nil {
                return nil, err
            }
            return conn, nil
        },
        TestOnBorrow:    nil,
        MaxIdle:         1,
        MaxActive:       10,
        IdleTimeout:     180 * time.Second,
        Wait:            true,
        MaxConnLifetime: 0,
    }

    return connPool.Get()
}

使用的時候記得defer 呼叫close()函式,正常的使用是Do的方式,舉個簡單的設定和獲取的栗子

//@router  /process/test [get]
func (c *ProcessControllers) SetRedis() {//redigo 連線獲取方式
    redisPool := redisClient.ConnectRedisPool()
    defer redisPool.Close()
    _, err := redisPool.Do("SET", "wjw_key", "wjw")
    if err == nil {
        c.Data["json"] = ReturnSuccess("請求成功", nil, 0)
    } else {
        c.Data["json"] = ReturnError(-4007, err.Error())
    }
    c.ServeJSON()
}

// @router  /process/getRedis [get]
func (c *ProcessControllers) GetRedis() {
    redisPool := redisClient.ConnectRedisPool()
    defer redisPool.Close()
    data, err := redis.String(redisPool.Do("GET", "wjw_key"))
    if err == nil {
        c.Data["json"] = ReturnSuccess("請求成功", data, 0)
    } else {
        c.Data["json"] = ReturnError(-4007, err.Error())
    }
    c.ServeJSON()
}

redigo,並不支援叢集的使用,也不知道為什麼這麼多選擇使用的,是不是也說明很多企業並沒有叢集或者哨兵模式?那容災容錯怎麼處理的呢?是不是恰恰說明,很多公司都是實用型的,沒必要整這麼多的彎彎繞。

go-redis的使用

谷歌出品的,支援叢集和哨兵方式等的連線。這也是比redigo更吸引的地方。

直接連線

func ConnectRedis() *redis.Client {
    client := redis.NewClient(&redis.Options{
        Addr:     "127.0.0.1:6379",
        Password: "", // 不設定密碼
        DB:       0,  // 使用預設的
    })
    return client
}

實際上直接連線的方式,效率很低,除了寫Demo驗證功能之外,實際開發應該不會想不開的用吧,大多是連線池的方式使用。

連線池連線

func ConnectRedisPool() *redis.Client {
    client := redis.NewClient(&redis.Options{
        Addr:         "127.0.0.1:6379",
        Password:     "",              // 不設定密碼
        DB:           0,               // 使用預設的
        PoolSize:     15,              // 連線池最大socket連線數,預設為5倍CPU數, 5 * runtime.NumCPU
        MinIdleConns: 10,              //在啟動階段建立指定數量的Idle連線,並長期維持idle狀態的連線數不少於指定數量
        DialTimeout:  5 * time.Second, //連線建立超時時間,預設5秒。
        ReadTimeout:  3 * time.Second, //讀超時,預設3秒, -1表示取消讀超時
        WriteTimeout: 3 * time.Second, //寫超時,預設等於讀超時
        PoolTimeout:  4 * time.Second, //當所有連線都處在繁忙狀態時,客戶端等待可用連線的最大等待時長,預設為讀超時+1秒。

        //閒置連線檢查包括IdleTimeout,MaxConnAge
        IdleCheckFrequency: 60 * time.Second, //閒置連線檢查的週期,預設為1分鐘,-1表示不做週期性檢查,只在客戶端獲取連線時對閒置連線進行處理。
        IdleTimeout:        5 * time.Minute,  //閒置超時,預設5分鐘,-1表示取消閒置超時檢查
        MaxConnAge:         0 * time.Second,  //連線存活時長,從建立開始計時,超過指定時長則關閉連線,預設為0,即不關閉存活時長較長的連線

        //命令執行失敗時的重試策略
        MaxRetries:      0,                      // 命令執行失敗時,最多重試多少次,預設為0即不重試
        MinRetryBackoff: 8 * time.Millisecond,   //每次計算重試間隔時間的下限,預設8毫秒,-1表示取消間隔
        MaxRetryBackoff: 512 * time.Millisecond, //每次計算重試間隔時間的上限,預設512毫秒,-1表示取消間隔
        Dialer: func() (conn net.Conn, err error) {
            netDialer := &net.Dialer{
                Timeout:   5 * time.Second,
                KeepAlive: 5 * time.Minute,
            }
            return netDialer.Dial("tcp", "127.0.0.1:6379")
        },
        OnConnect: func(conn *redis.Conn) error { //僅當客戶端執行命令時需要從連線池獲取連線時,如果連線池需要新建連線時則會呼叫此鉤子函式
            fmt.Printf("conn=%v\n", conn)
            return nil
        },
    })
    return client
}

這些註釋,可以在原始碼裡面看得到的,都有比較詳細的英文描述,稍微藉助工具翻譯下就知道了。重點在下面的,也是筆者使用的。

叢集/哨兵模式下的連線

func ConnectRedisClusterPool() *redis.ClusterClient {
    client := redis.NewClusterClient(&redis.ClusterOptions{//哨兵模式:NewSentinelClient
        //Addrs:    []string{ "127.0.0.1:6379", "127.0.0.1:6380"},//叢集節點地址,理論上只要填一個可用的節點客戶端就可以自動獲取到叢集的所有節點資訊。但是最好多填一些節點以增加容災能力,因為只填一個節點的話,如果這個節點出現了異常情況,則Go應用程式在啟動過程中無法獲取到叢集資訊。
        Addrs:        []string{"127.0.0.1:6379"},
        Password:     "",              // 不設定密碼
        MaxRedirects: 8,               // 當遇到網路錯誤或者MOVED/ASK重定向命令時,最多重試幾次,預設8
        PoolSize:     15,              // 連線池最大socket連線數,預設為5倍CPU數, 5 * runtime.NumCPU
        MinIdleConns: 10,              //在啟動階段建立指定數量的Idle連線,並長期維持idle狀態的連線數不少於指定數量
        DialTimeout:  5 * time.Second, //連線建立超時時間,預設5秒。
        ReadTimeout:  3 * time.Second, //讀超時,預設3秒, -1表示取消讀超時
        WriteTimeout: 3 * time.Second, //寫超時,預設等於讀超時
        PoolTimeout:  4 * time.Second, //當所有連線都處在繁忙狀態時,客戶端等待可用連線的最大等待時長,預設為讀超時+1秒。

        //閒置連線檢查包括IdleTimeout,MaxConnAge
        IdleCheckFrequency: 60 * time.Second, //閒置連線檢查的週期,預設為1分鐘,-1表示不做週期性檢查,只在客戶端獲取連線時對閒置連線進行處理。
        IdleTimeout:        5 * time.Minute,  //閒置超時,預設5分鐘,-1表示取消閒置超時檢查
        MaxConnAge:         0 * time.Second,  //連線存活時長,從建立開始計時,超過指定時長則關閉連線,預設為0,即不關閉存活時長較長的連線

        //命令執行失敗時的重試策略
        MaxRetries:      0,                      // 命令執行失敗時,最多重試多少次,預設為0即不重試
        MinRetryBackoff: 8 * time.Millisecond,   //每次計算重試間隔時間的下限,預設8毫秒,-1表示取消間隔
        MaxRetryBackoff: 512 * time.Millisecond, //每次計算重試間隔時間的上限,預設512毫秒,-1表示取消間隔
        //只含讀操作的命令的"節點選擇策略"。預設都是false,即只能在主節點上執行。
        ReadOnly: false, // 置為true則允許在從節點上執行只含讀操作的命令
        // 預設false。 置為true則ReadOnly自動置為true,表示在處理只讀命令時,可以在一個slot對應的主節點和所有從節點中選取Ping()的響應時長最短的一個節點來讀資料
        RouteByLatency: false,
        // 預設false。置為true則ReadOnly自動置為true,表示在處理只讀命令時,可以在一個slot對應的主節點和所有從節點中隨機挑選一個節點來讀資料
        RouteRandomly: false,

        //使用者可定製讀取節點資訊的函式,比如在非叢集模式下可以從zookeeper讀取。
        //但如果面向的是redis cluster叢集,則客戶端自動透過cluster slots命令從叢集獲取節點資訊,不會用到這個函式。
        //ClusterSlots: func() ([]redis.ClusterSlot, error) {
        //    return nil,nil
        //},
        OnConnect: func(conn *redis.Conn) error {
            fmt.Printf("conn=%v\n", conn)
            return nil
        },
    })
    return client
}

使用方式和連線池的一樣,只是呼叫的型別換成了NewClusterClient,記住,如果是哨兵模式,換成NewSentinelClient連線就行了。
呼叫方式,同樣直接貼出兩段程式碼:


// @router  /process/setRedis [get]
func (c *ProcessControllers) SetRedis() { //go-redis 連線獲取方式
    redisPool := redisClient.ConnectRedisClusterPool()
    defer redisPool.Close()
    _, err := redisPool.Ping().Result()
    if err == nil {
        err = redisPool.Set("wjw_key", "wjw hello redis", 0).Err()
        if err == nil {
            c.Data["json"] = ReturnSuccess("請求成功", nil, 0)
        } else {
            c.Data["json"] = ReturnError(-4007, err.Error())
        }
    } else {
        c.Data["json"] = ReturnError(-4007, err.Error())
    }
    c.ServeJSON()
}

// @router  /process/getRedis [get]
func (c *ProcessControllers) GetRedis() {
    redisPool := redisClient.ConnectRedisClusterPool()
    defer redisPool.Close()
    _, err := redisPool.Ping().Result()
    if err == nil {
        res, err := redisPool.Get("wjw_key").Result()
        if err == nil {
            c.Data["json"] = ReturnSuccess("請求成功", res, 0)
        } else {
            c.Data["json"] = ReturnError(-4008, err.Error())
        }
    } else {
        c.Data["json"] = ReturnError(-4009, err.Error())
    }
    c.ServeJSON()
}

不是什麼難點技術,後續的使用才是考驗技巧的時候。

本作品採用《CC 協議》,轉載必須註明作者和本文連結
歡迎轉載分享提意見,一起code

相關文章