从数据模型来看,种草社区 = 内容 + 关系 + 计数。在业务上,具体展开就是,
如何高效处理这几个主要元素,决定了社区系统的用户体验和服务容量。
最初项目为了尽快上线,这些数据都是直接到db里查询,前期访问量小,没什么关系。但到了后面,一旦访问量放大,db的资源瓶颈就会凸显。后来的压测结果也的确反应了这一点,在访问量稍微增长,数据库qps上升时,ddb的响应时间明显变长,平均响应时间有40~50ms。因此急需对这些数据进行缓存,以抵挡直接访问db的大部分流量。
实现这些数据的缓存做法很简单,但若是要把他做好,且各个业务模型能高效率的接入使用,则需要好好考量下。
内容缓存,这里特指DAO缓存,以数据库主键为查询key,数据库行记录为value。
DAO缓存的实现有一些开源框架可以直接拿来用,如spring->缓存key的设计
缓存key需要包含哪些元素?先来列举下之前遇到过的问题:
解决上述问题,key就需要包含:
缓存miss,需要知道是数据真的不存在,还是仅仅缓存过期了。有些黑客可能会恶意构造数据,导致缓存无限击穿。所以需要设计一个标识不存在的对象,从缓存里取出数据时做下判断,如果是特定的空对象,则不需要再去db获取了。
缓存未命中时读数据库,如果是单条数据,则同步设置到缓存,如果是多条,则异步设置到缓存。
关系数据通常用于列表场景,批量取符合条件的数据,然后按指定字段排序,分页展示。
这块比较难处理的就是过滤+排序+分页。业务体量小时可以不使用缓存,建立专门的索引表,把需要作为过滤条件的字段包含到索引表里,利用数据库去处理排序、过滤。然而访问量大了之后,数据库就不适合干这个事情了。为了解决这类问题,种草社区实现了一套基于redis sorted set的通用关系缓存API。大致的接口如下:
/**
* 以索引为边界批量获取有序集合中的数据
*
* @param keys 键名列表
* @param begin 偏移量开始
* @param end 偏移量结束
* @param orderType 排序类型
* @param relationCacheFilter 过滤器
* @return
*/
Map<K, Set<V>> multiGetByIndex(final List<K> keys, final long begin, final long end, final OrderType orderType,
RelationCacheFilter<V> relationCacheFilter);
/**
* 以分数为边界批量获取有序集合中的数据
*
* @param keys 键名列表
* @param min 最小分数
* @param max 最大分数
* @param offset 偏移量
* @param limit 条数
* @param orderType 排序类型
* @param relationCacheFilter 过滤器
* @return
*/
Map<K, Set<V>> multiGetByScore(final List<K> keys, final double min, final double max, final long offset,
final long limit, final OrderType orderType, RelationCacheFilter<V> relationCacheFilter);
/**
* 构建缓存key
*
* @param k 键名
* @return 缓存key
*/
String buildCacheKey(K k);
/**
* 返回分区
*
* @return
*/
String getRegion();
/**
* 获取过期时间,单位秒
*/
Long getExpireSeconds();
/**
* 获取全量初始化任务线程
*
* @return
*/
RelationCacheInitRunnable getCacheInitRunnable(K k);
实现要点:
计数主要面临的问题有几点:
种草社区的计数缓存基于redis,数据结构上主要用到了:
大致的数据流如下图:
之前专门写过一遍文章,详细可点击此处查看。
网易云大礼包:https://www.163yun.com/gift
本文来自网易实践者社区,经作者刘魏威授权发布