< Sergii Kostenko's blog

Wildfly domain mode cluster and load balancing from the box

15 April 2019

Wildfly Application Server provide us two possible modes to setup cluster environment for the Java EE applications.

  1. Standalone mode - every standalone instance has its own management interface and configuration. You can manage one instance at a time. Configuration placed in standalone.xml file.

  2. Domain mode - all Wildfly instances manage by special orchestration process called domain controller. Using domain controller you can manage group of servers. Also you can mange groups as well. Each servers group can has its own configuration, deployments etc. Configuration placed in domain.xml and host.xml files.
    Example of a Wildfly server groups:
    wildfly-cluster-domain-mode

From the version 10 Wildfly adds support for using the Undertow subsystem as a load balancer. So, now all we need to build Java EE clustered infrastructure is Wildfly only. Let's do it.

Download latest version of application server from the https://wildfly.org/downloads/ and unzip distributive after. To run Wildfly in domain mode, please execute:

kostenko@kostenko:/opt/wildfly-16.0.0.Final/bin$ ./domain.sh

Connect to the Wildfly CLI concole

kostenko@kostenko:/opt/wildfly-16.0.0.Final/bin$ ./jboss-cli.sh -c
[domain@localhost:9990 /]

By default Wildfly has preconfigured server groups main-server-group and other-server-group, so we need cleanup existing servers:

:stop-servers(blocking=true)
/host=master/server-config=server-one:remove
/host=master/server-config=server-two:remove
/host=master/server-config=server-three:remove
/server-group=main-server-group:remove
/server-group=other-server-group:remove

Create new server group and servers, using the full-ha profile so mod_cluster support is included:

/server-group=backend-servers:add(profile=full-ha, socket-binding-group=full-ha-sockets)
/host=master/server-config=backend1:add(group=backend-servers, socket-binding-port-offset=100)
/host=master/server-config=backend2:add(group=backend-servers, socket-binding-port-offset=200)

#start the backend servers
/server-group=backend-servers:start-servers(blocking=true)

#add system properties (so we can tell them apart)
/host=master/server-config=backend1/system-property=server.name:add(boot-time=false, value=backend1)
/host=master/server-config=backend2/system-property=server.name:add(boot-time=false, value=backend2)

Then set up the server group for load balancer

/server-group=load-balancer:add(profile=load-balancer, socket-binding-group=load-balancer-sockets)
/host=master/server-config=load-balancer:add(group=load-balancer)
/socket-binding-group=load-balancer-sockets/socket-binding=modcluster:write-attribute(name=interface, value=public)
/server-group=load-balancer:start-servers

Now let's develop simple JAX-RS endpoint to show how it works:

@Path("/clusterdemo")
@Stateless
public class ClusterDemoEndpoint {

    @GET
    @Path("/serverinfo")
    public Response getServerInfo() {

        return Response.ok().entity("Server: " + System.getProperty("server.name")).build();
    }
}

Build project and deploy it to the backend-servers group:

[domain@localhost:9990 /] deploy ee-jax-rs-examples.war --server-groups=backend-servers

And check the result by http://localhost:8080/ee-jax-rs-examples/clusterdemo/serverinfo :
wildfly-cluster-domain-mode-1-2

Now we can easily add servers to the group at runtime and requests will balancing automatically:

[domain@localhost:9990 /] /host=master/server-config=backend3:add(group=backend-servers, socket-binding-port-offset=300)
[domain@localhost:9990 /] /host=master/server-config=backend3/system-property=server.name:add(boot-time=false, value=backend3)
[domain@localhost:9990 /] /server-group=backend-servers/:start-servers(blocking=true)

wildfly-cluster-domain-mode-1-2-3

That is it!
Source code available on GitHub: Demo application, Wildlfly CLI

Comments


JBake blog pagination

10 April 2019

To enable pagintation support on your JBake based blog you need to provide next properties in your jbake.properties file:

index.paginate=true
index.posts_per_page=5

After that JBake will generate subdirectories 2,3,4... for your index.html. Next, you need to update index template to generate necessary count of posts per each index page and provide "previous","next" navigation buttons.

Below, I will show you freemarker template with pagination support, that I am using for this blog

<#include "header.ftl">
	<#include "menu.ftl">
	<#list posts as post>
  		<#if (post.status == "published"  && post?index >= (currentPageNumber-1) * config.index_posts_per_page?eval && post?index < currentPageNumber * config.index_posts_per_page?eval)>
				<a href="${post.uri}"><h1><#escape x as x?xml>${post.title}</#escape></h1></a>
				<p>${post.date?string("dd MMMM yyyy")}</p>
  			<p>${post.body}</p>
				<hr/>
  		</#if>
  	</#list>
		<ul class="pager">
			<#if (currentPageNumber > 1)><li class="previous"><a href="${config.site_host}/${(currentPageNumber==2)?then('', currentPageNumber-1)}">Previous</a></li></#if>
			<li>Page: ${currentPageNumber}/${numberOfPages} (<a href="${content.rootpath}${config.archive_file}">archive</a>)</li>
			<#if (currentPageNumber < numberOfPages)><li class="next"><a href="${config.site_host}/${currentPageNumber + 1}">Next</a></li></#if>
		</ul>
<#include "footer.ftl">

Blog sources available on GitHub

Comments


Java EE CDI events. Dynamic qualifier.

08 April 2019

Java EE provides us really nice mechanism for event processing. Which is part of the CDI for Java specification. Dynamic qualifier for CDI Events can be very useful, for example, in domain driven design, web socket messages routing or any other stuff depends on needs.

Firing simple event looks like:

@Named
public class MyEventSource {

    @Inject
    private Event<String> myEvent;

    public void fireEvent(){
        myEvent.fire("Hello World!");
    }
}

then event observer like:

@Named
public class MyEventObserver {
    public void observeEvent(@Observes String message){
        System.out.println(message);
    }
}

With CDI Qualifier (fancy annotation) you can specify which observer should serve the event

@Qualifier
@Retention(RUNTIME)
@Target({METHOD, FIELD, PARAMETER, TYPE})
public @interface Important {
}

For example:

@Named
public class MyEventSource {

    @Inject
    @Important
    private Event<String> myEvent;
    ...
@Named
public class MyEventObserver {
  public void observeEvent(@Observes @Important String message){
    ...

By default event will be fired in current transaction, but you can change this behavior with @Observes attribute during

@Named
public class TransactionEventObserver {
    public void observeImportantMessage(@Observes(during = TransactionPhase.AFTER_SUCCESS) String message){
        System.out.println(message);
    }
}

Available next values:

Now, let's take a look on dynamically qualification of CDI event. In the example below we will create observer to serve user events like login, logout, registration etc from the abstract event source. As was noticed earlier, event can be fired from different sources depends on your needs.

So, first we need to create Qualifier with available events values

@Qualifier
@Target({METHOD, FIELD, PARAMETER, TYPE})
@Retention(RUNTIME)
public @interface UserEvent {

    Routes value();
}

where Routes is enum with available values, for example:

public enum Routes {
  LOGIN,
  LOGOUT,
  REGISTRATION
}

Then we need to create child class of javax.enterprise.util.AnnotationLiteral to possibility of inline instantiation of annotation type instance.

public class UserEventBinding extends AnnotationLiteral<UserEvent> implements UserEvent {

    Routes routes;

    public UserEventBinding(Routes routes) {
        this.routes = routes;
    }

    @Override
    public Routes value() {
        return routes;
    }
}

Now, let's fire event with dynamically observer selection

@Named
public class UserEventSource {

    @Inject
    private Event<String> userEvent;

    public void fireEvent(Routes route){
        userEvent.select(new UserEventBinding(route)).fire("Instead of string you can use your object");
    }
}

So, time to show how our Observer looks like

import static Routes.*;
...

@Named
public class UserObserver {

    public void registration(@Observes @UserEvent(REGISTRATION) String eventData) {
      ....
    }

    public void login(@Observes @UserEvent(LOGIN) String eventData) {
      ....
    }

    public void logout(@Observes @UserEvent(LOGOUT) String eventData) {
      ....
    }
}

P.S. From Java EE 8 with CDI 2.0 you can use asynchronous CDI Events whith fireAsync method and @ObservesAsync annotation...

Comments


Wildfly large request processing

26 March 2019

By default Undertow subsystem on Wildfly AS configured to process requests with max-post-size= 10MB. So, in case your request larger than 10MB you will get

java.io.IOException: UT000020: Connection terminated as request was larger than 10485760

To increase this parameter you can edit directly standalone or domain configuration, like

<subsystem xmlns="urn:jboss:domain:undertow:3.1">
  <buffer-cache name="default"/>
  <server name="default-server">
    <http-listener name="default" socket-binding="http" max-post-size="15728640" redirect-socket="https" enable-http2="true"/>
    <https-listener name="https" socket-binding="https" max-post-size="15728640" security-realm="SSLRealm"/>
....

or use CLI commands as shown below:

/subsystem=undertow/server=default-server/http-listener=default/:write-attribute(name=max-post-size,value=15728640)
/subsystem=undertow/server=default-server/https-listener=https/:write-attribute(name=max-post-size,value=15728640)

Notice! If you are using Wildfly in domain mode with AJP load balancer, you also may need to change max-post-size for ajp-listener

/subsystem=undertow/server=default-server/ajp-listener=ajp/:write-attribute(name=max-post-size,value=15728640)

Comments


My Hibernate Search introduction video

20 March 2019

Have published my second YouTube video (about Hibernate Search and integration with Wildfly)!

Comments