日期:2014-05-16  浏览次数:21064 次

apache oro使用注意细节(并发问题)

背景

? 距离上一篇文章已经有4个多月了,这4个多月一直在忙着做一个数据库同步产品的代码研发和测试,现在基本运行稳定。 本文主要介绍一下,当时使用apache oro包进行正则过滤时,使用时出现的一个并发问题,排查了好几天才找到原因。希望大家使用时引以为戒,望周知。

?

过程

简单的描述下,我使用apache oro的场景: 进行数据库同步时,我们会根据定义的表名进行匹配,从binlog的数据流中提取出我们关心的表,然后进行解析,压缩,传输,写入目标库等一系列动作。?

然而在线下测试环境中,冒出一个比较异常的情况,数据没有被正常的同步到目标库(概率发生的比较小,每次jvm重启后才可能出现),一节一节的往上查,最后定位是正则匹配时出的问题。

?

原因

?

出问题的代码: (这是当时出问题的代码,犯了多个错误)

?

public class RegexFunction extends AbstractFunction {

    private Map<String, Pattern>  patterns = null;
    private final PatternCompiler pc       = new Perl5Compiler();

    public RegexFunction(){
        patterns = new MapMaker().softValues().makeComputingMap(new Function<String, Pattern>() {

            public Pattern apply(String pattern) {
                try {
                    return pc.compile(pattern, Perl5Compiler.CASE_INSENSITIVE_MASK);
                } catch (MalformedPatternException e) {
                    throw new CanalSinkException(e);
                }
            }
        });
    }

    public AviatorObject call(Map<String, Object> env, AviatorObject arg1, AviatorObject arg2) {
        String pattern = FunctionUtils.getStringValue(arg1, env);
        String text = FunctionUtils.getStringValue(arg2, env);
        Perl5Matcher matcher = new Perl5Matcher();
        boolean isMatch = matcher.matches(text, patterns.get(pattern));
        return AviatorBoolean.valueOf(isMatch);
    }

    public String getName() {
        return "regex";
    }

}

说明:

?

  1. 使用了aviator做为表达式引擎(?http://code.google.com/p/aviator/),扩展了一个regex表达式语法的支持。(默认是自带了jdk pattern的正则)
  2. 使用google collection,将pattern进行lazy-init处理,多线程共享。(map中没有对应pattern,就进行compiler)

错误1: ?

1. ?Perl5Compiler是有状态的,会在compiler过程中产生一些上下文状态。 (之前先入为主的认为类似于Compiler基本都是单例使用,线程安全,基本我自己写代码和命名是这个习惯)

?

对应的Perl5Compiler的javadoc: ?(已经比较明确的指出,Perl5Compiler和Perl5Matcher是非线程安全的)


?

我做了个测试:
public class MutliAviaterFilterTest {

    @Test
    public void test_simple() {
        int count = 5;
        ExecutorService executor = Executors.newFixedThreadPool(count);

        final CountDownLatch countDown = new CountDownLatch(count);
        final AtomicInteger successed = new AtomicInteger(0);
        for (int i = 0; i < count; i++) {
            executor.submit(new Runnable() {

                public void run() {
                    try {
                        for (int i = 0; i < 100; i++) {
                            doRegexTest();
                            // try {
                            // Thread.sleep(10);
                            // } catch (InterruptedException e) {
                            // }
                        }

                        successed.incrementAndGet();
                    } finally {
                        countDown.countDown();
                    }
                }
            });
        }

        try {
            countDown.await();
        } catch (InterruptedException e) {
        }

        Assert.assertEquals(count, successed.get());
        executor.shutdownNow();
    }

    private void doRegexTest() {
        AviaterRegexFilter filter3 = new AviaterRegexFilter("otter2.otter_stability1|otter1.otter_stability1|"
                                                            + RandomStringUtils.randomAlphabetic(200));
        boolean result = filter3.filter("otter1.otter_stability1");
        Assert.assertEquals(true, result);
        result = filter3.filter("otter2.otter_stability1");
        Assert.assertEqua