Apache Igniteの春のブートスターターを自分でやる







分散Apache Igniteプラットフォームの非常に長いシリーズのレビューで、2つの記事が既に公開されています( 1つ目はセットアップと実行に関するもの、 2つ目はトポロジの構築に関するもの)。 この記事は、Apache IgniteとSpring Bootで友達を作ろうとすることについてです。 ライブラリをSpring Bootに接続する標準的な方法は、このテクノロジーの「スターター」を作成することです。 Spring Bootは非常に人気があり、Habréで何度も説明されているという事実にもかかわらず、スターターの作り方についてはまだ書かれていないようです。 この迷惑なギャップを埋めようとします。



この記事は主にSpring BootとSpring Coreに焦点を当てているため、Apache Igniteテーマに興味のない人でも新しいことを学ぶことができます。 このコードは、 スターターおよびデモアプリケーションであるGitHubに投稿されています



Spring Bootはそれと何の関係がありますか?



ご存知のように、Spring Bootは非常に便利なものです。 その多くの快適な機能の中で、その特性は、いくつかのMaven依存関係を接続して小さなアプリケーションを強力なソフトウェア製品に変えることによって特に価値があります。 このため、Spring Bootでは、スターターメカニズムが責任を負います。 アイデアは、接続するとアプリケーションの基本的な動作を構成するデフォルト構成を設計および実装できるということです。 これらの構成は適応性があり、ユーザーの意図について想定することができます。 したがって、Spring Bootは適切なアーキテクチャのアイデアをアプリケーションに提供し、これらのクラスまたはクラスをクラスパスに配置するか、プロパティファイルで設定を指定することによって提供された情報から演ductive的に導き出していると言えます。 教科書の例では、Spring Bootは、わずか数行のアプリケーションコードで組み込みTomcatで実行されているWebアプリケーションを通じて「Hello World!」を表示します。 すべてのデフォルト設定はオーバーライドでき、極端な場合は、Spring Bootがなかったような状況になります。 技術的には、スターターは意味のあるデフォルト値を提供しながら、すべてが注入されることを保証する必要があります。



シリーズ最初の記事では、 Igniteオブジェクトを作成および使用する方法について説明しました。 これはそれほど難しくありませんが、もっと簡単にしたいと思います。 たとえば、この構文を使用するには:



@IgniteResource(gridName = "test", clientMode = true) private Ignite igniteClient;
      
      





次に、Apache Igniteのスターターについて説明します。これには、適切なIgniteアプリケーションの最も単純なビジョンが含まれています。 この例は純粋に実証的なものであり、ベストプラクティスを反映しているわけではありません。



スターターを作る



スターターを作成する前に、それが何であるか、それが接続するテクノロジーの提案された使用のシナリオが何であるかを把握する必要があります。 以前の記事から、Apache Igniteがクライアントおよびサーバータイプのノードからトポロジを作成する機能を提供し、それらがスプリングコアスタイルのxml構成を使用して説明されていることを知っています。 また、クライアントがサーバーに接続してタスクを実行できることもわかっています。 ジョブのサーバーは、いくつかの基準に従って選択できます。 開発者は私たちにベストプラクティスの説明を提供しなかったため、この最も単純なケースでは、この方法でそれを定式化します。アプリケーションには、gridName値を持つサーバーに負荷を送信するクライアントが少なくとも1つ必要です。

この一般的なアイデアに基づいて、スターターはアプリケーションが最小構成でクラッシュしないようにあらゆることを試みます。 これは、アプリケーションプログラマーの観点からすると、 Ignite型のオブジェクトを取得し、そのオブジェクトに対して何らかの操作を実行する必要があるという事実に要約されます。



まず、スターターのフレームを作成します。 まず、Mavenの依存関係を接続しましょう。 最初の近似では、これで十分です:



主な依存関係
  <properties> <java.version>1.8</java.version> <spring-boot.version>1.4.0.RELEASE</spring-boot.version> <spring.version>4.3.2.RELEASE</spring.version> <ignite.version>1.7.0</ignite.version> </properties> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.4.0.RELEASE</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.apache.ignite</groupId> <artifactId>ignite-core</artifactId> <version>${ignite.version}</version> </dependency> <dependency> <groupId>org.apache.ignite</groupId> <artifactId>ignite-spring</artifactId> <version>${ignite.version}</version> </dependency> </dependencies>
      
      







