redis 客户端windows redis 客户端分片_sharding

        在前5篇文章中我们分享了一个go语言redis客户端的基本实现,包括单机版(1.0)和Cluster版本(2.0),接下来我们分享一下客户端分片(3.0-Sharding)的集群方式的实现。

1.0 特性:
        基于原生golang开发
        连接池管理
        keepalive
        redisTemplate提供多种命令支持
2.0 特性
       cluster支持
       loadbalance支持
       heartBeat支持
       连接池的监控及动态扩容
3.0 特性
      支持客户端分片集群方式
      一致性哈希
    (不支持多key操作)

        redis3.0版本出现之前(3.0开始支持Cluster),对于集群的实现方案一般都是基于客户端分片的形式(Sharding),一般是在客户端根据一致性哈希算法,将集群节点散列到一个虚拟的哈希环上,使用的时候,对key进行hash计算,进而判断当前key应该交给哪个节点处理。今天我们分享一下,如何实现gedis的客户端分片集群方案。

/**
 * 分片信息
 *
 */
type Shard struct {
	Url        string
	Pwd        string
	InitActive int
	MinActive  int
	MaxActive  int
}
//分片配置
type ShardConfig struct {
	Shards             []*Shard
	HeartBeatInterval int
}

/**
 * 客户端分片
 * heartBeatInterval 心跳检测时间间隔,单位s
 * shardingPool key:连接串 value:连接池
 */
type Sharding struct {
	cHashRing   *Consistent
	config      *ShardConfig
	shardingPool map[string]*ConnPool
	m *sync.RWMutex
}

        Shard为分片信息的定义,包括url、密码及连接池设置信息等等;ShardConfig为分片的配置信息,包括节点的基础信息集合以及连接池的心跳频率;Sharding为客户端的定义,其中cHashRing为一致性哈希算法的相关实现内容,config为分片配置信息,shardingPool为连接池信息,m是一个读写锁,用于线程池的管理。

        然后,我们看一下客户端的初始化逻辑:

//初始化分片客户端
func NewSharding(shardConfig ShardConfig) *Sharding {
	shards := shardConfig.Shards
	//初始化一致性hash环
	cHashRing := NewConsistent()

	var sharding Sharding
	shardingPool := make(map[string]*ConnPool)

	for index, shard := range shards {
		var config = ConnConfig{shard.Url, shard.Pwd, shard.InitActive, shard.MinActive, shard.MaxActive}
		pool, _ := NewConnPool(config)
		shardingPool[shard.Url] = pool
		//将分片散列到hash环中
		cHashRing.Add(NewShardInfo(index, shard.Url, 1))
	}
	sharding.cHashRing = cHashRing
	sharding.config = &shardConfig
	sharding.shardingPool = shardingPool

	if sharding.m == nil {
		sharding.m = new(sync.RWMutex)
	}
	return &sharding
}

        如代码所示,首先获取到分片的基础信息,然后初始化一致性hash的“hash环”,接着就是遍历集群节点,创建连接池同时将节点散列到hash环上,最后初始化读写锁,完成客户端的初始化工作。

        连接池的心跳检测实现,复用Cluster模式的方案,这里不再赘述,接下来我们看一下,api层面的处理逻辑。

func (sharding *Sharding) Set(key string, value string) (interface{}, error) {
	return executeSet(sharding.shardingPool[sharding.cHashRing.GetShardInfo(key).Url], key, value)
}

func executeSet(pool *ConnPool, key string, value string) (interface{}, error) {
	conn, err := pool.GetConn()
	if err != nil {
		return nil, fmt.Errorf("get conn fail")
	}
	defer pool.PutConn(conn)
	result := SendCommand(conn, protocol.SET, protocol.SafeEncode(key), protocol.SafeEncode(value))
	return handler.HandleReply(result)
}

        首先根据key,进行一个hash计算,然后根据key的hash值定位到哈希环上的具体某一个节点上,取得节点的信息,最后根据节点的信息,从连接池中获取响应的连接进行后续的处理操作。

        注意:基于客户端分片(Sharding)的集群方式,是无法处理“多key”操作的,因为分片的规则是基于单个key的hash值进行定位的,如果是多key的业务场景,客户端没办法保证多个key散列在同一个服务器节点中。

        最后,我们看一下使用方式:

package main

import (
	. "client"
	"fmt"
	"log"
	"math/rand"
	"net"
	"time"
)

func main(){
	var s1 = Shard{"192.168.96.232:6379", "12345", 50,5,200}
	var s2 = Shard{"192.168.96.4:6379", "12345", 50,5,200}
	shards := []*Shard{&s1, &s2}
	var shardConfig = ShardConfig{shards, 10}
	sharding := NewSharding(shardConfig)
	rand.Seed(time.Now().UnixNano())
	for i:=0;i<20;i++{
		go func() {
			for {
				value, err := sharding.Get("teams")
				log.Printf("请求结果:%s, err: %s",value, err)
				fmt.Println("查询结果",value)
				time.Sleep(time.Duration(rand.Intn(3))*time.Second)
			}
		}()
	}
	time.Sleep(1000*10)
}

        测试结果如下:

redis 客户端windows redis 客户端分片_golang_02

        到这里为止,我们的《自己实现go语言的redis客户端》系列的文章基本就靠一段落了,感兴趣的朋友们多多交流,一起学习!

项目地址:

https://github.com/zhangxiaomin1993/gedis