刚刚开始研究Memcache,觉得Memcache Key的设计其实是十分重要的,搜了搜,感觉资料不是很多,下面这些资料主要是从官网上获得的,有些地方可能不太精确,仅供参考。
初始化Memcache Client
# perl my $memclient = Cache::Memcached->new({ servers => [ '10.0.0.10:11211', '10.0.0.11:11211' ]});
# pseudocode memcli = new Memcache memcli:add_server('10.0.0.10:11211')某些客户端的实现可能允许重复添加同一个Server,但最好每个Server是有一个实例,而且也要注意他们的顺序最好不要经常变化,防止key的重新映射;在初始化MemcacheClient的时候要注意实例化的次数,不要太过频繁的实例化,最好通过连接池来管理。
封装一个SQL作为key
Memcache在减轻数据库负载方面发挥着重要的作用,我们最常使用的场景也是用它来减少数据库查询次数,下面这个例子就是通过Cache缓存查询数据库结果的场景,把sql+userId作为一个key,相同用户第二次查询时就可以从缓存中直接提取:
# Don't load little bobby tables sql = "SELECT * FROM user WHERE user_id = ?" key = 'SQL:' . user_id . ':' . md5sum(sql) # We check if the value is 'defined', since '0' or 'FALSE' # can be # legitimate values! if (defined result = memcli:get(key)) { return result } else { handler = run_sql(sql, user_id) # Often what you get back when executing SQL is a special handler # object. You can't directly cache this. Stick to strings, arrays, # and hashes/dictionaries/tables rows_array = handler:turn_into_an_array # Cache it for five minutes memcli:set(key, rows_array, 5 * 60) return rows_array }注意:在通过set方法缓存数据时,我们可以指定数据过期时间,上面例子中设置的是5分钟,这样的话,五分钟内用户看到的都是同样的信息,即5分钟内的数据变化用户是感觉不到的,在开发时一定要注意根据实际情况对过期时间进行合理设置。
封装多个查询SQL作为Key
有些处理过程是复杂的,可能会用到多个sql,如果我们能把这些复杂的处理最后封装成一个key,这是最理想的结果。比如下面这个例子,我们把sql1+sql2+userId作为一个key,这样,我们两次查询的结果便封装成了一个CacheItem:
sql1 = "SELECT * FROM user WHERE user_id = ?" sql2 = "SELECT * FROM user_preferences WHERE user_id = ?" key = 'SQL:' . user_id . ':' . md5sum(sql1 . sql2) if (defined result = memcli:get(key)) { return result } else { # Remember to add error handling, kids ;) handler = run_sql(sql1, user_id) t[info] = handler:turn_into_an_array handler = run_sql(sql2, user_id) t[pref] = handler:turn_into_an_array # Client will magically take this hash/table/dict/etc # and serialize it for us. memcli:set(key, t, 5 * 60) return t }
缓存Object类型的数据
某些语言比如Java可以把对象进行序列化,进行序列化的对象就可以像普通字符串数据一样进行缓存了,但要注意的是,序列化和反序列化会耗费cpu时间,在缓存时仅仅序列化需要缓存对象会提高系统效率。
缓存一个片段(网页)
Memcache不仅仅可以用来减少数据库查询,任何可以提高我们应用速度的地方我们都可以用它来解决,比如下面这个例子,加载一个用户模板是非常耗时的,但是我们可以把一个封装好的模板(甚至是整个网页)缓存起来:
key
=
'FRAG-BIO:'
. user_id
if (result = memcli:get(key)) { return result } else { user = fetch_user_info(user_id) bio_template = fetch_biotheme_for(user_id) bio_fragment = apply_template(bio_template, user) memcli:set(key, bio_fragment, 5 * 15) return bio_fragment }
Get-By-Group-Key
在某些情况下,我们希望一系列的key只存储到一台服务器缓存中,比如我们在显示一个用户首页的时候,需要缓存他的姓名、年龄、简历、好友、日志等信息,而通常情况下客户端Hash算法会把它们的key映射到集群中的每一台机器的缓存中,如果这样的话,我们在查询一个人的主页时会从多台机器上取数据,会耗掉很多网络传输、Hash映射等时间,这是完全没有必要的,我们需要一种机制,在客户端内部把这些key组成一个group-key(可以是userId)。
其它需要注意的地方
- 数据有效性:某些时候我们需要保证对Cache中的数据进行同步,不要让用户感觉到自己用到的是过期的脏数据。
- 数据过期:我们可以设置缓存中的数据过期时间,最大过期时间是30天,如果参数值为0,则表示永不过期(LRU算法会自动删除需要删除的数据)。
- 删除Cache中数据:最直接的让Cache数据过期的办法就是delete。
- Key的值越短越好:key的大小被限制在250个字节(将来可能达到6K),key的长度会影响Hash算法寻找value值的效率,也会浪费更多内存空间(期间有对key的copy)。
使用伪命名空间
Memcache并不支持命名空间(其设计哲学就是简单高效),但是我们可以模拟实现它。比如,我们可以把一个用户的所有key都归档在一个命名空间下,比如可以把前缀设计为:user_namespace+userId。
user_prefix = memcli:get('user_namespace:' . user_id) bio_data = memcli:get(user_prefix . user_id . 'bio')缓存Set 或 List
存储集合到缓存的意思是把一个集合的所有数据作为一个CacheItem保存到缓存中,当然,具体的实现方式有多种。不过需要注意一点的是,Memcache的每个Item有不能超过1MB的限制,那么,我们该如何存储一个超大数据量的集合呢?
方案一:分二个阶段把数据缓存到Cache
第一个阶段存取的不是所有集合的数据,而是集合数据每一项的Id,然后第二阶段再把每个Id对应的数据对象存到缓存中。当然,当一个集合特别大的时候,所有的数据的Id有可能也会超过1MB的限制,那么我们在第一阶段存储时指定每个集合的数量即可。这么做还有个好处就是当我们在更新一个数据条目的时候仅更新指定Id的项目即可。
方案二:一个阶段分批次把数据存取到Cache
比如有30000条数据,我们可以每批次100条分300批次缓存到Cache。