ここで、IgniteとSpringの主要な依存関係である基本的なスターターspring-boot-starter-parentを接続します。 アプリケーション接続がスターターを接続するとき、彼はこれを既に行う必要はありません。 次のステップは、@ IgniteResourceアノテーションが、プログラマーの関与なしに、Ignite型のオブジェクトを正しく挿入し、デフォルトをオーバーライドする可能性を持たせることです。 注釈自体は非常に簡単です。



 @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Autowired public @interface IgniteResource { String gridName(); boolean clientMode() default true; boolean peerClassLoadingEnabled() default true; String localAddress() default ""; String ipDiscoveryRange() default ""; boolean createIfNotExists() default true; }
      
      





プロパティがそれに応じて設定されたIgniteオブジェクトは、この方法で注釈が付けられた変数に挿入されることが予想されます。 すべての構成が検索され、適切なものがあれば、それに基づいてIgniteが作成されます;そうでない場合、createIfNotExists()設定が考慮され、デフォルト値と転送された値に基づいてIgniteが作成されます。 これをどのように達成しますか? ビンのインスタンス化のプロセスでは、注釈のパラメーターを考慮する必要があります。 Springでは、 ConfigurableListableBeanFactoryタイプのオブジェクトがこのプロセスを担当します。具体的には、Spring BootではDefaultListableBeanFactoryです。 当然、このクラスはIgniteについて何も知りません。 Ignite構成は、Spring構成であるxml構成として保存されることを思い出します。 または、IgniteConfiguration型のオブジェクトを作成して、手動で作成することもできます。 したがって、Springをトレーニングして正しく注入する必要があります。 BeanFactoryはアプリケーションコンテキストによって作成されるため、独自に作成する必要があります。



 public class IgniteApplicationContext extends AnnotationConfigApplicationContext { public IgniteApplicationContext() { super(new IgniteBeanFactory()); } }
      
      





コンテキストはAnnotationConfigApplicationContextから継承されますが、Spring BootはWebアプリケーションに異なるクラスを使用します。 ここではこのケースを検討していません。 したがって、Ignite-Spring Bootアプリケーションは次のコンテキストを使用する必要があります。



  public static void main(String[] args) { SpringApplication app = new SpringApplication(DemoIgniteApplication.class); app.setApplicationContextClass(IgniteApplicationContext.class); app.run(args); }
      
      





次に、BeanFactoryを構成する必要があります。 ただし、最初に春の心の安らぎを守る必要があります。 Springはバカではありません。Springは賢く、@ AutowiredがあればBeanが必要だということを知っています。 したがって、自動構成をスターターに追加します。



 @Configuration @ConditionalOnClass(name = "org.apache.ignite.Ignite") public class IgniteAutoConfiguration { @Bean public Ignite ignite() { return null; } }
      
      





