千夜同学 发布的文章

package main

import (
    "fmt"
"strconv"
    "time"
)

// TimeUnix is time for unix
func TimeUnix() string {
naiveTime := time.Now().Unix()
naiveTimeString := strconv.FormatInt(naiveTime, 10)
return naiveTimeString
}

func main() {
    timeNow := time.Now()
    fmt.Println("tNow(time format): ", timeNow)
    
    // 时间转化为 string layout 必须为 "2006-01-02 15:04:05"
    timeNowStr := timeNow.Format("2006-01-02 15:04:05")
    fmt.Println("tNow(string format): ", timeNowStr)

    timeNowUnixInt := timeNow.Unix()
    fmt.Println(timeNowUnixInt)

    // string 转化为时间 layout 必须为 "2006-01-02 15:04:05"
    t1, _ := time.Parse("2006-01-02 15:04:05", "2014-06-15 08:37:18")
    fmt.Println("t(time format): ", t1)
    t2 := time.Unix(1389058332, 0).Format("2006-01-02 15:04:05")
    fmt.Println("t(time format): ", t2)
}

Go 中关于错误的哲学,应该尽可能优雅地处理错误。

先说结论:

  • 使用"隐藏内部细节的错误处理"
  • 使用 errors.Wrap 封装原始 error
  • 使用 errors.Cause 找出原始 error
  • 为了行为而断言,而不是类型
  • 尽量减少错误值的使用

error 的类型是 interface

type error interface {
    Error() string
}

创建error:

// example 1
func Sqrt(f float64) (float64, error) {
    if f < 0 {
        return 0, errors.New("math: square root of negative number")
    }
    // implementation
}

// example 2
if f < 0 {
    return 0, fmt.Errorf("math: square root of negative number %g", f)
}

- 阅读剩余部分 -

判断文件是否存在 存在返回 true 不存在返回 false

func checkFileIsExist(filename string) bool {
    var exist = true
    if _, err := os.Stat(filename); os.IsNotExist(err) {
        exist = false
    }
    return exist
}

打开文件 返回文件指针

file, error := os.Open("/tmp/1.txt")
if error != nil {
    fmt.Println(error)
}
fmt.Println(file)
file.Close()

以读写方式打开文件 返回文件指针 如果不存在则创建

file2, error := os.OpenFile("/tmp/2.txt", os.O_RDWR|os.O_CREATE, 0644)
if error != nil {
    fmt.Println(error)
}
fmt.Println(file2)
file2.Close()

创建文件 Create 函数也是调用的 OpenFile

file3, error := os.Create("/tmp/3.txt")
if error != nil {
    fmt.Println(error)
}
fmt.Println(file3)
file3.Close()

删除文件

del := os.Remove("/tmp/1.txt")
if del != nil {
    fmt.Println(del)
}

删除指定 path 下的所有文件

delDir := os.RemoveAll("/tmp/testdir")
if delDir != nil {
        fmt.Println(delDir)
}

- 阅读剩余部分 -

etcd3 和 etcd2 的 api 不太一样。
etcd3 移除了目录概念,只有单纯的 kv。
api 也只有 rpc 模式,http 需要 proxy。

package etcd

import (
    "context"
    "log"
    "time"

    "../conf"

    "go.etcd.io/etcd/clientv3"
)

var (
    endpoints   = make([]string, 1)
    dialTimeout = 3 * time.Second
    username    string
    password    string
)

// Init is init etcd
func Init() {
    endpoints = append(endpoints, conf.RunTimeInfo.EtcdHost)
    username = conf.RunTimeInfo.EtcdUser
    password = conf.RunTimeInfo.EtcdPasswd
}

// connetEtcd is connect etcd
func connetEtcd() *clientv3.Client {
    cli, cliErr := clientv3.New(clientv3.Config{
        Endpoints:   endpoints,
        DialTimeout: dialTimeout,
        Username:    username,
        Password:    password,
    })
    if cliErr != nil {
        log.Println(cliErr)
    }
    return cli
}

// KeepAlive is make etcd keepalive with ttl
func KeepAlive(keyID clientv3.LeaseID) {
    cli := connetEtcd()
    if cli != nil {
        ka, kaErr := cli.KeepAliveOnce(context.TODO(), keyID)
        if kaErr != nil {
            log.Println(kaErr)
        }
        log.Println("ttl:", ka)
        cli.Close()
    }
}

