作 者:haifeiWu
原文链接:https://www.hchstudio.cn/article/2018/2622/
版权声明:非特殊声明均为本站原创作品,转载时请注明作者和原文链接。
由于版权原因,请阅读原文 --> golang重构博客统计服务

作 者:haifeiWu
原文链接:https://www.hchstudio.cn/article/2018/2622/
版权声明:非特殊声明均为本站原创作品,转载时请注明作者和原文链接。
作 者:haifeiWu
原文链接:https://www.hchstudio.cn/article/2018/2622/
版权声明:非特殊声明均为本站原创作品,转载时请注明作者和原文链接。
作为一个后端开发,在docker,etcd,k8s等新技术不断涌现的今天,其背后的功臣golang在语言排行榜上持续走高,因此楼主也就开了这次使用golang自己开发的基础功能的二次装逼之旅。
源于Spring Boot
感兴趣的小伙伴可以看看楼主的上一篇,基于Spring Boot实现的功能,请移步使用Spring Boot实现博客统计服务
实现redis存储逻辑
选择redis而没选择数据库的原因是redis提供了丰富的数据结构与数据持久化策略,另外redis是基于内存的,相对于数据库来说,快了不止一个数量级。而统计阅读次数的场景对接口处理的速度还是有一定的要求的,因此楼主选择了redis作为阅读次数统计的db。
下面就是redis操作的基础代码,比较简单楼主贴一下代码,不做进一步的阐述。
redigo依赖下载
1
| go get github.com/gomodule/redigo/redis
|
redis操作的工具类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| func initRedisPool() { RedisClient = &redis.Pool{ 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 } c.Do("SELECT", RedisDb) return c, nil }, } }
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) } }
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 } }
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,下面是楼主实现该逻辑的主要代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
| 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() { initRedisPool()
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)) } 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)) }
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)) }
type ResultCode struct { Msg string `json:"msg"` Code int `json:"code"` Data string `json:"data"` }
|
实现过程中遇到的坑
出现的问题
使用golang原生的json工具序列化时,出现序列化失败的问题,如下所示的结构体定义,乍一看是没啥问题的,然而使用
1
| ret, _ := json.Marshal(result)
|
序列化时,出现无法序列化成json串的问题,另外还不报错,这让楼主很是头疼。
1 2 3 4 5
| type ResultCode struct { msg string `json:"msg"` code int `json:"code"` data string `json:"data"` }
|
问题解决
最终楼主通过各种姿势的排查,发现是结构体定义有问题,当定义结构体时首字母必须大写才能序列化成功,这个特点在golang里面很是明显,在函数调用时首字母小写的函数在其他文件里面是调不到的。下面给出正确的结构体定义
1 2 3 4 5
| type ResultCode struct { Msg string `json:"msg"` Code int `json:"code"` Data string `json:"data"` }
|
小结
目前很多大佬都写过关于golang web的教程,如有雷同,请略过不看,本文通过自己的亲身实战以及楼主自己踩到的坑完成的,另外本文是基于go内置的net/http库实现的web服务。
号外
楼主造了一个轮子,LIGHTCONF 是一个基于Netty实现的一个配置管理平台,其核心设计目标是“为业务提供统一的配置管理服务”,可以做到开箱即用。感兴趣的给个star支持一下。

作 者:haifeiWu
原文链接:https://www.hchstudio.cn/article/2018/2622/
版权声明:非特殊声明均为本站原创作品,转载时请注明作者和原文链接。