上两篇受益匪浅,秉着趁热打铁,不挖到最深不罢休的精神,我决定追加这篇。上一篇里最后我有提到实现分级缓存管理应该是个可行的方案,因此今天特别实践了一下。不过缓存分级之后也发现了一些问题,例如下图:

当appServerA修改了数据,并同步到Redis/DB之后,如何让appServerB也能更新本地缓存呢?虽然Redis的出现是为了解决这个问题的,但是分级方案里,MemoryCache还是需要保留。那么如何保存呢?我尝试了下面的几种方式,现在我们逐一来看。

全数据增量同步

所谓全数据校验,即所有的缓存数据首先都同步至Redis,然后根据数据的时间戳来进行同步。分解步骤如下:

  1. 首先将缓存的数据初始化,同步至Redis和MemoryCache,保持初始数据的同步
  2. 第二步,每当操作了数据之后,给记录一个时间戳标识最近的更新。
  3. MemoryCache定时或者每次取数据的时候,以最近的一个同步的时间戳开始同步到现在的时间戳

上面的方案咱们落地到.NET+Redis又该怎么处理呢?

第一步很简单直接跳过,第二步就有点问题了,这个时间戳要便于redis的排序和获取,考虑到这些问题,我觉得取Linux的数字型时间戳比较靠谱,再配合SortSet来建立索引,进行同步,看起来的确不错。那么来看下代码:

本篇还是以User为例,可能场景不适合,大家将就理解

public void UpdateToRedis(User user)
{
var redis = ConnectionMultiplexer.Connect("localhost");
var db = redis.GetDatabase(); TimeSpan ts = DateTime.Now.ToUniversalTime() - new DateTime(, , , , , , );
double time = Convert.ToInt64(ts.TotalSeconds);//计算Unix时间戳
db.SortedSetAdd("capqueen:user:index", user.Id, time);//更新记录
var json = JsonConvert.SerializeObject(user);
db.StringSet("capqueen:user" + user.Id, json);//user记录
}

同步:

public void Sync(double timespan)
{
var redis = ConnectionMultiplexer.Connect("localhost");
var db = redis.GetDatabase(); TimeSpan ts = DateTime.Now.ToUniversalTime() - new DateTime(, , , , , , );
double time = Convert.ToInt64(ts.TotalSeconds);//计算Unix时间戳 //同步时间范围内的记录,这里增加时间范围,以防止一些数据不准确的问题
var members = db.SortedSetRangeByScore("capqueen:user:index", timespan, time); var keys = members.ToList().Select<RedisValue, RedisKey>(i => i.ToString());
var values = db.StringGet(keys.ToArray()); //构造List Json以加速解析
var portsJson = new StringBuilder("["); values.ToList().ForEach(item =>
{
if (!string.IsNullOrWhiteSpace(item))
{
portsJson.Append(item).Append(",");
}
}); portsJson.Append("]"); var users = JsonConvert.DeserializeObject<List<User>>(portsJson.ToString()); //和内存的List<User>做同步
...
//END
}

上面的代码只是实例,实际运行的时候感觉还不是特别靠谱。

消息通知

增量同步是好,但是同时时机也是个问题,时机不对,就会影响用户体验。而且感觉我们还是无法很好的掌控数据。那么有没有一种方式可以及时的通知到appServer更新缓存呢?这里我脑子里冒出来的是.NET Event机制。

我觉得.NET因为有了优秀的Event机制,才让我觉得它使用起来很方便

但是这里是服务器之间的通信,我想非Scoket莫属了,之间做过Scoket双向通信,印象特别深刻,这样的场景让我自然而然的想到了这个方案。可是特别的增加一个socket,本身复杂的架构要更复杂了,还是寻求一个靠谱的中间件比较适合。看过Redis的功能列表的同学,一定没忘记Redis还有一个订阅发布,pub/sub功能。于是我就利用了这个功能,设计了如下的方案:

  1. 为app增加一个sub线程,用于接收redis订阅消息
  2. 在每一个缓存对象更新的同时,增加异步pub消息到redis

先来看下Redis的,pub/sub功能,请看文档

redis提供指定channel的订阅发布功能,每一个Client可以订阅指定的channel消息,也可以向指定的channel发送消息。

利用ServiceStack.Redis的pub/sub功能实现如下:

using (var redisConsumer = new RedisClient(TestConfig.SingleHost))
using (var subscription = redisConsumer.CreateSubscription())
{
subscription.OnSubscribe = channel =>
{
//订阅事件
};
subscription.OnUnSubscribe = channel =>
{
//退订事件
};
subscription.OnMessage = (channel, msg) =>
{
// 这里的msg,我为了测试定义成了userid
var user= redisConsumer.As<User>().GetById(msg);//从Redis获取记录
UpdateLocalCache(user);//更新本地
}; subscription.SubscribeToChannels("capqueen:redis:events"); //blocking
}

发送:

using (var redisPublisher = new RedisClient("localhost"))
{
redisPublisher.PublishMessage("capqueen:redis:events", userId);//发送消息
}

这里我觉得message应该定义成一个消息体,接收处理应该根据具体的消息定义来具体处理,因为订阅发布的通道显然应该是合理利用,如果一个业务一个通道,有点太多了。

当然这个实现起来又要讲一大篇了,这里不多说明。

总结

