Go語言系列(九)- Socket編程和Redis

Socket編程

1、socket編程概述

  什麼是socket編程? socket編程是計算機PC機器上2個程序經過一個雙向的通訊鏈接實現數據的交互,這個鏈接的一端就是一個socket。socket的翻譯意思上還有個插座的概念,其實,也能夠很形象的比喻爲插座插上去了就有通電了(網絡通了)。socket編程其實做爲UNIX系統的進程間通訊機制,一般稱爲「套接字」,用來描述IP地址和端口的集合,在unix系統下是一個通訊的句柄(文件描述符,由於UNIX下全部都是文件)。git

UNIX socket編程的流程大概以下:github

  • 服務端:socket(),bind(),listen(),accept(),read()/write(),close()
  • 服務端:socket(), connect(),read()/write(),close()

2、Go語言socket編程庫

go語言的基礎包中,網絡包net包含了不少網絡I/O,TCP/IP,UDP,域名解析,Unix 域套接字等。redis

雖然提供的都是直接面對很底層的網絡I/O訪問,可是主要的TCP通訊的接口也是封裝得比較簡單,好用,包括有:數據庫

  • 客戶端鏈接使用的Dial方法
  • 服務端進行監聽使用的Listen方法
  • 服務端進行接受連接的Accept方法
  • 封裝了鏈接對象Conn類型

3、客戶端和服務端

 1. 服務器處理流程

  • a. 監聽端口編程

  • b. 接收客戶端的連接api

  • c. 建立goroutine,處理該連接緩存

2.  客戶端處理流程

  • a. 創建與服務器的連接服務器

  • b. 進行數據收發網絡

  • c. 關閉連接數據結構

3. 服務端代碼

package main

import (
	"fmt"
	"net"
)

func main() {
	fmt.Println("start server...")
	listen, err := net.Listen("tcp", "0.0.0.0:50000")
	if err != nil {
		fmt.Println("listen failed, err:", err)
		return
	}
	for {
		conn, err := listen.Accept()
		if err != nil {
			fmt.Println("accept failed, err:", err)
			continue
		}
		go process(conn)
	}
}
func process(conn net.Conn) {
	defer conn.Close()
	for {
		buf := make([]byte, 512)
		n, err := conn.Read(buf)
		if err != nil {
			fmt.Println("read err:", err)
			return
		}

		fmt.Printf(string(buf[0:n]))
	}
}

4. 客戶端代碼

package main

import (
	"bufio"
	"fmt"
	"net"
	"os"
	"strings"
)

func main() {

	conn, err := net.Dial("tcp", "localhost:50000")
	if err != nil {
		fmt.Println("Error dialing", err.Error())
		return
	}

	defer conn.Close()
	inputReader := bufio.NewReader(os.Stdin)
	for {
		input, _ := inputReader.ReadString('\n')
		trimmedInput := strings.Trim(input, "\r\n")
		if trimmedInput == "Q" {
			return
		}
		_, err = conn.Write([]byte(trimmedInput))
		if err != nil {
			return
		}
	}
}

5. 發送http請求

package main

import (
	"fmt"
	"io"
	"net"
)

func main() {

	conn, err := net.Dial("tcp", "www.baidu.com:80")
	if err != nil {
		fmt.Println("Error dialing", err.Error())
		return
	}
	defer conn.Close()
	msg := "GET / HTTP/1.1\r\n"
	msg += "Host:www.baidu.com\r\n"
	msg += "Connection:keep-alive\r\n"
	// msg += "User-Agent:Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36\r\n"
	msg += "\r\n\r\n"

	//io.WriteString(os.Stdout, msg)
	n, err := io.WriteString(conn, msg)
	if err != nil {
		fmt.Println("write string failed, ", err)
		return
	}
	fmt.Println("send to baidu.com bytes:", n)
	buf := make([]byte, 4096)
	for {
		count, err := conn.Read(buf)
		fmt.Println("count:", count, "err:", err)
		if err != nil {
			break
		}
		fmt.Println(string(buf[0:count]))
	}
}

 Redis

1、Redis簡介

  redis是個開源的高性能的key-value的內存數據庫,能夠把它當成遠程的數據結構。支持的value類型很是多,好比string、list(鏈表)、set(集合)、hash表等等。redis性能很是高,單機可以達到15w qps,一般適合作緩存。

 1. 特色

  • 支持更多數據類型 
    和Memcached相似,它支持存儲的value類型相對更多,包括string(字符串)、list(鏈表)、set(集合)、zset(sorted set 有序集合)和hash(哈希類型)。[1]

  • 支持複雜操做 
    這些數據類型都支持push/pop、add/remove及取交集並集和差集及更豐富的操做,並且這些操做都是原子性的。在此基礎上,Redis支持各類不一樣方式的排序。[2]

  • 支持主從同步。 
    與memcached同樣,爲了保證效率,數據都是緩存在內存中。區別的是Redis會週期性的把更新的數據寫入磁盤或者把修改操做寫入追加的記錄文件,而且在此基礎上實現了master-slave(主從)同步。數據能夠從主服務器向任意數量的從服務器上同步,從服務器能夠是關聯其餘從服務器的主服務器。這使得Redis可執行單層樹複製。從盤能夠有意無心的對數據進行寫操做。因爲徹底實現了發佈/訂閱機制,使得從數據庫在任何地方同步樹時,可訂閱一個頻道並接收主服務器完整的消息發佈記錄。同步對讀取操做的可擴展性和數據冗餘頗有幫助。

