支付系统设计:元数据管理设计二
创始人
2024-06-02 05:57:41

文章目录

  • 前言
  • 一、扩展starter
    • 1. 定义缓存管理接口
    • 2. 接口实现
    • 3. HandlerFactory.regist
    • 4. 定义BeanPostProcessor
  • 二、去除MQ装配Servlet
    • 1. 编写CommonCacheRefreshServlet
    • 2. 编写CommonCacheHttpConfiguration
    • 3. 配置spring.factories
  • 总结


前言

在上篇中(支付系统设计:元数据管理设计一),我们的应用对元数据管理已经演进到这步了,接下来我们再接着满足使用方需求。

  1. 使用方系统如何将自身特有元数据也融入托管到这套缓存管理中;
  2. 使用方系统不想为了处理缓存而添加MQ依赖,并且担心万一有节点消息丢失了,多节点本地缓存不一致了!同时依旧还是什么都不想做;

在这里插入图片描述
本篇将就这两个问题展开,继续我们的元数据管理设计。


一、扩展starter

如果使用方系统如何将自身特有元数据也融入托管到这套缓存管理中,那么如何操作?假如支付路由系统自身的特有的路由规则、黑白名单、成本规则等元数据也想托管到我们这套进程内缓存管理中去,该如何对我们提供的paycommon-starter进行扩展?提供一套管理策略了,使用方系统再自己搞一套单独管理自己特有的元数据,那么我们提供的starter是失败的。

下面就讲述怎么对starter进行扩展。

1. 定义缓存管理接口

使用方系统定义自身缓存管理接口:

/*** @author Kkk* @Description: 缓存管理*/
public interface CacheManager {void init();Object get(String key);void remove(String key);void clear();void refresh();
}

2. 接口实现

以成本规则管理为例:

/*** @author Kkk* @Description: 成本规则管理*/
@Component
public class CostRuleCacheManager implements CacheManager {private static final Logger logger = LoggerFactory.getLogger(CostRuleCacheManager.class);private List costRuleList;/*** 成本规则的仓库依赖*/@Autowiredprivate CostRuleRepository costRuleRepository;/*** 规则条件的仓库依赖*/@Autowiredprivate RuleConditionRepository ruleConditionRepository;/*** 成本规则的缓存:key:交易编码 、value:该交易编码对应的成本规则*/private static ConcurrentHashMap> costRuleEntities = new ConcurrentHashMap<>();@Overridepublic void init() {LoggerUtil.info(logger, "成本规则缓存加载-开始");List costRulesTemp = costRuleRepository.findAllByStatus(RuleStatusEnum.ENABLE.code());if (!CollectionUtils.isEmpty(costRulesTemp)) {LoggerUtil.info(logger, "成本规则缓存加载总条数{}", costRulesTemp.size());}costRuleList = costRulesTemp;costRuleEntities = initCostRuleMap(costRulesTemp);LoggerUtil.info(logger, "成本规则缓存加载-结束-记录数({})", costRuleList.size());}private ConcurrentHashMap> initCostRuleMap(List costRulesTemp) {Set keys;ConcurrentHashMap> maps = new ConcurrentHashMap<>();keys = costRulesTemp.stream().map(costRule -> costRule.getInstCode() + costRule.getInstTransId()).collect(Collectors.toCollection(() -> new HashSet<>(costRulesTemp.size())));keys.forEach(key -> {List costRuleList = new ArrayList<>();for (CostRule costRule : costRulesTemp) {String costRuleKey = costRule.getInstCode() + costRule.getInstTransId();if (StringUtils.equals(key, costRuleKey)) {costRuleList.add(costRule);}}maps.put(key, costRuleList);});return maps;}@Overridepublic Object get(String key) {return costRuleEntities.get(key);}@Overridepublic void remove(String key) {}@Overridepublic void clear() {costRuleList.clear();costRuleEntities.clear();}@Overridepublic void refresh() {init();}
}

3. HandlerFactory.regist

将我们定义的缓存管理注册到PayCommonStarterAckHandlerFactory中:

