细说在一次中间件开发中如何实现每次业务调用的唯一标识之流水号生成以及应用

在web系统中,谁也不能保证每一次的业务调用都能正常的执行,在这样的情况下,我们该如何去处理?

如果能保证每一次的业务调用的结果都持久化到数据库,而出现异常后,能根据数据库记录去检索日志文件,定位到异常信息,进而分析异常快速找到异常产生原因,我觉得这是一个很好的处理方案,核心是确保每一次义务调用都可追溯。

由于前段时间工作需要,写了一个消息推送的中间件,其中业务涉及到上述问题和解决方案,所以特意写写如何去解决。

0x01 为何

话说,在公司实习半年多,一直写业务,曾经多次遇到项目中需要调用短信api、app通知推送api,而在每个项目中都加入这些逻辑代码,会让人特别烦恼。并且,运营朋友经常性的过来询问:“我能不能看看某某号码的短信验证码”等等,而需要看到这样的信息有两种方法

一:找老板登录短信服务平台查看记录

二:通过在项目中集成数据库对记录持久化

第一种方法显得特别麻烦,而第二种却要增加特别多的代码并且使用到数据库。两种方法都不太令人满意,故而一直想找到一个好的方案。

0x02 刚刚好

半个月前突然接到pm的需要,要求实现一个集中管理的消息推送中间件。消息推送中间件实现了短信发送和app通知推送,调用记录持久化到数据库,中间件通过远程调用的方式提供给分布式环境中的每一个系统使用。

0x03 设计

在设计这个消息推送中间件时,任何设计都基于一个前提:
1. 每一次服务调用都可追溯

初步业务逻辑的设计如下:

在短信发送逻辑中捕抓异常(try),然后(catch)输出异常日志,在finally中把调用记录和结果持久化到数据库中。但如果服务器日志中信息比较多的情况下,查找异常日志将会成为非常耗时的工作。

因此,想到了银行业务系统中的方法,使用流水号机制,只要确保每一次业务调用中,流水号唯一即可,而流水号会随着异常日志输出作为日志的唯一标识,以及数据库记录的唯一标识,这样数据库记录和异常日志就有了一个关联的信息。

而经过改进的流程是这样的:

0x04 核心实现

如何实现每一次业务调用都唯一的流水号生成?这里巧妙的利用了数据库的主键自增生成原理,每一次启动中间件后的首次调用业务中往数据库插入一条记录,得到一个新的id:

private volatile Long startServerSequence;
if (null == startServerSequence) {
    synchronized (SerialNumberServiceImpl.class) {
        if (null == startServerSequence) {
            ServerStartTimeBean serverStartTimeBean = new ServerStartTimeBean();
            serverStartTimeBean.setDateTime(new Timestamp(System.currentTimeMillis()));
            Long sequence = serverStertTimeDao.insertAndReturnId(serverStartTimeBean);
            if (null == sequence) {
                throw new RuntimeException("服务器启动后首次请求生成流水号异常,请求数据库获取服务启动序列失败!");
            } else {
                startServerSequence = sequence;
            }
        }
    }
}

得到的id作为流水号的前缀,从而实现每一次中间件启动后的流水号唯一性。

然每次启动后需要确保每个业务调用都具有一个唯一性的流水号的话,则利用到了java中的原子类。

private AtomicLong currentProcessSequence = new AtomicLong(1000);

这个AtomicLong作为流水号的后缀与数据库自增生成的id组成一个唯一性的流水号,每一次业务的调用都执行AtomicLong的getAndIncrement方法,实现行AtomicLong的自增。

private static final String SEPARATOR = "_";
private volatile Long startServerSequence;
private AtomicLong currentProcessSequence = new AtomicLong(1000);

public String generateSerialNumber() throws Exception {
    if (null == startServerSequence) {
        synchronized (SerialNumberServiceImpl.class) {
            if (null == startServerSequence) {
                ServerStartTimeBean serverStartTimeBean = new ServerStartTimeBean();
                serverStartTimeBean.setDateTime(new Timestamp(System.currentTimeMillis()));
                Long sequence = serverStertTimeDao.insertAndReturnId(serverStartTimeBean);
                if (null == sequence) {
                    throw new RuntimeException("服务器启动后首次请求生成流水号异常,请求数据库获取服务启动序列失败!");
                } else {
                    startServerSequence = sequence;
                }
            }
        }
    }
    return startServerSequence+SEPARATOR+currentProcessSequence.incrementAndGet();
}

如此,我们就实现了每一次中间件的启动的每一次业务调用都有唯一的流水号,用于异常日志与数据库调用记录的关联,达到每一次业务都可追溯,每一个异常都能快速定位。

你可能感兴趣的:(Java开发)