目录
- 设计原因
- 架构流程图
- 架构分析
- 设计模式
- ApiFactory
- AbstractApiTemplate
- 二、问题记录
- 缓存
- 线程池
- Redis
- threadlocal
- rabbitMQ
- docker
- 三、总结
设计原因
前几个月,公司有一个新项目的对外api接口需求,我在里面扮演api的架构设计。起初,拿到需求并不急于编写代码,而是从软件工程的设计角度出发,满足可扩展和开闭原则,设计了这个api的接口。
架构流程图
架构分析
设计模式
使用设计模式开发软件,能够让我们写出的类更能见名知其意,比如SQLSessionFactory是创建SQL会话的工厂类,APiFactory类是创建api对象的工厂类。同时,设计模式能够实现高内聚和低耦合。
但是,我们不能为了设计模式而写将代码模式化,主要是灵活运用设计模式,往往在代码架构中,不止使用一个设计模式,多种设计模式相互关联。
ApiFactory
这是创建型简单工厂设计模式,这种设计模式简单易用,但是耦合度比较大,如果有心的接口api需求过来,那么就要修改APIFactory内部的结构。
该类有一个重要原子性的属性noInstance
,默认是false。仿照 J.U.C报下的CAS模式,创建名为compareAndSetState
方法签名, 该方法内的逻辑是,如果工厂类已创建,将该属性置位true。
@Component
public class ApiFactory {
private volatile static ApiFactory instance = null;
private static volatile boolean noInstance = false;
@Bean
public ApiFactory ApiFactory() {
return ApiFactory.getInstance();
}
public static ApiFactory getInstance() {
if (noInstance) {
instance = new ApiFactory();
compareAndSetState();
if (noInstance) {
return instance;
}
instance = new ApiFactory();
}
return instance;
}
private static void compareAndSetState() {
if (null != instance) {
noInstance = true;
}
}
public AbstractApiTemplate createApi(HttpServletRequest request, PoolStrategy poolStrategy) {
if (poolStrategy.equals(POOL_STRATEGY_QUERY_USER_STATUS)) {
return new UserStatusApi(request);
} else if (poolStrategy.equals(POOL_STRATEGY_QUERY_ORDER_STATUS)) {
return new TaskOrderApi(request);
} else if (poolStrategy.equals(POOL_STRATEGY_REMIT)) {
return new RemitApi(request);
} else if (poolStrategy.equals(POOL_STRATEGY_UPLOAD)) {
return new UploadApi(request);
} else if (poolStrategy.equals(POOL_STRATEGY_AUTH)) {
return new AuthApi(request);
} else if (poolStrategy.equals(POOL_STRATEGY_SIGN)) {
return new SignApi(request);
}
return null;
}
}
使用无锁状态下实现单例创建工厂,并使用@bean返回api工厂对象。
AbstractApiTemplate
该模板是是实现各个api接口的父类,在父类中做统一的服务管理:
- 入参校验:采用MD5进行数据加签。使用ThereadLocal存储入参,
protected ThreadLocal<JSONObject> paramsCache = new ThreadLocal<>();
,因为改参数只在当前线程有效。使用完毕后及时调用paramsCache.remove();
方法, 以免内存泄漏。 - IP校验。当spring服务启动后,即从数据库加载IP并存储到
public static ConcurrentHashMap<String, CopyOnWriteArrayList<String>> ipMapping = new ConcurrentHashMap();
如果用户服务器请求的IP不在ipMapping
当中,此时去数据库中查询是否配置了该公司商户的IP,,如果配置了【缓存击穿】,并写入到ipMapping
之中,否则【缓存穿透】,返回调用者IP不存在的异常。 - 限流:使用将对应的接口以key的形式存储到Redis中,如果value值大于指定的数值,则返回调用者
通道繁忙,稍后重试
,并设置失效时间。 - 日志记录
- 接口超时时间设置
比如配置IP的代码如下:
private JSONObject configIp(String... ignores) {
JSONObject jsonObject = checkBlank(ignores);
if (!SUCCESS.getCode().equals(jsonObject.getString(RET_CODE))) {
return jsonObject;
}
DaoRegister register = DaoRegister.getInstance();
if (ipMapping.size() < 1) {
register.init(request);
}
String maskCode = paramsCache.get().getString(API_MASK_CODE);
String ipAddress = getIpAddr(request);
if (!ipMapping.containsKey(maskCode)) {
jsonObject.replace(RET_CODE, IP_NOT_REPORT.getCode());
jsonObject.replace(RET_MSG, IP_NOT_REPORT.getMsg());
return jsonObject;
}
CopyOnWriteArrayList<String> coaList = ipMapping.get(maskCode);
for (String ip : coaList) {
if (ipAddress.equals(ip)) {
return jsonObject;
}
}
// 如果不包含,再刷新数据库,是否缓存造成
boolean refresh = register.getIpDao(request).refresh(ipAddress, maskCode);
if (refresh) {
jsonObject.replace(RET_CODE, IP_NOT_REPORT.getCode());
jsonObject.replace(RET_MSG, IP_NOT_REPORT.getMsg());
return jsonObject;
}
coaList.add(ipAddress);
ipMapping.replace(maskCode, coaList);
return jsonObject;
}
二、问题记录
缓存
线程池
Redis
threadlocal
rabbitMQ
docker
三、总结