谈到缓存,我们自然而然就会想到缓存的好处,比如:

  • 降低高并发数据读取的系统压力:静态数据访问、动态数据访问
  • 存储预处理数据,提升系统响应速度和TPS
  • 降低高并发数据写入的系统压力
  • 提升系统可用性,后台宕机后,系统还存在可用的机会

缓存技术一直是优化程序性能的一个重要手段,在互联网技术体系中,也不例外。但是在分布式架构下,大家开始更多的使用分布式缓存,比如Redis、MemcacheD等等,对进程内的内存缓存使用的越来越少。其主要原因无外乎几点:

一是,数据不能做到强一致性,程序内存数据缓存同步的周期相对分布缓存更慢一些。

二是,需要对缓存的各种同步策略进行封装,并控制同步时机。进程内缓存的使用比分布式缓存的使用具有更高的技术门槛。没有分布缓存使用简单。

虽然分布式缓存具有非常多很好的特性,但是当完全抛弃了程序内存缓存后,分布式缓存将会被滥用,应用程序甚至过度的依赖分布式缓存。笔者认为,任何一种技术的滥用,都将可能导致系统架构在健壮性上存在缺陷。分布式缓存虽然很好用,性能也不错,但是与进程内存缓存比起来,性能还是差了好多个数量级。要想把系统的性能做到极致,仅仅依赖Redis等分布式缓存还不不够的,还需要充分利用进程内存缓存。

缓存技术,从出现到现在,总结来说,已有四个阶段的发展:本地缓存、分布式缓存、弹性缓存平台,弹性应用平台。本地缓存的特点是数据存储在应用代码所在内存空间,可提供快速的数据访问,纳秒级性能。缺点也很明显,数据无法分布式共享,无容错处理。分布式缓存的特点是数据在固定数目的集群节点间分布存储,缓存容量可扩展(静态扩展),但是扩展过程中需大量配置,无容错机制。弹性缓存平台的特性是数据在集群节点间分布存储,基于冗余机制实现高可用性。其优点是可动态扩展,具有容错能力,但是复制备份会对系统性能造成一定影响。弹性应用平台的特点是弹性缓存与代码执行的组合体,将业务逻辑代码转移到数据所在节点执行,极大地降低数据传输开销,提升系统性能。纵观整个缓存技术的发展,经历了从分散到集中,又到集中并分散的一个过程。弹性应用平台作为最终的缓存解决方案,已经不仅仅停留在缓存技术本身,而是更多的考虑了如何更好的与业务代码无缝集成,并提供进程内存级别的性能。

基于此,我们规划设计了一个通用的内存缓存组件。通过此组件,可以实现各种内存数据的缓存,以及缓存数据同步等,并提供了分布式缓存数据同步到进程内存的方案。此组件与传统的缓存组件有很大的不同,它是对进程内存缓存的使用和同步做了抽象和总结,直接提供了一套模型。上文也提到,使用进程内存缓存最大的挑战在与数据同步,那么,我们先看一下影响进程内存缓存同步的一些因素。通过下图,我们看到在同步策略、同步时机、同步模式上都有很多选择。进程内存缓存的使用,其实就是下面三个维度组合处理的一个结果。

在实际的业务中,同步策略更多的会基于时间、数据版本或者时间来做,然后选择合适的同步时机和模式来执行数据同步。所以,在组件的封装上,我们支持了三种应用模型:

  • 对缓存数据标记有效期,过期自动清理
  • 缓存数据时,同时缓存数据版本,定时校验或实时校验数据版本,发现版本不一致时清理或重新同步缓存数据
  • 缓存数据并订阅数据变化通知,当收到变化通知后,更新缓存数据

模型一:对缓存数据标记有效期,过期自动清理

此模型主要适用于, 比如:字符串资源信息查询、App充电地图数据、App最新动态、高频率日志记录、配置中心数据的缓存等等。

  • 数据实时性要求很低,访问频率很高,变化频率较小的数据查询

  • 访问频率很高,查询参数基本一致的数据查询

  • 访问频率很高,允许丢失的辅助信息写入

代码示例:

//nuget:Teld.Core
//引用:Teld.Core.Cache.ServiceEx.dll
 
//创建一个带过期时间的本地内存容器
using(var container = LocalCacheService.CreateExpirationContainer("TTP-Cache-CFG"))
{
    //向容器中增加项目,3秒钟的有效期
    container.Add("Name", "张三", new TimeSpan(0, 0, 3));
    //想容器中增加项目,永久有效
    container.Add("Address", "鑫盛大厦1号楼12层北特来电");
}

模型二:对缓存数据标记有效期,过期自动清理

