首页 新闻 会员 周边

缓存依赖和缓存主动更新如何做?

0
悬赏园豆:20 [已解决问题] 解决于 2014-07-01 15:37

Memcached在处理缓存时,大部分的接口都是封装一个get/set/delete操作。
然而缓存还存在这样的场景:
1:a,b数据被缓存,他们同事依赖于c数据。也就是c变化了,a,b也要跟着变化。
2:c变化了,他需要主动去告诉cache,我变化了。以免之后再取老的cache.

希望各位大牛不吝赐教。

不要随便摘抄一段网上的人云亦云的话。最好有实际的举例。

补充问题:有人说, 问题2,你不可以直接在程序里面控制?
是这样的,简单的可以,但复杂的,比如c变化了,c数据对应的可能涉及n张表。一般的后台修改数据都是只针对一张表的。所以如果你有好的方案,请举例。

问题补充:

说明下:本问题主要是针对memcached.

基本的知识大家都懂,请不要人云亦云。

请举例给出你的方案。只要思路,不要代码.

Tim Lee的主页 Tim Lee | 菜鸟二级 | 园豆:350
提问于:2014-06-25 17:15
< >
分享
最佳答案
1

主动:修改数据的同时发起缓存失效指令。适用于 CQRS 模式。

被动:使用数据库变更通知机制(Databas Change Notification),例如: SqlDependency。

收获园豆:20
Launcher | 高人七级 |园豆:45045 | 2014-06-25 17:22

主动的我在补充问题里面有提到一些复杂情况的问题,具体是怎么样的思路呢?请举例下。

另外目前memcached是没有实现缓存依赖的,你说的是.net的缓存.

Tim Lee | 园豆:350 (菜鸟二级) | 2014-06-25 17:34

@Tim Lee: 我先回答你的第二个,可以自己建立一个缓存更新的独立进程,然后利用数据库变更通知机制来让缓存服务器的缓存失效。

对于第一个问题,为了应对模型建立的不够友好,你需要建立表,将你的对象同数据库中的表映射起来,当某个对象修改后,你就通过该表查询出受影响的表,然后通过这些受影响的表再反查出关联的对象,这样你知道哪些对象的缓存项需要失效了。更复杂点,你可以把数据库中表的字段也映射上,做更精细的更新控制,更进一步,设置过滤器,通过过滤器过滤字段的值来控制更新。

 

如果你的模型建立的足够好,是不需要这么复杂的,因此 CQRS 天生就支持事件聚合,专门为更新而设计的。

Launcher | 园豆:45045 (高人七级) | 2014-06-25 17:54

@Launcher: 我可以这么理解你想表达的的意思吗:建立缓存key同表之间的依赖关系配置文件,再通过独立的进程去跟踪表的变化,当某个或某些表(或者表的某个记录)变化了,查找配置文件中的依赖关系,然后更新(删除)缓存?

 

我还看到另外一种思路:使用 图 的数据结构来记录缓存项及其上游源数据的关系,每个缓存项在建立时,就要找到它在这个缓存项关系图中的位置,并且捕捉事件。当缓存关系图中任何一个节点数据有变化时,缓存管理程序会按照图的深度优先方式遍历相关节点,即清除这些相关节点的缓存项,通过这样可以实现精确的缓存项数据更新。

 

不过这个思路也很抽象.

使用图的话,你这部分的缓存是不是就常驻内存(或分布式内存)了。不如不是的话,一旦缓存过期,又是怎么处理的呢?
还有就是,数据可能分布在几块,那你这个图应该就是非联通的图了?具体又是怎么做的呢?

期望更详细的讨论.

Tim Lee | 园豆:350 (菜鸟二级) | 2014-06-26 12:59

@Tim Lee: 就是这个意思,你的另一种思路其实跟这个是一样的,只是采用的数据结构不一样而已,一个用的是链表,另一个用的图。

Launcher | 园豆:45045 (高人七级) | 2014-06-26 13:04

@Launcher: 你的意思似乎是依赖关系通过配置文件独立出来去实现,而不是像.net自带的缓存依赖那样直接在缓存的时候加入依赖。这样就有一个问题,每次如果要新缓存数据,就要去改动这个配置文件。

Tim Lee | 园豆:350 (菜鸟二级) | 2014-06-26 13:14

@Tim Lee: 你思路可能有点乱,你需要静下心来好好整理下。我们先说“依赖关系”的问题,针对”依赖关系“我们有四个操作:

1、查询依赖项;

2、添加依赖项;

3、删除依赖项;

4、修改依赖项; // 不是必须,可以用 2 和 3 实现

那么”依赖关系“采用的数据结构比较多,同时存储的方式也比较多(针对你的”改动这个配置文件“的疑问,我把这叫着”添加、删除、修改依赖项“):

1、内存;

2、数据库(SQL、NoSQL);

3、本地文件(针对你提出的”配置文件“);

那么现在请你告诉我,“.net自带的缓存依赖”的存储超出了我上面的三种方式了吗?

