前言
无论是网站还是APP,在用户登陆成功后都会有一个主页,这个页面渲染时间的快慢直接决定了用户要不要放弃、会不会吐槽、问不问候技术全家。
而更过分的是上面都发生了,还不停的在刷,卡的问题很有可能就会由点到面到瘫痪。
调优
这个问题就比较广泛了有很多方面可以下手,相对来说,正常情况下比较耗时的有http网络开销和磁盘IO开销,多数会首先从这2个下手优化,本人这边放一些经历过的操作:
前端:合理发送http请求、合理使用localstorage|sessionstorage、合理控制页面按钮、放静态目录前压缩js|css,组件化(不是专业的,先说这么多)
后端:合理应用层代码、合理的传输数据、合理的数据提取范围、合理的应用(数据库)架构、合理的引入缓存
Redis、Memcache Or MongoDB
我的工具类
最后
缓存多数公司都应该引入得有,看过之后若觉得有用可以试一下。本文同时也是为后续我要写的另一个主题做准备,欢迎订阅。Redis在 >= 3.2.0版本中引入了地理信息相关的操作,方便做地图的同学使用,参考链接:
http://redisdoc.com/index.html
有问题欢迎指出,长按可以找到我哦!!!
这三者都用过,当前正使用着Redis & MongoDB,总的来说Redis多一些,根据自己的业务,选择合适的即可。
Memcache:在360时,架构师(徐榕)写了一个注解,只要在查询方法上使用,即可自动将查询结果放到缓存中。
MongoDB:当前正在使用,与第三方(其它部门)协作时,前期可能业务不成熟,导致外部输入经常会增减字段,这个时候如果用Mysql就比较麻烦,需要不停的应对写DDL修改表结构,MongoDB很好的解决了这个问题,查询上与Mysql语法类似
Redis:在360与小鹏都在使用,自己写的一个工具类,主要实现将查询结果放置到缓存且尽量前置,方便下一次相同条件时从缓存获取,有效节约了微服务间RPC的时间开销与DB的IO开销,需要注意:后台管理系统编辑一些(产品)数据时,与该数据关联的缓存应该无效;至于缓存与DB间的数据一致性,这个问题不在本篇范围,网上也有很多,有兴趣的可以了解一下。
Redis与其它二者间的对比,资料整理如下:
/**
* 自定义缓存读取接口
* @param <T>
* @param <I>
*/
public interface CacheLoadable<T, I extends Number> {
T load();
}
/**
* 缓存模板方法实现
*/
"xpilot.tools.redis.enable",havingValue = "true") (value =
public class CacheTemplateService {
private RedisComponent redisComponent;
"redisTemplate") (name=
private RedisTemplate redisTemplate;
private ConcurrentHashMap<String,String> keyMap = new ConcurrentHashMap<>();
private volatile String cacheKey;
/**
* 缓存只是用来在某些场景增加查询效率,如果想直接当DB使用,请考虑具体业务声明
* @param redisTemplate 可以自行配置一个template,比如想配置不一样的redis db,可以重新配置一个即得到一个新的template
* @param key 避免在某些时候运维介入,key的定义应该尽量区分服务名; eg:err_map_mgr_key(错误图层前缀_key)
* @param expire 过期时间
* @param unit 与expire配合使用 过期单位
* @param clazz 返回类型
* @param callback 当缓存不存在时要执行的 方法(业务逻辑)
* @return
*/
public <T> T loadFromCache(RedisTemplate redisTemplate,String key, Long expire, TimeUnit unit, TypeReference<T> clazz, CacheLoadable<T, Number> callback){
Object obj = redisComponent.get(redisTemplate,key);
if(!Objects.isNull(obj)){
return JSON.parseObject(obj+"",clazz);
}
cacheKey = keyMap.get(key);
cacheKey = StringUtils.isEmpty(cacheKey) ? key : cacheKey;
synchronized (cacheKey){
obj = redisComponent.get(redisTemplate,key);
if(!Objects.isNull(obj)){
return JSON.parseObject(obj+"",clazz);
}
T result = callback.load();
if(Objects.isNull(result)){
return null;
}
if(redisComponent.setNxAndTimeWithTransaction(redisTemplate,key,JSON.toJSONString(result) , expire,unit)){
keyMap.put(key,key);
}
return result;
}
}
/**
* 缓存只是用来在某些场景增加查询效率,如果想直接当DB使用,请考虑具体业务声明
* @param key 避免在某些时候运维介入,key的定义应该尽量区分服务名; eg:err_map_mgr_key(错误图层前缀_key)
* @param expire 过期时间
* @param unit 与expire配合使用 过期单位
* @param clazz 返回类型
* @param callback 当缓存不存在时要执行的 方法(业务逻辑)
* @return
*/
public <T> T loadFromCache(String key, Long expire, TimeUnit unit, TypeReference<T> clazz, CacheLoadable<T, Number> callback){
return this.loadFromCache(redisTemplate, key,expire ,unit , clazz, callback);
}
/**
* Simple type of set
* @param key
*/
public Object loadFromCache(String key){
return loadFromCache(redisTemplate, key);
}
public Object loadFromCache(RedisTemplate redisTemplate,String key){
Object cache = redisComponent.get(redisTemplate,key);
if(Objects.isNull(cache)){
return null;
}
return cache;
}
/**
* Simple type of set
* @param key
* @param object
*/
public void setCache(String key,Object object){
this.setCacheWithTime(key,object,null,null);
}
public boolean setCacheWithTime(String key,Object object,Long expiredTime,TimeUnit unit){
return redisComponent.setNxAndTimeWithTransaction(key,object,expiredTime,unit);
}
public Long decrement(String key,Long delta){
return redisComponent.decrement(key,delta);
}
public Long increment(String key,Long delta){
return redisComponent.increment(key,delta);
}
public Boolean expire(String key,Long expire,TimeUnit unit){
return redisComponent.expire(key,expire,unit);
}
public Long exists(String key,TimeUnit unit){
return redisComponent.ttl(key,unit);
}
}
/***********************************时间工具类*****************************************/
/**
* 避免跨天缓存导致用户看到数据不实时,所以动态计算缓存时间,确保缓存时间不超过当天
* @param def 默认值
* @param unit 单位
* @return
*/
public static Long dynamicCalcCacheTime(Long def,TimeUnit unit){
long base = 1000; //Default SECOND
if(TimeUnit.MINUTES.equals(unit)){
base *= 60; //Cast To MINUTES
}
else if(TimeUnit.HOURS.equals(unit)){
base *= 60*60; //Cast To HOURS
}
Date now = Calendar.getInstance().getTime();
Date last = calcLastTimeOfDay(now);
long diff = (last.getTime() - now.getTime()) / base;
return diff > def ? def : diff;
}
/**
* 获取指定日期对应的最后一刻时间
* @param date
* @return
*/
public static Date calcLastTimeOfDay(Date date){
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
calendar.set(Calendar.HOUR_OF_DAY,23);
calendar.set(Calendar.MINUTE,59);
calendar.set(Calendar.SECOND,59);
calendar.set(Calendar.MILLISECOND, 999);
return calendar.getTime();
}
/**
* 获取指定日期对应的开始一刻时间
* @param date
* @return
*/
public static Date calcFirstTimeOfDay(Date date){
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
calendar.set(Calendar.HOUR_OF_DAY,0);
calendar.set(Calendar.MINUTE,0);
calendar.set(Calendar.SECOND,0);
calendar.set(Calendar.MILLISECOND, 000);
return calendar.getTime();
}
说明:上面的代码可能会因当前实现Redis操作的方式所有不同,有的用RedisTemplate、有的也直接用RedissonClient,但不影响缓存实现的方法,之所以loadFromCache方法还提供一个RedisTemplate是考虑到有的应用同时配置多个redis数据库,这样可以通过构建RedisTemplate来实现同一个应用操作不同的dbindex。