首選,如果之前使用過(guò)redis容器,我們需要先remove掉之前的容器

讓客戶(hù)滿(mǎn)意是我們工作的目標(biāo),不斷超越客戶(hù)的期望值來(lái)自于我們對(duì)這個(gè)行業(yè)的熱愛(ài)。我們立志把好的技術(shù)通過(guò)有效、簡(jiǎn)單的方式提供給客戶(hù),將通過(guò)不懈努力成為客戶(hù)在信息化領(lǐng)域值得信任、有價(jià)值的長(zhǎng)期合作伙伴,公司提供的服務(wù)項(xiàng)目有:域名注冊(cè)、網(wǎng)絡(luò)空間、營(yíng)銷(xiāo)軟件、網(wǎng)站建設(shè)、五臺(tái)網(wǎng)站維護(hù)、網(wǎng)站推廣。
然后創(chuàng)建redis容器,并運(yùn)行
進(jìn)入redis容器中
接著我們通過(guò) redis-cli 連接測(cè)試使用 redis 服務(wù)
setex指令 可以設(shè)置數(shù)據(jù)存在的時(shí)間, setex key second value
MSET 一次設(shè)置多個(gè)key-value
MGET一次獲取多個(gè)key-value
HGET
HGETALL
Hlen和hexist
Lpush 和 Lrange
Lpop和Rpop 從鏈表取出并移走數(shù)據(jù)
刪除鏈表所有數(shù)據(jù) DEL
字符串無(wú)序 不能重復(fù)
從連接池中Get出一個(gè)conn連接
可以參考: windows環(huán)境下redis的安裝
啟動(dòng)redis服務(wù)器:redis-server.exe redis.windows.conf
獲取包:
導(dǎo)入包
訪(fǎng)問(wèn):
redis事務(wù)可以一次執(zhí)行多個(gè)命令, 并且?guī)в幸韵聝蓚€(gè)重要的保證:
事務(wù)是一個(gè)單獨(dú)的隔離操作:事務(wù)中的所有命令都會(huì)序列化、按順序地執(zhí)行。事務(wù)在執(zhí)行的過(guò)程中,不會(huì)被其他客戶(hù)端發(fā)送來(lái)的命令請(qǐng)求所打斷。
事務(wù)是一個(gè)原子操作:事務(wù)中的命令要么全部被執(zhí)行,要么全部都不執(zhí)行。
下面介紹golang redis事務(wù)用法。
go redis事務(wù)常用函數(shù):
TxPipeline - 以Pipeline的方式操作事務(wù)
Watch - redis樂(lè)觀(guān)鎖支持
1.TxPipeline
以Pipeline的方式操作事務(wù)
2.watch
redis樂(lè)觀(guān)鎖支持,可以通過(guò)watch監(jiān)聽(tīng)一些Key, 如果這些key的值沒(méi)有被其他人改變的話(huà),才可以提交事務(wù)。
與memcached一樣,為了保證效率,數(shù)據(jù)都是緩存在內(nèi)存中。
區(qū)別的是Redis會(huì)周期性的把更新的數(shù)據(jù)寫(xiě)入磁盤(pán)或者把修改操作寫(xiě)入追加的記錄文件,并且在此基礎(chǔ)上實(shí)現(xiàn)了master-slave(主從)同步。數(shù)據(jù)可以從主服務(wù)器向任意數(shù)量的從服務(wù)器上同步,從服務(wù)器可以是關(guān)聯(lián)其他從服務(wù)器的主服務(wù)器。
這使得Redis可執(zhí)行單層樹(shù)復(fù)制。從盤(pán)可以有意無(wú)意的對(duì)數(shù)據(jù)進(jìn)行寫(xiě)操作。
由于完全實(shí)現(xiàn)了發(fā)布/訂閱機(jī)制,使得從數(shù)據(jù)庫(kù)在任何地方同步樹(shù)時(shí),可訂閱一個(gè)頻道并接收主服務(wù)器完整的消息發(fā)布記錄。同步對(duì)讀取操作的可擴(kuò)展性和數(shù)據(jù)冗余很有幫助。
一、關(guān)于連接池
一個(gè)數(shù)據(jù)庫(kù)服務(wù)器只擁有有限的資源,并且如果你沒(méi)有充分使用這些資源,你可以通過(guò)使用更多的連接來(lái)提高吞吐量。一旦所有的資源都在使用,那么你就不 能通過(guò)增加更多的連接來(lái)提高吞吐量。事實(shí)上,吞吐量在連接負(fù)載較大時(shí)就開(kāi)始下降了。通常可以通過(guò)限制與可用的資源相匹配的數(shù)據(jù)庫(kù)連接的數(shù)量來(lái)提高延遲和吞 吐量。
如何在Go語(yǔ)言中使用Redis連接池
如果不使用連接池,那么,每次傳輸數(shù)據(jù),我們都需要進(jìn)行創(chuàng)建連接,收發(fā)數(shù)據(jù),關(guān)閉連接。在并發(fā)量不高的場(chǎng)景,基本上不會(huì)有什么問(wèn)題,一旦并發(fā)量上去了,那么,一般就會(huì)遇到下面幾個(gè)常見(jiàn)問(wèn)題:
性能普遍上不去
CPU 大量資源被系統(tǒng)消耗
網(wǎng)絡(luò)一旦抖動(dòng),會(huì)有大量 TIME_WAIT 產(chǎn)生,不得不定期重啟服務(wù)或定期重啟機(jī)器
服務(wù)器工作不穩(wěn)定,QPS 忽高忽低
要想解決這些問(wèn)題,我們就要用到連接池了。連接池的思路很簡(jiǎn)單,在初始化時(shí),創(chuàng)建一定數(shù)量的連接,先把所有長(zhǎng)連接存起來(lái),然后,誰(shuí)需要使用,從這里取走,干完活立馬放回來(lái)。 如果請(qǐng)求數(shù)超出連接池容量,那么就排隊(duì)等待、退化成短連接或者直接丟棄掉。
二、使用連接池遇到的坑
最近在一個(gè)項(xiàng)目中,需要實(shí)現(xiàn)一個(gè)簡(jiǎn)單的 Web Server 提供 Redis 的 HTTP interface,提供 JSON 形式的返回結(jié)果。考慮用 Go 來(lái)實(shí)現(xiàn)。
首先,去看一下 Redis 官方推薦的 Go Redis driver。官方 Star 的項(xiàng)目有兩個(gè):Radix.v2 和 Redigo。經(jīng)過(guò)簡(jiǎn)單的比較后,選擇了更加輕量級(jí)和實(shí)現(xiàn)更加優(yōu)雅的 Radix.v2。
Radix.v2 包是根據(jù)功能劃分成一個(gè)個(gè)的 sub package,每一個(gè) sub package 在一個(gè)獨(dú)立的子目錄中,結(jié)構(gòu)非常清晰。我的項(xiàng)目中會(huì)用到的 sub package 有 redis 和 pool。
由于我想讓這種被 fork 的進(jìn)程最好簡(jiǎn)單點(diǎn),做的事情單一一些,所以,在沒(méi)有深入去看 Radix.v2 的 pool 的實(shí)現(xiàn)之前,我選擇了自己實(shí)現(xiàn)一個(gè) Redis pool。(這里,就不貼代碼了。后來(lái)發(fā)現(xiàn)自己實(shí)現(xiàn)的 Redis pool 與 Radix.v2 實(shí)現(xiàn)的 Redis pool 的原理是一樣的,都是基于 channel 實(shí)現(xiàn)的, 遇到的問(wèn)題也是一樣的。)
不過(guò)在測(cè)試過(guò)程中,發(fā)現(xiàn)了一個(gè)詭異的問(wèn)題。在請(qǐng)求過(guò)程中經(jīng)常會(huì)報(bào) EOF 錯(cuò)誤。而且是概率性出現(xiàn),一會(huì)有問(wèn)題,一會(huì)又好了。通過(guò)反復(fù)的測(cè)試,發(fā)現(xiàn) bug 是有規(guī)律的,當(dāng)程序空閑一會(huì)后,再進(jìn)行連續(xù)請(qǐng)求,會(huì)發(fā)生3次失敗,然后之后的請(qǐng)求都能成功,而我的連接池大小設(shè)置的是3。再進(jìn)一步分析,程序空閑300秒 后,再請(qǐng)求就會(huì)失敗,發(fā)現(xiàn)我的 Redis server 配置了 timeout 300,至此,問(wèn)題就清楚了。是連接超時(shí) Redis server 主動(dòng)斷開(kāi)了連接。客戶(hù)端這邊從一個(gè)超時(shí)的連接請(qǐng)求就會(huì)得到 EOF 錯(cuò)誤。
然后我看了一下 Radix.v2 的 pool 包的源碼,發(fā)現(xiàn)這個(gè)庫(kù)本身并沒(méi)有檢測(cè)壞的連接,并替換為新server{location/pool{content_by_lua_block{localredis=require"resty.redis"localred=redis:new()localok,err=red:connect("127.0.0.1",6379)ifnotokthenngx.say("failedtoconnect:",err)returnendok,err=red:set("hello","world")ifnotokthenreturnendred:set_keepalive(10000,100)}}}
發(fā)現(xiàn)有個(gè) set_keepalive 的方法,查了一下官方文檔,方法的原型是 syntax: ok, err = red:set_keepalive(max_idle_timeout, pool_size) 貌似 max_idle_timeout 這個(gè)參數(shù),就是我們所缺少的東西,然后進(jìn)一步跟蹤源碼,看看里面是怎么保證連接有效的。
function_M.set_keepalive(self,...)localsock=self.sockifnotsockthenreturnnil,"notinitialized"endifself.subscribedthenreturnnil,"subscribedstate"endreturnsock:setkeepalive(...)end
至此,已經(jīng)清楚了,使用了 tcp 的 keepalive 心跳機(jī)制。
于是,通過(guò)與 Radix.v2 的作者一些討論,選擇自己在 redis 這層使用心跳機(jī)制,來(lái)解決這個(gè)問(wèn)題。
四、最后的解決方案
在創(chuàng)建連接池之后,起一個(gè) goroutine,每隔一段 idleTime 發(fā)送一個(gè) PING 到 Redis server。其中,idleTime 略小于 Redis server 的 timeout 配置。連接池初始化部分代碼如下:
p,err:=pool.New("tcp",u.Host,concurrency)errHndlr(err)gofunc(){for{p.Cmd("PING")time.Sleep(idelTime*time.Second)}}()
使用 redis 傳輸數(shù)據(jù)部分代碼如下:
funcredisDo(p*pool.Pool,cmdstring,args...interface{})(reply*redis.Resp,errerror){reply=p.Cmd(cmd,args...)iferr=reply.Err;err!=nil{iferr!=io.EOF{Fatal.Println("redis",cmd,args,"erris",err)}}return}
其中,Radix.v2 連接池內(nèi)部進(jìn)行了連接池內(nèi)連接的獲取和放回,代碼如下:
//Cmdautomaticallygetsoneclientfromthepool,executesthegivencommand//(returningitsresult),andputstheclientbackinthepoolfunc(p*Pool)Cmd(cmdstring,args...interface{})*redis.Resp{c,err:=p.Get()iferr!=nil{returnredis.NewResp(err)}deferp.Put(c)returnc.Cmd(cmd,args...)}
這樣,我們就有了 keepalive 的機(jī)制,不會(huì)出現(xiàn) timeout 的連接了,從 redis 連接池里面取出的連接都是可用的連接了。看似簡(jiǎn)單的代碼,卻完美的解決了連接池里面超時(shí)連接的問(wèn)題。同時(shí),就算 Redis server 重啟等情況,也能保證連接自動(dòng)重連。
從上一節(jié)的內(nèi)容可知,Do() 和 Receive() 等方法的返回值,除了 error 外,是一個(gè) interface{} 類(lèi)型的返回值,因此當(dāng)我們的復(fù)雜操作返回的不是基本數(shù)據(jù)類(lèi)型時(shí),就需要我們自己解析返回值,例如,當(dāng)我們利用 HMGET 方法獲取一批返回值時(shí),就需要對(duì)返回結(jié)果進(jìn)行解析,具體如下:
由于返回值是多條數(shù)據(jù),因此需要先將 reply 轉(zhuǎn)成 []interface 類(lèi)型,然后在遍歷結(jié)果時(shí)在分別轉(zhuǎn)成 []uint8 (byte數(shù)組), 最后再轉(zhuǎn)成 string 類(lèi)型。
隨著我們操作復(fù)雜度,數(shù)據(jù)解析的工作量也會(huì)非常大,(lua 腳本的使用,會(huì)使結(jié)果的解析更為復(fù)雜,因?yàn)榭赡艽嬖诙喾N類(lèi)型的結(jié)果一起返回的情況,lua 腳本相關(guān)的內(nèi)容會(huì)在下一節(jié)介紹)。
redigo 包中的返回值助手函數(shù)的存在,就是為了幫助我們完成這些枯燥繁瑣的數(shù)據(jù)解析過(guò)程。
返回值助手函數(shù)相關(guān)源碼路徑為 github.com/gomodule/redigo/redis/reply.go 提供的主要方法如下:
上述返回值助手函數(shù)的具體使用,應(yīng)該依據(jù)具體的命令進(jìn)行選擇。如果大家還記得上一節(jié)介紹的 Redis 基本數(shù)據(jù)類(lèi)型,可能會(huì)有些疑問(wèn),對(duì)于 redis 來(lái)說(shuō),其數(shù)據(jù)據(jù)存儲(chǔ)本質(zhì)都是 []bytes, 為什么可以解析出 Int、int64、float等類(lèi)型的數(shù)據(jù)呢?
我們以 Float64() 為例進(jìn)行說(shuō)明,具體源碼如下:
其實(shí),返回值助手函數(shù)是將 []byte 類(lèi)型的原始數(shù)據(jù),利用 strconv.ParseFloat(string(reply), 64) 轉(zhuǎn)換成了 float64類(lèi)型,因此在我們使用過(guò)程中返回值助手函數(shù)的選擇,應(yīng)該基于業(yè)務(wù)和實(shí)際存儲(chǔ)的數(shù)據(jù)格式為依據(jù)。我們以第一小節(jié)的示例為例,看返回值助手函數(shù)如何降低我們的工作量,具體如下:
除了使用返回值助手函數(shù)對(duì)上述固定結(jié)構(gòu)的結(jié)果進(jìn)行解析外,redigo 包還提供了一個(gè) Scan()函數(shù)用于解析自定義的復(fù)雜數(shù)據(jù)結(jié)構(gòu),我們依然以上一個(gè)示例進(jìn)行說(shuō)明,具體示例如下:
如果返回結(jié)果為結(jié)構(gòu)化切片,也可以使用 canSlice() 方法,從而簡(jiǎn)化 loop 處理的部分,具體示例如下:
通過(guò)上述的示例,我們介紹了 scan 函數(shù)的基本用法,但是細(xì)心的同學(xué)可能會(huì)發(fā)現(xiàn)嗎,為什么數(shù)據(jù)寫(xiě)入時(shí),value 的類(lèi)型為 []int64 但是讀取時(shí)只能按照 string 類(lèi)型讀取呢。這是因?yàn)?Redis 底層存儲(chǔ)的數(shù)據(jù)本質(zhì)都是 string 類(lèi)型,。 無(wú)論是 HMSET 還是 MSET 最終都只能按照 string 類(lèi)型讀取,因?yàn)槠浔举|(zhì)都是 hash 結(jié)構(gòu),不同之處僅在于 HMSET 是嵌套的 hash類(lèi)型。 因此,[]int64 數(shù)據(jù)在寫(xiě)入階段,就已經(jīng)被自動(dòng)處理為 []byte,寫(xiě)入 redis 之后,len 和 類(lèi)型 屬性會(huì)丟失。
如果強(qiáng)行按照 []int64解析將出錯(cuò):
如果 value 必須以結(jié)構(gòu)化的數(shù)據(jù)存儲(chǔ),那么可以提前對(duì)要寫(xiě)入的數(shù)據(jù)進(jìn)行編碼,例如 json、protobuf 等,取出后再進(jìn)行解碼獲得原始數(shù)據(jù)。
新聞標(biāo)題:go語(yǔ)言連接reids,go語(yǔ)言連接mysql數(shù)據(jù)庫(kù)
瀏覽地址:http://chinadenli.net/article0/dsehoio.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供品牌網(wǎng)站制作、網(wǎng)站策劃、網(wǎng)站建設(shè)、網(wǎng)站收錄、微信公眾號(hào)、響應(yīng)式網(wǎng)站
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶(hù)投稿、用戶(hù)轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話(huà):028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)