golang 重構博客統計服務

歡迎關注樓主與他的小夥伴們的小站,每週分享一些技術文章,讓咱們在技術上一塊兒成長------> 戳這裏,歡迎光臨小站 -_-git

做爲一個後端開發,在docker,etcd,k8s等新技術不斷涌現的今天,其背後的功臣golang在語言排行榜上持續走高,所以樓主也就開了此次使用golang本身開發的基礎功能的二次裝逼之旅。
github

源於Spring Boot

感興趣的小夥伴能夠看看樓主的上一篇,基於Spring Boot實現的功能,請移步使用Spring Boot實現博客統計服務golang

實現redis存儲邏輯

選擇redis而沒選擇數據庫的緣由是redis提供了豐富的數據結構與數據持久化策略,另外redis是基於內存的,相對於數據庫來講,快了不止一個數量級。而統計閱讀次數的場景對接口處理的速度仍是有必定的要求的,所以樓主選擇了redis做爲閱讀次數統計的db。
下面就是redis操做的基礎代碼,比較簡單樓主貼一下代碼,不作進一步的闡述。web

  • redigo依賴下載
go get github.com/gomodule/redigo/redis
  • redis操做的工具類
func initRedisPool() {
    // 創建鏈接池
    RedisClient = &redis.Pool{
        // 從配置文件獲取maxidle以及maxactive,取不到則用後面的默認值
        MaxIdle:     1,
        MaxActive:   10,
        IdleTimeout: 180 * time.Second,
        Dial: func() (redis.Conn, error) {
            c, err := redis.Dial("tcp", RedisAddress)
            if err != nil {
                return nil, err
            }
            // 選擇db
            c.Do("SELECT", RedisDb)
            return c, nil
        },
    }
}

/**
 * 設置redis的對應key的value
 */
func redisSet(key string, value string) {
    c, err := RedisClient.Dial()
    if err != nil {
        fmt.Println("Connect to redis error", err)
        return
    }
    _, err = c.Do("SET", key, value)
    if err != nil {
        fmt.Println("redis set failed:", err)
    }
}

/**
 * 獲取redis的對應key的value
 */
func redisGet(key string) (value string) {
    c, err := RedisClient.Dial()
    if err != nil {
        fmt.Println("Connect to redis error", err)
        return
    }
    val, err := redis.String(c.Do("GET", key))
    if err != nil {
        fmt.Println("redis get failed:", err)
        return ""
    } else {
        fmt.Printf("Got value is %v \n", val)
        return val
    }
}

/**
 * redis使得對應的key的值自增
 */
func redisIncr(key string) (value string) {
    c, err := RedisClient.Dial()
    _, err = c.Do("INCR", key)
    if err != nil {
        fmt.Println("incr error", err.Error())
    }

    incr, err := redis.String(c.Do("GET", key))
    if err == nil {
        fmt.Println("redis key after incr is : ", incr)
    }
    return incr
}

博客閱讀次數統計接口實現

博客閱讀次數統計的基本業務邏輯就是,對應每篇博客的blogId做爲redis的key,而訪問次數就是這個key所對應的value,每訪問一次該接口就要將對應的blogId自增一次,並返回對應的value。這裏樓主選擇的redis的數據結構是redis的Stirng,下面是樓主實現該邏輯的主要代碼:redis

package main

import (
    "encoding/json"
    "fmt"
    "github.com/garyburd/redigo/redis"
    "log"
    "net/http"
    "time"
    "strings"
)

const RedisAddress = "127.0.0.1:6379"
const RedisDb = 0

const AllowRequestUrlH = "*"
const  AllowRequestUrlW = "*"
const  IllegalCharacters = "?"
const  DefaultReadCount = "1"

var (
    // 定義常量
    RedisClient *redis.Pool
)

func main() {
    // 初始化redis鏈接池
    initRedisPool()

    // 啓動web服務監聽
    http.HandleFunc("/*-*/*/", blogReadCountIncr)       //設置訪問的路由
    err := http.ListenAndServe(":9401", nil) //設置監聽的端口
    if err != nil {
        log.Fatal("ListenAndServe: ", err)
    }
}

func blogReadCountIncr(responseWriter http.ResponseWriter, request *http.Request) {

    // 解析參數,默認不解析
    request.ParseForm()

    blogId := request.Form.Get("blogId")

    log.Println(">>>>>> method blogReadCountIncr exec , request params is : ",blogId)

    // 判斷請求參數是否爲空
    if "" == blogId {
        result := ResultCode{
            Code: 200,
            Msg:  "success",
        }

        ret, _ := json.Marshal(result)
        fmt.Fprintf(responseWriter, string(ret)) //這個寫入到w的是輸出到客戶端的
    }
    
    readCount := redisGet(blogId)
    if "" == readCount {
        // 不符合規則,直接返回
        flag := strings.Index(blogId, AllowRequestUrlH) != 0 ||strings.Index(blogId, AllowRequestUrlW) != 0||strings.Contains(blogId, IllegalCharacters)
        if  !flag {
            result := ResultCode{
                Code: 200,
                Msg:  "success",
            }

            ret, _ := json.Marshal(result)
            fmt.Fprintf(responseWriter, string(ret)) //這個寫入到w的是輸出到客戶端的
        }

        redisSet(blogId, DefaultReadCount)
        readCount = DefaultReadCount
    } else {
        readCount = redisIncr(blogId)
    }
    log.Println(">>>>>> readCount is : ",readCount)
    result := ResultCode{
        Code: 200,
        Msg:  "success",
        Data: readCount,
    }
    ret, _ := json.Marshal(result)
    fmt.Fprintf(responseWriter, string(ret)) //這個寫入到w的是輸出到客戶端的
}
// 結構體定義返回值
type ResultCode struct {
    Msg  string `json:"msg"`
    Code int    `json:"code"`
    Data string `json:"data"`
}

實現過程當中遇到的坑

出現的問題

使用golang原生的json工具序列化時,出現序列化失敗的問題,以下所示的結構體定義,乍一看是沒啥問題的,然而使用spring

ret, _ := json.Marshal(result)

序列化時,出現沒法序列化成json串的問題,另外還不報錯,這讓樓主非常頭疼。docker

type ResultCode struct {
    msg  string `json:"msg"`
    code int    `json:"code"`
    data string `json:"data"`
}

問題解決

最終樓主經過各類姿式的排查,發現是結構體定義有問題,當定義結構體時首字母必須大寫才能序列化成功,這個特色在golang裏面非常明顯,在函數調用時首字母小寫的函數在其餘文件裏面是調不到的。下面給出正確的結構體定義數據庫

type ResultCode struct {
    Msg  string `json:"msg"`
    Code int    `json:"code"`
    Data string `json:"data"`
}

小結

目前不少大佬都寫過關於golang web的教程,若有雷同,請略過不看,本文經過本身的親身實戰以及樓主本身踩到的坑完成的,另外本文是基於go內置的net/http庫實現的web服務。json

號外

樓主造了一個輪子,LIGHTCONF 是一個基於Netty實現的一個配置管理平臺,其核心設計目標是「爲業務提供統一的配置管理服務」,能夠作到開箱即用。感興趣的給個star支持一下。後端

相關文章
相關標籤/搜索