本篇文章,根据上一篇提的方案做了一些实现,不足之处太多了。例如事件丢了怎么办?如何正确的处理消息缺失,记录少了之类的特殊情况应该是很重要的。我暂时还没想到方案,不知道前辈们如何看待这样的问题。可能我还是滥用了Redis,为了技术而技术了,不过不尝试碰下壁,怎么能得到成长。所谓兼听则明 偏信则暗,我感觉自己有点不见棺材不落泪了。请前辈们不吝指教!

Redis到底该如何利用(三)?的更多相关文章

  1. Redis到底该如何利用?

    Redis是个好东西,经过上两个星期的研究和实践,目前正在项目里大规模的替换掉原来的本地内存cache.但是替换过程中却发现,Redis这东西高端,大气上档次,似乎不是我想象里的使用方法. 在没有深入 ...

  2. Redis到底该如何利用?【转自:http://www.cnblogs.com/capqueen/p/HowToUseRedis.html】

    Redis是个好东西,经过上两个星期的研究和实践,目前正在项目里大规模的替换掉原来的本地内存cache.但是替换过程中却发现,Redis这东西高端,大气上档次,似乎不是我想象里的使用方法. 在没有深入 ...

  3. Redis到底该如何利用(二)?

    上一篇文章里我简述了使用Keys作为Redis搜索的方式,确实感受到了社区的力量,写文章好处多.首先谢谢各位前辈的指导,我知道了拿Redis作为搜索是个错误的方向.本来这篇文章我觉得确实没必要发了,但 ...

  4. Redis源码阅读(三)集群-连接初始化

    Redis源码阅读(三)集群-连接建立 对于并发请求很高的生产环境,单个Redis满足不了性能要求,通常都会配置Redis集群来提高服务性能.3.0之后的Redis支持了集群模式. Redis官方提供 ...

  5. Redis07——Redis到底能用在什么地方(下)

    在前一篇文章中,我们已经介绍过Redis的一些实际应用.如KV缓存.分布式锁.消息队列,由于篇幅原因,并未介绍完全.接下来将继续为各位带来Redis的更多应用. bitmat(位图) 实现 位图的基本 ...

  6. Redis入门和Java利用jedis操作redis

    Redis入门和Java利用jedis操作redis Redis介绍 Redis 是完全开源的,遵守 BSD 协议,是一个高性能的 key-value 数据库. Redis 与其他 key - val ...

  7. redis成长之路——(三)

    redis连接封装 StackExchange.Redis中有一些常功能是不在database对中,例如发布订阅.获取全部key(本代码中已封装到operation中了)等,而且StackExchan ...

  8. Python操作redis字符串(String)详解 (三)

    # -*- coding: utf-8 -*- import redis #这个redis不能用,请根据自己的需要修改 r =redis.Redis(host=") 1.SET 命令用于设置 ...

  9. docker-compose一键部署redis一主二从三哨兵模式(含密码,数据持久化)

    本篇基于centos7服务器进行部署开发 一.拉取redis镜像,使用如下命令 docker pull redis 1.查看镜像是否拉取成功,使用如下命令 docker images 显示如下则证明拉 ...

随机推荐

  1. XTREE随笔

    1.XTREE简介: XTREE是一个基于AJAX实现的树形菜单.它的原理就是每次都只加载当前结点下的所有结点,而对开发人员来说,就是只需要按一定的格式,生成一段XML代码.XTREE可以自己定制每个 ...

  2. Collections.unmodifiableMap

    1. Collections.unmodifiableMap 是什么? Java的官方解释: public static <K,V> Map<K,V> unmodifiable ...

  3. MySQL练习-employees数据库(一)

    安装一个数据库示例--employees 使用工具为Navicat--(ctrl + q: 打开新查询窗口 ctrl + r: 运行当前窗口内的所有语句) 参考http://www.cnblogs.c ...

  4. sessionState详解

    asp.net Session的默认时间设置是20分钟,即超过20分钟后,服务器会自动放弃Session信息. 当我们在asp.net程序中打开web.config的时候,可以看到一段如下的代码: A ...

  5. R可视化lend_club 全球最大的P2P平台数据75W条

    lend_club 全球最大的P2P平台2007~2012年贷款数据百度云下载. 此文章基于R语言做简单分析. rm(list=ls()) #清除变量 gc() #释放内存 step1 考虑到后续分析 ...

  6. 硬盘安装linux的两条命令

    kernel (hd0,0)/vmlinuz boot=casper iso-scan/filename=/ubuntu-10.04.1-desktop-i386.iso ro quiet splas ...

  7. Uva 11732 strcmp() Anyone?

    strcmp() Anyone? Time Limit: 2000MS   Memory Limit: Unknown   64bit IO Format: %lld & %llu [Subm ...

  8. dom解析和sax解析的区别及优缺点

    dom解析一开始就将文档所有内容装入内存,每个元素(标签)都作为一个element对象存储,形成对象树,缺点是对内存占用大,不能解析数据量很大的文档:优点是方便进行crud操作. sax解析,逐行解析 ...

  9. php中的高危函数

    phpinfo() 功能描述:输出 PHP 环境信息以及相关的模块.WEB 环境等信息. 危险等级:中 passthru() 功能描述:允许执行一个外部程序并回显输出,类似于 exec(). 危险等级 ...

  10. IIS部署遇到的一些问题

    IIS部署时候会遇到一些具体的问题,记录一下.此处的部署环境为Windows Server 2012 64位版本 1.基本部署:角色和功能管理-->web服务器,勾选相应的服务与功能,然后安装即 ...