Launcher | 园豆:45045 (高人七级) | 2014-06-26 13:42

@Launcher: .net 的依赖一般是键值依赖,文件依赖和数据库依赖。

比如:public void AddObjectWithDepend(string objId, object o, string[] dependKey)

我这个地方表述的意思是,你把依赖关系分离出来了,通过一个独立的进程去处理这个事情。

假如我们用的是文件保存依赖关系.这个依赖在每次我们新建立一个缓存的时候,都需要手动去设置这个配置文件,我是这个意思。

Tim Lee | 园豆:350 (菜鸟二级) | 2014-06-26 14:25

@Tim Lee: .net 的依赖一般是键值依赖,文件依赖和数据库依赖。————你这是说的依赖的规则,我跟你讲的是依赖关系如何存储的问题,就好比 Asp.Net 会话状态,我可以保存在进程内,也可以保存在数据库,还可以保存在一个外部独立进程里,但是这都不影响依赖规则的制定。

我区分的是依赖规则和依赖关系,通过规则得出关系后,这个关系是需要存储的,选定什么存储方案同你的需求(为了防止你转不过弯,我特别强调下,需求也包括性能需求,不然你又要跟我扯“只管需求,不管性能吗?”)有关。

你要选择手动,我也没办法,你也可以选择自动,就是自己建立依赖关系接口,建立缓存接口,缓存接口调用依赖关系接口来查询、添加、删除、修改依赖关系项。

.net 的依赖一般是键值依赖,文件依赖和数据库依赖 —— 这三种依赖规则产生的依赖关系项都是存储在进程内存中的

Launcher | 园豆:45045 (高人七级) | 2014-06-26 14:39

@Launcher: 非常感谢你耐心的回复!

你说的我理解了,只是还有一些问题还是不能很好的去解决。

我需要重新整理下思路,理顺了再说。

希望最终能讨论出一个可行的方案出来,我会实现了再发一个总结博文。

Tim Lee | 园豆:350 (菜鸟二级) | 2014-06-27 12:10

@Tim Lee: 最终会讨论出来的,因为我们从07年开始就一直在用。

Launcher | 园豆:45045 (高人七级) | 2014-06-27 13: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 | 园豆:350 (菜鸟二级) | 2014-06-27 15:10

@Tim Lee: 我直接修改的 memcached 源码,从服务端实现的缓存依赖。

Launcher | 园豆:45045 (高人七级) | 2014-06-27 15:27

@Launcher: 你指的是c源代码?还是类似Enyim.Caching的客户端?

Tim Lee | 园豆:350 (菜鸟二级) | 2014-06-27 15:30

@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

Launcher | 园豆:45045 (高人七级) | 2014-06-27 15:35

@Tim Lee: 你指的是c源代码?还是类似Enyim.Caching的客户端?

我直接修改的 memcached 源码,从服务端实现的缓存依赖。你觉得会是 Enyim.Caching的客户端 吗?

Launcher | 园豆:45045 (高人七级) | 2014-06-27 15:36

@Tim Lee: 

疑问:因为这里讨论的是memcached,如果依赖放在了1,3中,还会涉及到,memcached在集群时候的问题.应该怎么处理?

你跟我讨论问题的时候总是不在状态,你是不是以为这里的“内存”就只能是在调用方的进程内?不能是进程外的吗?不能是分布式内存吗?memcached 从本质上来讲不就是一个分布式内存系统吗?

Launcher | 园豆:45045 (高人七级) | 2014-06-27 15:49

@Launcher: 绝对没有半点不在状态或者打算浪费你时间的意思。可能一个是技术上比较落伍。另外一个不在一个工作环境说话的上下文需要确认。再者和你的水平也相差比较远,需要狂补.

根据你的回复提示,我有这么一个方案,望指点下:(主要针对你说的客户端实现方式.c没太多研究,我必须承认不具备改的能力):

我把依赖放入memcached(依赖关系我这么建:将每个sql语句和其对应的表之间建立依赖,我在memcached中缓存table->Linklist<sqlstring>的键值对),
数据变更后,我根据变更的表名,就可以找到对应的所有缓存的sqlstring,把对应的cache清掉。
这个思路其实还是和我老版本的hashtable一个思路,原来在分布式缓存取不到这个依赖现在缓存到了memcached。带来的问题其实和前面说的[同生同灭]相近.
如果这个创建依赖放入memcached的动作失败,还是有问题。另外这么实现我感觉也非常的别扭.

Tim Lee | 园豆:350 (菜鸟二级) | 2014-06-27 17:16

@Tim Lee: 你的问题在于你的依赖关系的设计上。数据存储都可能失败,你需要有系统鲁棒性的考虑。你以前的代码不会总认为从 Sql Server 查询数据就一定会成功吧?

Launcher | 园豆:45045 (高人七级) | 2014-06-27 17:52

@Launcher: 你说的对:我确实觉得这么做很恶心.

Tim Lee | 园豆:350 (菜鸟二级) | 2014-06-27 18:15