// GetKV is get kv from etcd
func GetKV(dir string) []string {
    cli := connetEtcd()
    var kvList = make([]string, 0)
    if cli != nil {
        resp, respErr := cli.Get(context.TODO(), dir, clientv3.WithPrefix())
        if respErr != nil {
            log.Println(respErr)
        }
        for _, ev := range resp.Kvs {
            kvTemp := string(ev.Key) + ":" + string(ev.Value)
            kvList = append(kvList, kvTemp)
        }
        cli.Close()
    }
    return kvList
}

// PutKV is set kv to etcd
func PutKV(writePath string, writeValue string) {
    cli := connetEtcd()
    if cli != nil {
        resp, respErr := cli.Put(context.TODO(), writePath, writeValue)
        if respErr != nil {
            log.Println(respErr)
        }
        if resp != nil {
            log.Println(resp.Header)
            cli.Close()
        }
    }
}

// PutKVTTL is set kv to etcd with ttl
func PutKVTTL(registKey, registValue string) {
    cli := connetEtcd()
    if cli != nil {
        resp, respErr := cli.Grant(context.TODO(), 10)
        if respErr != nil {
            log.Println(respErr)
        }
        conf.ServID = resp.ID
        putResp, putErr := cli.Put(context.TODO(), registKey, registValue, clientv3.WithLease(resp.ID))
        if putErr != nil {
            log.Println(putErr)
        }
        log.Println(putResp.Header)
        cli.Close()
    }
}

// DelKV is del kv to etcd
func DelKV(writePath string) {
    cli := connetEtcd()
    if cli != nil {
        resp, respErr := cli.Delete(context.TODO(), writePath)
        if respErr != nil {
            log.Println(respErr)
        }
        log.Println(resp.Header)
        cli.Close()
    }
}

[]string to string

package main

import (
    "fmt"
    "strings"
)

var naiveList string

func main() {
    naiveArray := []string{"a", "b", "c", "d", "e"}
    for _, v := range naiveArray {
        if naiveList == "" {
        naiveList = v
        } else {
        naiveList = strings.Join([]string{naiveList, v}, ",")
        }
    }
    fmt.Println(naiveList)
}

map to struct

import (
    "testing"
    "github.com/goinggo/mapstructure"
)

func TestMap2Struct(t *testing.T) {
    mapInstance := make(map[string]interface{})
    mapInstance["Name"] = "liang637210"
    mapInstance["Age"] = 28
    var person Person
    // 将 map 转换为指定的结构体
    if err := mapstructure.Decode(mapInstance, &person); err != nil {
        t.Fatal(err)
    }
    t.Logf("map2struct 后得到的 struct 内容为:%v", person)
}

struct to map

import (
    "testing"
    "reflect"
)

type User struct {
    Id        int    `json:"id"`
    Username    string    `json:"username"`
    Password    string    `json:"password"`
}

func Struct2Map(obj interface{}) map[string]interface{} {
    t := reflect.TypeOf(obj)
    v := reflect.ValueOf(obj)
    var data = make(map[string]interface{})
    for i := 0; i < t.NumField(); i++ {
        data[t.Field(i).Name] = v.Field(i).Interface()
    }
    return data
}

func TestStruct2Map(t *testing.T) {
    user := User{5, "zhangsan", "password"}
    data := Struct2Map(user)
    t.Logf("struct2map 得到的 map 内容为:%v", data)
}

高效json库.png

直接替换 json.Marshal 替为 jsoniter.Marshal

type ColorGroup struct {
    ID     int
    Name   string
    Colors []string
}
group := ColorGroup{
    ID:     1,
    Name:   "Reds",
    Colors: []string{"Crimson", "Red", "Ruby", "Maroon"},
}
b, err := jsoniter.Marshal(group)

- 阅读剩余部分 -

json 的规范中,对于数字类型,并不区分是整型还是浮点型。

go json number.gif

对于如下 json 文本:

{
    "name": "ethancai",
    "fansCount": 9223372036854775807
}

如果反序列化的时候指定明确的结构体和变量类型

package main

