如何在ColdFusion中正确实现共享缓存?

时间:2022-01-07 05:41:17

I have built a CFC designed to serve as a dynamic, aging cache intended for almost everything worth caching. LDAP queries, function results, arrays, ojects, you name it. Whatever takes time or resources to calculate and is needed more than once. I'd like to be able to do a few things:

我已经构建了一个CFC,旨在作为一个动态的老化缓存,用于几乎所有值得缓存的东西。 LDAP查询,函数结果,数组,对象,您可以命名它。无论需要多少时间或资源来计算,都需要不止一次。我希望能做一些事情:

  • share the CFC between applications
  • 在应用程序之间共享CFC

  • define the scope of the cache (server / application / session / current request only)
  • 定义缓存的范围(仅限服务器/应用程序/会话/当前请求)

  • use different cache instances at the same time, in the same request
  • 在同一请求中同时使用不同的缓存实例

  • be independent from CFCs using the cache component
  • 使用缓存组件独立于CFC

  • generally adhere to common sense (decoupling, encapsulation, orthogonality, locking)
  • 一般坚持常识(解耦,封装,正交,锁定)

I would of course be using a different cache instance for every distinct task, but I'd like to be able to use the same CFC across applications. The cache itself is (what else) a Struct, private to the cache instance. How would I properly implement caching and locking when the scope itself is subject to change?

我当然会为每个不同的任务使用不同的缓存实例,但我希望能够跨应用程序使用相同的CFC。缓存本身是(还有什么)一个Struct,对缓存实例是私有的。当范围本身可能发生变化时,我如何正确实现缓存和锁定?

For locking, I use named locks ('CacheRead', 'CacheWrite') currently, this is safe but strikes me as odd. Why would I want a server-wide lock for, say, a session-only operation? (Yes, maybe this is academic, but anyway.)

对于锁定,我目前使用命名锁('CacheRead','CacheWrite'),这是安全的,但让我感到奇怪。为什么我想要一个服务器范围的锁,例如,仅限会话的操作? (是的,也许这是学术性的,但无论如何。)

Passing in the APPLICATION scope as a reference when I want application level caching also seems the wrong thing to do. Is there a better way?

当我想要应用程序级缓存时,将APPLICATION范围作为参考传递似乎也是错误的。有没有更好的办法?

2 个解决方案

#1


1  

I understand your desire to avoid passing in the actual scope structure that you want to cache to, but your alternatives are limited. The first thing that comes to mind is just passing the name (a string) of the scope you want your cache stored in, and evaluating. By its nature, evaluation is inefficient and should be avoided. That said, I was curious how it might be accomplished. I don't have your code so I just made a dirt-simple "storage" abstraction CFC (skipped caching, as it's irrelevant to what I want to test) here:

我理解你希望避免传递你想要缓存的实际范围结构,但你的选择是有限的。首先想到的是传递您希望缓存存储在其中的范围的名称(字符串),并进行评估。就其性质而言,评估效率低下,应予以避免。那就是说,我很好奇它是如何实现的。我没有你的代码所以我只是做了一个简单的“存储”抽象CFC(跳过缓存,因为它与我想要测试的内容无关)在这里:

cache.cfc:

<cfcomponent>
    <cfset variables.cacheScope = "session" /><!--- default to session --->
    <cfset variables.cache = ""/>

    <cfscript>
    function init(scope){
        variables.cacheScope = arguments.scope;
        return this;
    }

    function cacheWrite(key, value){
        structInsert(evaluate(variables.cacheScope),arguments.key,arguments.value,true);
        return this;
    }

    function cacheRead(key){
        if (not structKeyExists(evaluate(variables.cacheScope), arguments.key)){
            return "";
        }else{
            variables.cache = evaluate(variables.cacheScope);
            return variables.cache[arguments.key];
        }
    }   
    </cfscript>
</cfcomponent>

And a view to test it:

以及测试它的观点:

<!--- clear out any existing session vars --->
<cfset structClear(session)/>
<!--- show empty session struct --->
<cfdump var="#session#" label="session vars">
<!--- create storage object --->
<cfset cacher = createObject("component", "cache").init("session")/>
<!--- store a value --->
<cfset cacher.cacheWrite("foo", "bar")/>
<!--- read stored value --->
<cfset rtn = cacher.cacheRead("foo")/>
<!--- show values --->
<cfdump var="#rtn#">
<cfdump var="#session#" label="session vars">

Off topic: I like to write my setter functions to return "this" [as seen above] so that I can chain method calls like jQuery. Part of the view could just as easily been written as:

偏离主题:我喜欢编写我的setter函数来返回“this”[如上所示],以便我可以像jQuery一样链接方法调用。部分视图可以很容易地写成:

<cfset rtn = createObject("component", "cache")
    .init("session")
    .cacheWrite("foo", "bar")
    .cacheRead("foo")/>

It's interesting that this is possible, but I probably wouldn't use it in production due to the overhead cost of Evaluate. I'd say that this is valid enough reason to pass in the scope you want to cache into.

有趣的是,这是可能的,但由于Evaluate的开销成本,我可能不会在生产中使用它。我要说这是足够的理由传递你想要缓存的范围。

If you're still bothered by it (and maybe rightly so?), you could create another CFC that abstracts reading and writing from the desired scope and pass that into your caching CFC as the storage location (a task well-suited for ColdSpring), that way if you ever decide to move the cache into another scope, you don't have to edit 300 pages all using your cache CFC passing in "session" to init, and instead you can edit 1 CFC or your ColdSpring config.

如果你仍然为它烦恼(也许是这样吗?),你可以创建另一个CFC,从所需范围抽象读取和写入,并将其作为存储位置传递到缓存CFC(一个非常适合ColdSpring的任务)这样,如果您决定将缓存移动到另一个范围,则不必使用缓存CFC将“会话”传递到init来编辑300个页面,而是可以编辑1个CFC或ColdSpring配置。

I'm not entirely sure why you would want to have single-request caching though, when you have the request scope. If what you're looking for is a way to cache something for the current request and have it die shortly afterward, request scope may be what you want. Caching is usually more valuable when it spans multiple requests.

当你有请求范围时,我不完全确定为什么你会想要单一请求缓存。如果您正在寻找的是一种为当前请求缓存某些东西并让它在不久之后死亡的方法,请求范围可能就是您想要的。当跨越多个请求时,缓存通常更有价值。

#2


2  

Okay - since I misunderstood your question initially I've deleted my previous answer as to not cause any further confusion.

好的 - 因为我最初误解了你的问题,所以我删除了之前的答案,以免引起任何进一步的混淆。

To answer your question about locking:

要回答有关锁定的问题:

Named locks should be fine because they don't have to always have the same name. You can name them dynamically depending on what cache you are accessing. When you need to access an element of the private struct you could do something like have the named lock use the key as its name.

命名锁应该没问题,因为它们不必总是具有相同的名称。您可以根据要访问的缓存动态命名它们。当您需要访问私有结构的元素时,您可以执行类似命令的锁定,使用密钥作为其名称。

This way, the only time a lock would have an effect is if something was trying to access the same cache by name.

这样,锁定产生影响的唯一时间是某些东西试图按名称访问同一缓存。

#1


1  

I understand your desire to avoid passing in the actual scope structure that you want to cache to, but your alternatives are limited. The first thing that comes to mind is just passing the name (a string) of the scope you want your cache stored in, and evaluating. By its nature, evaluation is inefficient and should be avoided. That said, I was curious how it might be accomplished. I don't have your code so I just made a dirt-simple "storage" abstraction CFC (skipped caching, as it's irrelevant to what I want to test) here:

我理解你希望避免传递你想要缓存的实际范围结构,但你的选择是有限的。首先想到的是传递您希望缓存存储在其中的范围的名称(字符串),并进行评估。就其性质而言,评估效率低下,应予以避免。那就是说,我很好奇它是如何实现的。我没有你的代码所以我只是做了一个简单的“存储”抽象CFC(跳过缓存,因为它与我想要测试的内容无关)在这里:

cache.cfc:

<cfcomponent>
    <cfset variables.cacheScope = "session" /><!--- default to session --->
    <cfset variables.cache = ""/>

    <cfscript>
    function init(scope){
        variables.cacheScope = arguments.scope;
        return this;
    }

    function cacheWrite(key, value){
        structInsert(evaluate(variables.cacheScope),arguments.key,arguments.value,true);
        return this;
    }

    function cacheRead(key){
        if (not structKeyExists(evaluate(variables.cacheScope), arguments.key)){
            return "";
        }else{
            variables.cache = evaluate(variables.cacheScope);
            return variables.cache[arguments.key];
        }
    }   
    </cfscript>
</cfcomponent>

And a view to test it:

以及测试它的观点:

<!--- clear out any existing session vars --->
<cfset structClear(session)/>
<!--- show empty session struct --->
<cfdump var="#session#" label="session vars">
<!--- create storage object --->
<cfset cacher = createObject("component", "cache").init("session")/>
<!--- store a value --->
<cfset cacher.cacheWrite("foo", "bar")/>
<!--- read stored value --->
<cfset rtn = cacher.cacheRead("foo")/>
<!--- show values --->
<cfdump var="#rtn#">
<cfdump var="#session#" label="session vars">

Off topic: I like to write my setter functions to return "this" [as seen above] so that I can chain method calls like jQuery. Part of the view could just as easily been written as:

偏离主题:我喜欢编写我的setter函数来返回“this”[如上所示],以便我可以像jQuery一样链接方法调用。部分视图可以很容易地写成:

<cfset rtn = createObject("component", "cache")
    .init("session")
    .cacheWrite("foo", "bar")
    .cacheRead("foo")/>

It's interesting that this is possible, but I probably wouldn't use it in production due to the overhead cost of Evaluate. I'd say that this is valid enough reason to pass in the scope you want to cache into.

有趣的是,这是可能的,但由于Evaluate的开销成本,我可能不会在生产中使用它。我要说这是足够的理由传递你想要缓存的范围。

If you're still bothered by it (and maybe rightly so?), you could create another CFC that abstracts reading and writing from the desired scope and pass that into your caching CFC as the storage location (a task well-suited for ColdSpring), that way if you ever decide to move the cache into another scope, you don't have to edit 300 pages all using your cache CFC passing in "session" to init, and instead you can edit 1 CFC or your ColdSpring config.

如果你仍然为它烦恼(也许是这样吗?),你可以创建另一个CFC,从所需范围抽象读取和写入,并将其作为存储位置传递到缓存CFC(一个非常适合ColdSpring的任务)这样,如果您决定将缓存移动到另一个范围,则不必使用缓存CFC将“会话”传递到init来编辑300个页面,而是可以编辑1个CFC或ColdSpring配置。

I'm not entirely sure why you would want to have single-request caching though, when you have the request scope. If what you're looking for is a way to cache something for the current request and have it die shortly afterward, request scope may be what you want. Caching is usually more valuable when it spans multiple requests.

当你有请求范围时,我不完全确定为什么你会想要单一请求缓存。如果您正在寻找的是一种为当前请求缓存某些东西并让它在不久之后死亡的方法,请求范围可能就是您想要的。当跨越多个请求时,缓存通常更有价值。

#2


2  

Okay - since I misunderstood your question initially I've deleted my previous answer as to not cause any further confusion.

好的 - 因为我最初误解了你的问题,所以我删除了之前的答案,以免引起任何进一步的混淆。

To answer your question about locking:

要回答有关锁定的问题:

Named locks should be fine because they don't have to always have the same name. You can name them dynamically depending on what cache you are accessing. When you need to access an element of the private struct you could do something like have the named lock use the key as its name.

命名锁应该没问题,因为它们不必总是具有相同的名称。您可以根据要访问的缓存动态命名它们。当您需要访问私有结构的元素时,您可以执行类似命令的锁定,使用密钥作为其名称。

This way, the only time a lock would have an effect is if something was trying to access the same cache by name.

这样,锁定产生影响的唯一时间是某些东西试图按名称访问同一缓存。