此模型主要适用于, 比如:帮助查询等。

  • 数据实时性要求不高,访问频率很高的数据查询
  • 访问频率很高,查询参数基本一致的数据查询

     代码示例1: 定时同步,每分钟执行一次版本同步

static voidMain(string[] args)

{

//创建缓存数据同步代理

   CacheValidateEventHandler<UserInfo> handler = new CacheValidateEventHandler<UserInfo>(SyncUserData);
   //创建一个带版本校验的本地内存缓存容器,每隔60s,自动进行一次数据全量同步
   using(varcontainer = LocalCacheService.CreateVersionContainer<UserInfo>("TTP-Cache-User", handler, SyncTime.Timing, SyncModel.All, 60))

{

container.SyncFailed += Container_SyncFailed;   //数据同步失败,事件通知

container.SyncData();   //立即执行一次数据同步

var user = container.Get("Name");                     //从缓存中获取数据

Console.WriteLine(JsonConvert.SerializeObject(user));

}

Console.ReadLine();

}
 

//数据同步,返回全量或者增量数据,并返回数据的最新版本

static Dictionary<string, UserInfo> SyncUserData(CacheValidateEventArgs e,out string newVersion)

{

//通过e.Version获取上次同步的数据数据

Dictionary<string, UserInfo> result = new Dictionary<string, Cache.UserInfo>();

   Random r = new Random(DateTime.Now.Second);
   result.Add("Name", new Cache.UserInfo() { Name = "Name", Age =r.Next(1,20) , IsMale = true , LastModifyTime = DateTime.Now});

newVersion = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");

return result;

}

    代码示例2: 调用缓存Get方法时,自动同步缓存    

static void Main (string[] args)

{ //创建缓存数据同步代理

CacheValidateEventHandler<UserInfo> handler = new CacheValidateEventHandler<UserInfo>(SyncUserData);

//创建一个带版本校验的本地内存缓存容器

varcontainer = LocalCacheService.CreateVersionContainer<UserInfo>("TTP-Cache-User", handler, SyncTime.GetCheck, SyncModel.All);

container.SyncFailed += Container_SyncFailed; //数据同步失败,事件通知

var user = container.Get("Name"); //从缓存数据中获取数据

Console.WriteLine(JsonConvert.SerializeObject(user));

Console.ReadLine();

}

模型三:缓存数据并订阅数据变化通知,当收到变化通知后,更新缓存数据

此模型主要适用于, 比如:电站状态缓存等。

    • 数据实时性要求比较高,访问频率很高的数据查询

    代码示例1: 带事件更新通知的缓存    

public void GetString_Invoke()

{

//创建一个带MQ变化通知的本地内存缓存容器

using (var container = LocalCacheService.CreateEventContainer<string>("TTP-Cache-EventCacheUnitTest",

(CacheValidateEventArgs e, out string newVersion) =>

{

newVersion = Guid.NewGuid().ToString();

return BuildStringData();

}, SyncModel.All, 1))

{

container.SyncData(); //为容器初始化数据

var data = container.Get("lastModifytime");  //获取数据项

Assert.IsNotNull(data);

var data1 = container.Get("lastModifytime");

Assert.AreEqual(data, data1);

//发送数据项的更新通知事件

LocalCacheService.SendDataChangedEvent(container.Name, "lastModifytime");

Thread.Sleep(5000);

var data2 = container.Get("lastModifytime");

Assert.AreNotEqual(data2, data);

}

}

    代码示例2:数据删除的事件通知