@Tim Lee: 你这个思路有问题,不能眉毛胡子一把抓,你先得把你的依赖关系设计好了,把数据流或活动流程给描绘清楚了,然后再考虑用什么数据结构表示,如何持久化,怎样实现分布式,如何容错等其它问题。

Launcher | 园豆:45045 (高人七级) | 2014-06-30 15:32

@Launcher: 你说的很精辟,指出了我目前的问题所在。

我看了下MemcachedProviders的最新的程序,他们有做了部分依赖的尝试,没有写完,而且很久没更新了。

不过还是可以看出他们的思路:针对依赖关系,他们做了个TcpListen,客户端发送依赖关系给到服务端,服务端收到消息,将依赖关系放到IDictionary<string, IMemcachedDependancy>.这其实解决了依赖关系的设计和如何存储的问题.剩下的其实只需要在普通的memcached操作外面封装一层依赖的外壳即可。另外程序的健壮性需要加强,容错也是一个方面。

剩下的问题我打算再开一个提问。这个先结贴。主要针对你提搞到的这几点征集一些好的实现idea.

非常感谢你不厌其烦的答复。都是真知灼见,受益匪浅.

Tim Lee | 园豆:350 (菜鸟二级) | 2014-07-01 15:35

@Launcher: 当然也欢迎你继续贡献自己的见解.

http://q.cnblogs.com/q/63590/

Tim Lee | 园豆:350 (菜鸟二级) | 2014-07-01 16:28

@Tim Lee: 顺便说下最后的结果:最后我的实现是用WCF封装了memcached的服务接口,缓存只提供基本的add,delete,get服务。缓存的依赖我没有去实现。不是不需要实现,而是针对于缓存服务来说,它只需要提供这些基本的服务接口即可。而缓存依赖的解决应该是由具体的应用去独立实现,由于公司项目的时间上的关系,我没有去实现,但思路已经有了,在此标记下:可以将查每次add的缓存写入MQ,然后通过一个程序去异步读MQ将所需的依赖关系放入DB,再通过跟踪表或数据的变化,从DB取出满足依赖条件的数据,进行缓存的Reload.也有人是通过Redis去放依赖关系,无论哪种方式,依赖关系都应该是可遍历的。

Tim Lee | 园豆:350 (菜鸟二级) | 2014-07-28 10:18
其他回答(2)
0

有道理.数据库操作CQRS真的很有必要

吴瑞祥 | 园豆:29449 (高人七级) | 2014-06-26 11:30
0

还是使用Memcached实现,C每次变化时, 去修改Memcached中键值为C的值:为true。

每次读取Memcached中 A和B时,先判断Memcached中键值为C的值,如果为True,则重新读取A和B,

同时去修改Memcached中键值为C的值:为false。

这样就可以通过C的变化控制A和B了,省时省力!

kimi_gyj | 园豆:192 (初学一级) | 2014-06-27 15:11

@kimi_gyj :你提到的这个方法和我上面说到的[同生同灭] 的实现方式一样的。我觉得这个方式不太可靠。不知道你怎么看?

支持(0) 反对(0) Tim Lee | 园豆:350 (菜鸟二级) | 2014-06-27 15:21

@Tim Lee: 从那方面觉得不靠谱?我觉得使用单列模式,实现的基本没有问题,除非缓存服务器当掉。

支持(0) 反对(0) kimi_gyj | 园豆:192 (初学一级) | 2014-06-27 16:34

@kimi_gyj: 这本身是没有问题的。

我指的是:你每次取A,B时去检查c,你为什么要去检查c呢,因为你前面要设置A,B去依赖C.

问题在于你怎么在memcached方式下去实现A,B去依赖C. 所以我提到了青羽的[同生同灭]的实现方式.

所以问题又回到了缓存依赖.

支持(0) 反对(0) Tim Lee | 园豆:350 (菜鸟二级) | 2014-06-27 17:27

@Tim Lee: 首先,我们使用memcached缓存A,B,是因为提高我们的性能,但是性能提高后,我们无法做到A,B更新的时候去更新缓存,那么我们有很多种模式去完成A,B的更新。如:使用消息中间件,A,B更新时写消息,然后通过服务去处理这个消息,完成缓存A,B的更新。但是这么做复杂了,因为不是所有的项目做会有自己的消息中间件,那么使用缓存C来控制缓存A,B的更新,就是一个简单的实现。这就是为啥要C,因为这是比较简单的实现,省时省力!当然如果项目觉得这个不行,不靠谱,那么我举的列子这是可以做到的。说白了,不是我们为啥要去检查C,而是我们如何控制缓存A,B的更新,C只是其中的一种方式!

支持(0) 反对(0) kimi_gyj | 园豆:192 (初学一级) | 2014-06-30 18:25

@kimi_gyj: 我并没有否定你的做法,我只是表示这点我想到了。而剩余的问题关键在缓存依赖.

支持(0) 反对(0) Tim Lee | 园豆:350 (菜鸟二级) | 2014-07-01 15:21
清除回答草稿
   您需要登录以后才能回答,未注册用户请先注册