最近项目中遇到了performance问题,页面刷新,loading时间很长,需要优化的问题。期间查询了各种资料,准备用cache来解决这个问题,其中感谢一个博主的思路《Rails缓存套娃机制》,让我深受启发。我的大牛leader用了一种全新的方式解决了这个大难题,我用自己理解的方式,整下如下。
问题环境:页面引用数据量比较大,引用的表也非常多,1对多,1对1。
关于Fragment Cache的相关概念请自行查找官网解释。我的简单理解就是,生成唯一key,保存缓存片段,每次load,当key不一样的时候,重新缓存,当相同的时候,获取缓存
在需要缓存的页面地方加入cache
%tbody
- line_index = 0
- @students.each do |student|
- cache @cache_ids_hash[student.id] do
%tr
%td
= line_index.to_s
%td
= student.id
在cache方法内部,会把对象进行一番处理,最后生成一个唯一字符串,views/students/xxxxxxxxx,或者其他字符串
这里重点介绍如何利用这个唯一字符串来解决缓存问题。
问题环境:students表,classes表,parents表,teachers表,scores表
后面几张表都于student表有关联,需要将这几张表中的数据,挑选一些数据显示在一个view里。
这里设计的模式是在页面层次一次cache住所有数据,在help里处理自主生成独立的cahce key,然后在DB层加入trigger,更新主表student,实现缓存
具体分布说明
1.页面加入cache,如上述所述
2.主表students加入唯一标识符column,命名为cache_trigger_timestamp, timestamp类型,主要为生成唯一编号做准备
3.增加trigger,关联唯一标识符cache_trigger_timestamp,当监听的表有数据变动的时候,更新主表的cache_trigger_timestamp字段
这样做的好处:
对classes表,parents表,teachers表,scores表做trigger,当发生变化的时候, 更新student主表的cache_trigger_timestamp字段,在数据层,就很会容易判断是否有新的cache,是否需要缓存
具体trigger(举例一个)
DROP TRIGGER IF EXISTS 'teachers_cache_timestamps_update'
DROP TRIGGER IF EXISTS 'teachers_cache_timestamps_insert'
DROP TRIGGER IF EXISTS 'teachers_cache_timestamps_delete'
DELIMITER $$
CREATE TRIGGER 'teachers_cache_timestamps_update' AFTER UPDATE ON 'teachers'
FOR EACH ROW BEGIN
UPDATE students SET cache_trigger_timestamp = NOW() WHERE teacher_id = NEW.id
END$$
CREATE TRIGGER `teachers_cache_timestamps_insert` AFTER INSERT ON `teachers`
FOR EACH ROW BEGIN
UPDATE students SET cache_trigger_timestamp = NOW() WHERE teacher_id = NEW.id
END$$
CREATE TRIGGER `teachers_cache_timestamps_delete` AFTER DELETE ON `teachers`
FOR EACH ROW BEGIN
UPDATE students SET cache_trigger_timestamp = NOW() WHERE teacher_id = OLD.id
END$$
DELIMITER ;
4.建立保存Fragment cache 相关数据的表 ,命名为fragment_caches,
保存一些有用的数据,如item_type(数据类型,如:student),item_id(数据id),user_id(登录用户id),cache_id(缓存编号),等等需要的字段
问题点:这些字段有什么用?
答:为了扩展需要,比如item_type,区分缓存的是student数据,还是学校数据,或者是其他表数据
item_id,目标主要缓存数据的唯一编号id
user_id为了区分哪个用户的缓存数据,对每个登录用户数据分别缓存
总结,主要就是为了区分缓存数据,不至于混淆,至于怎么设计,具体问题具体分析
5.重点来了,controller层,为每条数据生成唯一的cache,并且设计逻辑,判断cache是否相同,是否需要重新cache
cache_id如何设计,需要应用上各个独特的字符,比如user_id,item_id,item_type,student.updated_at, student.cache_trigger_timestamp等,中间使用"_"关联,生成一个辨识度高的字符串
具体生成cache_key逻辑: 每条数据都生成唯一的编号,并返回。
def build_uniq_cache_key(students, item_type, user_id)
stu_cache_key_hash = Hash.new
if !students.blank?
for stu in students
stu_cache_id = ""
stu_cache_id << item_type
stu_cache_id << "_"
stu_cache_id << user_id.to_s
stu_cache_id << "_"
stu_cache_id << students.id.to_s
stu_cache_id << "_"
stu_cache_id << stu.id.to_s
stu_cache_id << "_"
stu_cache_id << stu.updated_at.strftime("%Y%m%d%H%M%S")
stu_cache_id << "_"
stu_cache_id << (!stu.cache_trigger_timestamp.blank? ? stu.cache_trigger_timestamp.strftime("%Y%m%d%H%M%S") : "00000000000000")
stu_cache_key_hash[stu.id] = stu_cache_id
end
end
return stu_cache_key_hash
end
刷新并判断是否生成新的cache
def feature_fragment_caches_refresh(item_type, students, cache_ids_hash, user_id)
stu_ids = cache_ids_hash.keys
if !stu_ids.blank?
cached_stu_ids = []
fragment_caches = FragmentCache.where("item_type = ? and item_id in (?) and user_id = ? ", item_type, stu_ids, user_id)
for fragment_cache in fragment_caches
cached_stu_ids.push(fragment_cache.item_id)
current_cache_id = cache_ids_hash[fragment_cache.item_id]
if current_cache_id != fragment_cache.cache_id
ActionController::Base.new.expire_fragment(fragment_cache.cache_id)
if Rails.cache.fetch("views/#{fragment_cache.cache_id}").nil?
fragment_cache.cache_id = current_cache_id
fragment_cache.save
end
end
end
new_save_cache_stu_ids = stu_ids - cached_stu_ids
new_save_cache_stu_ids.uniq!
if !new_save_cache_stu_ids.blank?
for new_save_cache_stu_id in new_save_cache_stu_ids
new_cache_id = cache_ids_hash[new_save_cache_stu_id]
new_cache = FragmentCache.new
new_cache.item_type = "student"
new_cache.item_id = new_save_cache_stu_id
new_cache.cache_id = new_cache_id
new_cache.user_id = user_id
new_cache.save
end
end
end
end
上述的两个方法需要在def show 里调用,完成cache操作
总结,整个cache思想点在于
1.新建cache_trigger_timestamp,其他表的变化,更新此数据,来控制多表的缓存更新
2.生成唯一有特点的cache_id标识符,辨识是否需要更新缓存
3.逻辑判断cache_id,控制缓存
以上是我的总结想法,或许还有些不成熟,不妥之处,请指正,谢谢。
name: eric
email: oldlock1988@163.com