/*** @author Kkk* @Description: 成本缓存刷新*/
@Component
public class CostRuleCacheRefreshHandler implements PayCommonStarterFanoutAckHandler {private Logger logger = LoggerFactory.getLogger(CostRuleCacheRefreshHandler.class);@Autowiredprivate CostRuleCacheManager costRuleCacheManager;public CostRuleCacheRefreshHandler() {PayCommonStarterAckHandlerFactory.regist(CacheIdEnum.COST_RULE_CACHE.getCode(), this);}@Overridepublic boolean handle(MessageEntity messageEntity) {try {costRuleCacheManager.init();return true;} catch (Exception e) {LoggerUtil.error(logger, "刷新成本规则缓存异常", e);}return false;}
}

4. 定义BeanPostProcessor

定义BeanPostProcessor处理我们的缓存管理类,BeanPostProcessor这个不懂…找个坑把自己埋了吧。

/*** @author Kkk* @Description: Bean 后置处理器触发init()*/
@Component
public class CacheManagerPostProcessor implements BeanPostProcessor {private static final Logger logger = LoggerFactory.getLogger(CacheManagerPostProcessor.class);@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {return bean;}@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {//缓存初始化if (bean instanceof CacheManager) {LoggerUtil.info(logger, "缓存初始化:{}", bean.getClass().getName());CacheManager cacheManager = (CacheManager) bean;cacheManager.init();}return bean;}
}

从上面四个步骤我们可以看出,元数据首次加载触发是由业务系统自己触发,然后将缓存后期的刷新过程托管到我们的starter管理了。使用进程内缓存最头疼的是多节点缓存刷新问题,给业务方解决了最头疼的事了。

二、去除MQ装配Servlet

有的使用方系统不想为了处理缓存而添加MQ依赖,并且担心万一有节点消息丢失了,多节点本地缓存不一致了!同时依旧还是什么都不想做!这怎么搞?这个也是很现实的问题,仅仅为了处理缓存刷新问题,使得自身系统依赖了MQ,虽然非运行时依赖,但是依旧不想搞。

这里要着重解释下运行时依赖问题:这也是系统设计中很重的一个问题,回想下我们写的系统接口虽然很多,但是接口可以归类为两类:运行时接口、后台配置接口,

运行时接口:运行时接口也就是我们系统对外提供的线上业务服务接口,在支付系统中可以简单理解为下单、退款这类接口,这类接口应该最小依赖化,能不依赖中间件就不要依赖。不然有中间件挂了,整个系统也就挂了。

后台配置接口:后台配置接口也就是提供后后台管理系统运营进行一些东西的配置管理,这类接口压力相对较小,出错容忍度相对运行时接口较高,这类接口多依赖点中间件什么的出问题不至于太大。

在这次元数据缓存管理中,使用MQ进行多节点刷新,依赖MQ属于后台配置接口的依赖。

撑死胆大的饿死胆小的,接着分析处理问题,首先为什么觉得不靠谱?因为异步,并且有没有正确执行也没给个响应,运营人员变更过信息后点了下缓存刷新按钮,页面显示刷新成功,这个成功也仅仅代表发布刷新任务到MQ成功,各个节点有没有正常消费并正确刷新对应的本地缓存,那就不知道了,所以心里没底。

经过上面简单分析,发现问题所在:异步没个响应。解决方式也就对应两种:同步给个响应。那么那种解决方案简单呢?没思路哪个都不简单。

这里给出第一种解决方案:同步,那么就系统间直接调用。那么系统架构转变成如下:
在这里插入图片描述

假如paycore应用不想使用MQ,本来系统没有使用MQ,不想再依赖MQ,那么paycommon进行元数据变更后刷缓存操作将是直接调用paycore。那么新问题又来了,该如何调用paycore的各个节点呢?老问题没解决,又来了新问题…

我们先来解决老问题,使用方系统不想为了处理缓存而添加MQ依赖,并且担心万一有节点消息丢失了,多节点本地缓存不一致了!同时依旧还是什么都不想做!先解决什么都不想做问题。这时候要变更starter赋予更强的装配能力了,装配Servlet,

1. 编写CommonCacheRefreshServlet

/*** @author Kkk* @Description: 缓存刷新Servlet*/
public class CommonCacheRefreshServlet extends HttpServlet {private Logger logger = LoggerFactory.getLogger(CommonCacheRefreshServlet.class);@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {MessageEntity respMessageEntity = new MessageEntity();Header header = new Header();resp.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);resp.setCharacterEncoding(Charsets.UTF_8.name());PrintWriter out = resp.getWriter();try {req.setCharacterEncoding(Charsets.UTF_8.name());BufferedReader br = new BufferedReader(new InputStreamReader(req.getInputStream()));String line = null;StringBuilder jsonStrBuilder = new StringBuilder();while((line = br.readLine())!=null){jsonStrBuilder.append(line);}MessageEntity messageEntity =JSONObject.parseObject(jsonStrBuilder.toString() , MessageEntity.class);String transCode = messageEntity.getHeader().getTransCode();logger.info("收到PAYCOMMON 的http刷缓存消息({})", transCode);PayCommonStarterFanoutAckHandler payFanoutAckHandler = PayCommonStarterAckHandlerFactory.getPayFanoutAckHandler(transCode);if (payFanoutAckHandler != null) {boolean isSuccess = payFanoutAckHandler.handle(messageEntity);if (isSuccess){header.setSuccess(true);header.setErrorCode(SystemErrorCode.SUCCESS.getCode());header.setErrorMsg("刷新成功");}else {header.setSuccess(false);header.setErrorCode(SystemErrorCode.SYSTEM_ERROR.getCode());header.setErrorMsg(SystemErrorCode.SYSTEM_ERROR.getMessage());}} else {header.setSuccess(false);header.setErrorCode(SystemErrorCode.TRANS_CODE_UNDEFINED.getCode());header.setErrorMsg(SystemErrorCode.TRANS_CODE_UNDEFINED.getMessage());logger.warn("http缓存刷新-没有找到监听处理器({})", transCode);}} catch (Exception e) {header.setSuccess(false);header.setErrorCode(SystemErrorCode.SYSTEM_ERROR.getCode());header.setErrorMsg(SystemErrorCode.SYSTEM_ERROR.getMessage());logger.error("http缓存刷新-处理异常", e);}finally {respMessageEntity.setHeader(header);out.write(JSON.toJSONString(respMessageEntity));out.close();}}
}

