Статьи

Java: запланировать выполнение задания на интервале времени

Недавно я потратил некоторое время на создание набора тестов для обновления обновлений между   версиями 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!