网上关于redis高可用基本都是用redis-sentinel 哨兵 或者 redis cluster 集群来实现, 但是有没有更简单的方式,比如我现在就只有2个redis实例。我试验的结果是我们可用采用主备的方式来实现(我们的实际需求很简单,有2个redis实例分布在不同的计算机,在一个实例down掉后我们的应用程序有继续读写redis,主从配置可用手动修改)。需求很简单, 实现也就很简单。首先下载 https://github.com/StackExchange/StackExchange.Redis 源码。启动StackExchange.Redis-master\Redis Configs里面的主从2个实例,我最终demo的code如下:

 class Program
{
static IDatabase database;
static ConnectionMultiplexer conn;
static void Main(string[] args)
{
ConfigurationOptions option = new ConfigurationOptions() {
EndPoints =
{
{ "127.0.0.1", },
{ "127.0.0.1", }
},
AllowAdmin =true,
}; conn = ConnectionMultiplexer.Connect(option);
database = conn.GetDatabase(); Random rand = new Random(); while (true)
{
string val = "gavin_" + rand.Next(, ).ToString();
TestWriteRead(val);
Thread.Sleep();
} } static void TestWriteRead(string value) {
string key = "gavinteststring";
try
{
database.StringSet(key, value);
Console.WriteLine($"写入{key}={value}成功");
}
catch (Exception ex)
{
var points = conn.GetEndPoints();
foreach (var item in points)
{
var server = conn.GetServer(item);
if (server.IsConnected)
{
server.MakeMaster(ReplicationChangeOptions.All);
}
else
{
server.SlaveOf(points[],CommandFlags.FireAndForget);
} }
database.StringSet(key, value);
Console.WriteLine($"写入{key}={value}成功");
//Console.WriteLine($"写入{key}={value}失败:"+ex.ToString());
// Console.ReadKey();
}
string temp = string.Empty;
try
{
temp=database.StringGet(key);
Console.WriteLine($"读取{key}={temp}成功");
}
catch (Exception ex)
{
Console.WriteLine($"读取{key}失败:" + ex.ToString());
}
}
}

大家请先忽略我catch里面的code,当我把redis的master关闭后,程序报错:

No connection is available to service this operation: SET gavinteststring; 远程主机强迫关闭了一个现有的连接。; IOCP: (Busy=0,Free=1000,Min=4,Max=1000), WORKER: (Busy=0,Free=1023,Min=4,Max=1023), Local-CPU: 100%

找到源码后发现在ConnectionMultiplexer的ExecuteSyncImpl方法里面有这么一段:

if (!TryPushMessageToBridge(message, processor, source, ref server))
{
throw ExceptionFactory.NoConnectionAvailable(IncludeDetailInExceptions, message.Command, message, server, GetServerSnapshot());
}

也就是说StackExchange没有找到redis的服务器实例,继续跟踪code发现具体查找server的code在ConnectionMultiplexer的AnyConnected方法里面:

 internal ServerEndPoint AnyConnected(ServerType serverType, uint startOffset, RedisCommand command, CommandFlags flags)
{
var tmp = serverSnapshot;
int len = tmp.Length;
ServerEndPoint fallback = null;
for (int i = ; i < len; i++)
{
var server = tmp[(int)(((uint)i + startOffset) % len)];
if (server != null && server.ServerType == serverType && server.IsSelectable(command))
{
if (server.IsSlave)
{
switch (flags)
{
case CommandFlags.DemandSlave:
case CommandFlags.PreferSlave:
return server;
case CommandFlags.PreferMaster:
fallback = server;
break;
}
} else
{
switch (flags)
{
case CommandFlags.DemandMaster:
case CommandFlags.PreferMaster:
return server;
case CommandFlags.PreferSlave:
fallback = server;
break;
}
}
}
}
return fallback;
}

因为主的server已经down掉了,所以可用访问的server就是Slave,但是这里的flags默认是CommandFlags.DemandMaster。所以是找不到server。那么我们把现在的从的server改为主的server如:  server.MakeMaster(ReplicationChangeOptions.All); 我以为就可以了,但是还是不行。 后来我想 如果我把主的也改为从是否可以了  server.SlaveOf(points[1],CommandFlags.FireAndForget);(我测试的时候还用过quit方法,调试有,但是release的时候说没有该方法)。运行效果如下