import (
    "encoding/json"
    "fmt"
)

type User struct {
    Name      string
    FansCount int64
}

func main() {
    const jsonStream = `{"name":"ethancai", "fansCount": 9223372036854775807}`
    // 类型为User
    var user User
    err := json.Unmarshal([]byte(jsonStream), &user)
    if err != nil {
        fmt.Println("error:", err)
    }
    fmt.Printf("%+v \n", user)
}

Output:

{Name:ethancai FansCount:9223372036854775807}

如果反序列化不指定结构体类型或者变量类型,则 json 中的数字类型,默认被反序列化成 float64 类型:

package main

import (
    "encoding/json"
    "fmt"
    "reflect"
)

func main() {
    const jsonStream = `{"name":"ethancai", "fansCount": 9223372036854775807}`
    // 不指定反序列化的类型
    var user interface{}
    err := json.Unmarshal([]byte(jsonStream), &user)
    if err != nil {
        fmt.Println("error:", err)
    }
    m := user.(map[string]interface{})
    fansCount := m["fansCount"]
    fmt.Printf("%+v \n", reflect.TypeOf(fansCount).Name())
    fmt.Printf("%+v \n", fansCount.(float64))
}

Output:

float64
9.223372036854776e+18

另一个程序

package main

import (
    "encoding/json"
    "fmt"
)

type User struct {
    Name      string
    // 不指定 FansCount 变量的类型
    FansCount interface{}
}

func main() {
    const jsonStream = `{"name":"ethancai", "fansCount": 9223372036854775807}`
    var user User
    err := json.Unmarshal([]byte(jsonStream), &user)
    if err != nil {
        fmt.Println("error:", err)
    }
    fmt.Printf("%+v \n", user)
}

Output:

{Name:ethancai FansCount:9.223372036854776e+18}

从上面的程序可以发现,如果 fansCount 精度比较高,反序列化成 float64 类型的数值时存在丢失精度的问题。

package main

import (
    "encoding/json"
    "fmt"
    "reflect"
    "strings"
)

func main() {
    const jsonStream = `{"name":"ethancai", "fansCount": 9223372036854775807}`
    decoder := json.NewDecoder(strings.NewReader(jsonStream))
    // UseNumber causes the Decoder to unmarshal a number into an interface{} as a Number instead of as a float64.
    decoder.UseNumber()
    var user interface{}
    if err := decoder.Decode(&user); err != nil {
        fmt.Println("error:", err)
        return
    }
    m := user.(map[string]interface{})
    fansCount := m["fansCount"]
    fmt.Printf("%+v \n", reflect.TypeOf(fansCount).PkgPath() + "." + reflect.TypeOf(fansCount).Name())
    v, err := fansCount.(json.Number).Int64()
    if err != nil {
        fmt.Println("error:", err)
        return
    }
    fmt.Printf("%+v \n", v)
}

Output:

encoding/json.Number
9223372036854775807

上面的程序,使用了 func (*Decoder) UseNumber 方法告诉反序列化 json 的数字类型的时候,不要直接转换成 float64,而是转换成 json.Number 类型。
json.Number 内部实现机制:

// A Number represents a JSON number literal.
type Number string

// String returns the literal text of the number.
func (n Number) String() string { return string(n) }

// Float64 returns the number as a float64.
func (n Number) Float64() (float64, error) {
    return strconv.ParseFloat(string(n), 64)
}

// Int64 returns the number as an int64.
func (n Number) Int64() (int64, error) {
    return strconv.ParseInt(string(n), 10, 64)
}

json.Number 本质是字符串,反序列化的时候将 json 的数值先转成 json.Number,其实是一种延迟处理的手段,待后续逻辑需要时候,再把 json.Number 转成 float64 或者 int64。

json to struct

import (
    "testing"
    "encoding/json"
)

// 这里对应的 N 和 A 不能为小写,首字母必须为大写,这样才可对外提供访问,具体 json 匹配是通过后面的 tag 标签进行匹配的,与 N 和 A 没有关系
// tag 标签中 json 后面跟着的是字段名称,都是字符串类型,要求必须加上双引号
type Person struct {
    N string     `json:"name"`
    A int        `json:"age"`
}

