Memcached在处理缓存时,大部分的接口都是封装一个get/set/delete操作。
然而缓存还存在这样的场景:
1:a,b数据被缓存,他们同事依赖于c数据。也就是c变化了,a,b也要跟着变化。
2:c变化了,他需要主动去告诉cache,我变化了。以免之后再取老的cache.
希望各位大牛不吝赐教。
不要随便摘抄一段网上的人云亦云的话。最好有实际的举例。
补充问题:有人说, 问题2,你不可以直接在程序里面控制?
是这样的,简单的可以,但复杂的,比如c变化了,c数据对应的可能涉及n张表。一般的后台修改数据都是只针对一张表的。所以如果你有好的方案,请举例。
主动:修改数据的同时发起缓存失效指令。适用于 CQRS 模式。
被动:使用数据库变更通知机制(Databas Change Notification),例如: SqlDependency。
主动的我在补充问题里面有提到一些复杂情况的问题,具体是怎么样的思路呢?请举例下。
另外目前memcached是没有实现缓存依赖的,你说的是.net的缓存.
@Tim Lee: 我先回答你的第二个,可以自己建立一个缓存更新的独立进程,然后利用数据库变更通知机制来让缓存服务器的缓存失效。
对于第一个问题,为了应对模型建立的不够友好,你需要建立表,将你的对象同数据库中的表映射起来,当某个对象修改后,你就通过该表查询出受影响的表,然后通过这些受影响的表再反查出关联的对象,这样你知道哪些对象的缓存项需要失效了。更复杂点,你可以把数据库中表的字段也映射上,做更精细的更新控制,更进一步,设置过滤器,通过过滤器过滤字段的值来控制更新。
如果你的模型建立的足够好,是不需要这么复杂的,因此 CQRS 天生就支持事件聚合,专门为更新而设计的。
@Launcher: 我可以这么理解你想表达的的意思吗:建立缓存key同表之间的依赖关系配置文件,再通过独立的进程去跟踪表的变化,当某个或某些表(或者表的某个记录)变化了,查找配置文件中的依赖关系,然后更新(删除)缓存?
我还看到另外一种思路:使用 图 的数据结构来记录缓存项及其上游源数据的关系,每个缓存项在建立时,就要找到它在这个缓存项关系图中的位置,并且捕捉事件。当缓存关系图中任何一个节点数据有变化时,缓存管理程序会按照图的深度优先方式遍历相关节点,即清除这些相关节点的缓存项,通过这样可以实现精确的缓存项数据更新。
不过这个思路也很抽象.
使用图的话,你这部分的缓存是不是就常驻内存(或分布式内存)了。不如不是的话,一旦缓存过期,又是怎么处理的呢?
还有就是,数据可能分布在几块,那你这个图应该就是非联通的图了?具体又是怎么做的呢?
期望更详细的讨论.
@Tim Lee: 就是这个意思,你的另一种思路其实跟这个是一样的,只是采用的数据结构不一样而已,一个用的是链表,另一个用的图。
@Launcher: 你的意思似乎是依赖关系通过配置文件独立出来去实现,而不是像.net自带的缓存依赖那样直接在缓存的时候加入依赖。这样就有一个问题,每次如果要新缓存数据,就要去改动这个配置文件。
@Tim Lee: 你思路可能有点乱,你需要静下心来好好整理下。我们先说“依赖关系”的问题,针对”依赖关系“我们有四个操作:
1、查询依赖项;
2、添加依赖项;
3、删除依赖项;
4、修改依赖项; // 不是必须,可以用 2 和 3 实现
那么”依赖关系“采用的数据结构比较多,同时存储的方式也比较多(针对你的”改动这个配置文件“的疑问,我把这叫着”添加、删除、修改依赖项“):
1、内存;
2、数据库(SQL、NoSQL);
3、本地文件(针对你提出的”配置文件“);
那么现在请你告诉我,“.net自带的缓存依赖”的存储超出了我上面的三种方式了吗?
@Launcher: .net 的依赖一般是键值依赖,文件依赖和数据库依赖。
比如:public void AddObjectWithDepend(string objId, object o, string[] dependKey)
我这个地方表述的意思是,你把依赖关系分离出来了,通过一个独立的进程去处理这个事情。
假如我们用的是文件保存依赖关系.这个依赖在每次我们新建立一个缓存的时候,都需要手动去设置这个配置文件,我是这个意思。
@Tim Lee: .net 的依赖一般是键值依赖,文件依赖和数据库依赖。————你这是说的依赖的规则,我跟你讲的是依赖关系如何存储的问题,就好比 Asp.Net 会话状态,我可以保存在进程内,也可以保存在数据库,还可以保存在一个外部独立进程里,但是这都不影响依赖规则的制定。
我区分的是依赖规则和依赖关系,通过规则得出关系后,这个关系是需要存储的,选定什么存储方案同你的需求(为了防止你转不过弯,我特别强调下,需求也包括性能需求,不然你又要跟我扯“只管需求,不管性能吗?”)有关。
你要选择手动,我也没办法,你也可以选择自动,就是自己建立依赖关系接口,建立缓存接口,缓存接口调用依赖关系接口来查询、添加、删除、修改依赖关系项。
.net 的依赖一般是键值依赖,文件依赖和数据库依赖 —— 这三种依赖规则产生的依赖关系项都是存储在进程内存中的。
@Launcher: 非常感谢你耐心的回复!
你说的我理解了,只是还有一些问题还是不能很好的去解决。
我需要重新整理下思路,理顺了再说。
希望最终能讨论出一个可行的方案出来,我会实现了再发一个总结博文。
@Tim Lee: 最终会讨论出来的,因为我们从07年开始就一直在用。
@Launcher:
我整理的下,一并问下:
1.”依赖关系“的存储的方式你提到有3种,1内存,2数据库,3文件
疑问:因为这里讨论的是memcached,如果依赖放在了1,3中,还会涉及到,memcached在集群时候的问题.应该怎么处理?
2. memcached依赖实现你提到了:依赖关系接口,这个应该是标准的做法.但这个说起来容易实现起来真的不容易。你们是怎么做的呢?
我有看到一篇博文做了另一方面的尝试,地址如下:
http://www.cnblogs.com/tenghoo/archive/2010/02/23/Memcached_key_Depend_Performance.html
问题作者本身也指出来了。 就是他采取的[同生同灭]的方式本身难以保证[同生同灭].
我更倾向实现依赖接口的做法,MemcachedProviders最新的代码里面有做了这方面的尝试,但没有出发布版本。我也正在研究中.
3.关于缓存主动更新的问题,目前我们网站本身是有一套自己实现的单机内存缓存的机制.我们也有类似依赖项,主要是建立sql和表名称的关系
另外通过表跟踪(或表加版本号),后台跑一个线程去监测表变更如果变更了,就会主动Reload或者remove缓存。
目前是通过hahstable去缓存,单机。因此我想改为memcached.同时把前述提到的问题一并解决。
如果是memcached,我目前的问题是,我不是单机,我没办法用单机的方式直接从hashtable拿到所有的CacheItem,因此也没法去做主动更新和处理缓存之前的依赖。
@Tim Lee: 我直接修改的 memcached 源码,从服务端实现的缓存依赖。
@Launcher: 你指的是c源代码?还是类似Enyim.Caching的客户端?
@Tim Lee: 从客户端实现的方式是这样的:
struct ICacheDependencyProxy {
HRESULT Get
HRESULT Add
HRESULT Update
HRESULT Delete
}
struct ICacheDependencyServer {
HRESULT Get
HRESULT Add
HRESULT Update
HRESULT Delete
}
ICacheDependencyProxy 由客户端调用,请求会被发送到 ICacheDependencyServer,两者直接可以采用进程内通讯机制、也可以采用进程间通讯机制(比如 TCP)。ICacheDependencyServer 可以使用 memcached 替换。
比如:
class SqlServerCacheDependencyProxy : public ICacheDependencyProxy
class MemcachedCacheDependencyProxy : public ICacheDependencyProxy
class MongDBCacheDependencyProxy : public ICacheDependencyProxy
@Tim Lee: 你指的是c源代码?还是类似Enyim.Caching的客户端?
我直接修改的 memcached 源码,从服务端实现的缓存依赖。你觉得会是 Enyim.Caching的客户端 吗?
@Tim Lee:
疑问:因为这里讨论的是memcached,如果依赖放在了1,3中,还会涉及到,memcached在集群时候的问题.应该怎么处理?
你跟我讨论问题的时候总是不在状态,你是不是以为这里的“内存”就只能是在调用方的进程内?不能是进程外的吗?不能是分布式内存吗?memcached 从本质上来讲不就是一个分布式内存系统吗?
@Launcher: 绝对没有半点不在状态或者打算浪费你时间的意思。可能一个是技术上比较落伍。另外一个不在一个工作环境说话的上下文需要确认。再者和你的水平也相差比较远,需要狂补.
根据你的回复提示,我有这么一个方案,望指点下:(主要针对你说的客户端实现方式.c没太多研究,我必须承认不具备改的能力):
我把依赖放入memcached(依赖关系我这么建:将每个sql语句和其对应的表之间建立依赖,我在memcached中缓存table->Linklist<sqlstring>的键值对),
数据变更后,我根据变更的表名,就可以找到对应的所有缓存的sqlstring,把对应的cache清掉。
这个思路其实还是和我老版本的hashtable一个思路,原来在分布式缓存取不到这个依赖现在缓存到了memcached。带来的问题其实和前面说的[同生同灭]相近.
如果这个创建依赖放入memcached的动作失败,还是有问题。另外这么实现我感觉也非常的别扭.
@Tim Lee: 你的问题在于你的依赖关系的设计上。数据存储都可能失败,你需要有系统鲁棒性的考虑。你以前的代码不会总认为从 Sql Server 查询数据就一定会成功吧?
@Launcher: 你说的对:我确实觉得这么做很恶心.
@Tim Lee: 你这个思路有问题,不能眉毛胡子一把抓,你先得把你的依赖关系设计好了,把数据流或活动流程给描绘清楚了,然后再考虑用什么数据结构表示,如何持久化,怎样实现分布式,如何容错等其它问题。
@Launcher: 你说的很精辟,指出了我目前的问题所在。
我看了下MemcachedProviders的最新的程序,他们有做了部分依赖的尝试,没有写完,而且很久没更新了。
不过还是可以看出他们的思路:针对依赖关系,他们做了个TcpListen,客户端发送依赖关系给到服务端,服务端收到消息,将依赖关系放到IDictionary<string, IMemcachedDependancy>.这其实解决了依赖关系的设计和如何存储的问题.剩下的其实只需要在普通的memcached操作外面封装一层依赖的外壳即可。另外程序的健壮性需要加强,容错也是一个方面。
剩下的问题我打算再开一个提问。这个先结贴。主要针对你提搞到的这几点征集一些好的实现idea.
非常感谢你不厌其烦的答复。都是真知灼见,受益匪浅.
@Launcher: 当然也欢迎你继续贡献自己的见解.
http://q.cnblogs.com/q/63590/
@Tim Lee: 顺便说下最后的结果:最后我的实现是用WCF封装了memcached的服务接口,缓存只提供基本的add,delete,get服务。缓存的依赖我没有去实现。不是不需要实现,而是针对于缓存服务来说,它只需要提供这些基本的服务接口即可。而缓存依赖的解决应该是由具体的应用去独立实现,由于公司项目的时间上的关系,我没有去实现,但思路已经有了,在此标记下:可以将查每次add的缓存写入MQ,然后通过一个程序去异步读MQ将所需的依赖关系放入DB,再通过跟踪表或数据的变化,从DB取出满足依赖条件的数据,进行缓存的Reload.也有人是通过Redis去放依赖关系,无论哪种方式,依赖关系都应该是可遍历的。
有道理.数据库操作CQRS真的很有必要
还是使用Memcached实现,C每次变化时, 去修改Memcached中键值为C的值:为true。
每次读取Memcached中 A和B时,先判断Memcached中键值为C的值,如果为True,则重新读取A和B,
同时去修改Memcached中键值为C的值:为false。
这样就可以通过C的变化控制A和B了,省时省力!
@kimi_gyj :你提到的这个方法和我上面说到的[同生同灭] 的实现方式一样的。我觉得这个方式不太可靠。不知道你怎么看?
@Tim Lee: 从那方面觉得不靠谱?我觉得使用单列模式,实现的基本没有问题,除非缓存服务器当掉。
@kimi_gyj: 这本身是没有问题的。
我指的是:你每次取A,B时去检查c,你为什么要去检查c呢,因为你前面要设置A,B去依赖C.
问题在于你怎么在memcached方式下去实现A,B去依赖C. 所以我提到了青羽的[同生同灭]的实现方式.
所以问题又回到了缓存依赖.
@Tim Lee: 首先,我们使用memcached缓存A,B,是因为提高我们的性能,但是性能提高后,我们无法做到A,B更新的时候去更新缓存,那么我们有很多种模式去完成A,B的更新。如:使用消息中间件,A,B更新时写消息,然后通过服务去处理这个消息,完成缓存A,B的更新。但是这么做复杂了,因为不是所有的项目做会有自己的消息中间件,那么使用缓存C来控制缓存A,B的更新,就是一个简单的实现。这就是为啥要C,因为这是比较简单的实现,省时省力!当然如果项目觉得这个不行,不靠谱,那么我举的列子这是可以做到的。说白了,不是我们为啥要去检查C,而是我们如何控制缓存A,B的更新,C只是其中的一种方式!
@kimi_gyj: 我并没有否定你的做法,我只是表示这点我想到了。而剩余的问题关键在缓存依赖.