public void GetString_Delete()
        {
            //创建一个带MQ变化通知的本地内存缓存容器
            using (var container = LocalCacheService.CreateEventContainer<string>("TTP-Cache-EventCacheUnitTest",
                 (CacheValidateEventArgs e, out string newVersion) =>
                 {
                     newVersion = Guid.NewGuid().ToString();
                     return BuildStringData();
                 }, SyncModel.All, 1))
            {
                container.SyncData();                                                   //为容器初始化数据
                var data = container.Get("lastModifytime");           //获取数据项
                Assert.IsNotNull(data);
                var data1 = container.Get("lastModifytime");
                Assert.AreEqual(data, data1);
                LocalCacheService.SendDataChangedEvent(container.Name, "lastModifytime", EventType.Delete);   //发送数据项的删除通知事件
                Thread.Sleep(5000);
                var data2 = container.Get("lastModifytime");
                Assert.IsNull(data2);
            }
        }

    以上是此缓存组件应用的三种模型。三种模型,可通过 LocalCacheService 创建。其代码如下:

 public class LocalCacheService

    {
        /// <summary>
        /// 创建过期自动清理的本地内存缓存容器
        /// </summary>
        /// <param name="key">容器标识,三段式命名,全局唯一:TTP-Resource-DataCache</param>
        /// <param name="limitSize">限制使用的内存大小(M)</param>
        public static IExpirationCacheContainer CreateExpirationContainer(string key,long? limitSize =null)
        {
            if (string.IsNullOrEmpty(key))
            {
                throw new ArgumentException("Key值不能为空!", nameof(key));
            }
            return new InMemoryExpirationContainer(key, limitSize);
        }
        /// <summary>
        /// 创建基于版本比较的本地内存缓存容器
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key">容器标识,三段式命名,全局唯一:TTP-Resource-DataCache</param>
        /// <param name="handler">基于版本比较的数据处理器</param>
        /// <param name="syncTime">同步时机</param>
        /// <param name="model">同步模式</param>
        /// <param name="syncTimeMin">同步时机为Timing时,同步时间间隔</param>
        /// <returns></returns>
        public static IVersionCacheContainer<T> CreateVersionContainer<T>(string key, CacheValidateEventHandler<T> handler, SyncTime syncTime = SyncTime.Invoke,SyncModel model = SyncModel.All,int syncTimeSec=180) where T : class
        {
            if (string.IsNullOrEmpty(key))
            {
                throw new ArgumentException("Key值不能为空!", nameof(key));
            }
            if (handler == null)
            {
                throw new ArgumentNullException(nameof(handler));
            }
            if (syncTimeSec == 0)
                syncTimeSec = 180;
            return new InMemoryVersionCacheContainer<T>(key, handler, syncTime, model, TimeSpan.FromSeconds(syncTimeSec));
        }
        /// <summary>
        /// 创建基于事件通知的本地内存缓存容器
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key">容器标识,三段式命名,全局唯一:TTP-Resource-DataCache</param>
        /// <param name="handler">基于版本比较的数据处理器</param>
        /// <param name="model">同步模式</param>
        /// <param name="syncTimeMin">同步时机为Timing时,同步时间间隔</param>
        /// <returns></returns>
        public static IVersionCacheContainer<T> CreateEventContainer<T>(string key,CacheValidateEventHandler<T> handler, SyncModel model = SyncModel.All, int syncTimeSec = 180) where T : class
        {
            if (string.IsNullOrEmpty(key))
            {
                throw new ArgumentException("Key值不能为空!", nameof(key));
            }
            if (handler == null)
            {
                throw new ArgumentNullException(nameof(handler));
            }
            if (syncTimeSec == 0)
                syncTimeSec = 180;
            return new InMemoryEventCacheContainer<T>(key,model,handler, TimeSpan.FromSeconds(syncTimeSec));
        } 
    }

  同步模式和同步时机的定义如下:

/// <summary>

/// 同步模式
/// </summary>
public enum SyncModel : int
{
    All, //全量同步,清楚历史缓存信息,重新插入
    Increase, //增量同步,同步变化部分,不对历史缓存数据清理
    Clear //仅清理历史数据
}

/// <summary>
/// 同步时机
/// </summary>
public enum SyncTime : int
{
    Invoke, //调用方执行SyncData方法主动同步
    Timing, //定时同步
    GetCheck //Get方法是自动同步
}

  以上是我们在进程内存组件的一些实践心得,更多技术细节,欢迎同学们来电沟通。微信号:vveiliang