func TestStruct2Json(t *testing.T) {
    jsonStr := `{
        "name":"liangyongxing",
        "age":12
    }`
    var person Person
    json.Unmarshal([]byte(jsonStr), &person)
    t.Log(person)
}

- 阅读剩余部分 -

image

sync.map 底层使用 map[interface{}]*entry 来做存储,所以无论 key 还是 value 都是支持多种数据类型。

package main

import (
    "fmt"
    "sync"
)

type MySyncMap struct {
    sync.Map
}

func (m MySyncMap) Print(k interface{}) {
    value, ok := m.Load(k)
    fmt.Println(value, ok)
}

func main() {
    var syncMap MySyncMap
    syncMap.Print("Key1")
    syncMap.Store("Key1", "Value1")
    syncMap.Print("Key1")
    syncMap.Store("Key2", "Value2")
    syncMap.Store("Key3", 2)
    syncMap.Print("Key3")
    syncMap.Store(4, 4)
    syncMap.Print(4)
    syncMap.Delete("Key1")
    syncMap.Print("Key1")
}
<nil> false
Value1 true
2 true
4 true
<nil> false

并发 hashmap 的方案有很多。

  1. 直接在不支持并发的 hashmap 上,使用一个读写锁的保护,这也是 sync map 还没出来前,常用的方法。这种方法的缺点是写会堵塞读。
  2. 分段锁,每一个读写锁保护一段区间。平均情况下这样的性能还挺好的,但是极端情况下,如果某个区间有热点写,那么那个区间的读请求也会受到影响。
  3. 使用使用链表法解决冲突,然后链表使用 CAS 去解决并发下冲突。

An overview of sync.Map 中有提到,在 cpu 核数很多的情况下,因为 cache contention,reflect.New、sync.RWMutex、atomic.AddUint32 都会很慢。

读写分离

使用了两个 map,一个叫 read,一个叫 dirty,两个 map 存储的都是指针,指向 value 数据本身,所以两个 map 是共享 value 数据的,更新 value 对两个 map 同时可见。

dirty 可以进行增删查,要进行加互斥锁。

read 中存在的 key,可以无锁的读,借助 CAS 进行无锁的更新、删除操作,但是不能新增 key,相当于 dirty 的一个 cache,由于 value 共享,所以能通过 read 对已存在的 value 进行更新。

sync.Map 中会记录 miss cache 的次数,当 miss 次数大于等于 dirty 元素个数时,就会把 dirty 变成 read,原来的 dirty 清空。

为了方便 dirty 直接变成 read,那么得保证 read 中存在的数据 dirty 必须有,所以在 dirty 是空的时候,如果要新增一个 key,那么会把 read 中的元素复制到 dirty 中,然后写入新 key。

删除操作使用的是延迟删除,优先看 read 中没有,read 中有,就把 read 中的对应 entry 指针中的 p 置为 nil,作为一个标记。在 read 中标记为 nil 的,只有在 dirty 提升为 read 时才会被实际删除。

// The zero Map is empty and ready for use. A Map must not be copied after first use.
type Map struct {
    mu Mutex

    // read contains the portion of the map's contents that are safe for
    // concurrent access (with or without mu held).
    //
    // The read field itself is always safe to load, but must only be stored with
    // mu held.
    //
    // Entries stored in read may be updated concurrently without mu, but updating
    // a previously-expunged entry requires that the entry be copied to the dirty
    // map and unexpunged with mu held.
    read atomic.Value // readOnly

    // dirty contains the portion of the map's contents that require mu to be
    // held. To ensure that the dirty map can be promoted to the read map quickly,
    // it also includes all of the non-expunged entries in the read map.
    //
    // Expunged entries are not stored in the dirty map. An expunged entry in the
    // clean map must be unexpunged and added to the dirty map before a new value
    // can be stored to it.
    //
    // If the dirty map is nil, the next write to the map will initialize it by
    // making a shallow copy of the clean map, omitting stale entries.
    dirty map[interface{}]*entry

    // misses counts the number of loads since the read map was last updated that
    // needed to lock mu to determine whether the key was present.
    //
    // Once enough misses have occurred to cover the cost of copying the dirty
    // map, the dirty map will be promoted to the read map (in the unamended
    // state) and the next store to the map will make a new dirty copy.
    misses int
}

