Example: Creating a Simple DXP Cluster
A fast, easy way to learn DXP clustering is to set up a two node DXP cluster environment on one machine using Docker containers. This example creates two DXP server containers along with server containers for a database, search engine, and file store.
Server Type | Implementation | Container Name |
---|---|---|
Database | MariaDB | some-mariadb |
File Store | DBStore | some-mariadb |
Search Engine | Elasticsearch | elasticsearch |
DXP Server | Tomcat | dxp-1 |
DXP Server | Tomcat | dxp-2 |
This example is for learning purposes and is not suitable for production use cases. For production environments, you should include an HTTP server for load balancing requests to the DXP servers, use separate database servers for read only and read-write operations, and consider clustering and load balancing database servers, file store servers, and search engine servers. See Clustering for High Availability for more information.
Here are the main steps:
- Start a Database Server
- Start a File Store Server
- Start a Search Engine Server
- Configure the Search Engine for Each Node
- Start the DXP Cluster
- Test the DXP Cluster
Start a Database Server
A DXP cluster requires a data source that’s accessible to all of the DXP cluster nodes. The data source can be a JNDI data source or a direct connection to a database server or a database server cluster. See the compatibility matrix for the database servers your DXP version supports.
Create the database server and DXP database:
-
Start a MariaDB Docker container.
docker run --name some-mariadb -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mariadb:10.2
-
In a shell on the container, create the DXP database.
Sign in to the database server.
docker exec -it some-mariadb bash -c "/usr/bin/mysql -uroot -pmy-secret-pw"
Create a database for DXP.
create database dxp_db default character set utf8mb4 collate utf8mb4_unicode_ci;
End your database session.
quit
End your bash session.
exit
Your database server is ready for DXP. See Database Configuration for Cluster Nodes for more information.
Start a File Store Server
A DXP cluster requires a File Store accessible to all of the DXP cluster nodes. For convenience, this example uses a DBStore File Store configured on the DXP database. The database server already started in this example includes the File Store. See Configuring File Storage for information on configuring all File Store types.
Start a Search Engine Server
A DXP cluster requires a search engine (running as a separate process) accessible to all of the DXP cluster nodes. See Installing a Search Engine for more information.
Create and configure an Elasticsearch server:
-
Set a local folder for storing an Elasticsearch server’s data volume. For example,
mkdir -p elasticsearch/es_data_volume
-
Start an Elasticsearch container named
elasticsearch
.docker run -it --name elasticsearch -p 9200:9200 -p 9300:9300 -e cluster.name=LiferayElasticsearchCluster -e ES_JAVA_OPTS="-Xms512m -Xmx512m" -v $(pwd)/elasticsearch/es_data_volume:/var/lib/elasticsearch/data elasticsearch:6.8.7
NoteIf the container reports
max virtual memory areas vm.max_map_count [xxxxx] is too low, increase to at least [xxxxxx]
, then setvm.max_map_count
to a sufficient value using a command like this one:sudo sysctl -w vm.max_map_count=[xxxxxx]
. Then start the container. -
Install the required Elasticsearch plugins.
docker exec -it elasticsearch bash -c '/usr/share/elasticsearch/bin/elasticsearch-plugin install analysis-icu && /usr/share/elasticsearch/bin/elasticsearch-plugin install analysis-kuromoji && /usr/share/elasticsearch/bin/elasticsearch-plugin install analysis-smartcn && /usr/share/elasticsearch/bin/elasticsearch-plugin install analysis-stempel'
Your search engine is ready to manage search indexes.
Configure the Search Engine Server For Each Node
Use Configuration Files to configure Elasticsearch for each DXP node:
-
Create the Configuration Files locations.
mkdir -p dxp-1/files/osgi/configs dxp-2/files/osgi/configs
-
Configure Elasticsearch for the
dxp-1
server node.cat <<EOT >> dxp-1/files/osgi/configs/com.liferay.portal.search.elasticsearch6.configuration.ElasticsearchConfiguration.config operationMode="REMOTE" transportAddresses="elasticsearch:9300" clusterName="LiferayElasticsearchCluster" EOT
-
Configure Elasticsearch for the
dxp-2
server node.cat <<EOT >> dxp-2/files/osgi/configs/com.liferay.portal.search.elasticsearch6.configuration.ElasticsearchConfiguration.config operationMode="REMOTE" transportAddresses="elasticsearch:9300" clusterName="LiferayElasticsearchCluster" EOT
You’ll make these configuration files accessible to the cluster nodes via bind mounts on the DXP server containers.
The docker run --add-host elasticsearch:[ip] ...
commands used later for the DXP servers add /etc/hosts/
entries that map the name elasticsearch
to the Elasticsearch server host IP address.
Start the DXP Cluster
-
Get the container IP addresses for the
elasticsearch
andsome-mariadb
containers by executing thedocker network inspect bridge
command. Thebridge
network is the default network.ImportantIn the
docker run
commands that follow, replace[IP address]
with theelasticsearch
andsome-mariadb
container IP addresses. -
Start
dxp-1
.Command expanded for readability:
docker run -it \ --add-host elasticsearch:[IP address] \ --add-host some-mariadb:[IP address] \ -e LIFERAY_JDBC_PERIOD_DEFAULT_PERIOD_JNDI_PERIOD_NAME="" \ -e LIFERAY_JDBC_PERIOD_DEFAULT_PERIOD_DRIVER_UPPERCASEC_LASS_UPPERCASEN_AME=org.mariadb.jdbc.Driver \ -e LIFERAY_JDBC_PERIOD_DEFAULT_PERIOD_URL="jdbc:mariadb://some-mariadb:3306/dxp_db?useUnicode=true&characterEncoding=UTF-8&useFastDateParsing=false" \ -e LIFERAY_JDBC_PERIOD_DEFAULT_PERIOD_USERNAME=root \ -e LIFERAY_JDBC_PERIOD_DEFAULT_PERIOD_PASSWORD=my-secret-pw \ -e LIFERAY_CLUSTER_PERIOD_LINK_PERIOD_ENABLED=true \ -e LIFERAY_CLUSTER_PERIOD_LINK_PERIOD_CHANNEL_PERIOD_LOGIC_PERIOD_NAME_PERIOD_CONTROL=control-channel-logic-name-1 \ -e LIFERAY_CLUSTER_PERIOD_LINK_PERIOD_CHANNEL_PERIOD_LOGIC_PERIOD_NAME_PERIOD_TRANSPORT_PERIOD_NUMBER0=transport-channel-logic-name-1 \ -e LIFERAY_CLUSTER_PERIOD_LINK_PERIOD_AUTODETECT_PERIOD_ADDRESS=some-mariadb:3306 \ -e LIFERAY_WEB_PERIOD_SERVER_PERIOD_DISPLAY_PERIOD_NODE=true \ -e LIFERAY_DL_PERIOD_STORE_PERIOD_IMPL=com.liferay.portal.store.db.DBStore \ --name dxp-1 \ -p 11311:11311 \ -p 8009:8009 \ -p 8080:8080 \ -v $(pwd)/dxp-1:/mnt/liferay \ liferay/portal:7.4.3.120-ga120
Command condensed to one line:
docker run -it --name dxp-1 --add-host elasticsearch:[IP address] --add-host some-mariadb:[IP address] -e LIFERAY_JDBC_PERIOD_DEFAULT_PERIOD_JNDI_PERIOD_NAME="" -e LIFERAY_JDBC_PERIOD_DEFAULT_PERIOD_DRIVER_UPPERCASEC_LASS_UPPERCASEN_AME=org.mariadb.jdbc.Driver -e LIFERAY_JDBC_PERIOD_DEFAULT_PERIOD_URL="jdbc:mariadb://some-mariadb:3306/dxp_db?useUnicode=true&characterEncoding=UTF-8&useFastDateParsing=false" -e LIFERAY_JDBC_PERIOD_DEFAULT_PERIOD_USERNAME=root -e LIFERAY_JDBC_PERIOD_DEFAULT_PERIOD_PASSWORD=my-secret-pw -e LIFERAY_CLUSTER_PERIOD_LINK_PERIOD_ENABLED=true -e LIFERAY_CLUSTER_PERIOD_LINK_PERIOD_CHANNEL_PERIOD_LOGIC_PERIOD_NAME_PERIOD_CONTROL=control-channel-logic-name-1 -e LIFERAY_CLUSTER_PERIOD_LINK_PERIOD_CHANNEL_PERIOD_LOGIC_PERIOD_NAME_PERIOD_TRANSPORT_PERIOD_NUMBER0=transport-channel-logic-name-1 -e LIFERAY_CLUSTER_PERIOD_LINK_PERIOD_AUTODETECT_PERIOD_ADDRESS=some-mariadb:3306 -e LIFERAY_WEB_PERIOD_SERVER_PERIOD_DISPLAY_PERIOD_NODE=true -e LIFERAY_DL_PERIOD_STORE_PERIOD_IMPL=com.liferay.portal.store.db.DBStore --name dxp-1 -p 11311:11311 -p 8009:8009 -p 8080:8080 -v $(pwd)/dxp-1:/mnt/liferay liferay/portal:7.4.3.120-ga120
-
Start
dxp-2
.Command expanded for readability:
docker run -it \ --add-host elasticsearch:[IP address] \ --add-host some-mariadb:[IP address] \ -e LIFERAY_JDBC_PERIOD_DEFAULT_PERIOD_JNDI_PERIOD_NAME="" \ -e LIFERAY_JDBC_PERIOD_DEFAULT_PERIOD_DRIVER_UPPERCASEC_LASS_UPPERCASEN_AME=org.mariadb.jdbc.Driver \ -e LIFERAY_JDBC_PERIOD_DEFAULT_PERIOD_URL="jdbc:mariadb://some-mariadb:3306/dxp_db?useUnicode=true&characterEncoding=UTF-8&useFastDateParsing=false" \ -e LIFERAY_JDBC_PERIOD_DEFAULT_PERIOD_USERNAME=root \ -e LIFERAY_JDBC_PERIOD_DEFAULT_PERIOD_PASSWORD=my-secret-pw \ -e LIFERAY_CLUSTER_PERIOD_LINK_PERIOD_ENABLED=true \ -e LIFERAY_CLUSTER_PERIOD_LINK_PERIOD_CHANNEL_PERIOD_LOGIC_PERIOD_NAME_PERIOD_CONTROL=control-channel-logic-name-2 \ -e LIFERAY_CLUSTER_PERIOD_LINK_PERIOD_CHANNEL_PERIOD_LOGIC_PERIOD_NAME_PERIOD_TRANSPORT_PERIOD_NUMBER0=transport-channel-logic-name-2 \ -e LIFERAY_CLUSTER_PERIOD_LINK_PERIOD_AUTODETECT_PERIOD_ADDRESS=some-mariadb:3306 \ -e LIFERAY_WEB_PERIOD_SERVER_PERIOD_DISPLAY_PERIOD_NODE=true \ -e LIFERAY_DL_PERIOD_STORE_PERIOD_IMPL=com.liferay.portal.store.db.DBStore \ --name dxp-2 \ -p 11312:11311 \ -p 9009:8009 \ -p 9080:8080 \ -v $(pwd)/dxp-2:/mnt/liferay \ liferay/portal:7.4.3.120-ga120
Command condensed to one line:
docker run -it --add-host elasticsearch:[IP address] --add-host some-mariadb:[IP address] -e LIFERAY_JDBC_PERIOD_DEFAULT_PERIOD_JNDI_PERIOD_NAME="" -e LIFERAY_JDBC_PERIOD_DEFAULT_PERIOD_DRIVER_UPPERCASEC_LASS_UPPERCASEN_AME=org.mariadb.jdbc.Driver -e LIFERAY_JDBC_PERIOD_DEFAULT_PERIOD_URL="jdbc:mariadb://some-mariadb:3306/dxp_db?useUnicode=true&characterEncoding=UTF-8&useFastDateParsing=false" -e LIFERAY_JDBC_PERIOD_DEFAULT_PERIOD_USERNAME=root -e LIFERAY_JDBC_PERIOD_DEFAULT_PERIOD_PASSWORD=my-secret-pw -e LIFERAY_CLUSTER_PERIOD_LINK_PERIOD_ENABLED=true -e LIFERAY_CLUSTER_PERIOD_LINK_PERIOD_CHANNEL_PERIOD_LOGIC_PERIOD_NAME_PERIOD_CONTROL=control-channel-logic-name-2 -e LIFERAY_CLUSTER_PERIOD_LINK_PERIOD_CHANNEL_PERIOD_LOGIC_PERIOD_NAME_PERIOD_TRANSPORT_PERIOD_NUMBER0=transport-channel-logic-name-2 -e LIFERAY_CLUSTER_PERIOD_LINK_PERIOD_AUTODETECT_PERIOD_ADDRESS=some-mariadb:3306 -e LIFERAY_WEB_PERIOD_SERVER_PERIOD_DISPLAY_PERIOD_NODE=true -e LIFERAY_DL_PERIOD_STORE_PERIOD_IMPL=com.liferay.portal.store.db.DBStore --name dxp-2 -p 11312:11311 -p 9009:8009 -p 9080:8080 -v $(pwd)/dxp-2:/mnt/liferay liferay/portal:7.4.3.120-ga120
The --add-host [domain]:[IP address]
options add /etc/hosts
file entries that map the domain names to the IP addresses. This enables configurations (such as environment variables, portal properties, and .config
files) to refer to the servers by domain name.
The -e [variable]=[value]
options set the DXP container environment variables. See Appendix A: Environment Settings for more information.
The DXP cluster node containers have these unique settings:
Configuration | dxp-1 | dxp-2 |
---|---|---|
AJP port mapping | 8009:8009 | 9009:8009 |
HTTP port mapping | 8080:8080 | 9080:8080 |
OSGi container port mapping | 11311:11311 | 11312:11311 |
Bind mount | $(pwd)/dxp-1:/mnt/liferay | $(pwd)/dxp-2:/mnt/liferay |
Cluster Link control channel logic name | control-channel-logic-name-1 | control-channel-logic-name-2 |
Cluster Link transport channel logic name | transport-channel-logic-name-1 | transport-channel-logic-name-2 |
Visit the DXP Nodes
The DXP cluster nodes are available at the following URLs:
- DXP-1: http://localhost:8080
- DXP-2: http://localhost:9080
The figure below shows the cluster node home pages.
Each node’s container ID and port (Node: [id]:[port]
) appear at the bottom of each page. The LIFERAY_WEB_PERIOD_SERVER_PERIOD_DISPLAY_PERIOD_NODE=true
environment setting enabled this display feature. You can find a container’s ID using the docker container ls
command.
Index the Content into the Search Engine
-
Navigate to Control Panel → Configuration → Search.
-
In the Index Actions tab, click Reindex all search indexes and Reindex all spell check indexes.
Content indexes into the search engine. See Search Overview for more information.
Test the DXP Cluster
Test data synchronization between the nodes:
-
Add content to one of the cluster nodes. For example, add a new Widget Page called New Stuff and add the Language Selector widget to it.
-
Refresh the UI on the other cluster node.
Both nodes show the same new content.
Congratulations on creating a working DXP cluster!
What’s Next
Configure your database for your DXP cluster.
Appendix A: Environment Settings
The example DXP server containers use these settings.
Configuration | Description |
---|---|
LIFERAY_JDBC_PERIOD_DEFAULT_PERIOD_JNDI_PERIOD_NAME= | Data source JNDI name |
LIFERAY_JDBC_PERIOD_DEFAULT_PERIOD_DRIVER_UPPERCASEC_LASS\ _UPPERCASEN_AME=\ org.mariadb.jdbc.Driver | Database driver class |
LIFERAY_JDBC_PERIOD_DEFAULT_PERIOD_URL=\ jdbc:mariadb://some-mariadb:3306/dxp_db?\ useUnicode=true&characterEncoding=UTF-8&useFastDateParsing=false | Data source URL |
LIFERAY_JDBC_PERIOD_DEFAULT_PERIOD_USERNAME=\ root | Database admin user name |
LIFERAY_JDBC_PERIOD_DEFAULT_PERIOD_PASSWORD=\ my-secret-pw | Database admin user password |
LIFERAY_CLUSTER_PERIOD_LINK_PERIOD_ENABLED=\ true | Enables Cluster Link |
LIFERAY_CLUSTER_PERIOD_LINK_PERIOD_CHANNEL_PERIOD_LOGIC_PERIOD_NAME\ _PERIOD_CONTROL=\ control-channel-logic-name-2 | The cluster node’s unique control channel name |
LIFERAY_CLUSTER_PERIOD_LINK_PERIOD_CHANNEL_PERIOD_LOGIC_PERIOD_NAME\ _PERIOD_TRANSPORT_PERIOD_NUMBER0=\ transport-channel-logic-name-2 | The cluster node’s unique transport channel name |
LIFERAY_CLUSTER_PERIOD_LINK_PERIOD_AUTODETECT_PERIOD_ADDRESS=\ some-mariadb:3306 | Known address to ping to get cluster node addresses |
LIFERAY_WEB_PERIOD_SERVER_PERIOD_DISPLAY_PERIOD_NODE=\ true | Displays the server address and web server port |
LIFERAY_DL_PERIOD_STORE_PERIOD_IMPL=\ com.liferay.portal.store.db.DBStore | File Store (Document Library Store) class |
See the Env/Portal Property definitions for more information.