基于.net的通用内存缓存模型组件的更多相关文章

  1. 基于.net的分布式系统限流组件 C# DataGridView绑定List对象时,利用BindingList来实现增删查改 .net中ThreadPool与Task的认识总结 C# 排序技术研究与对比 基于.net的通用内存缓存模型组件 Scala学习笔记:重要语法特性

    基于.net的分布式系统限流组件   在互联网应用中,流量洪峰是常有的事情.在应对流量洪峰时,通用的处理模式一般有排队.限流,这样可以非常直接有效的保护系统,防止系统被打爆.另外,通过限流技术手段,可 ...

  2. Go/Python/Erlang编程语言对比分析及示例 基于RabbitMQ.Client组件实现RabbitMQ可复用的 ConnectionPool(连接池) 封装一个基于NLog+NLog.Mongo的日志记录工具类LogUtil 分享基于MemoryCache(内存缓存)的缓存工具类,C# B/S 、C/S项目均可以使用!

    Go/Python/Erlang编程语言对比分析及示例   本文主要是介绍Go,从语言对比分析的角度切入.之所以选择与Python.Erlang对比,是因为做为高级语言,它们语言特性上有较大的相似性, ...

  3. 基于 IOCP 的通用异步 Windows Socket TCP 高性能服务端组件的设计与实现

    设计概述 服务端通信组件的设计是一项非常严谨的工作,其中性能.伸缩性和稳定性是必须考虑的硬性质量指标,若要把组件设计为通用组件提供给多种已知或未知的上层应用使用,则设计的难度更会大大增加,通用性.可用 ...

  4. cache4j轻量级java内存缓存框架,实现FIFO、LRU、TwoQueues缓存模型

    简介 cache4j是一款轻量级java内存缓存框架,实现FIFO.LRU.TwoQueues缓存模型,使用非常方便. cache4j为java开发者提供一种更加轻便的内存缓存方案,杀鸡焉用EhCac ...

  5. 基于JVM原理、JMM模型和CPU缓存模型深入理解Java并发编程

    许多以Java多线程开发为主题的技术书籍,都会把对Java虚拟机和Java内存模型的讲解,作为讲授Java并发编程开发的主要内容,有的还深入到计算机系统的内存.CPU.缓存等予以说明.实际上,在实际的 ...

  6. 基于STSdb和fastJson的磁盘/内存缓存

    更新 1. 增加了对批量处理的支持,写操作速度提升5倍,读操作提升100倍 2. 增加了对并发的支持 需求 业务系统用的是数据库,数据量大,部分只读或相对稳定业务查询复杂,每次页面加载都要花耗不少时间 ...

  7. 一个基于STSdb和fastJson的磁盘/内存缓存

    一个基于STSdb和fastJson的磁盘/内存缓存 需求 业务系统用的是数据库,数据量大,部分只读或相对稳定业务查询复杂,每次页面加载都要花耗不少时间(不讨论异步),觉得可以做一下高速缓存,譬如用n ...

  8. 分享基于MemoryCache(内存缓存)的缓存工具类,C# B/S 、C/S项目均可以使用!

    using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Caching; usi ...

  9. 通用高性能 Windows Socket 组件 HP-Socket v2.2.2 正式发布

    HP-Socket 是一套通用的高性能 Windows Socket 组件包,包含服务端组件(IOCP 模型)和客户端组件(Event Select 模型),广泛适用于 Windows 平台的 TCP ...

随机推荐

  1. Python CSV 超简明用法

    平常经常会用CSV存储数据,不可避免的会跟CSV文件的读写操作扯上关系. Python有CSV这个Package来解决这个问题,官网也有比较详细的教程 https://docs.python.org/ ...

  2. Cow Exhibition 变种背包

    Cow Exhibition Time Limit:1000MS     Memory Limit:65536KB     64bit IO Format:%I64d & %I64u Subm ...

  3. 支持向量机(Support Vector Machine)-----SVM之SMO算法(转)

    此文转自两篇博文 有修改 序列最小优化算法(英语:Sequential minimal optimization, SMO)是一种用于解决支持向量机训练过程中所产生优化问题的算法.SMO由微软研究院的 ...

  4. UWP 改变Button样式

    -----some words------ 1.Control:控制 (我们理解成控件) 2.Template:模板 3.Ellipse 椭圆 4.Content 内容 5.Presenter 节目主 ...

  5. Log4net日志使用教程-控制台、文本、数据库三种记录方式

    一.log4net简介: 1. Log4net的优点: 几乎所有的大型应用都会有自己的用于跟踪调试的API.因为一旦程序被部署以后,就不太可能再利用专门的调试工具了.然而一个管理员可能需要有一套强大的 ...

  6. Revit二次开发初体验

    最近换了下工作,由之前的互联网企业转入了BIM软件开发行列.具体原因不多说,作为一个程序员来说学习永无止境.下面来一个Hello World体验下Revit的二次开发 事前准备 VS Revit 20 ...

  7. HDU1423 LCIS

    1,先离散化,然后DP: 注意这个解法中,dp[i][j][k]代表a序列中前i个和b序列中前j个数结尾为k或小于k时的最大. 但是由于i是单增(一次1->n),而j反复变化(多次1->m ...

  8. SQL查找 删除重复数据只保留一条

    --用SQL语句,删除掉重复项只保留一条 --在几千条记录里,存在着些相同的记录,如何能用SQL语句,删除掉重复的呢 --1.查找表中多余的重复记录,重复记录是根据单个字段(peopleId)来判断 ...

  9. dmesg和addr2line 定位 segfault

    程序长时间运行崩溃,但是没有保存core dump消息.可以用下面的方法定位出程序出错位置: 1. 用dmesg查找出错的代码段地址 ip 000000000041ccec 发生错误时指令的地址, s ...

  10. redis的发布订阅模式pubsub

    前言 redis支持发布订阅模式,在这个实现中,发送者(发送信息的客户端)不是将信息直接发送给特定的接收者(接收信息的客户端),而是将信息发送给频道(channel),然后由频道将信息转发给所有对这个 ...