程序异常/失败处理的几种方式:failover、failback、failfast、failsafe

697 阅读2分钟

Failover:失败立即重试

failover是指:失败后立即重试。 适用于同步数据、查询第三方数据等业务上幂等的场景。

Failback:记录失败,后置处理

failback是指:失败后记录失败日志。 适用于对账、补偿等场景,常常和 failover 同时使用:失败了先记录下来用专门的服务去重试,或者就地重试,失败了再记录下来。

Failfast:快速失败,返回异常

failfast 是指基于当前的数据、状态等因素,判断出之后肯定会失败或异常的情况下,直接抛出异常。

举个例子,ArrayList 的 iterator() 方法的注释上就标明了:返回的迭代器是 failfast 的。 image.png 图 - ArrayList.iterator() 源码截图

来看看代码。 下面这段代码,先是构造一个 ArrayList,紧接着迭代 ArrrayList。 但是迭代过程中修改了 ArrayList,迭代器再继续迭代下去,肯定会出问题,那干脆直接抛出异常得了。

    public static void main(String[] args) {
        // 1.构造 ArrayList
        List<Long> idList = new ArrayList<>();
        idList.add(1L);
        idList.add(2L);
        idList.add(3L);
        
        // 2.迭代 ArrrayList
        Iterator<Long> iter = idList.iterator();
        while (iter.hasNext()) {
            Long id = iter.next();
            if (Objects.equals(id, 2L)) {
                idList.addAll(Arrays.asList(4L, 5L)); // Exception in thread "main" java.util.ConcurrentModificationException
            }
            System.out.println("id = " + id);
        }
    }

具体原理我们再来看看源码。 Itr 类就是 ArrayList 内部的迭代器。每次执行 next(),remove() 方法的时候,第一步就是调用 checkForComodification() 方法,校验当前迭代器的预期修改次数(expectedModCount属性),和外部类 ArrayList 的修改次数是否相等(modCount属性)。 如果不相等,就说明生成迭代器后 ArrayList 还有修改。 迭代器再继续迭代下去,肯定会出问题,那就采用了failfast 策略,直接抛异常。

    /**
     * An optimized version of AbstractList.Itr
     */
    private class Itr implements Iterator<E> {
        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
        int expectedModCount = modCount; // modCount 是外部类 ArrayList 维护的一个属性,表示集合本身的修改次数

        Itr() {}

        public boolean hasNext() {
            return cursor != size;
        }

        @SuppressWarnings("unchecked")
        public E next() {
            checkForComodification();
            // ......
        }

        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();
            // ......
        }

        final void checkForComodification() {
            if (modCount != expectedModCount) // expectedModCount 一旦和 modCount 不等,就说明生成迭代器后,集合本身有了修改,那接下来继续迭代,就可能会出问题,干脆直接抛出异常
                throw new ConcurrentModificationException();
        }
    }

Failsafe:忽略失败,继续流程

Failsafe是指如果有异常,不抛出异常,不中断程序,继续执行下去。在一些第三方调用场景、配置异常返回默认值的场景常常会用到。

举个例子。CopyOnWriteArrayList 是在并发环境下使用的工具类,一个线程在迭代,另一个线程往容器中添加元素的场景是存在且合理的。也就是说迭代的时候是允许往容器里面添加元素而不报错。 具体见这段代码,构造的是 CopyOnWriteArrayList,迭代的时候也往容器里面添加了元素。但是因为 CopyOnWriteArrayList 采用的是 failsafe 机制,所以不会抛异常,能正常进行迭代。

    public static void main(String[] args) {
        // 1.构造 CopyOnWriteArrayList
        List<Long> idList = new CopyOnWriteArrayList<>();
        idList.add(1L);
        idList.add(2L);
        idList.add(3L);
        
        // 2.迭代 CopyOnWriteArrayList
        Iterator<Long> iter = idList.iterator();
        while (iter.hasNext()) {
            Long id = iter.next();
            if (Objects.equals(id, 2L)) {
                idList.addAll(Arrays.asList(4L, 5L));
            }
            System.out.println("id = " + id);
        }
    }

总结

程序运行如果异常了,有四种处理方式(Failover、Failback、Failfast、Failsafe),需要根据具体的业务场景选择合适的异常处理方式,而不是无脑抛异常。