git 网站开发应用,怎么删除安装wordpress,关键词首页排名优化平台,一般使用的分辨率是多少dpi一、前言
大家好#xff01;我是sum墨#xff0c;一个一线的底层码农#xff0c;平时喜欢研究和思考一些技术相关的问题并整理成文#xff0c;限于本人水平#xff0c;如果文章和代码有表述不当之处#xff0c;还请不吝赐教。
作为一名从业已达六年的老码农#xff0c…一、前言
大家好我是sum墨一个一线的底层码农平时喜欢研究和思考一些技术相关的问题并整理成文限于本人水平如果文章和代码有表述不当之处还请不吝赐教。
作为一名从业已达六年的老码农我的工作主要是开发后端Java业务系统包括各种管理后台和小程序等。在这些项目中我设计过单/多租户体系系统对接过许多开放平台也搞过消息中心这类较为复杂的应用但幸运的是我至今还没有遇到过线上系统由于代码崩溃导致资损的情况。这其中的原因有三点一是业务系统本身并不复杂二是我一直遵循某大厂代码规约在开发过程中尽可能按规约编写代码三是经过多年的开发经验积累我成为了一名熟练工掌握了一些实用的技巧。
前面的文章都是写接口如何设计、接口怎么验权以及一些接口常用的组件这篇写点接口性能相关的。接口性能优化有很多途径比如表建索引、SQL优化、加缓存、重构代码等等本篇文章主要讲一下我是怎么在项目中使用缓存来提高接口响应速度的。我觉得缓存的使用主要有下面几个方面
缓存预热 定时任务预热定时任务在系统低峰期预加载数据到缓存中。启动预热系统启动时预加载必要的数据到缓存中。 缓存层次化 多级缓存实现本地缓存和分布式缓存相结合例如先在本地缓存中查询如果没有再查询Redis等分布式缓存最后才查询数据库。热点数据缓存对频繁访问的数据进行缓存如用户会话、热门商品信息、高频访问的内容等。
缓存提高接口响应速度主要是上面这些思路不过我不是来讲概念的那是面试要用的东西。我要讲的是如何用代码实现这些思路把它们真正用到项目中来水平有限我尽力说不喜勿喷。
由于文章经常被抄袭开源的代码甚至被当成收费项所以源码里面不是全部代码有需要的同学可以留个邮箱我给你单独发
二、缓存预热手撸一个缓存处理器 上面说了缓存预热主要是定时任务预热、启动预热那么我们实现这个功能的时候一般使用ConcurrentHashMap或Redis来暂存数据然后加上SpringBoot自带的Scheduled定时刷新缓存就够了。虽然这样可以实现缓存预热但缺陷很多一旦需要预热的东西多起来就会变得越来越复杂那么如何实现一个好的缓存处理器呢接着看 1、缓存处理器设计
1一个好的缓存处理器应该是这样搭建的
DAL实现产出DAO和DO对象定义缓存领域模型定义缓存名称特别关注缓存的初始化顺序编写数据仓库通过模型转换器实现数据模型到缓存模型的转化编写缓存管理器推荐继承抽象管理器 {link AbstractCacheManager}根据业务需求设计缓存数据接口putAll,get,getCacheInfo等基础API完成bean配置最好是可插拔的注册方式缓存管理器和数据仓库、扩展点服务
2思路分析 2、代码实现
a. 每个处理器都有缓存名字、描述信息、缓存初始化顺序等信息所以应该定义一个接口名字为CacheNameDomain
CacheNameDomain.java
package com.summo.demo.cache;public interface CacheNameDomain {/*** 缓存初始化顺序级别越低越早被初始化* p* 如果缓存的加载存在一定的依赖关系通过缓存级别控制初始化或者刷新时缓存数据的加载顺序br* 级别越低越早被初始化br* p* 如果缓存的加载没有依赖关系可以使用默认顺序codeOrdered.LOWEST_PRECEDENCE/code** return 初始化顺序* see org.springframework.core.Ordered*/int getOrder();/*** 缓存名称推荐使用英文大写字母表示** return 缓存名称*/String getName();/*** 缓存描述信息用于打印日志** return 缓存描述信息*/String getDescription();
}b. 可以使用一个枚举类将不同的缓存处理器分开有利于管理取名为CacheNameEnum
CacheNameEnum.java
package com.summo.demo.cache;import org.springframework.core.Ordered;/*** description 缓存枚举*/
public enum CacheNameEnum implements CacheNameDomain {/*** 系统配置缓存*/SYS_CONFIG(SYS_CONFIG, 系统配置缓存, Ordered.LOWEST_PRECEDENCE),;private String name;private String description;private int order;CacheNameEnum(String name, String description, int order) {this.name name;this.description description;this.order order;}Overridepublic String getName() {return name;}public void setName(String name) {this.name name;}Overridepublic String getDescription() {return description;}public void setDescription(String description) {this.description description;}Overridepublic int getOrder() {return order;}public void setOrder(int order) {this.order order;}
}c. 缓存信息转换工具以便dump出更友好的缓存信息取名为CacheMessageUtil
CacheMessageUtil.java
package com.summo.demo.cache;import java.util.Iterator;
import java.util.List;
import java.util.Map;/*** description 缓存信息转换工具以便dump出更友好的缓存信息*/
public final class CacheMessageUtil {/** 换行符 */private static final char ENTERSTR \n;/** Map 等于符号 */private static final char MAP_EQUAL ;/*** 禁用构造函数*/private CacheMessageUtil() {// 禁用构造函数}/*** 缓存信息转换工具以便dump出更友好的缓存信息br* 对于List?的类型转换** param cacheDatas 缓存数据列表* return 缓存信息*/public static String toString(List? cacheDatas) {StringBuilder builder new StringBuilder();for (int i 0; i cacheDatas.size(); i) {Object object cacheDatas.get(i);builder.append(object);if (i ! cacheDatas.size() - 1) {builder.append(ENTERSTR);}}return builder.toString();}/*** 缓存信息转换工具以便dump出更友好的缓存信息br* 对于MapString, Object的类型转换** param map 缓存数据* return 缓存信息*/public static String toString(Map?, ? map) {StringBuilder builder new StringBuilder();int count map.size();for (Iterator? i map.keySet().iterator(); i.hasNext();) {Object name i.next();count;builder.append(name).append(MAP_EQUAL);builder.append(map.get(name));if (count ! count - 1) {builder.append(ENTERSTR);}}return builder.toString();}}d. 每个处理器都有生命周期如初始化、刷新、获取处理器信息等操作这应该也是一个接口处理器都应该声明这个接口名字为CacheManager
CacheManager.java
package com.summo.demo.cache;import org.springframework.core.Ordered;public interface CacheManager extends Ordered {/*** 初始化缓存*/public void initCache();/*** 刷新缓存*/public void refreshCache();/*** 获取缓存的名称** return 缓存名称*/public CacheNameDomain getCacheName();/*** 打印缓存信息*/public void dumpCache();/*** 获取缓存条数** return*/public long getCacheSize();
}e. 定义一个缓存处理器生命周期的处理器会声明CacheManager做第一次的处理也是所有处理器的父类所以这应该是一个抽象类名字为AbstractCacheManager
AbstractCacheManager.java
package com.summo.demo.cache;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;/*** description 缓存管理抽象类缓存管理器都要集成这个抽象类*/
public abstract class AbstractCacheManager implements CacheManager, InitializingBean {/*** LOGGER*/protected static final Logger LOGGER LoggerFactory.getLogger(AbstractCacheManager.class);/*** 获取可读性好的缓存信息用于日志打印操作** return 缓存信息*/protected abstract String getCacheInfo();/*** 查询数据仓库并加载到缓存数据*/protected abstract void loadingCache();/*** 查询缓存大小** return*/protected abstract long getSize();/*** see InitializingBean#afterPropertiesSet()*/Overridepublic void afterPropertiesSet() {CacheManagerRegistry.register(this);}Overridepublic void initCache() {String description getCacheName().getDescription();LOGGER.info(start init {}, description);loadingCache();afterInitCache();LOGGER.info({} end init, description);}Overridepublic void refreshCache() {String description getCacheName().getDescription();LOGGER.info(start refresh {}, description);loadingCache();afterRefreshCache();LOGGER.info({} end refresh, description);}/*** see org.springframework.core.Ordered#getOrder()*/Overridepublic int getOrder() {return getCacheName().getOrder();}Overridepublic void dumpCache() {String description getCacheName().getDescription();LOGGER.info(start print {} {}{}, description, \n, getCacheInfo());LOGGER.info({} end print, description);}/*** 获取缓存条目** return*/Overridepublic long getCacheSize() {LOGGER.info(Cache Size Count: {}, getSize());return getSize();}/*** 刷新之后其他业务处理比如监听器的注册*/protected void afterInitCache() {//有需要后续动作的缓存实现}/*** 刷新之后其他业务处理比如缓存变通通知*/protected void afterRefreshCache() {//有需要后续动作的缓存实现}
}f. 当有很多缓存处理器的时候那么需要一个统一注册、统一管理的的地方可以实现对分散在各处的缓存管理器统一维护名字为CacheManagerRegistry
CacheManagerRegistry.java
package com.summo.demo.cache;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.OrderComparator;
import org.springframework.stereotype.Component;import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;/*** description 缓存管理器集中注册接口可以实现对分散在各处的缓存管理器统一维护*/
Component
public final class CacheManagerRegistry implements InitializingBean {/*** LOGGER*/private static final Logger logger LoggerFactory.getLogger(CacheManagerRegistry.class);/*** 缓存管理器*/private static MapString, CacheManager managerMap new ConcurrentHashMapString, CacheManager();/*** 注册缓存管理器** param cacheManager 缓存管理器*/public static void register(CacheManager cacheManager) {String cacheName resolveCacheName(cacheManager.getCacheName().getName());managerMap.put(cacheName, cacheManager);}/*** 刷新特定的缓存** param cacheName 缓存名称*/public static void refreshCache(String cacheName) {CacheManager cacheManager managerMap.get(resolveCacheName(cacheName));if (cacheManager null) {logger.warn(cache manager is not exist,cacheName, cacheName);return;}cacheManager.refreshCache();cacheManager.dumpCache();}/*** 获取缓存总条数*/public static long getCacheSize(String cacheName) {CacheManager cacheManager managerMap.get(resolveCacheName(cacheName));if (cacheManager null) {logger.warn(cache manager is not exist,cacheName, cacheName);return 0;}return cacheManager.getCacheSize();}/*** 获取缓存列表** return 缓存列表*/public static ListString getCacheNameList() {ListString cacheNameList new ArrayList();managerMap.forEach((k, v) - {cacheNameList.add(k);});return cacheNameList;}public void startup() {try {deployCompletion();} catch (Exception e) {logger.error(Cache Component Init Fail:, e);// 系统启动时出现异常不希望启动应用throw new RuntimeException(启动加载失败, e);}}/*** 部署完成执行缓存初始化*/private void deployCompletion() {ListCacheManager managers new ArrayListCacheManager(managerMap.values());// 根据缓存级别进行排序以此顺序进行缓存的初始化Collections.sort(managers, new OrderComparator());// 打印系统启动日志logger.info(cache manager component extensions:);for (CacheManager cacheManager : managers) {String beanName cacheManager.getClass().getSimpleName();logger.info(cacheManager.getCacheName().getName(), , beanName);}// 初始化缓存for (CacheManager cacheManager : managers) {cacheManager.initCache();cacheManager.dumpCache();}}/*** 解析缓存名称大小写不敏感增强刷新的容错能力** param cacheName 缓存名称* return 转换大写的缓存名称*/private static String resolveCacheName(String cacheName) {return cacheName.toUpperCase();}Overridepublic void afterPropertiesSet() throws Exception {startup();}
}3、使用方式
项目结构如下 这是完整的项目结构图具体的使用步骤如下 step1、在CacheNameEnum中加一个业务枚举如 SYS_CONFIG(SYS_CONFIG, 系统配置缓存, Ordered.LOWEST_PRECEDENCE) step2、自定义一个CacheManager继承AbstractCacheManager如public class SysConfigCacheManager extends AbstractCacheManager step3、实现loadingCache()方法这里将你需要缓存的数据查询出来但注意不要将所有的数据都放在一个缓存处理器中前面CacheNameEnum枚举类的作用就是希望按业务分开处理 step4、在自定义的CacheManager类中写自己的查询数据方法因为不同业务的场景不同查询参数、数据大小、格式、类型都不一致所以AbstractCacheManager并没有定义统一的取数方法没有意义 下面是一个完整的例子 SysConfigCacheManager.java
package com.summo.demo.cache.manager;import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;import com.summo.demo.cache.AbstractCacheManager;
import com.summo.demo.cache.CacheMessageUtil;
import com.summo.demo.cache.CacheNameDomain;
import com.summo.demo.cache.CacheNameEnum;
import org.springframework.stereotype.Component;/*** 系统配置管理器*/
Component
public class SysConfigCacheManager extends AbstractCacheManager {/*** 加个锁防止出现并发问题*/private static final Lock LOCK new ReentrantLock();/*** 底层缓存组件可以使用ConcurrentMap也可以使用Redis推荐使用Redis*/private static ConcurrentMapString, Object CACHE;Overrideprotected String getCacheInfo() {return CacheMessageUtil.toString(CACHE);}Overrideprotected void loadingCache() {LOCK.lock();try {//存储数据这里就模拟一下了CACHE new ConcurrentHashMap();CACHE.put(key1, value1);CACHE.put(key2, value2);CACHE.put(key3, value3);} finally {LOCK.unlock();}}Overrideprotected long getSize() {return null CACHE ? 0 : CACHE.size();}Overridepublic CacheNameDomain getCacheName() {return CacheNameEnum.SYS_CONFIG;}/*** 自定义取数方法** param key* return*/public static Object getConfigByKey(String key) {return CACHE.get(key);}
}三、缓存层次化使用函数式编程实现
1、先举个例子 现有一个使用商品名称查询商品的需求要求先查询缓存查不到则去数据库查询从数据库查询到之后加入缓存再查询时继续先查询缓存。 1思路分析
可以写一个条件判断伪代码如下
//先从缓存中查询
String goodsInfoStr redis.get(goodsName);
if(StringUtils.isBlank(goodsInfoStr)){//如果缓存中查询为空则去数据库中查询Goods goods goodsMapper.queryByName(goodsName);//将查询到的数据存入缓存goodsName.set(goodsName,JSONObject.toJSONString(goods));//返回商品数据return goods;
}else{//将查询到的str转换为对象并返回return JSON.parseObject(goodsInfoStr, Goods.class);
}流程图如下 上面这串代码也可以实现查询效果看起来也不是很复杂但是这串代码是不可复用的只能用在这个场景。假设在我们的系统中还有很多类似上面商品查询的需求那么我们需要到处写这样的if(...)else{...}。作为一个程序员不能把类似的或者重复的代码统一起来是一件很难受的事情所以需要对这种场景的代码进行优化。 上面这串代码的问题在于入参不固定、返回值也不固定如果仅仅是参数不固定使用泛型即可。但最关键的是查询方法也是不固定的比如查询商品和查询用户肯定不是一个查询方法吧。 所以如果我们可以把一个方法(即上面的各种查询方法)也能当做一个参数传入一个统一的判断方法就好了类似于
/*** 这个方法的作用是先执行method1方法如果method1查询或执行不成功再执行method2方法*/
public staticT T selectCacheByTemplate(method1,method2)想要实现上面的这种效果就不得不提到Java8的新特性函数式编程
2、什么是函数式编程
在Java中有一个packagejava.util.function 里面全部是接口并且都被FunctionalInterface注解所修饰。
Function分类
Consumer消费接受参数无返回值Function函数接受参数有返回值Operator操作接受参数返回与参数同类型的值Predicate断言接受参数返回boolean类型Supplier供应无参数有返回值
具体我就不再赘述了可以参考https://blog.csdn.net/hua226/article/details/124409889
3、代码实现
核心代码非常简单如下
/*** 缓存查询模板** param cacheSelector 查询缓存的方法* param databaseSelector 数据库查询方法* return T*/
public static T T selectCacheByTemplate(SupplierT cacheSelector, SupplierT databaseSelector) {try {log.info(query data from redis ······);// 先查 Redis缓存T t cacheSelector.get();if (t null) {// 没有记录再查询数据库return databaseSelector.get();} else {return t;}} catch (Exception e) {// 缓存查询出错则去数据库查询log.info(query data from database ······);return databaseSelector.get();}
}这里的Supplier 就是一个加了FunctionalInterface注解的接口。 4、使用方式
使用方式也非常简单如下
Component
public class UserManager {Autowiredprivate CacheService cacheService;public SetString queryAuthByUserId(Long userId) {return BaseUtil.selectCacheByTemplate(//从缓存中查询() - this.cacheService.queryUserFromRedis(userId),//从数据库中查询() - this.cacheService.queryUserFromDB(userId));}
}这样就可以做到先查询Redis查询不到再查询数据库非常简单也非常好用我常用于查询一些实体信息的场景。不过这里有一个注意的点缓存一致性。因为有时候底层数据会变化需要做好一致性否则会出问题。 四、小结一下
首先缓存确实可以提高API查询效率这点大家应该不会质疑但缓存并不是万能的不应该将所有数据都缓存起来应当评估数据的访问频率和更新频率以决定是否缓存。 其次在实施缓存策略时需要平衡缓存的开销、复杂性和所带来的性能提升。此外缓存策略应该根据实际业务需求和数据特征进行定制不断调整优化以适应业务发展。 最后缓存虽好但不要乱用哦否则会出现令你惊喜的BUG