原文是发表在文章中,刚看了下文章主要用于转载,所以在随笔中从新发布一下。html
正如前文中《海量并发下充电业务优化实践》所述,在充电过程当中因为涉及到大量的实时数据处理,随着设备规模的扩大,各个节点和服务均感觉到较大的压力。为了解决这个状况,文中探讨内存缓存组件的使用及相关的过程优化,预计优化后对于Redis的调用频率能够降低50%以上,极大缓解当前Redis的服务压力的同时为将来的系统规模增加打下良好基础。数据库
充电过程当中设计的实时数据主要有四类:遥信、遥测、电量和BMS数据。这四类数据在充电过程当中持续上传直到充电结束。云端在终端充电过程当中的处理压力也是由这些实时数据引发的。缓存
因为当前实时数据处理过程的无状态性和通信集群与实时数据处理集群之间负载均衡的存在(图1),没法肯定由哪一个节点来处理哪些终端的实时数据,在此场景下只能依赖中心化的缓存服务,各个处理节点经过中心化的缓存服务来获取数据恢复上下文。
安全
图1 充电和通信服务部署图
随着终端规模的扩大,虽然实时数据处理集群的性能问题能够经过水平扩展来解决,可是却给中心化的缓存和数据库服务带来了较大压力。此时须要对于充电业务的实时数据处理流程进行优化(图2)。优化从两方面入手:服务器
图2 优化后充电和通信服务部署图
所以在目前的程序架构下形成访问Redis和数据库较多的问题。平时对于Redis的每分钟调用次数(TPM)平均值为一百万次(月均值),遇到设备繁忙或者网络波动时,峰值有可能到达两百万次的量级,对于Redis的性能产生了很大的压力,并且也给整个架构带来一些隐患。
网络
图3 Redis单日访问量(TPM_Pool是指各个链接池的每分钟调用数)数据结构
对充电业务的优化改造方案计划充分利用现有计算资源,增长内存缓存的使用力度,缓解Redis和数据库的压力。
使用内存缓存须要考虑的有以下三个方面:架构
基于上面一些考虑,在公共技术部门的帮助下肯定使用MemoryCache做为内存缓存改造的主要组件。并发
MemoryCache类是.Net 4.0以后出现的,其命名空间是System.Runtime.Caching(位于 System.Runtime.Caching.dll中)。MemoryCache继承自ObjectCache并实现了IEnumerable和IDisposable接口。负载均衡
MemoryCache的构造函数有两个:
MemoryCache(String, NameValueCollection)
MemoryCache(String, NameValueCollection, Boolean)
使用构造函数能够构造出非默认的缓存实例并能够自定义部分属性。
MemoryCache类有7个属性:
名称 | 描述 |
---|---|
CacheMemoryLimit | 获取计算机上缓存可以使用的内存量(以字节为单位)。 |
Default | 获取对默认 MemoryCache 实例的引用。 |
DefaultCacheCapabilities | 获取缓存提供的功能的说明。 |
Item[String] | 经过使用 MemoryCache 类的实例的默认索引器属性,获取或设置缓存中的值。 |
Name | 获取缓存的名称。 |
PhysicalMemoryLimit | 获取缓存可以使用的物理内存的百分比。 |
PollingInterval | 获取在缓存更新其内存统计信息以前需等待的最大时间量。 |
MemoryCache的属性都是只读属性,设置这些属性能够经过程序配置文件中的<memoryCache>
配置节进行配置。配置实例:
<configuration> <system.runtime.caching> <memoryCache> <namedCaches> <add name="Default" cacheMemoryLimitMegabytes="10" physicalMemoryLimitPercentage="10" pollingInterval="00:01:00" /> </namedCaches> </memoryCache> </system.runtime.caching> </configuration>
注意上文中配置节的name属性值为Default
,若是写其余名字没法设置MemoryCache.Default的相关属性,只能使用MemoryCache的构造函数构造对应名称的实例来使用。
一、Add方法
MemoryCache的Add方法有多个重载,该方法的用处是缓存项插入MemoryCache的实例中。
二、AddOrGetExisting方法
MemoryCache的AddOrGetExisting方法有多个重载,该方法的用处是缓存项插入MemoryCache的实例中,若是该缓存的key值已经存在,则返回该key值对应的缓存项。
三、Contains方法
该方法用于检查MemoryCache实例中是否存在某个key值
四、CreateCacheEntryChangeMonitor方法
建立 CacheEntryChangeMonitor 实例。此更改监视器用于监视中指定的缓存条目 keys 集合项更改时触发事件。
五、Get方法
返回某个key值对应的缓存项内容的引用。
六、GetCacheItem方法
从缓存中返回CacheItem实例形式的指定项。
七、GetEnumerator方法
建立缓存项的枚举器,用于循环访问缓存项的集合。
八、GetValues方法
该方法有多个重载,用于批量获取一组key值对应的缓存项。
九、Remove方法
该方法有多个重载,用于从缓存中删除缓存项。
十、Set方法
若是key值存在则更新缓存项,若是不存在则插入缓存项。
十一、Trim方法
从缓存中删除总数百分比的缓存项。删除规则参考MemoryCache.Trim 方法 (Int32)
在前文中提到了对于内存缓存的三个基本要求,下面看MemoryCache如何知足这三个方面的要求。
一、控制内存占用
能够经过在配置文件中配置cacheMemoryLimitMegabytes
和physicalMemoryLimitPercentage
属性分别对于MemoryCache的实例占用内存的绝对值和物理内存相对值进行限制。
二、缓存项失效
在MemoryCache的实例增长缓存项时能够指定缓存项策略,即CacheItemPolicy类对象。该类中有两个属性SlidingExpiration和SlidingExpiration:
三、跟主缓存之间的同步机制
在指定缓存项策略时还能够在其ChangeMonitors属性中增长监视器,实现监视数据源变化的功能。这些监视器都是派生自ChangeMonitor
抽象类,目前在.Net中已经实现的监视器有四种:
若是有必要的话,能够根据业务时间实现自定义的监视器实现内存缓存和主缓存之间的同步机制。
另外,根据MSDN的资料,MemoryCache的操做是线程安全的,这一点在处理充电实时数据的时候很是重要。
内存缓存是Redis数据和部分数据库数据在内存中的映射。每一个处理节点的内存缓存只是Redis中数据的子集(图4)。为了使用方便,缓存数据在内存中的数据类型和数据结构跟Redis中的数据组织方式基本一致。
图4 内存缓存和Redis数据集的关系
缓存项在获取时若是获取不到则从Redis中进行恢复,不然直接使用MemoryCache中的数据;MemoryCache缓存项设置后(好比调用Add或者Set方法)若是发现数据有变化则同步设置Redis的值,保持二者的数据同步。对于数据的超时时间则根据不一样类型数据上传频率来肯定,超时自动清理,有须要时再从Redis中恢复数据。
使用MemoryCache将缓存放在内存中后,不但能够显著减小对于Redis数据的查询。并且只在终端数据变化须要更新时,将数据同步到Redis中也能够减小对于Redis的更新频率。预计改造后对于Redis的查询操做可以减小80%`90%,对于Redis的设置操做能够减小60%~70%。
使用内存缓存对于原业务流程进行优化改造,看起来并不复杂,可是引入内存缓存后带来的不只仅只是对于缓存存储方式的改变,而是牵扯到一系列相关的流程的变化,包括引入消息队列,合理分配处理接口的负载,多级缓存之间的同步机制等等。这些业务流程的变化对于充电稳定性的影响很是大,在系统运行过程当中改变业务流程,犹如在给行驶在高速路上的汽车更换轮胎,如何保证不一样流程之间的切换也是须要在设计和部署时须要详细考虑的问题。
对于实时数据处理系统而言,有多种多样的架构设计,可是没有一种架构是普适性的。各类架构都有本身的局限性。随着规模的扩大,须要不断地优化架构设计,平衡系统各个模块之间的负载,深刻挖掘系统总体性能。正如上文所言,为了解决处理节点的高负载而选择了无状态的处理机制,规模扩大后为了下降中心化的缓存服务(Redis)的压力,又有必要引入内存缓存,缓解Redis的压力,优化处理流程,提升系统内部模块交互的费效比。经过一系列的优化改造,在不增长服务器的状况下,系统总体稳定性和处理性能又会有较大提高,为将来发展打下良好基础。
原文连接:http://www.cnblogs.com/zhu-wj/p/7461104.html