缓存使用说明?
李小翔
2014-08-04 19:23

内容列表
浏览器缓存 	 # 
数据库缓存 	 # 
页面缓存 	 # 
缓存API 	 # 
缓存配置 	 # 

BroToolkit/BroFramework的缓存解决方案有多个插件与API组成，涉及到浏览器缓存、页面缓存、数据库缓存、缓存集群同步等多个方面。

浏览器缓存
浏览器缓存是通过设置 response 的 http 头来设置的。
所有控制器操作中，可以直接调用 nocache() 方法来禁用浏览器缓存
附件下载时，会自动设置长期有效的浏览器缓存
插件 cache-headers 提供了多个设置 http 头的 API 和配置，详见 插件首页
插件 assets-pipeline 提供了图片、js、css等资源缓存的解决方案（自动在 url 后面拼上一个文件唯一标示）
在浏览器端，关于 AJAX 缓存的问题，可以参考 AJAX 缓存概述

数据库缓存
Hibernate 的默认缓存解决方案是 EhCache，因此，BroToolkit 中内置了 cache-ehcache 插件作为缓存实现方案。
BroTooklie 的 DataSource.groovy 模板中，默认配置了下面几个缓存参数，启用了查询缓存、二级缓存等
hibernate { 
    cache.use_second_level_cache = true
    cache.use_query_cache = true
    cache.region.factory_class = ’org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory’
} 
此外，Grails 的动态查阅API、Domain类中都有相关的配置，详见 Caching Strategy

页面缓存
当一个用户访问某个页面（GSP、控制器操作）时，插件 cache 提供了将渲染结果缓存下来的功能，以便下次访问时，直接从缓存中读出渲染结果返回给浏览器，同时，我们采用了 cache-ehcache 插件作为缓存的实现。
cache 的文档详见：Cache Plugin，常用的功能简介如下。
缓存、清空控制器操作的渲染结果，如下例所示：
import grails.plugin.cache.CacheEvict
import grails.plugin.cache.Cacheable
class TestController { 
   // 缓存到名为 message 的缓存中
    @Cacheable(’message’)
   def lookup() { 
        println ”called ‘lookup’”
   } 
   