// read的实际结构体
// readOnly is an immutable struct stored atomically in the Map.read field.
type readOnly struct {
    m       map[interface{}]*entry
    amended bool // true if the dirty map contains some key not in m.
}

// expunged is an arbitrary pointer that marks entries which have been deleted
// from the dirty map.
var expunged = unsafe.Pointer(new(interface{}))

// An entry is a slot in the map corresponding to a particular key.
type entry struct {
    // p points to the interface{} value stored for the entry.
    //
    // If p == nil, the entry has been deleted and m.dirty == nil.
    //
    // If p == expunged, the entry has been deleted, m.dirty != nil, and the entry
    // is missing from m.dirty.
    //
    // Otherwise, the entry is valid and recorded in m.read.m[key] and, if m.dirty
    // != nil, in m.dirty[key].
    //
    // An entry can be deleted by atomic replacement with nil: when m.dirty is
    // next created, it will atomically replace nil with expunged and leave
    // m.dirty[key] unset.
    //
    // An entry's associated value can be updated by atomic replacement, provided
    // p != expunged. If p == expunged, an entry's associated value can be updated
    // only after first setting m.dirty[key] = e so that lookups using the dirty
    // map find the entry.
    p unsafe.Pointer // *interface{}
}

image

mu 是用来保护 dirty 的互斥锁。

missed 是记录没命中read的次数。

注意对于 entry.p,有两个特殊值,一个是 nil,另一个是 expunged。

nil 代表的意思是,在read中被删除了,但是dirty中还在,所以能直接更新值。如果 dirty==nill 的特殊情况,下次写入新值时会复制。

expunged 代表数据在 ditry 中已经被删除了,更新值的时候要先把这个 entry 复制到 dirty。

Load 读取

// Load returns the value stored in the map for a key, or nil if no
// value is present.
// The ok result indicates whether value was found in the map.
func (m *Map) Load(key interface{}) (value interface{}, ok bool) {
    read, _ := m.read.Load().(readOnly)
    e, ok := read.m[key]
    if !ok && read.amended {
        m.mu.Lock()
        // Avoid reporting a spurious miss if m.dirty got promoted while we were
        // blocked on m.mu. (If further loads of the same key will not miss, it's
        // not worth copying the dirty map for this key.)
        read, _ = m.read.Load().(readOnly)
        e, ok = read.m[key]
        if !ok && read.amended {
            e, ok = m.dirty[key]
            // Regardless of whether the entry was present, record a miss: this key
            // will take the slow path until the dirty map is promoted to the read
            // map.
            m.missLocked()
        }
        m.mu.Unlock()
    }
    if !ok {
        return nil, false
    }
    return e.load()
}

func (e *entry) load() (value interface{}, ok bool) {
    p := atomic.LoadPointer(&e.p)
    if p == nil || p == expunged {
        return nil, false
    }
    return *(*interface{})(p), true
}

func (m *Map) missLocked() {
    m.misses++
    if m.misses < len(m.dirty) {
        return
    }
    m.read.Store(readOnly{m: m.dirty})
    m.dirty = nil
    m.misses = 0
}

读取时,先去 read 读取。如果没有,就加锁,然后去 dirty 读取,同时调用 missLocked(),再解锁。在m issLocked 中,会递增 misses 变量,如果 misses>len(dirty),那么把 dirty 提升为 read,清空原来的 dirty。

在代码中,我们可以看到一个 double check,检查 read 没有,上锁,再检查 read 中有没有,是因为有可能在第一次检查之后,上锁之前的间隙,dirty 提升为 read 了,这时如果不 double check,可能会导致一个存在的 key 却返回给调用方说不存在。

Store 写入

// Store sets the value for a key.
func (m *Map) Store(key, value interface{}) {
    read, _ := m.read.Load().(readOnly)
    if e, ok := read.m[key]; ok && e.tryStore(&value) {
        return
    }

    m.mu.Lock()
    read, _ = m.read.Load().(readOnly)
    if e, ok := read.m[key]; ok {
        if e.unexpungeLocked() {
            // The entry was previously expunged, which implies that there is a
            // non-nil dirty map and this entry is not in it.
            m.dirty[key] = e
        }
        e.storeLocked(&value)
    } else if e, ok := m.dirty[key]; ok {
        e.storeLocked(&value)
    } else {
        if !read.amended {
            // We're adding the first new key to the dirty map.
            // Make sure it is allocated and mark the read-only map as incomplete.
            m.dirtyLocked()
            m.read.Store(readOnly{m: read.m, amended: true})
        }
        m.dirty[key] = newEntry(value)
    }
    m.mu.Unlock()
}

