Wednesday, April 27, 2016

memcached installation

memcached & memcache on Linux CentOS 5/Plesk 9

The second part of my tutorial on optimizing your server for hosting high traffic websites: installing and configuring memcached and the memcache php extension for your server. This is a little easier than the first step (setting up nginx as reverse proxy, see article below) and can be applied to any kind of dynamic website.


What's the point ?
On highly dynamic websites such as forums, news sites or any user content based website, the database server load is often very high. The more traffic you get, the more cluttered your database server becomes, sometimes rendering your website completely unavailable to visitors. Using a data caching daemon will allow you to save some data in memory instead of fetching the data from the database every time. You should know that memcached is used by major websites such as Wikipedia, SourceForge, SlashDot... need I say more?

What is memcached ?
Memcached is the daemon running on your server. Its usage is extremely simple, there are no configuration files, all you do is start the daemon on a given port, and your websites will connect to this daemon to store data in memory. Yes, the data is stored in your RAM. So when starting memcached you'll have to decide how much RAM memcached will be allowed to use. If you start memcached with a 1GB memory space, memcached will store this much data; when the cache is full some of the older data will begin to disappear from the cache.

What is memcache ?
Memcache, in our case, is the PHP extension that will allow us to connect to and make use of Memcached. This PHP extension is not part of the default ones so you'll have to download and install it (see step 2). It provides classes as well as functions that I must admit are very easy to use and understandable. In this article, I provide a mysql+memcache wrapper class for anyone to use.

What is the difference between memcached and memcache ?
Well if you've read the two points above, you should already know. In short, memcached is the daemon running on your machine; memcache is the PHP extension allowing you to make use of memcached.


1. Setting up memcached

I haven't found memcached in my repositories (might aswell try # yum install memcached just in case?) so I'll download the source and compile it. First go get the latest version from the official website.
# wget http://memcached.googlecode.com/files/memcached-1.4.1.tar.gz
# tar zxvf memcached-1.4.1.tar.gz
# cd memcached-1.4.1
# ./configure
If like me you get this message "libevent is missing" or something, you can run this command:
# yum install libevent-devel
And then run configure again: 
# ./configure
Install memcached:
# make install
That's it, you're set! That was pretty easy wasn't it? We'll now see the command line arguments for starting memcached:
# memcached -d -m 1024 -l 127.0.0.1 -p 11211 -u nobody
The arguments are:
-d : start as daemon, running in the background
-m 1024: allow memcached to use up to 1024 MB of RAM (1GB)
-l 127.0.0.1: listen on local interface
-p 11211: listen on port 11211
-u nobody: run as user "nobody"

If you're not sure how much memory you should allocate to memcached, try running this command first:
# free
It will tell you how much free RAM you've got left.
Note that upon starting memcached, if all is OK, you will see no output message. To see if memcached is correctly started, run this command:
# ps aux | grep memcached
You should be seeing something like this:
nobody   13133  0.0  0.0  43580   732 ?        Ssl  07:11   0:00 memcached -d -m 128 -l 127.0.0.1 -p 11211 -u nobody
user     13143  0.0  0.0   4152   648 pts/0    R+   07:11   0:00 grep memcached



2. Setting up memcache PHP extension
The memcache PHP extension should be found in the classic repositories, so try this command:
# yum install php-pecl-memcache
If you're lucky (why should you be unlucky anyway?) the install will work fine and you'll be seeing these messages:
Installed: php-pecl-memcache.i386 0:2.2.3-1.el5_2
Dependency Installed: php-pear.noarch 1:1.4.9-4.el5.1
Complete!
Just for reference, here's a link to the official memcache website, if you need to grab the sources.

 Let's see if memcache was installed properly. First restart the httpd:
 # service httpd restart
Then place a simple php file on your website containing the following code:
phpinfo();  
Open the PHP file in your browser (eg. http://mydomain.com/phpinfo.php ) and have a look at the output. If you can find a "memcache" section looking like the following picture, it means memcache was successfully installed.
We will now have a look at the memcache configuration file. First locate your php module configuration files folder, in my case /etc/php.d/ . You should find the newly installed "memcache.ini" configuration file. Open it up to see a list of configuration keys and their meaning.
The default options are just fine, but if you're interested, you should know that memcache offers load-balancing features through the "allow_failover" configuration key. I'm not going to make use of this feature so I will not be editing any of the settings.


3. Using memcache in your code
Unfortunately, installing both components isn't enough. You'll have to edit your code in order to make use of the caching features. Be reassured though, it couldn't be easier! There are a couple of functions you'll need to use, nothing complex.
If you want to find out the complete listing of the memcache php functions, visit the official website. Basically we'll be using 5 methods:
Memcache::connect($host, $port, $timeout): connect to your daemon
Memcache::get($key) : fetch data from your cache
Memcache::set($key, $var, $flag, $expire): store data in your cache
Memcache::delete($key): remove data from your cache
Memcache::close(): disconnect.

You can cache any data that you want:
$mc = new Memcache;
$mc->connect("localhost", 11211);
$saved_data = $mc->get("saved_data");
if (!$saved_data) {
  $saved_data = file_get_contents("myfile.txt");
  $mc->set("saved_data", $saved_data, MEMCACHE_COMPRESSED, 60*60*24*7); // store for 7 days
}
echo $saved_data;
$mc->close();

Applied to MySQL queries:
$mc = new Memcache;
$mc->connect("localhost", 11211);
$news_articles = $mc->get("news_articles");
if (!$news_articles) {
  $news_articles = array();
  $query = "SELECT * FROM news_articles ORDER BY article_id DESC LIMIT 0,10";
  $result = mysql_query($query);
  while($row = mysql_fetch_assoc($result)) $news_articles[] = $row;
  $mc->set("news_articles", serialize($news_articles), MEMCACHE_COMPRESSED, 60*60*24*7); // store for 7 days, but don't forget to rebuild the cache when a new article is posted!
} else {
  $news_articles = unserialize($news_articles);
}
// Display articles.. 
$mc->close();
As you can see in the example above, I use the "serialize" and "unserialize" php functions. Why is that? The reason is because the Memcache::get() function always returns a string. So if you want to store an array of data (or an object), you'll have to serialize said array, and unserialize it after having read it from the database.
If you know a better workaround for this problem please feel free to leave a comment.


4. Wrapper class for memcache & mysql
I have just written a simple wrapper class for MySQL, making use of the powerful caching system offered by memcache. You can download the class here, I included a simple example for testing the class.
The principle is very simple: when executing a query, the script will check if the query result is already in the cache. If the data is in the cache, it is returned immediatly (no query executed). if the data is not in the cache, the query is executed, and the results are then placed in the cache with the specified "time to live".

Here are the wrapper class functions:
function MySQLMemcache($mysql_info, $memcache_info, $autoconnect=true, $enable_logging=true);
function connect();
function disconnect();
function dataQuery($query, $usecache=true, $ttl=0);
function nonDataQuery($query);
function fieldDataQuery($query, $field, $usecache=true, $ttl=0);

More documentation is provided inside the actual php file.

No comments:

Post a Comment