后来把上面的code简单封装为一个方法:

  void ChangeMaster(IDatabase database)
{
var mex = database.Multiplexer;
var endpoints = mex.GetEndPoints();
if (endpoints.Count() < )
{
return;
}
//多个endpoint 才切换主备服务器
List<EndPoint> connectedPoints = new List<EndPoint>();
List<EndPoint> disconnetedPoints = new List<EndPoint>();
foreach (var item in endpoints)
{
//判断哪些服务器可以连接
var server = mex.GetServer(item);
if (server.IsConnected)
{
connectedPoints.Add(item);
}
else
{
disconnetedPoints.Add(item);
}
}
var connectedPoint = connectedPoints.FirstOrDefault();
if (connectedPoint == null)
{
throw new Exception("没有可用的redis服务器");
}
mex.GetServer(connectedPoint).MakeMaster(ReplicationChangeOptions.All);
for (int i = ; i < connectedPoints.Count; i++)
{
mex.GetServer(connectedPoints[i]).SlaveOf(connectedPoint, CommandFlags.FireAndForget);
}
foreach (var item in disconnetedPoints)
{
mex.GetServer(item).SlaveOf(connectedPoint, CommandFlags.FireAndForget);
}
}

-----------------------------------------2017-4-14--------------------------------------------------------

我们知道读写redis的时候都是Message包

protected Message(int db, CommandFlags flags, RedisCommand command)
{
bool dbNeeded = RequiresDatabase(command);
if (db < )
{
if (dbNeeded)
{
throw ExceptionFactory.DatabaseRequired(false, command);
}
}
else
{
if (!dbNeeded)
{
throw ExceptionFactory.DatabaseNotRequired(false, command);
}
} bool masterOnly = IsMasterOnly(command);
Db = db;
this.command = command;
this.flags = flags & UserSelectableFlags;
if (masterOnly) SetMasterOnly(); createdDateTime = DateTime.UtcNow;
createdTimestamp = System.Diagnostics.Stopwatch.GetTimestamp();
}
 internal void SetMasterOnly()
{
switch (GetMasterSlaveFlags(flags))
{
case CommandFlags.DemandSlave:
throw ExceptionFactory.MasterOnly(false, command, null, null);
case CommandFlags.DemandMaster:
// already fine as-is
break;
case CommandFlags.PreferMaster:
case CommandFlags.PreferSlave:
default: // we will run this on the master, then
flags = SetMasterSlaveFlags(flags, CommandFlags.DemandMaster);
break;
}
}
        internal static CommandFlags SetMasterSlaveFlags(CommandFlags everything, CommandFlags masterSlave)
{
// take away the two flags we don't want, and add back the ones we care about
return (everything & ~(CommandFlags.DemandMaster | CommandFlags.DemandSlave | CommandFlags.PreferMaster | CommandFlags.PreferSlave))
| masterSlave;
}