// tryStore stores a value if the entry has not been expunged.
//
// If the entry is expunged, tryStore returns false and leaves the entry
// unchanged.
func (e *entry) tryStore(i *interface{}) bool {
    p := atomic.LoadPointer(&e.p)
    if p == expunged {
        return false
    }
    for {
        if atomic.CompareAndSwapPointer(&e.p, p, unsafe.Pointer(i)) {
            return true
        }
        p = atomic.LoadPointer(&e.p)
        if p == expunged {
            return false
        }
    }
}

func (m *Map) dirtyLocked() {
    if m.dirty != nil {
        return
    }

    read, _ := m.read.Load().(readOnly)
    m.dirty = make(map[interface{}]*entry, len(read.m))
    for k, e := range read.m {
        if !e.tryExpungeLocked() {
            m.dirty[k] = e
        }
    }
}

func (e *entry) tryExpungeLocked() (isExpunged bool) {
    p := atomic.LoadPointer(&e.p)
    for p == nil {
        if atomic.CompareAndSwapPointer(&e.p, nil, expunged) {
            return true
        }
        p = atomic.LoadPointer(&e.p)
    }
    return p == expunged
}

// unexpungeLocked ensures that the entry is not marked as expunged.
//
// If the entry was previously expunged, it must be added to the dirty map
// before m.mu is unlocked.
func (e *entry) unexpungeLocked() (wasExpunged bool) {
    return atomic.CompareAndSwapPointer(&e.p, expunged, nil)
}

写入的时候,先看 read 中能否查到 key,在 read 中存在的话,直接通过 read 中的 entry 来更新值。在 read 中不存在,那么就上锁,然后 double check。

double check 情况:

  1. double check 发现 read 中存在,如果是 expunged,那么就先尝试把 expunged 替换成 nil,最后如果 entry.p==expunged 就复制到 dirty 中,再写入值。否则不用替换直接写入值。
  2. dirty 中存在,直接更新。dirty 中不存在,如果此时 dirty 为空,那么需要将 read 复制到 dirty 中,最后再把新值写入到 dirty 中。复制的时候调用的是 dirtyLocked(),在复制到 dirty 的时候,read 中为 nil 的元素,会更新为 expunged,并且不复制到 dirty 中。

在更新 read 中的数据时,使用的是 tryStore,通过 CAS 来解决冲突,在 CAS 出现冲突后,如果发现数据被置为 expung,tryStore 那么就不会写入数据,而是会返回 false,在 Store 流程中,就是接着往下走,在 dirty 中写入。

double check 的时候,在 read 中存在,那么就是说在加锁之前,有并发线程先写入了 key,然后由 Load 触发了 dirty 提升为 read,这时 dirty 可能为空,也可能不为空,但无论 dirty 状态如何,都是可以直接更新 entry.p。如果是 expunged 的话,那么要先替换成 nil,再复制 entry 到 dirty 中。

Delete 删除

// Delete deletes the value for a key.
func (m *Map) Delete(key interface{}) {
    read, _ := m.read.Load().(readOnly)
    e, ok := read.m[key]
    if !ok && read.amended {
        m.mu.Lock()
        read, _ = m.read.Load().(readOnly)
        e, ok = read.m[key]
        if !ok && read.amended {
            delete(m.dirty, key)
        }
        m.mu.Unlock()
    }
    if ok {
        e.delete()
    }
}

func (e *entry) delete() (hadValue bool) {
    for {
        p := atomic.LoadPointer(&e.p)
        if p == nil || p == expunged {
            return false
        }
        if atomic.CompareAndSwapPointer(&e.p, p, nil) {
            return true
        }
    }
}

删除很简单,read 中存在,就把 read 中的 entry.p 置为 nil,如果只在 ditry 中存在,那么就直接从 dirty 中删掉对应的 entry。