在前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)
}
测试结果如下:
到这里为止,我们的《自己实现go语言的redis客户端》系列的文章基本就靠一段落了,感兴趣的朋友们多多交流,一起学习!支持原创,支持开源!
项目地址:
https://github.com/zhangxiaomin1993/gedis
参考资料: