15 April 2019
Wildfly Application Server provide us two possible modes to setup cluster environment for the Java EE applications.
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.
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:
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
:
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)
That is it!
Source code available on GitHub: Demo application, Wildlfly CLI
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
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...
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)
20 March 2019
Have published my second YouTube video (about Hibernate Search and integration with Wildfly)!