8

データベースの読み取りと書き込みを含むビジネス ロジックを並列化できるように、マルチスレッド ソリューションを実装しようとしています。

テクノロジースタック: Spring 4.0.2、Hibernate 4.3.8

議論するコードは次のとおりです。

構成

@Configuration
public class PartitionersConfig {

    @Bean
    public ForkJoinPoolFactoryBean forkJoinPoolFactoryBean() {
        final ForkJoinPoolFactoryBean poolFactory = new ForkJoinPoolFactoryBean();
        return poolFactory;
    }
}

サービス

@Service
@Transactional
public class MyService {

    @Autowired
    private OtherService otherService;

    @Autowired
    private ForkJoinPool forkJoinPool;

    @Autowired
    private MyDao myDao;

    public void performPartitionedActionOnIds() {
        final ArrayList<UUID> ids = otherService.getIds();

        MyIdPartitioner task = new MyIdsPartitioner(ids, myDao, 0, ids.size() - 1);
        forkJoinPool.invoke(task);
    }
}

リポジトリ/DAO

@Repository
@Transactional(propagation = Propagation.MANDATORY)
public class IdsDao {

    public MyData getData(List<UUID> list) {
        // ... 
    }
}

再帰アクション

public class MyIdsPartitioner extends RecursiveAction {

    private static final long serialVersionUID = 1L;
    private static final int THRESHOLD = 100;

    private ArrayList<UUID> ids;
    private int fromIndex;
    private int toIndex;

    private MyDao myDao;

    public MyIdsPartitioner(ArrayList<UUID> ids, MyDao myDao, int fromIndex, int toIndex) {
        this.ids = ids;
        this.fromIndex = fromIndex;
        this.toIndex = toIndex;
        this.myDao = myDao;
    }

    @Override
    protected void compute() {
        if (computationSetIsSamllEnough()) {
            computeDirectly();
        } else {
            int leftToIndex = fromIndex + (toIndex - fromIndex) / 2;
            MyIdsPartitioner leftPartitioner = new MyIdsPartitioner(ids, myDao, fromIndex, leftToIndex);
            MyIdsPartitioner rightPartitioner = new MyIdsPartitioner(ids, myDao, leftToIndex + 1, toIndex);

            invokeAll(leftPartitioner, rightPartitioner);
        }
    }

    private boolean computationSetIsSamllEnough() {
        return (toIndex - fromIndex) < THRESHOLD;
    }

    private void computeDirectly() {
        final List<UUID> subList = ids.subList(fromIndex, toIndex);
        final MyData myData = myDao.getData(sublist);
        modifyTheData(myData);
    }

    private void modifyTheData(MyData myData) {
        // ...
        // write to DB
    }
}

これを実行すると、次のようになります。

伝播「必須」でマークされたトランザクションの既存のトランザクションが見つかりません

トランザクションは異なるスレッドを介して伝播しないため、これは完全に正常であることを理解しました。したがって、1 つの解決策は、別の同様の質問で提案されているように、すべてのスレッドでトランザクションを手動で作成することです。しかし、これは私にとって十分に満足できるものではなかったので、探し続けました。

Spring のフォーラムで、トピックに関するディスカッションを見つけました。私が非常に興味深いと思うパラグラフ:

「トランザクションコンテキストを手動で別のスレッドに伝播できると想像できますが、実際に試してみるべきではないと思います.トランザクションは、理由で単一のスレッドにバインドされています-基本的な基礎となるリソース-jdbc接続-はスレッドセーフではありません.複数のスレッドでの単一の接続は、基本的な jdbc 要求/応答契約を破り、些細な例で機能するかどうかは少し不思議です."

したがって、最初の疑問が生じます。データベースへの読み取り/書き込みを並列化する価値はありますか?これは DB の一貫性を本当に損なう可能性がありますか?
上記の引用が当てはまらない場合、私は疑問に思いますが、次のことを達成する方法はあります か?トランザクション管理はSpringに任せますか?

4

2 に答える 2

1

さらに読んだ後、私は自分の問題を解決することができました。のようなものです(私が今見ているように、そもそも問題はありませんでした)。

DBからの読み取りはチャンクで行われるため、その間は結果が編集されないことが確実であるため、トランザクション外で実行できます。

私が書くすべての値は一意であり、制約違反は発生しないため、私の場合も書き込みは安全です。そこで、そこからもトランザクションを削除しました。

「トランザクションを削除しました」と言う意味は、DAO でメソッドの伝播モードを次のようにオーバーライドするだけです。

@Repository
@Transactional(propagation = Propagation.MANDATORY)
public class IdsDao {

    @Transactional(propagation = Propagation.SUPPORTS)
    public MyData getData(List<UUID> list) {
        // ... 
    }
}

または、何らかの理由でトランザクションが必要であると判断した場合でも、伝播を に設定することにより、トランザクション管理を Spring に任せることができますREQUIRED

したがって、解決策は私が思っていたよりもはるかに簡単であることがわかりました。

そして、私の他の質問に答えるために:

データベースへの読み取り/書き込みを並列化する価値はありますか?これは DB の一貫性を本当に損なう可能性がありますか?

はい、それだけの価値があります。そして、スレッドごとにトランザクションがある限り、あなたはクールです。

次のことを達成する方法はありますか: MyIdPartitioner を @Scope("prototype") を使用して Spring で管理し、再帰呼び出しに必要な引数を渡して、トランザクション管理を Spring に任せますか?

はい、プールを使用する方法があります(別のスタックオーバーフローの質問)。または、Bean を次のように定義することもできます@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)が、インスタンスを使用するたびに新しいインスタンスが作成されるため、パラメーターを設定する必要がある場合は機能しません。元。

@Autowire
MyIdsPartitioner partitioner;

public void someMethod() {
    ...
    partitioner.setIds(someIds);
    partitioner.setFromIndex(fromIndex);
    partitioner.setToIndex(toIndex);
    ...
}

これにより 3 つのインスタンスが作成され、フィールドが設定されないため、有益なオブジェクトを使用できなくなります。

要するに、方法はありますが、最初からその方法を選択する必要はありませんでした。

于 2015-08-05T10:46:28.537 に答える
0

これは、atomikos ( http://www.atomikos.com ) と、オプションでネストされたトランザクションで可能です。

これを行う場合は、同じルート トランザクションの複数のスレッドがデータベース内の同じテーブルに書き込む場合にデッドロックが発生しないように注意してください。

于 2015-08-01T08:13:28.640 に答える