这里根据我们的Command来判断是否必须是Master主库,如果是 就代用SetMasterOnly来设置flags,那么那些指令需要Master了:

 public static bool IsMasterOnly(RedisCommand command)
{
switch (command)
{
case RedisCommand.APPEND:
case RedisCommand.BITOP:
case RedisCommand.BLPOP:
case RedisCommand.BRPOP:
case RedisCommand.BRPOPLPUSH:
case RedisCommand.DECR:
case RedisCommand.DECRBY:
case RedisCommand.DEL:
case RedisCommand.EXPIRE:
case RedisCommand.EXPIREAT:
case RedisCommand.FLUSHALL:
case RedisCommand.FLUSHDB:
case RedisCommand.GETSET:
case RedisCommand.HDEL:
case RedisCommand.HINCRBY:
case RedisCommand.HINCRBYFLOAT:
case RedisCommand.HMSET:
case RedisCommand.HSET:
case RedisCommand.HSETNX:
case RedisCommand.INCR:
case RedisCommand.INCRBY:
case RedisCommand.INCRBYFLOAT:
case RedisCommand.LINSERT:
case RedisCommand.LPOP:
case RedisCommand.LPUSH:
case RedisCommand.LPUSHX:
case RedisCommand.LREM:
case RedisCommand.LSET:
case RedisCommand.LTRIM:
case RedisCommand.MIGRATE:
case RedisCommand.MOVE:
case RedisCommand.MSET:
case RedisCommand.MSETNX:
case RedisCommand.PERSIST:
case RedisCommand.PEXPIRE:
case RedisCommand.PEXPIREAT:
case RedisCommand.PFADD:
case RedisCommand.PFMERGE:
case RedisCommand.PSETEX:
case RedisCommand.RENAME:
case RedisCommand.RENAMENX:
case RedisCommand.RESTORE:
case RedisCommand.RPOP:
case RedisCommand.RPOPLPUSH:
case RedisCommand.RPUSH:
case RedisCommand.RPUSHX:
case RedisCommand.SADD:
case RedisCommand.SDIFFSTORE:
case RedisCommand.SET:
case RedisCommand.SETBIT:
case RedisCommand.SETEX:
case RedisCommand.SETNX:
case RedisCommand.SETRANGE:
case RedisCommand.SINTERSTORE:
case RedisCommand.SMOVE:
case RedisCommand.SPOP:
case RedisCommand.SREM:
case RedisCommand.SUNIONSTORE:
case RedisCommand.ZADD:
case RedisCommand.ZINTERSTORE:
case RedisCommand.ZINCRBY:
case RedisCommand.ZREM:
case RedisCommand.ZREMRANGEBYLEX:
case RedisCommand.ZREMRANGEBYRANK:
case RedisCommand.ZREMRANGEBYSCORE:
case RedisCommand.ZUNIONSTORE:
return true;
default:
return false;
}
}

如果我们执行脚本则是用的ScriptEvalMessage类,其构造函数:

   private sealed class ScriptEvalMessage : Message, IMultiMessage
{
private readonly RedisKey[] keys;
private readonly string script;
private readonly RedisValue[] values;
private byte[] asciiHash, hexHash;
public ScriptEvalMessage(int db, CommandFlags flags, string script, RedisKey[] keys, RedisValue[] values)
: this(db, flags, ResultProcessor.ScriptLoadProcessor.IsSHA1(script) ? RedisCommand.EVALSHA : RedisCommand.EVAL, script, null, keys, values)
{
if (script == null) throw new ArgumentNullException(nameof(script));
}
public ScriptEvalMessage(int db, CommandFlags flags, byte[] hash, RedisKey[] keys, RedisValue[] values)
: this(db, flags, RedisCommand.EVAL, null, hash, keys, values)
{
if (hash == null) throw new ArgumentNullException(nameof(hash));
} private ScriptEvalMessage(int db, CommandFlags flags, RedisCommand command, string script, byte[] hexHash, RedisKey[] keys, RedisValue[] values)
: base(db, flags, command)
{
this.script = script;
this.hexHash = hexHash; if (keys == null) keys = RedisKey.EmptyArray;
if (values == null) values = RedisValue.EmptyArray;
for (int i = ; i < keys.Length; i++)
keys[i].AssertNotNull();
this.keys = keys;
for (int i = ; i < values.Length; i++)
values[i].AssertNotNull();
this.values = values;
}

也就是说 执行脚本可以在从库上执行,很多查询语句也可以在从库上执行。

redis StackExchange 主备 实现 demo的更多相关文章

  1. 08.简单学习redis哨兵主备切换和选举算法

    一.选举的授权 每次一个哨兵要做主备切换,首先需要quorum数量的哨兵认为odown,然后选举出一个哨兵来做切换,这个哨兵还得得到majority哨兵的授权,才能正式执行切换 如果quorum &l ...

  2. 09.redis 哨兵主备切换时数据丢失的解决方案

    一.两种数据丢失的情况 1. 异步复制导致的数据丢失   因为master->slave的复制是异步的,所以可能有部分数据还没复制到slave,master就宕机了,此时这些部分数据就丢失了 2 ...

  3. 分布式Redis主备复制

    当数据落在不同节点上时,如何保证数据节点之间的一致性是非常关键的 Redis采用主备复制的方式保证一致性,所有节点中,只有一个节点为主节点(master),它对外提供写服务,然后异步的将数据复制到其他 ...

  4. ycache中redis主备功能设计及使用说明

