redis3.0之后提供了新的HA的解决方案,即Cluster模式,由多个节点组成的集群模式。集群master之间基于crc16算法,对key进行校验,得到的值对16384取余,就是key的hash slot(槽)值,每个节点各自存储一部分的hash槽值,主从节点之间基于异步复制方式同步数据。

基于redis集群的基本原理,gedis需要提供一下方面的能力:

1、统一的客户端Cluster;

2、集群连接池的实现;

3、集群节点的健康检查(后续实现);

4、负载均衡机制实现;

5、协议的封装保证对上层透明。

模型基本设计如下:

基础模型定义

/**
* 节点
* master:主节点ip+port
* slaves:从节点ip+port集合
*/
type Node struct {
Url string
Pwd string
InitActive int
}

type ClusterConfig struct {
Nodes []*Node
HeartBeatInterval int
}

/**
* 集群客户端
* heartBeatInterval 心跳检测时间间隔,单位s
* clusterPool key:连接串 value:连接池
*/
type Cluster struct {
config *ClusterConfig
clusterPool map[string]*ConnPool
}
 Cluster初始化

/**
* 初始化Cluster client
*/
func NewCluster(clusterConfig ClusterConfig) *Cluster {
nodes := clusterConfig.Nodes

var cluster Cluster
clusterPool := make(map[string]*ConnPool)

for _, node := range nodes {
var config = ConnConfig{node.Url, node.Pwd}
pool, _ := NewConnPool(node.InitActive, config)
clusterPool[node.Url] = pool
}
cluster.config = &clusterConfig
cluster.clusterPool = clusterPool
//初始化节点健康检测线程
defer func() {
go cluster.heartBeat()
}()
if m==nil {
m = new(sync.RWMutex)
}
return &cluster
}
节点心跳检测

cluster创建后,开启异步线程定时轮询各个节点,向节点发出ping请求,若未响应pong,则表示当前节点异常,然后将当前节点退出连接池,并将该节点加入失败队列,定时轮询队列,检测是否恢复连接,若恢复,则重新创建连接池,从失败队列中退出当前节点。

/**
* 连接池心跳检测,定时ping各个节点,ping失败的,从连接池退出,并将节点加入失败队列
* 定时轮询失败节点队列,检测节点是否已恢复连接,若恢复,则重新创建连接池,并从失败队列中移除
*/
func (cluster *Cluster) heartBeat() {
clusterPool := cluster.GetClusterPool()
interval := cluster.config.HeartBeatInterval
if interval <= 0 {
interval = defaultHeartBeatInterval
}
var nodes = make(map[string]*Node)

for i := 0; i < len(cluster.GetClusterNodesInfo()); i++ {
node := cluster.GetClusterNodesInfo()[i]
nodes[node.Url] = node
}

var failNodes = make(map[string]*Node)
for {
for url, pool := range clusterPool {
result, err := executePing(pool)
if err != nil {
log.Printf("节点[%s] 健康检查异常,原因[%s], 节点将被移除\n", url, err)
//加锁
m.Lock()
time.Sleep(time.Duration(5)*time.Second)
failNodes[url] = nodes[url]
delete(clusterPool, url)
m.Unlock()
} else {
log.Printf("节点[%s] 健康检查结果[%s]\n", url, result)
}
}
//恢复检测
recover(failNodes, clusterPool)

time.Sleep(time.Duration(interval) * time.Second)
}
}

/**
* 检测fail节点是否已恢复正常
*/
func recover(failNodes map[string]*Node, clusterPool map[string]*ConnPool) {
for url,node:=range failNodes{
conn := Connect(url)
if conn != nil {
//节点重连,恢复连接
var config = ConnConfig{url, node.Pwd}
pool, _ := NewConnPool(node.InitActive, config)
//加锁
m.Lock()
clusterPool[node.Url] = pool
delete(failNodes,url)
m.Unlock()
log.Printf("节点[%s] 已重连\n", url)
}
}
}
 测试结果:

loadbalance目前仅实现随机模式,每次访问前随机选择一个节点进行通信

func (cluster *Cluster) RandomSelect() *ConnPool {
m.RLock()
defer m.RUnlock()
pools := cluster.GetClusterPool()
for _,pool:= range pools{
if pool !=nil{
return pool
}
}
fmt.Errorf("none pool can be used")
return nil
}
通信模块的大致流程如下:

1、cluster随机选择一个健康的节点,进行访问;

2、如果节点返回业务数据则通信结束;

3、如果节点返回的消息协议上满足“-MOVED”,例如 -MOVED 5678 127.0.0.1,则表明当前数据不在该节点;

4、重定向到redis指定的节点访问;

func (cluster *Cluster) Set(key string, value string) (interface{}, error) {
result, err := executeSet(cluster.RandomSelect(), key, value)
if err.Error() != protocol.MOVED {
return result, err
}

//重定向到新的节点
return executeSet(cluster.SelectOne(result.(string)), key, value)
}

func executeSet(pool *ConnPool, key string, value string) (interface{}, error) {
conn, err := GetConn(pool)
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)
}
这样,对于应用层来讲,无论访问的哪个节点,都能得到最终的结果,相对是透明的。

调用测试:

package main

import (
. "client"
"net"
"fmt"
)

func main() {
var node7000 = Node{"127.0.0.1:7000", "123456", 10}
var node7001 = Node{"127.0.0.1:7001", "123456", 10}
var node7002 = Node{"127.0.0.1:7002", "123456", 10}
var node7003 = Node{"127.0.0.1:7003", "123456", 10}
var node7004 = Node{"127.0.0.1:7004", "123456", 10}
var node7005 = Node{"127.0.0.1:7005", "123456", 10}

nodes := []*Node{&node7000, &node7001, &node7002, &node7003, &node7004, &node7005}
var clusterConfig = ClusterConfig{nodes,10}
cluster := NewCluster(clusterConfig)
value,err:=cluster.Get("name")
fmt.Println(value, err)
}
 响应结果:

心跳检查和其他loadbalance机制后续补充实现。

项目地址:

https://github.com/zhangxiaomin1993/gedis

go语言的redis客户端的更多相关文章

  1. Redis客户端——Jedis的使用

    本文介绍基于Java语言的Redis客户端——Jedis的使用,包括Jedis简介.获取Jedis.Jedis直连.Jedis连接池以及二者的对比的选择. Jedis简介 Jedis 是 Redis  ...

  2. 用C、python手写redis客户端,兼容redis集群 (-MOVED和-ASK),快速搭建redis集群

    想没想过,自己写一个redis客户端,是不是很难呢? 其实,并不是特别难. 首先,要知道redis服务端用的通信协议,建议直接去官网看,博客啥的其实也是从官网摘抄的,或者从其他博客抄的(忽略). 协议 ...

  3. Redis客户端之Spring整合Jedis,ShardedJedisPool集群配置

    Jedis设计 Jedis作为推荐的java语言redis客户端,其抽象封装为三部分: 对象池设计:Pool,JedisPool,GenericObjectPool,BasePoolableObjec ...

  4. redis客户端(三)

    redis客户端 一.>redis自带的客户端 启动 启动客户端命令:[root@ming bin]# ./redis-cli -h xxx.xxx.xx.xxx-p 6379 注意: -h:指 ...

  5. Java语言访问Redis数据库之Set篇

    如果想通过Java语言对Redis数据库进行访问. 首先,需要安装Redis数据库,可以是Windows系统,或者Linux系统.(本文以Windows系统的本地Redis数据库为例,代码说明如何操作 ...

  6. Redis学习笔记--Redis客户端(三)

    1.Redis客户端 1.1 Redis自带的客户端 (1)启动 启动客户端命令:[root@kwredis bin]# ./redis-cli -h 127.0.0.1 -p 6379 -h:指定访 ...

  7. spring 5.x 系列第8篇 —— 整合Redis客户端 Jedis和Redisson (代码配置方式)

    文章目录 一.说明 1.1 Redis 客户端说明 1.2 Redis可视化软件 1.3 项目结构说明 1.3 依赖说明 二.spring 整合 jedis 2.1 新建基本配置文件和其映射类 2.2 ...

  8. spring 5.x 系列第7篇 —— 整合Redis客户端 Jedis和Redisson (xml配置方式)

    文章目录 一.说明 1.1 Redis 客户端说明 1.2 Redis可视化软件 1.3 项目结构说明 1.3 依赖说明 二.spring 整合 jedis 2.1 新建基本配置文件 2.2 单机配置 ...

  9. 一文彻底理解Redis序列化协议,你也可以编写Redis客户端

    前提 最近学习Netty的时候想做一个基于Redis服务协议的编码解码模块,过程中顺便阅读了Redis服务序列化协议RESP,结合自己的理解对文档进行了翻译并且简单实现了RESP基于Java语言的解析 ...

随机推荐

  1. WPF/C# 快捷键 自动生成方法

    原文:WPF/C# 快捷键 自动生成方法 这一篇文章会很短~ 在写依赖属性的会后   propdb 会自动生成依赖属性所有的内容 但是如果我写属性变化通知的时候   希望有一个快捷键能自动生成方法 怎 ...

  2. 【转】MyBatis缓存机制

    转载:https://blog.csdn.net/bjweimengshu/article/details/79988252. 本文转载自公众号 美团技术点评 前言 MyBatis是常见的Java数据 ...

  3. IT兄弟连 HTML5教程 HTML5的曲折发展过程 HTML5的诞生

    十年磨一剑,正如我们所看到的一样,HTML5大潮正来势汹汹.但也正如我们所知道的一样,HTML5是一种技术标准,它的语义之美.人性之美.简单之美.实用之美……如同一场革命,它的主要应用场景是浏览器,不 ...

  4. HDU - 6351 Beautiful Now

    Beautiful Now HDU - 6351 Anton has a positive integer n, however, it quite looks like a mess, so he ...

  5. svn版本管理配置权限

    修改svn配置 编辑svnserve.conf文件 第19,20行删掉前面的#--意思就是打开 ancon-access = none  匿名用户不可读 auth-access = write 认证可 ...

  6. 通过 SCQA 的框架来讲故事

    SCQA:Situation情景.Complication冲突.Question疑问. Answer回答   SCQA模型是一个"结构化表达"工具,是麦肯锡咨询顾问芭芭拉·明托在& ...

  7. .net core event bus

    NServiceBus (收费) https://docs.particular.net/tutorials/quickstart/ MassTransit http://masstransit-pr ...

  8. .net core 的 aop 实现方法汇总

    decorator 不借助第三方DI容器,通过装饰模式通过内置的DI容器实现 https://medium.com/@willie.tetlow/net-core-dependency-injecti ...

  9. nodejs通过钉钉群机器人推送消息

    nodejs 通过钉钉群机器人推送消息 Intro 最近在用 nodejs 写爬虫,之前的 nodejs 爬虫代码用 js 写的,感觉可维护性太差,也没有智能提示,于是把js改用ts(typescri ...

  10. 诚聘.NET架构师、高级开发工程师(2019年8月29日发布)

    招聘单位是ABP架构设计交流群(134710707)群主阳铭所在的公司 公司简介 七二四科技有限公司成立于2015年,成立之初便由金茂资本按估值2亿投资2200万,进行“健康724”平台搭建,2017 ...