How to distribute PHP sessions on multiple memcached instances on Ubuntu 16
Memached is an open-source distributed memory caching system commonly used to speed up dynamic websites by, as the name suggests, caching data objects in RAM. By caching the data in RAM, Memcached is able to reduce the number of times that the database or API must be read when performing operations on the website. Memcached is free to use and runs on Linux, OS X, and Microsoft Windows. Available under the Revised BSD license, it is possible to install the Memcached host-client infrastructure on your own Ubuntu 16 servers.
This tutorial will cover the process of PHP session saving and replication for Ubuntu 16 on multiple Memcached servers.
Getting Started
For completing this tutorial, you will need the following setup for a landscape with three servers running Ubuntu 16:
• 1 Small Server/Cloud Instance (to be used for HAProxy)
• 2 Web Servers/Cloud Instances (to run Apache, PHP and Memcached)
Kindly note that before proceeding, you are able to act as the root user on all three servers. All commands in this tutorial, unless otherwise specified, must be executed as the root user or using the command sudo (when possessing superuser privileges) to act as root.
Tutorial
Throughout the tutorial, we will refer to the servers in our landscape using the following information in the table below. This information includes the hostname for each server, its Public IP, LAN IP, and the services that should be running on it. Make sure to refer back to this table whenever you may need to confirm which server an instruction should be executed on for a quick overview of the landscape.
Server Hostname Public IP LAN IP Services Running
LB1 173.209.44.220 10.0.0.96 Haproxy
WEB1 173.209.44.221 10.0.0.97 Apache, PHP, Memcached
WEB2 173.209.44.230 10.0.0.98 Apache, PHP, Memcached
Initial Setup for All Three Servers
We will begin the process with the preparation steps that must be executed on all three servers. During this preparation, you will have to ensure that the system is up to date, the firewall is disabled, and the servers are running with the latest kernel.
As root, on each node execute the following command using the native Ubuntu package manager apt to update the local package index with the latest information concerning available packages and package versions:
apt-get update
After fetching the new package information, apt must then be used again in order to upgrade the installed packages with available updates:
apt-get upgrade
Your Ubuntu server comes equipped with a firewall known as the Uncomplicated Firewall, or ufw. While it is disabled by default, likely you have enabled it as some prior point. For this tutorial, we will need to disable the firewall. This is done easily with the ufw command:
ufw disable
To know whether the kernel version will need to be updated, check the current version of the kernel using the uname command:
uname -r
The output of this command will look somewhat like the following (depending on which version is installed):
4.4.0-22-generic
The latest version at this time of writing is 4.7. If you have any other version, you can update your kernel by downloading the required packages from the Ubuntu website. First, make sure you have the web utility wget, which will allow you to download files. Install wget if you do not have it using apt:
apt-get install
apt-get upgrade
Finally, reboot the server for the changes to take into effect.
reboot
Verify that you are now running the new kernel version using uname a second time:
uname -r
Don’t forget to run all the steps in this section on all three servers before proceeding to the next section.
Proxy Server Configuration for the HAProxy Node
This section will concern itself with the configuration of the node that you will use for your HAProxy load-balancer. HAProxy is a free open-source software that provides load-balancing, high-availability, and proxying services for both TCP and HTTP applications. For the purposes of this guide, we will provide a basic HAProxy configuration that should be suitable out of the box for the majority of applications.
On your dedicated HAProxy server, begin by installing the HAProxy package as root:
apt-get install haproxy
After the package finishes installing, you will need to enable HAProxy with the following command as it is disabled by default:
sed -i "s/ENABLED=0/ENABLED=1/g" /etc/default/haproxy
You can start the HAProxy service after enabling it by executing:
/etc/init.d/haproxy start
However, you must complete some further configuration steps for HAProxy before you can use it. Open up the configuration file haproxy.cfg in the text editor vi:
vi /etc/haproxy/haproxy.cfg
Remove all the contents from the open configuration file. Next, insert the following code to create the new configuration file. This will add a listener on port 80 on localhost for HTTP and port 443 for HTTPS:
global
log 127.0.0.1 local2
chroot /var/lib/haproxy
pidfile /var/run/haproxy.pid
maxconn 4096
user haproxy
group haproxy
daemon
stats socket /var/run/haproxy.cmd
defaults
mode http
log global
option httplog
option dontlognull
option httpclose
option forwardfor except 127.0.0.0/8
option redispatch
retries 3
timeout http-request 10s
timeout queue 1m
timeout connect 10s
timeout client 45s
timeout server 45s
timeout check 10s
maxconn 4096
listen http :80
mode http
balance roundrobin
option forwardfor
option httpclose
option http-server-close
timeout http-keep-alive 3000
http-request del-header Proxy
server web1 10.0.0.94:80 weight 1 check inter 3000 rise 2 fall 1
server web2 10.0.0.95:80 weight 1 check inter 3000 rise 2 fall 1
Save and close the configuration file once you are done editing it. Restart HAProxy for your changes to take into effect:
/etc/init.d/haproxy restart
To enable the HAProxy service to start on boot (recommended), use the update-rc.de command as shown:
update-rc.d haproxy defaults
You can check the status of HAProxy at any time to verify it is working by checking the output of the following:
service haproxy status
Configure the Web Servers for Apache, PHP, and Memcached
Once you have set up your dedicated HAProxy node, you will now have to configure your two remaining web servers. The steps in this section will need to be executed twice, once on each server.
Begin by installing the packages for the Apache web server, PHP, and Memcached using apt. We will also install some PHP extensions as well the PHP-Memcache extension. This package provides integration functions for Memcached into your applications as well as activating Memcached support in other existing applications. Be careful when typing the name of the PHP-Memcache extension. There is another library that is not suitable for the purposes of this guide that is named php-memcached. Double check that you are instead typing the name of the extension we need, which is php-memcache (without the letter “d”). This package is lighter and has less dependencies than php-memcached.
apt-get -y install apache2 php libapache2-mod-php php-mcrypt memcached php-memcache libmemcached-devel php-pecl-memcache httpd
When the installation of the packages completes, you will need to configure the Apache web server, particularly the ServerName directive. You can do this by opening the following file using the text editor vi:
vi /etc/httpd/conf/httpd.conf
Modify this file to include the following line with the a server name of your choice, which can represent the site or infrastructure. This will be used in order to easily and quickly identify the server.
ServerName mysite.com
Save and close the file to proceed. Next, you will need to verify that PHP has been properly installed and that the Memcached library is present. You can do this by checking the output of the following command, which will call PHP with the -m option to show compiled modules, before passing the output to grep using a pipe (“|”). grep will search the output it receives for the term “memcache” and return successfully if it is found.
php -m | grep memcache
After verifying that PHP and PHP-Memcache have been installed, the Memcached module will need to be configured in order to allow distributed PHP sessions. Open the memcache.ini file in the text editor vi:
vi /etc/php/7.0/mods-available/memcache.ini
You will need to again remove everything that is already in this file by default. Replace the contents with the following:
extension=memcache.so
memcache.maxreclevel=0
memcache.maxfiles=0
memcache.archivememlim=0
memcache.maxfilesize=0
memcache.maxratio=0
memcache.hash_strategy = consistent
memcache.allow_failover = 1
memcache.session_redundancy = 3
Be careful that the memcache.session_redundancy parameter has a value that corresponds to one plus the sum of your configured Memcached instances. This means that if you have two Memcached servers, you will need to enter 3 as the value. Interestingly enough, this PHP bug has never been fixed despite being widely known.
With the Memcached configuration complete for now, we will move on to configuring PHP to store its user sessions. Open the php.ini file for editing with vi:
vi /etc/php.ini
Search this file for the line session.save_handler = files. When you find this line, delete it. In the same space as the deleted line was previously, insert the following two lines:
session.save_handler = memcache
session.save_path = "tcp://10.0.0.94:11211, tcp://10.0.0.95:11211"
You will also need to repeat this process for the CLI-related php.ini file. Open the file for editing, delete the session.save_handler = files line, and replace it with the two lines specified above. This will be useful if you have cron (a task scheduler) jobs running PHP scripts that require session access.
Please ensure that you have repeated the above steps for the two servers before proceeding to the following section.
Configuration for the Two Memcached Servers
Both of the Memcached servers will need to be configured in order to allow Memcached to listed on their private LAN IPs. You will also need to modify the amount of memory that is allocated to each instance in this section.
The goal of this guide will be to set both the servers to accept 1024 connections coming from Apache processes with a cache size of 256MB per server. Such a cache size is large enough in order to store thousands of PHP sessions in RAM and works for most applications. Note that for a heavy usage scenario, it will be necessary to increase the size.
To begin, open the Memcached configuration file with vi:
vi /etc/sysconfig/memcached
You will need to modify the file contents so that they match the following. Note that the IP listed must be the respective machine’s LAN IP that you are currently working on. Remember to change it when you repeat these steps for the second server.
PORT="11211"
USER="memcached"
MAXCONN="1024"
CACHESIZE="256"
OPTIONS="-l 10.0.0.95"
Restart Memcached and Apache to accept the new changes:
systemctl restart httpd.service
systemctl restart memcached.service
To enable these two services to run on boot, which is recommended, execute additionally the following lines:
systemctl enable httpd.service
systemctl enable memcached.service
Remember again that all of the above steps must be executed on both servers in order for Memcached to run before proceeding.
Testing PHP Sessions Creation and Balancing
To ensure that our landscape has been successfully configured to the correct state, we will place a script on both the web servers that will help test the sessions and load balancing. More particularly, we will need to ensure that sessions are being created in the first place, and then that the sessions are being balanced between the two web servers. All the steps in this section must be executed on both the servers.
First, navigate to the default Apache webroot located in /var/www/html. This repository is the source of what is being hosted on the server, and where we will create the test script. Use the following commands to change directories using cd and create the script file:
cd /var/www/html
vi session.php
With the file open for editing in the text editor vi, insert the following code to form the session PHP script:
<?php
header('Content-Type: text/plain');
session_start();
if(!isset($_SESSION['visit']))
{
echo "Welcome from GloboTech! Thank you for visiting this server.\n";
$_SESSION['visit'] = 0;
}
else
echo "You have visited this server ".$_SESSION['visit'] . " times. \n";
$_SESSION['visit']++;
echo "Server IP: ".$_SERVER['SERVER_ADDR'] . "\n";
echo "Client IP: ".$_SERVER['REMOTE_ADDR'] . "\n";
print_r($_COOKIE);
?>
After saving and closing the file, we now need to visit this script using the load balancer IP address. By using the load balancer IP address (the HAProxy server), some visits will end up on the WEB1 server, while others will access the WEB2 server instead. Open the script at the following URL in your browser:
http://173.209.44.220/session.php
When the page is open, the output should look as follows:
Welcome from GloboTech! Thank you for visiting this server.
This server has seen you 30 times.
Server IP: 10.0.0.95
Client IP: 10.0.0.93
Array
(
[PHPSESSID] => aeoj7s47i7ke2l0fr7b534v684
)
Check the session ID that is output to ensure that is remains the same for each load, and that the total amount of visits increases by one upon refresh as it should. Keep refreshing until you end up on the other web server, at which point the visitor counter should still be increasing and the PHPSESSID should be the same. The output should look like the following sample:
Welcome from GloboTech! Thank you for visiting this server.
This server has seen you 31 times.
Server IP: 10.0.0.94
Client IP: 10.0.0.93
Array
(
[PHPSESSID] => aeoj7s47i7ke2l0fr7b534v684
)
If all looks correct, we can now confirm that the session is persisting across different web servers. With a second browser open to the same page, refresh a couple of times so that a session ID is created.
Testing Failover Memcached Servers
Switch to the load balancer node. To test the Memcached servers, you will need to install the additional netcat utility that will allow you to query the servers for their statistics. Install it with:
apt-get install netcat
Use netcat to see the statistics of any given server by executing:
echo 'stats' | nc 10.0.0.94 11211
This will check the statistics of the first web server. The output should look as follows:
STAT pid 28882
STAT uptime 39
STAT time 1471052900
STAT version 1.4.15
STAT libevent 2.0.21-stable
STAT pointer_size 64
STAT rusage_user 0.016870
STAT rusage_system 0.020244
STAT curr_connections 5
STAT total_connections 104
STAT connection_structures 6
STAT reserved_fds 20
STAT cmd_get 1
STAT cmd_set 195
STAT cmd_flush 0
STAT cmd_touch 0
STAT get_hits 0
STAT get_misses 1
STAT delete_misses 0
STAT delete_hits 0
STAT incr_misses 1
STAT incr_hits 0
STAT decr_misses 0
STAT decr_hits 0
STAT cas_misses 0
STAT cas_hits 0
STAT cas_badval 0
STAT touch_hits 0
STAT touch_misses 0
STAT auth_cmds 0
STAT auth_errors 0
STAT bytes_read 10212
STAT bytes_written 2598
STAT limit_maxbytes 268435456
STAT accepting_conns 1
STAT listen_disabled_num 0
STAT threads 4
STAT conn_yields 0
STAT hash_power_level 16
STAT hash_bytes 524288
STAT hash_is_expanding 0
STAT bytes 202
STAT curr_items 2
STAT total_items 195
STAT expired_unfetched 0
STAT evicted_unfetched 0
STAT evictions 0
STAT reclaimed 0
END
Next, you can execute the same command as above in order to check the statistics of the second server:
echo 'stats' | nc 10.0.0.95 11211
This will also provide output like the following:
STAT pid 28117
STAT uptime 50
STAT time 1471052915
STAT version 1.4.15
STAT libevent 2.0.21-stable
STAT pointer_size 64
STAT rusage_user 0.013569
STAT rusage_system 0.039201
STAT curr_connections 5
STAT total_connections 103
STAT connection_structures 6
STAT reserved_fds 20
STAT cmd_get 97
STAT cmd_set 291
STAT cmd_flush 0
STAT cmd_touch 0
STAT get_hits 96
STAT get_misses 1
STAT delete_misses 0
STAT delete_hits 0
STAT incr_misses 1
STAT incr_hits 96
STAT decr_misses 0
STAT decr_hits 0
STAT cas_misses 0
STAT cas_hits 0
STAT cas_badval 0
STAT touch_hits 0
STAT touch_misses 0
STAT auth_cmds 0
STAT auth_errors 0
STAT bytes_read 21822
STAT bytes_written 8479
STAT limit_maxbytes 268435456
STAT accepting_conns 1
STAT listen_disabled_num 0
STAT threads 4
STAT conn_yields 0
STAT hash_power_level 16
STAT hash_bytes 524288
STAT hash_is_expanding 0
STAT bytes 202
STAT curr_items 2
STAT total_items 291
STAT expired_unfetched 0
STAT evicted_unfetched 0
STAT evictions 0
STAT reclaimed 0
END
These statistics for the servers show that activity has occurred on this instance, as expected. Both Memcached servers should have had activity and stored content. Still, it is important to note that while Memcached does utilize replication, it is not 100% fail proof. This means that if one of the two Memcached servers were to shut down, for any reason, we would not be able to guarantee that both servers did in fact contain the same exact sessions. However, if such a case were to occur, it is no cause for worry as the affected users would only need to login again to their accounts.
To test the failover, shutdown Memcached on any of the two web servers (but not both!) with the command:
systemctl stop memcached.service
Refresh the browser page you have open and observe the behavior. Does the visit counter continue to increase when you refresh the page? If so, then the failover setup is working as it should.
If the counter did not increase, but rather stayed the same, we will need to make an additional test to confirm that replication is working correctly. Restart Memcached on the instance you stopped it on:
systemctl restart memcached.service
Now, you will have to go onto the other server and kill the Memcached instance there instead. On the second server (web1 for example), kill Memcached using:
systemctl stop memcached.service
Go to your webpage again. If the counter increases upon refresh, then you can safely confirm that your Memcached PHP session setup works correctly with both failover and replication.
Conclusion
Congratulations! While setting up PHP session replication on multiple Memcached Ubuntu 16 servers is a lengthy process, it pays off in terms of the speed boost it gives to dynamic web applications you may be running such as WordPress or Moodle. Share this tutorial with your friends if you found it useful!