    方案概述: 对于ycache-client,如下图,在一致性hash环上的每个节点都有一个备用的节点.正常情况下slave节点不参与key的分配(冷备).只有当master挂了,ycache clie ...

  5. redis配置主从备份以及主备切换方案配置

    前提:redis中,主从切换场景中,没有绝对的主和从,只有初始化的主和从,然后当主down后,从就变成主了,而主即使连接上,也是从,不会变为主 1.redis-server的主备关系: 需要配置的机器 ...

  6. Redis主备自动切换

    Sentinel(哨兵)是用于监控redis集群中Master状态的工具. 一.Sentinel作用  1.Master状态检测   2.如果Master异常,则会进行Master-Slave切换,将 ...

  7. 测试redis+keepalived实现简单的主备切换【转载】

    转自: 测试redis+keepalived实现简单的主备切换 - Try My Best 尽力而为 - ITeye技术网站http://raising.iteye.com/blog/2311757 ...

  8. 搭建和测试 Redis 主备和集群

    本文章只是自我学习用,不适宜转载. 1. Redis主备集群 1.1 搭建步骤 机器:海航云虚机(2核4GB内存),使用 Centos 7.2 64bit 操作系统,IP 分别是 192.168.10 ...

  9. Redis安装,主从,主备切换

    网络环境: 主:10.187.120.5 从:10.187.69.58 从:10.187.69.59 一.安装 mv redis-2.8.19.tar.gz /export/servers/ cd / ...

随机推荐

  1. cf799c 树状数组魔改

    这题的树状数组是用来维护区间最大值的!非常神奇 第一次见到这种用法,其实和区间求和也没什么差别 /* 树状数组魔改版 有三种方案:选两种c,选两个d,选一个c一个d 前两种方案需要选出符合条件的魅力值 ...

  2. 绝对定位后,position:absolute;不能使用margin: 0 auto;实现居中;

    声明: web小白的笔记,欢迎大神指点!联系QQ:1522025433. 我们都知道margin: 0 auto:可也实现块状元素的水平居中:但是对于绝对顶为的元素就会失效: 请看实例: <!d ...

  3. 步步为营-12-Dictionary-翻译

    说明:https://pan.baidu.com/s/1nvPqhDJ所需文件在此目录下对应的位置 1 先做一个简单的英汉翻译词典.先搭UI页面 2 将百度网盘中提供的资料放置到bin\debug目录 ...

  4. linux下安装ruby环境

    安装步骤: ruby的shell叫irb,我们可以输入irb -v查看下irb的版本号 1.安装ruby解释器shell: yum install ruby-irb -y Installed: rub ...

  5. 【Algorithm | 链表】单链表“环”、“环的起点”、“环的长度”问题

    参考资料 • Floyd判圈算法 { 链接 } • 单链表“环”.“环的起点”.环的长度”问题 { 链接 } 链表环的问题 一.判断链表有换 使用两个指针slow和fast.两个指针开始时均在头节点处 ...

  6. 小丸工具箱FAQ

    下载地址:https://maruko.appinn.me/index.html 本文章是把一些使用小丸工具箱中常见的操作失误或出错的问题集中写出并提出解决方法,以便大家寻找解决并避免重复提问. 文章 ...

  7. SpringBank 开发日志 一种简单的拦截器设计实现

    当交易由Action进入Service之前,需要根据不同的Service实际负责业务的不同,真正执行Service的业务逻辑之前,做一些检查工作.这样的拦截器应该是基于配置的,与Service关联起来 ...

  8. POJ 3040 Allowance【贪心】

    POJ 3040 题意: 给奶牛发工资,每周至少 C 元.约翰手头上有面值V_i的硬币B_i个,这些硬币的最小公约数为硬币的最小面值.求最多能发几周? 分析: 贪心策略是使多发的面额最小(最优解).分 ...

  9. ubuntu axel

    ubuntu下rar解压工具安装方法: 压缩功能 安装 sudo apt-get install rar 卸载 sudo apt-get remove rar 解压功能 安装 sudo apt-get ...

  10. java:矩阵面积

    实现一个矩阵类Rectangle,包含如下的一些成员变量与函数: 两个共有的成员变量 width 和 height 分别代表宽度和高度. 一个构造函数,接受2个参数 width 和 height 来设 ...