2.  常見使用場景

  • 高併發下數據緩存。 好比在某個場景下,大量日誌同時寫入數據庫會給服務器帶來巨大壓力,這時能夠先將數據寫入redis中,再由redis寫入數據庫,減輕同時寫入壓力。
  • 熱點信息快速顯示。假設如今有一個新聞首頁,須要快速顯示各欄目前20條熱點新聞,若是直接查詢數據庫,在大量用戶同時訪問下,會消耗極大數量的數據庫請求。這時就能夠用redis來優化,在新聞錄入的時候將標題、時間和來源寫入redis中,客戶端訪問時,能夠從內存中一次性取出當天熱單新聞列表,極大地提升請求速度和節約了服務器開銷。
  • 保存會話信息。能夠將登陸後用戶信息緩存入redis並同時設置key過時時間,這樣後臺api過濾請求時,就能夠從內存中讀取用戶信息,並且redis的過時機制,自然支持用戶身份有效期校驗,用起來十分方便。
  • 統計計數。好比系統中常見一個功能是限制同一用戶固定時間段內的登陸次數或者全部請求次數,這時就能夠以用戶id爲key,次數值爲value,將計數信息緩存起來,而且有INCRBY命令原生支持。
  • 其餘。Redis的應用場景十分廣發,隊列、發佈訂閱、統計分析等等,能夠看看其餘文章的介紹說明。

  Redis的出現,很大程度補償了memcached這類key/value存儲的不足,在部 分場合能夠對關係數據庫起到很好的補充做用。它提供了Java,C/C++,C#,PHP,JavaScript,Perl,Object-C,Python,Ruby,Erlang等客戶端,使用很方便。Redis的官網地址,很是好記,是redis.io。目前,Vmware在資助着Redis項目的開發和維護。

2、Redis的主要數據結構

  Redis主要有五種基本數據結構,知足了絕大多數緩存結構的須要,若是你在使用一種結構存儲時感受彆扭時,頗有多是選錯了存儲結構,能夠考慮一下其餘結構的正確實現。

  • String ,能夠是字符串、整數和浮點數。若是是序列化數據,並涉及到修改操做的話,不推薦用string,能夠考慮用Hash
  • Hash, key-value 對象,能夠存放對象數據,好比用戶信息之類。
  • List,有序數據集合,元素能夠重複,用LPUSHLPOPRPUSHRPOP等指令組合能夠實現棧和隊列操做。
  • Set,無序集合,元素惟一。
  • Sorted Set,Sort的有序版,能夠設定Score值來決定元素排序,適合用戶排名這樣的業務場景。

 2、Redis的使用

 1. redigo

  redigo是GO語言的一個redis客戶端實現。項目位於https://github.com/garyburd/redigo

 2. 安裝redigo

redigo沒有其餘依賴項,能夠直接經過go get進行安裝 

go get github.com/garyburd/redigo/redis

 3. 鏈接redis

package main

import (
	"fmt"
	"github.com/garyburd/redigo/redis"
)

func main() {
	c, err := redis.Dial("tcp", "localhost:6379")
	if err != nil {
		fmt.Println("conn redis failed,", err)
		return
	}

	defer c.Close()
}

 4. 創建鏈接池

  Redigo Pool 結構維護一個 Redis 鏈接池。應用程序調用 Get 方法從池中獲取鏈接,並使用鏈接的 Close 方法將鏈接的資源返回到池中。通常咱們在系統初始化時聲明一個全局鏈接池,而後在須要操做redis時得到鏈接,執行指令。

pool := &redis.Pool{
        MaxIdle:     3, /*最大的空閒鏈接數*/
        MaxActive:   8, /*最大的激活鏈接數*/
        Dial: func() (redis.Conn, error) {
            c, err := redis.Dial("tcp", "連接地址,例如127.0.0.1:6379", redis.DialPassword("密碼"))
            if err != nil {
                return nil, err
            }
            return c, nil
        },
}
c:=pool.Get()
defer c.Close()
package main

import (
    "fmt"

    "github.com/garyburd/redigo/redis"
)

var pool *redis.Pool