2. 编写CommonCacheHttpConfiguration

编写自动配置类:

/*** @author Kkk* @Description: 缓存刷新Servlet配置类*/
@Configuration
public class CommonCacheHttpConfiguration {private static final String SUFFIX="/refresh/cache";@Bean(name = "commonCacheServlet")public ServletRegistrationBean commonCacheServlet() {ServletRegistrationBean sevlet = new ServletRegistrationBean();sevlet.setServlet(new CommonCacheRefreshServlet());//刷新缓存接口sevlet.addUrlMappings(SUFFIX + "/*");return sevlet;}
}

3. 配置spring.factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=
com.msxf.paycommon.starter.config.PaycommonDemoConfiguration,
com.msxf.paycommon.starter.config.PayMetaDataInitConfiguration,
com.msxf.paycommon.starter.config.CommonCacheHttpConfiguration

完成如上三步,使用方应用系统即具备了处理http请求刷新缓存的能力了,相当于给业务系统写了个Controller,如果有一个节点刷新失败则响应页面操作刷新失败,当所有节点都刷新成功才为成功。

下面我们解决上面引发的新问题,如何调用同一个应用的各个节点呢?这里就需要服务发现机制了,在Spring当道的今天…有要开始吹Spring的节奏,还是打住了…

如果使用传统微服务的话,我们会有注册中心,可以根据服务名称获取到提供服务的服务列表,这样我们就可以逐个发起调用了。


总结

经过如上两篇的讲解,有人可能会疑惑,只是个数据管理有必要搞那么麻烦么?费时费力,又非业务核心功能,如果有这么想法的话,洗洗睡吧。做支付系统可以有这种设计对系统元数据管理,做其他大型系统整个系统设计思想完全可以复用,代码也是可以完全复用的,将此作为一个场景解决方案参考。就像我们的路由系统,关于路由系统的设计见:支付路由系统设计,表面是个路由系统其实也是个风控系统。

拙技蒙斧正,不胜雀跃。

相关内容

热门资讯

苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
阿西吧是什么意思 阿西吧相当于... 即使你没有受到过任何外语培训,你也懂四国语言。汉语:你好英语:Shit韩语:阿西吧(아,씨발! )日...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...