Недавно я потратил некоторое время на создание набора тестов для обновления обновлений между версиями Neo4j, и как часть этого я хотел записать состояние кластера в процессе обновления.
Основной поток тестовых блоков ожидает завершения обновления, поэтому я хотел входить в другой поток каждые несколько секунд. Алистер указал мне на ScheduledExecutorService, который работал довольно хорошо.
Я закончил тестом, который выглядел примерно так:
public class MyUpgradeTest { @Test public void shouldUpgradeFromOneVersionToAnother() throws InterruptedException { ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); scheduledExecutorService.scheduleAtFixedRate( new LogAllTheThings(), 0, 1, TimeUnit.SECONDS ); Thread.sleep(10000); // do upgrade of cluster scheduledExecutorService.shutdown(); } static class LogAllTheThings implements Runnable { @Override public void run() { Date time = new Date( System.currentTimeMillis() ); try { Map<String, Object> masterProperties = selectedProperties( client(), URI.create( "http://localhost:7474/" ) ); System.out.println( String.format( "%s: %s", time, masterProperties ) ); } catch ( Exception ignored ) { ignored.printStackTrace(); } } private static Client client() { DefaultClientConfig defaultClientConfig = new DefaultClientConfig(); defaultClientConfig.getClasses().add( JacksonJsonProvider.class ); return Client.create( defaultClientConfig ); } public static Map<String, Object> selectedProperties( Client client, URI uri ) { Map<String, Object> jmxProperties = new HashMap<String, Object>(); ArrayNode transactionsProperties = jmxBean( client, uri, "org.neo4j/instance%3Dkernel%230%2Cname%3DTransactions" ); addProperty( jmxProperties, transactionsProperties, "LastCommittedTxId" ); ArrayNode kernelProperties = jmxBean( client, uri, "org.neo4j/instance%3Dkernel%230%2Cname%3DKernel" ); addProperty( jmxProperties, kernelProperties, "KernelVersion" ); ArrayNode haProperties = jmxBean( client, uri, "org.neo4j/instance%3Dkernel%230%2Cname%3DHigh+Availability" ); addProperty( jmxProperties, haProperties, "Role" ); addProperty( jmxProperties, haProperties, "InstanceId" ); return jmxProperties; } private static void addProperty( Map<String, Object> jmxProperties, ArrayNode properties, String propertyName ) { jmxProperties.put( propertyName, getProperty( properties, propertyName ) ); } private static String getProperty( ArrayNode properties, String propertyName ) { for ( JsonNode property : properties ) { if ( property.get( "name" ).asText().equals( propertyName ) ) { return property.get( "value" ).asText(); } } throw new RuntimeException( "Could not find requested property: " + propertyName ); } private static ArrayNode jmxBean( Client client, URI uri, String beanExtension ) { ClientResponse clientResponse = client .resource( uri + "db/manage/server/jmx/domain/" + beanExtension ) .accept( MediaType.APPLICATION_JSON ) .get( ClientResponse.class ); JsonNode transactionsBean = clientResponse.getEntity( JsonNode.class ); return (ArrayNode) transactionsBean.get( 0 ).get( "attributes" ); } } }
LogAllTheThings вызывается раз в секунду и регистрирует KernelVersion, InstanceId, LastCommittedTxId и Role, которые сервер Neo4j предоставляет в качестве свойств JMX.
Если мы запустим это с локальным кластером Neo4j, мы увидим что-то вроде следующего:
Sun Nov 17 22:31:55 GMT 2013: {KernelVersion=Neo4j - Graph Database Kernel 2.0.0-M06, InstanceId=1, LastCommittedTxId=18, Role=master} Sun Nov 17 22:31:56 GMT 2013: {KernelVersion=Neo4j - Graph Database Kernel 2.0.0-M06, InstanceId=1, LastCommittedTxId=18, Role=master} Sun Nov 17 22:31:57 GMT 2013: {KernelVersion=Neo4j - Graph Database Kernel 2.0.0-M06, InstanceId=1, LastCommittedTxId=18, Role=master} Sun Nov 17 22:31:58 GMT 2013: {KernelVersion=Neo4j - Graph Database Kernel 2.0.0-M06, InstanceId=1, LastCommittedTxId=18, Role=master} Sun Nov 17 22:31:59 GMT 2013: {KernelVersion=Neo4j - Graph Database Kernel 2.0.0-M06, InstanceId=1, LastCommittedTxId=18, Role=master} ... removed for brevity
Следующим шагом было получить свойства всех членов кластера одновременно и сделать это, мы можем представить еще один ExecutorService, который имеет пул потоков, равный 3, чтобы он мог оценивать каждую машину (по крайней мере, близко) одновременно:
static class LogAllTheThings implements Runnable { private ExecutorService executorService = Executors.newFixedThreadPool( 3 ); @Override public void run() { List<URI> machines = new ArrayList<>( ); machines.add(URI.create( "http://localhost:7474/" )); machines.add(URI.create( "http://localhost:7484/" )); machines.add(URI.create( "http://localhost:7494/" )); Map<URI, Future<Map<String, Object>>> futureJmxProperties = new HashMap<>( ); for ( final URI machine : machines ) { Future<Map<String, Object>> futureProperties = executorService.submit( new Callable<Map<String, Object>>() { @Override public Map<String, Object> call() throws Exception { try { return selectedProperties( client(), machine ); } catch ( Exception ignored ) { ignored.printStackTrace(); return new HashMap<>(); } } } ); futureJmxProperties.put( machine, futureProperties ); } Date time = new Date( System.currentTimeMillis() ); System.out.println( time ); for ( Map.Entry<URI, Future<Map<String, Object>>> uriFutureEntry : futureJmxProperties.entrySet() ) { try { System.out.println( "==> " + uriFutureEntry.getValue().get() ); } catch ( Exception ignored ) { } } } // other methods the same as above }
Мы отправляем каждую работу в ExecutorService и получаем обратно Будущее, которое мы сохраняем на карте, прежде чем получить его результат позже. Если мы запустим это, мы увидим следующий вывод:
Sun Nov 17 22:49:58 GMT 2013 ==> {KernelVersion=Neo4j - Graph Database Kernel 2.0.0-M06, InstanceId=1, LastCommittedTxId=18, Role=master} ==> {KernelVersion=Neo4j - Graph Database Kernel 2.0.0-M06, InstanceId=2, LastCommittedTxId=18, Role=slave} ==> {KernelVersion=Neo4j - Graph Database Kernel 2.0.0-M06, InstanceId=3, LastCommittedTxId=18, Role=slave} Sun Nov 17 22:49:59 GMT 2013 ==> {KernelVersion=Neo4j - Graph Database Kernel 2.0.0-M06, InstanceId=1, LastCommittedTxId=18, Role=master} ==> {KernelVersion=Neo4j - Graph Database Kernel 2.0.0-M06, InstanceId=2, LastCommittedTxId=18, Role=slave} ==> {KernelVersion=Neo4j - Graph Database Kernel 2.0.0-M06, InstanceId=3, LastCommittedTxId=18, Role=slave} Sun Nov 17 22:50:00 GMT 2013 ==> {KernelVersion=Neo4j - Graph Database Kernel 2.0.0-M06, InstanceId=1, LastCommittedTxId=18, Role=master} ==> {KernelVersion=Neo4j - Graph Database Kernel 2.0.0-M06, InstanceId=2, LastCommittedTxId=18, Role=slave} ==> {KernelVersion=Neo4j - Graph Database Kernel 2.0.0-M06, InstanceId=3, LastCommittedTxId=18, Role=slave} ... removed for brevity
Overall the approach works quite well although I’m always open to learning of a better way if there is one!