強制キャッシュ:L2キャッシュApache IgniteをActivitiに固定する

良いライブラリが存在することはよくありますが、そこには何か、真珠のようなボタンがありません。 そこで、 Activitiを使用しました。これは、Javaのネイティブ性に価値のある、BPMN 2.0をサポートするかなり人気のあるビジネスプロセスエンジンです。 このオープンソース製品の内部構造の詳細に触れることなく、その作業において、ビジネスプロセス定義のメタデータ、インスタンスデータ、履歴データなどのさまざまなデータを使用していることは明らかです。 ActivitiはDBMSを使用してそれらを保存し、DB2、H2、Oracle、MySQL、MS SQL、PostgreSQLから選択できるようにします。 このエンジンは非常に優れており、小さな工芸品だけでなく使用されます。 おそらく、この製品でデータベース呼び出しのキャッシュをサポートするという疑問が生じたのは、私だけではありません。 少なくとも一度、彼メタデータがキャッシュされているという意味で彼に答えた開発者に尋ねましたが、残りのデータについてはあまり意味がなく、簡単ではありません。 原則として、多くの意味がないことに同意することができます-特定のインスタンスのデータまたは小さな確率でその履歴データを再利用できます。 しかし、これがまだ発生するシナリオも可能です。 たとえば、共通ベースを持つActivitiサーバーのクラスターがある場合。 一般に、探究心のある人は、Activitiにまともなキャッシュを持ちたいと思うでしょう。 たとえば、これとしてApache Igniteを使用します。



この問題を解決する例であるkatの下で、コードはGitHubに投稿されています



タスク思考



これには何がありますか? まず第一に、開発者が保証するプロセス定義キャッシュ。java.util.HashMapに格納され、エンタープライズソリューションとは呼ばれません。 データベースにアクセスするために、Activitiは、もちろんキャッシュをサポートするMybatisライブラリを使用します。 その機能のために、Mybatisはxml-configurationsを使用し、Activitiにはこれらのxmlが多くあり、おおよそ次のタイプのクエリ定義が含まれています。



<select id="selectJob" parameterType="string" resultMap="jobResultMap"> select * from ${prefix}ACT_RU_JOB where ID_ = #{id, jdbcType=VARCHAR} </select>
      
      





以下のリンクは、Apache IgniteとMybatisをクロスさせる方法に関するhabrostatを示しています。 それから、タグuseCache = "true"selectタグに設定されていて、キャッシュタイプが指定されていることが明らかになります...



 <cache type="org.mybatis.caches.ignite.IgniteCacheAdapter" />
      
      





...それでほぼ十分でしょう。 そこにはマイクロライブラリorg.mybatis.cachesも示されています。mybatis-igniteには、正確に2つのクラスがあり、具体的にはMybatisがありません。 つまり、完全に一般的なソリューションです。



ActivitiはGitHubに常駐し、非選択的にフォークでき、Mybatisの構成を変更してキャッシュを楽しむことができますが、この方法はお勧めしません。 これは、ナンセンスな変更を加えるために作成された、かなり大きなプロジェクトの独自バージョンを維持する運命にあります。 しかし、ActivitiはSpring Bootをサポートしており、これにより新しい視点が開かれます。 実験では、執筆時点での最後の1つは、Activitiバージョン6.0の4番目のベータ版でした。



解決策



MybatisのSQLクエリは、org.apache.ibatis.mapping.MappedStatementクラスによって記述されます。このクラスには、 isUseCacheメソッドがあります。 MappedStatementオブジェクトは、org.apache.ibatis.session.Configurationクラスによって返されます。このクラスにはgetMappedStatementメソッドがあります。 また、構成はクラスorg.activiti.spring.SpringProcessEngineConfigurationで作成され、Spring Bootの自動構成中に挿入されます。 したがって、MappedStatementクラスによって返される結果に何らかの影響を与える必要があります。 残念ながら、これを行う簡単な方法はありません。また、cglibライブラリを使用してすべてを指示する方法よりも良い方法は見つかりませんでした。 アルゴリズムは簡単に次のようになります:SpringProcessEngineConfigurationオブジェクトのSpring Boot自動構成を再定義します。これは、Activitiのアクティブ化を制御し、オブジェクトをインストルメント済みバージョンに置き換えます。インストルメント済みコンフィギュレーションオブジェクトを返します。キャッシュを使用すべきだと考える人。 はい、新しいConfigurationオブジェクトはApache Igniteの存在を認識しています。 複雑に聞こえるかもしれませんが、実際にはすべてが透過的です(念のため、cglibガイドリンクが添付されています)。



最終的なコードは次のようになります
 @Configuration @ConditionalOnClass(name = "javax.persistence.EntityManagerFactory") @EnableConfigurationProperties(ActivitiProperties.class) public class CachedJpaConfiguration extends JpaProcessEngineAutoConfiguration.JpaConfiguration { @Bean @ConditionalOnMissingBean public SpringProcessEngineConfiguration springProcessEngineConfiguration( DataSource dataSource, EntityManagerFactory entityManagerFactory, PlatformTransactionManager transactionManager, SpringAsyncExecutor springAsyncExecutor) throws IOException { return getCachedConfig(super.springProcessEngineConfiguration (dataSource, entityManagerFactory, transactionManager, springAsyncExecutor)); } private SpringProcessEngineConfiguration getCachedConfig(final SpringProcessEngineConfiguration parentConfig) { Enhancer enhancer = new Enhancer(); CallbackHelper callbackHelper = new CallbackHelper(SpringProcessEngineConfiguration.class, new Class[0]) { @Override protected Object getCallback(Method method) { if (method.getName().equals("initMybatisConfiguration")) { return (MethodInterceptor) (obj, method1, args, proxy) -> getCachedConfiguration( (org.apache.ibatis.session.Configuration) proxy.invokeSuper(obj, args)); } else { return NoOp.INSTANCE; } } }; enhancer.setSuperclass(SpringProcessEngineConfiguration.class); enhancer.setCallbackFilter(callbackHelper); enhancer.setCallbacks(callbackHelper.getCallbacks()); SpringProcessEngineConfiguration result = (SpringProcessEngineConfiguration) enhancer.create(); result.setDataSource(parentConfig.getDataSource()); result.setTransactionManager(parentConfig.getTransactionManager()); result.setDatabaseSchemaUpdate("create-drop"); return result; } private org.apache.ibatis.session.Configuration getCachedConfiguration(org.apache.ibatis.session.Configuration configuration) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(org.apache.ibatis.session.Configuration.class); enhancer.setCallback(new CachedConfigurationHandler(configuration)); return (org.apache.ibatis.session.Configuration) enhancer.create(); } private class CachedConfigurationHandler implements InvocationHandler { private org.apache.ibatis.session.Configuration configuration; CachedConfigurationHandler(org.apache.ibatis.session.Configuration configuration) { this.configuration = configuration; this.configuration.addCache(IgniteCacheAdapter.INSTANCE); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object originalResult = method.invoke(configuration, args); if (method.getName().equals("getMappedStatement")) { return getCachedMappedStatement((MappedStatement) originalResult); } return originalResult; } } private MappedStatement getCachedMappedStatement(MappedStatement mappedStatement) { return new MappedStatement .Builder(mappedStatement.getConfiguration(), mappedStatement.getId(), mappedStatement.getSqlSource(), mappedStatement.getSqlCommandType()) .databaseId(mappedStatement.getDatabaseId()) .resource(mappedStatement.getResource()) .fetchSize(mappedStatement.getFetchSize()) .timeout(mappedStatement.getTimeout()) .statementType(mappedStatement.getStatementType()) .resultSetType(mappedStatement.getResultSetType()) .parameterMap(mappedStatement.getParameterMap()) .resultMaps(mappedStatement.getResultMaps()) .cache(IgniteCacheAdapter.INSTANCE) .useCache(true) .build(); } }
      
      







次の行に注意してください。



 result.setDatabaseSchemaUpdate("create-drop");
      
      





ここでは、Activitiテーブルの自動作成を提供しています。 実稼働環境ではこれを行わないでください。



次に、Igniteを接続する必要があります。 ここでは、インストールと構成については説明しません。バージョンは1.7.0を使用しました。 私が使用した最も単純なバージョンでは、ダウンロードして解凍するのは非常に簡単です。 IgniteはSpringアプリケーションであるため、XMLを使用してアプリケーションで構成する方法とJavaコードの2つの方法があります。 2番目のオプションを選択しました。



JavaでのIgniteの最も単純な構成
  IgniteConfiguration igniteCfg = new IgniteConfiguration(); igniteCfg.setGridName("testGrid"); igniteCfg.setClientMode(true); igniteCfg.setIgniteHome("<IGNITE_HOME>"); CacheConfiguration config = new CacheConfiguration(); config.setName("myBatisCache"); config.setCacheMode(CacheMode.LOCAL); config.setStatisticsEnabled(true); config.setWriteSynchronizationMode(CacheWriteSynchronizationMode.FULL_SYNC); igniteCfg.setCacheConfiguration(config); TcpDiscoverySpi tcpDiscoverySpi = new TcpDiscoverySpi(); TcpDiscoveryJdbcIpFinder jdbcIpFinder = new TcpDiscoveryJdbcIpFinder(); jdbcIpFinder.setDataSource(dataSource); tcpDiscoverySpi.setIpFinder(jdbcIpFinder); tcpDiscoverySpi.setLocalAddress("localhost"); igniteCfg.setDiscoverySpi(tcpDiscoverySpi); TcpCommunicationSpi tcpCommunicationSpi = new TcpCommunicationSpi(); tcpCommunicationSpi.setLocalAddress("localhost"); igniteCfg.setCommunicationSpi(tcpCommunicationSpi);
      
      







この構成が存在するIgniteCacheAdapterクラスは、org.mybatis.cachesライブラリーからのクラスの単純化された最大バージョン:mybatis-igniteに基づいています。 それだけです、リクエストはキャッシュされます。 Igniteランタイムへの指定されたパスに注意してください。ここでは、独自のパスに置き換える必要があります。



結果



ガイド[2]で説明されているRESTサービス呼び出しを使用してアプリケーションをテストできます。履歴書を確認するための簡単なビジネスプロセスがあります。 数回実行すると、config.setStatisticsEnabled(true)コマンドで収集が有効になった統計を表示できます。



 Ignition.ignite("testGrid").getOrCreateCache("myBatisCache").metrics();
      
      





デバッグでは、これらのメトリック、特にキャッシュからの読み取り数とミス数を確認できます。 プロセスの2回の開始後、16回の読み取りと16回のミス。 つまり、キャッシュにヒットすることはありません。



結論



具体的には、検討した例では、判明したとおり、L2キャッシュは不要です。 しかし、これは非常に単純であり、示唆的な例ではありません。 おそらく、より複雑なトポロジで、負荷の性質が異なる場合、複数のユーザーでは状況が異なります。 彼らが言うように、我々は検索します...



また、この記事では、大規模なライブラリーの動作が大幅に変更されたため、大規模なライブラリーでの干渉があまり粗くないことを示しました。



参照資料






All Articles