   // 清空名为 message 的缓存
    @CacheEvict(value=’message’, allEntries=true)
   def evict() { 
        println ”called ‘evict’”
   } 
} 
需注意的是，这里没有指定缓存的 key，默认会使用 params 来作为key，比如：
用户访问 /test 时，会生成一个 key 为空的缓存条目
用户再次访问 /test 时，会直接访问缓存中的数据
如果用户访问 /test?foo=bar，由于参数不同，则会创建一个新的缓存条目
但是，当 params 一致，但是由不同用户访问时，由于权限等各种原因，看到的内容应该是不一样的，这里就必须为不同用户设置不同的key，因此，我们扩展了一下注解 @Cachable 的 key 参数，如下例所示：
@Cacheable(value=”foobar”, key=”#bropen.toolkit.web.cache.UserKeyGenerator”)
def test() { 
   ......
} 
即，当key为 #bropen.toolkit.web.cache.UserKeyGenerator 时，缓存的 key 中会自动拼上当前用户信息，从而实现为不同用户缓存不同的内容。另外，还可以设置 key 为 #bropen.toolkit.web.cache.SessionKeyGenerator，在 key 中拼上当前的 Session 信息，用户注销后缓存就不再可用了。
除了控制器缓存外，cache插件还提供了gsp标签来缓存视图中的一部分或者一个视图模板，下面贴两个官方示例：
<cache:block>
   <!-- Any valid markup may be included here, including dynamic expressions, invoking other tags, etc.... -->
</cache:block>
<cache:block key=”${ currentUser.id} ”>
   <!-- Any valid markup may be included here, including dynamic expressions, invoking other tags, etc.... -->
</cache:block>
<cache:render template=”myTemplate” model=”[name: ‘Some Value’]”/>
<cache:render template=”myTemplate” model=”[name: ‘Some Value’]” key=”${ currentUser.id} ”/>
此外，cache插件的功能并非仅限于此，详见官方文档。

缓存API
cache插件提供的API，开发不是方便，且缺乏API文档，因此，BroToolkit 提供了一个基于 EhCache 的 bropen.toolkit.utils.grails.CacheUtils 的工具类，主要如下：
Ehcache createCache
创建缓存，一般用不到，get、put、doWithBlockingCache时都会自动创建缓存
Object get(String cacheName, Serializable key)
从缓存中取一个值，如果key所对应的缓存条目存在且未过期，则返回缓存值，否则返回null
println CacheUtils.get( ”foobar”, ”a key”)
boolean put(String cacheName, Serializable key, Object value, boolean blocking=false)
将键值对缓存，如果老缓存存在，会自动更新
CacheUtils.put( ”foobar”, ”a key”, ”the value” )
boolean evict(String cacheName, Serializable key)
从缓存中删除 key 对应的缓存条目
flushCache(String cacheName)
清空名为 cacheName 的缓存中的所有条目
Object doWithBlockingCache(String cacheName, Serializable key, Closure closure)
执行闭包参数 closure 中的代码，并将其结果缓存下来。
下次调用此 API 时，如果 key 在缓存中存在，则直接返回缓存中的值，而不执行闭包。
BTW：人组组织树都是采用这种方式缓存的
示例如下：
// 将闭包执行的结果缓存下来
CacheUtils.doWithBlockingCache( ”foobar”, ”a key”, { 
   return ”the value”
} )
当然，cache 的 key 或 value 除了字符串外，还可以为任何可以序列化（implements Serializable）的 Java Bean。
此外，cache等插件还在系统中注册了一些 spring bean，常见的如 ehcacheCacheManager，即ehcache的管理器，通过它可以获得各个缓存，并进行操作，如：
def cache = ctx.ehcacheCacheManager.getEhcache(”osm”)
println cache.getSize()
println cache.getKeys()
println cache.getCacheConfiguration()
cache.remove(”a key”)

缓存配置
ehcache 的配置详情可以参考官方文档 Cache EhCache。
BroToolkit 中，通过 BroToolkitCacheConfig 配置了：
一些缓存默认值最长发呆期（timeToIdleSeconds）：run-app模式下设为 30 秒，war包模式下设为 30 分
最长存活期（timeToLiveSeconds）：run-app模式下设为 30 秒，war包模式下设为 4 小时
内存中的最大条目数（maxElementsInMemory）：10000
磁盘缓存位置（diskStore）根据当前主机和应用信息，在系统临时文件夹下生成唯一的缓存文件夹
系统启动时会在日志中打印出上述文件夹的位置
同时设置了两个缓存监听器工程，用于集群环境下的缓存同步配置，默认为异步方式：cacheEventListenerFactorySync：同步
cacheEventListenerFactoryAsync：异步
通过外部配置文件 clusters.properties，设置集群的同步地址信息
通过阅读 BroToolkitCacheConfig、BroFrameworkCacheConfig、BroBPMCacheConfig 中的示例，以及官方文档中的示例，缓存配置本身非常简单，如下例所示：
// 在下面的文件夹下新建一个以 CacheConfig 结尾的配置文件
// grails-app/conf/MyCacheConfig.groovy
// 最后加载（大于 BroBPMCacheConfig 中的值）
order  = 2000
config = { 
   
   // osm相关的缓存：30’
    cache { 
        name                ”osm”
        timeToLiveSeconds    (application.warDeployed ? 30*60 : 60)
        timeToIdleSeconds    0        // 没有idle时间，缓存创建时间+live时间后就过期。详细可以参考Element方法getExpirationTime的源码
        maxElementsInMemory  100
   } 
   
   // SSO缓存：10’，同步
    cache { 
        name                ”sso”
        timeToLiveSeconds    10*60
        timeToIdleSeconds    0
        cacheEventListenerFactoryName    ’cacheEventListenerFactorySync’
        maxElementsInMemory  50
   } 
   
} 
集群的情况下，如果是水平集群，并且主机名映射在对外IP地址上（不是127.xxx），则无须任何配置即可支持缓存的集群同步，否则，需要在每个集群节点（JVM）下部署 clusters.properties 文件。该文件的部署位置默认为 { user.dir} /bropen/clusters.properties，可以通过 Config 配置 bropen.toolkit.cluster.config.file 改变其位置。示例如下：
# 节点1的 clusters.properties 文件
# 主机地址、应用服务器端口
server.host = 192.168.1.11
server.port = 8080
# 节点2的 clusters.properties 文件
# 主机地址、应用服务器端口
server.host = 192.168.1.12
server.port = 8080
需要注意的是，如果是垂直集群，需要将不同的JVM节点绑定到不同的IP地址上。