func init() {
    pool = &redis.Pool{
        MaxIdle:     16, /* 最大的空閒鏈接數 */
        MaxActive:   0,  /* 最大的激活鏈接數 */
        IdleTimeout: 300,
        Dial: func() (redis.Conn, error) {
            c, err := redis.Dial("tcp", "localhost:6379", redis.DialPassword("0000"))
            if err != nil {
                return nil, err
            }
            return c, nil
        },
    }
}

func main() {

    c := pool.Get()
    defer c.Close()

    _, err := c.Do("Set", "abc", 100)
    if err != nil {
        fmt.Println(err)
        return
    }

    r, err := redis.Int(c.Do("Get", "abc"))
    if err != nil {
        fmt.Println("get abc failed,", err)
        return
    }

    fmt.Println(r)
    pool.Close()
}
redis鏈接池實例

 5.  執行指令

  查看源碼,發現Conn 接口有一個執行 Redis 命令的通用方法:

//gomodule/redigo/redis/redis.go

// Conn represents a connection to a Redis server.
type Conn interface {
    // Close closes the connection.
    Close() error

    // Err returns a non-nil value when the connection is not usable.
    Err() error

    // Do sends a command to the server and returns the received reply.
    Do(commandName string, args ...interface{}) (reply interface{}, err error)

    // Send writes the command to the client's output buffer.
    Send(commandName string, args ...interface{}) error

    // Flush flushes the output buffer to the Redis server.
    Flush() error

    // Receive receives a single reply from the Redis server
    Receive() (reply interface{}, err error)
}

 http://redis.io/commands 中的 Redis 命令參考列出了可用的命令。do的參數和redis-cli命令參數格式一致,好比SET key value EX 360 對應函數調用爲Do("SET", "key", "value","EX",360),經常使用的命令示例有:  

c := pool.Get()
defer c.Close()
//存值,
_, err := c.Do("SET", "key", "value")
//設置過時時間
_, err := c.Do("SET", "key", "value","EX",360)
//存int
_, err := c.Do("SET", "key", 2)

//取值
v,err:=redis.String(c.Do("GET","key"))
bytes, err := redis.Bytes(c.Do("GET", "key"))
package main

import (
    "fmt"

    "github.com/garyburd/redigo/redis"
)

func main() {
    var p *int
    var a int
    p = &a
    *p = 0

    c, err := redis.Dial("tcp", "localhost:6379")
    if err != nil {
        fmt.Println("conn redis failed,", err)
        return
    }

    defer c.Close()
    _, err = c.Do("Set", "abc", 100)
    if err != nil {
        fmt.Println(err)
        return
    }

    r, err := redis.Int(c.Do("Get", "abc"))
    if err != nil {
        fmt.Println("get abc failed,", err)
        return
    }

    fmt.Println(r)
}
set操做
package main

import (
    "fmt"

    "github.com/garyburd/redigo/redis"
)

func main() {
    c, err := redis.Dial("tcp", "localhost:6379")
    if err != nil {
        fmt.Println("conn redis failed,", err)
        return
    }

    defer c.Close()
    _, err = c.Do("MSet", "abc", 100, "efg", 300)
    if err != nil {
        fmt.Println(err)
        return
    }

    r, err := redis.Ints(c.Do("MGet", "abc", "efg"))
    if err != nil {
        fmt.Println("get abc failed,", err)
        return
    }

    for _, v := range r {
        fmt.Println(v)
    }
}
mset操做
package main

import (
    "fmt"

    "github.com/garyburd/redigo/redis"
)

func main() {
    c, err := redis.Dial("tcp", "localhost:6379")
    if err != nil {
        fmt.Println("conn redis failed,", err)
        return
    }

    defer c.Close()
    _, err = c.Do("HSet", "books", "abc", 100)
    if err != nil {
        fmt.Println(err)
        return
    }

    r, err := redis.Int(c.Do("HGet", "books", "abc"))
    if err != nil {
        fmt.Println("get abc failed,", err)
        return
    }

    fmt.Println(r)
}
hset操做
package main

import (
    "fmt"

    "github.com/garyburd/redigo/redis"
)

func main() {
    c, err := redis.Dial("tcp", "localhost:6379")
    if err != nil {
        fmt.Println("conn redis failed,", err)
        return
    }

    defer c.Close()
    _, err = c.Do("lpush", "book_list", "abc", "ceg", 300)
    if err != nil {
        fmt.Println(err)
        return
    }

    r, err := redis.String(c.Do("lpop", "book_list"))
    if err != nil {
        fmt.Println("get abc failed,", err)
        return
    }

    fmt.Println(r)
}
list隊列操做
package main

import (
    "fmt"

    "github.com/garyburd/redigo/redis"
)

func main() {
    c, err := redis.Dial("tcp", "localhost:6379")
    if err != nil {
        fmt.Println("conn redis failed,", err)
        return
    }

    defer c.Close()
    _, err = c.Do("expire", "abc", 10)
    if err != nil {
        fmt.Println(err)
        return
    }
}
設置過時時間
相關文章
相關標籤/搜索