これはorg.apache.ignite.Igniteクラスでロードされ、誰かがIgniteオブジェクトを返す方法を知っているふりをします。 実際、ここからは何も返されません。これは、@ IgniteResourceアノテーションで指定された構成パラメーターが表示されないためです。 自動構成接続は、META-INFに配置されているspring.factories構成によって提供されます 。詳細は、 Spring Bootのドキュメントを参照してください。 BeanFactoryに戻ってこれを行います。



 public class IgniteBeanFactory extends DefaultListableBeanFactory { private IgniteSpringBootConfiguration configuration; @Override public Object resolveDependency(DependencyDescriptor descriptor, String beanName, Set<String> autowiredBeanNames, TypeConverter typeConverter) throws BeansException { if (descriptor == null || descriptor.getField() == null || !descriptor.getField().getType().equals(Ignite.class)) return super.resolveDependency(descriptor, beanName, autowiredBeanNames, typeConverter); else { if (configuration == null) configuration = new IgniteSpringBootConfiguration( createBean(DefaultIgniteProperties.class)); return configuration.getIgnite( descriptor.getField().getAnnotationsByType(IgniteResource.class)); } }
      
      





つまり、タイプIgniteのオブジェクトを要求された場合、以下で説明するIgniteSpringBootConfigurationの実行を委任し、そうでない場合はすべてをそのままにします。 IgniteSpringBootConfigurationで、フィールドにハングしたIgniteResource注釈を渡します。 このもつれの解明を続け、どのようなIgniteSpringBootConfigurationであるかを確認します。



IgniteSpringBootConfiguration、パート1
 public class IgniteSpringBootConfiguration { private Map<String, List<IgniteHolder>> igniteMap = new HashMap<>(); private boolean initialized = false; private DefaultIgniteProperties props; IgniteSpringBootConfiguration(DefaultIgniteProperties props) { this.props = props; } private static final class IgniteHolder { IgniteHolder(IgniteConfiguration config, Ignite ignite) { this.config = config; this.ignite = ignite; } IgniteHolder(IgniteConfiguration config) { this(config, null); } IgniteConfiguration config; Ignite ignite; }
      
      







ここでは、プロパティクラスを参照し、Igniteデータを格納するための構造を定義します。 次に、DefaultIgnitePropertiesは「タイプセーフな構成プロパティ」メカニズムを使用します。このメカニズムについては説明せず、マニュアルも参照しません。 ただし、その下にメインのデフォルト値が定義されている設定があることが重要です。



 ignite.configuration.default.configPath=classpath:ignite/**/*.xml ignite.configuration.default.gridName=testGrid ignite.configuration.default.clientMode=true ignite.configuration.default.peerClassLoadingEnabled=true ignite.configuration.default.localAddress=localhost ignite.configuration.default.ipDiscoveryRange=127.0.0.1:47500..47509 ignite.configuration.default.useSameServerNames=true
      
      





これらのオプションは、アプリケーションでオーバーライドされる場合があります。 それらの最初はIgnite xml構成を探す場所を示し、残りはプロファイルが見つからなかった場合に使用する構成プロパティを決定し、新しい構成を作成する必要があります。 次に、IgniteSpringBootConfigurationクラスで、構成を探します。



IgniteSpringBootConfiguration、パート2
  List<IgniteConfiguration> igniteConfigurations = new ArrayList<>(); igniteConfigurations.addAll(context.getBeansOfType(IgniteConfiguration.class).values()); PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); try { Resource[] igniteResources = resolver.getResources(props.getConfigPath()); List<String> igniteResourcesPaths = new ArrayList<>(); for (Resource igniteXml : igniteResources) igniteResourcesPaths.add(igniteXml.getFile().getPath()); FileSystemXmlApplicationContext xmlContext = new FileSystemXmlApplicationContext (igniteResourcesPaths.stream().toArray(String[]::new)); igniteConfigurations.addAll(xmlContext.getBeansOfType(IgniteConfiguration.class).values());
      
      







まず、アプリケーションで既に知られているIgniteConfigurationタイプのビンを探し、次に設定で指定されたパスに従って構成を探し、それらを見つけた後、それらからビンを作成します。 構成Beanをキャッシュに追加します。 次に、ビンのリクエストが届くと、このキャッシュでgridNameという名前のIgniteConfigurationを探し、見つかった場合は、この設定に基づいてIgniteオブジェクトを作成して保存し、2回目のリクエストの後に返せるようにします。 目的の構成が見つからない場合は、設定に基づいて新しい構成を作成します。



IgniteSpringBootConfiguration、パート3
  public Ignite getIgnite(IgniteResource[] igniteProps) { if (!initialized) { initIgnition(); initialized = true; } String gridName = igniteProps == null || igniteProps.length == 0 ? null : igniteProps[0].gridName(); IgniteResource gridResource = igniteProps == null || igniteProps.length == 0 ? null : igniteProps[0]; List<IgniteHolder> configs = igniteMap.get(gridName); Ignite ignite; if (configs == null) { IgniteConfiguration defaultIgnite = getDefaultIgniteConfig(gridResource); ignite = Ignition.start(defaultIgnite); List<IgniteHolder> holderList = new ArrayList<>(); holderList.add(new IgniteHolder(defaultIgnite, ignite)); igniteMap.put(gridName, holderList); } else { IgniteHolder igniteHolder = configs.get(0); if (igniteHolder.ignite == null) { igniteHolder.ignite = Ignition.start(igniteHolder.config); } ignite = igniteHolder.ignite; } return ignite; } private IgniteConfiguration getDefaultIgniteConfig(IgniteResource gridResource) { IgniteConfiguration igniteConfiguration = new IgniteConfiguration(); igniteConfiguration.setGridName(getGridName(gridResource)); igniteConfiguration.setClientMode(getClientMode(gridResource)); igniteConfiguration.setPeerClassLoadingEnabled(getPeerClassLoadingEnabled(gridResource)); TcpDiscoverySpi tcpDiscoverySpi = new TcpDiscoverySpi(); TcpDiscoveryMulticastIpFinder ipFinder = new TcpDiscoveryMulticastIpFinder(); ipFinder.setAddresses(Collections.singletonList(getIpDiscoveryRange(gridResource))); tcpDiscoverySpi.setIpFinder(ipFinder); tcpDiscoverySpi.setLocalAddress(getLocalAddress(gridResource)); igniteConfiguration.setDiscoverySpi(tcpDiscoverySpi); TcpCommunicationSpi communicationSpi = new TcpCommunicationSpi(); communicationSpi.setLocalAddress(props.getLocalAddress()); igniteConfiguration.setCommunicationSpi(communicationSpi); return igniteConfiguration; } private String getGridName(IgniteResource gridResource) { return gridResource == null ? props.getGridName() : ifNullOrEmpty(gridResource.gridName(), props.getGridName()); } private boolean getClientMode(IgniteResource gridResource) { return gridResource == null ? props.isClientMode() : gridResource.clientMode(); } private boolean getPeerClassLoadingEnabled(IgniteResource gridResource) { return gridResource == null ? props.isPeerClassLoadingEnabled() : gridResource.peerClassLoadingEnabled(); } private String getIpDiscoveryRange(IgniteResource gridResource) { return gridResource == null ? props.getGridName() : ifNullOrEmpty(gridResource.ipDiscoveryRange(), props.getIpDiscoveryRange()); } private String getLocalAddress(IgniteResource gridResource) { return gridResource == null ? props.getGridName() : ifNullOrEmpty(gridResource.localAddress(), props.getLocalAddress()); } private String ifNullOrEmpty(String value, String defaultValue) { return StringUtils.isEmpty(value) ? defaultValue : value; }
      
      







次に、標準のIgniteの動作を変更して、タスクを分散するサーバーを選択します。これは、すべてのサーバーに負荷が分散されるという事実に基づいています。 クライアントと同じgridNameを持つサーバーをデフォルトで選択するとします。 前の記事では、これを通常の方法で行う方法について説明しました。 ここで少し変質させ、cglibを使用して結果のIgniteオブジェクトに指示します。 これにはひどいものは何もないことに注意してください。Springは自分でやっています。



IgniteSpringBootConfiguration、パート4
  if (props.isUseSameServerNames()) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(Ignite.class); enhancer.setCallback(new IgniteHandler(ignite)); ignite = (Ignite) enhancer.create(); } return ignite; } private class IgniteHandler implements InvocationHandler { private Ignite ignite; IgniteHandler(Ignite ignite) { this.ignite = ignite; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return method.getName().equals("compute") ? ignite.compute(ignite.cluster() .forAttribute(ATTR_GRID_NAME, ignite.configuration().getGridName()) .forServers()) : method.invoke(ignite, args); } }
      
      







これで、設定に従ってIgniteビンが発行されました。 Spring Bootアプリケーションでは、次のようにIgniteを呼び出すことができます。

  @Bean public CommandLineRunner runIgnite() { return new CommandLineRunner() { @IgniteResource(gridName = "test", clientMode = true) private Ignite igniteClient; public void run(String... args) throws Exception { igniteClient.compute().broadcast(() -> System.out.println("Hello World!")); igniteClient.close(); } }; }
      
      





JUnitテストでは、@ IgniteResourceは機能しません。これは演習として残されています。



結論



Apache Igniteの最も単純なスターターが作成されました。これにより、クライアントコードを大幅に簡素化して、Ignite固有のほとんどを削除することができます。 さらに、このスターターを変更し、より便利な設定を行い、より適切なデフォルトを提供できます。 さらなる開発として、たとえば、 私の記事で説明されいるように IgniteをActivitiのL2キャッシュとしてIgniteに固定させるなど、多くのことができます。



参照資料






All Articles