Mar 18, 2011

Configuring repcached service on Debian/Ubuntu

Repcached is a slightly modified version of memcached, that supports replication of data between two repcached nodes.

Let's say you've read all about these two and you know the benefits of replication and why this article could be useful to you.

One example of why replicated memcache could be useful are replicated PHP sessions between servers.

If you want to configure that PHP sessions are stored in memcache's memory, you need to edit these settings in /etc/php5/apache2/php.ini:
session.save_handler = memcache
session.save_path = "tcp://IP_OF_REPCACHE_1:11311, tcp://IP_OF_REPCACHE_2:11311"
and these optional settings in /etc/php5/apache2/conf.d /memcache.ini:
memcache.maxratio=0
memcache.allow_failover=1
memcache.allow_failover setting is used if one of the servers becomes unreachable, so there is an automatic failover.
Read more about configuring PHP sessions in memcached here.

Now, let's set up repcached to start on boot and System V init scripts, so you can easly start and stop the daemon with the service command.

Steps described here imitate memcached's default configuration in great detail, so you shoud set up memcached before repcached.
sudo apt-get install memcached

Obtain, configure, compile and install repcached. There is a dependancy with libevent-dev for repcached.
sudo apt-get install libevent-dev
tar xvf memcached-1.2.8-repcached-2.2.tar 
cd memcached-1.2.8-repcached-2.2/
./configure --enable-replication
make
make install

(Read this if it won't compile)

At this point you have two installations of memcached. Default memcached that came from apt packages, which is installed in /usr/bin/memcached and repcached, that installed itself in /usr/local/bin/memcached, leaving the original memcached intact.

Now that we have both versions installed, we can copy memcached's default settings and init script and modify them to use repcached. This way you can quickly switch between versions. I would even recommend using default ports (just remember to firewall them!) Arguments are saved in /etc/memcached.conf, so we will create /etc/repcached.conf

See example here.

Note that the only differences with memcached.conf is the name (repcached) and two extra arguments: -x for the server IP and -X for replication port.

Memcached has an enable/disable config in /etc/default so you can quickly switch between daemons or disable them. We will copy this as well.
cp /etc/default/memcached /etc/default/repcached
vi /etc/default/repcached
Change the line to: ENABLE_REPCACHED=yes, and then edit /etc/default/memcached
vi /etc/default/memcached
and disable it, by changing the line to ENABLE_MEMCACHED=no.

Now let's move on to init scripts.
cd /etc/init.d
cp memcached repcached
Edit the file /etc/init.d/repcached.

Here is my example.

Again, we didn't change much, mostly changed from memcached to repcached, but note that the actual start-up of the service happens in this file: /usr/share/memcached/scripts/start-repcached which doesn't exist yet, so we will copy and edit it.
cp /usr/share/memcached/scripts/start-memcached /usr/share/memcached/scripts/start-repcached
File contents or /usr/share/memcached/scripts/start-repcached

Setting up repcached to start at boot

We need to be sure that /etc/init.d/repcached is executable. If you copied it from memcached, everything should be OK, but if init's not recognising the repcached service, you need to chmod +x /etc/init.d/repcached

After you've run update-rc.d command in the terminal it will create shortcuts in rc?.d files which are read at boot.
update-rc.d repcached defaults
For more information on update-rc.d, click here.

You have successfully configured repcached as a service and to start on boot.

To start/stop repcached use
service repcached start
service repcached stop
Try to run repcached by hand at first with the configuration you provided in /etc/repcached.conf.
In my example it's this:
/usr/local/bin/memcached -m 64 -p 11211 -u memcache -X 11212 -x 22.163.130.33

After installing repcached on another machine I've found out that the default user for memcached is nobody, not memcache, so please always check the differences from the default memcache config with the repcached config you've modified or copied from here.

Feb 5, 2011

PHP:: socket_select(), socket_write() and socket_recv()

As it was already said, some clients need \0 character to end transmission, for example Flash's XMLSocket.

You should also be prepared to read less data than you have requested.

Here is an example of a socket buffer - it's an array which has socket resources for keys and an array of a timestamp and recieved data as values.

I find that the best practice for sending data is trailing it with a new line and zero character (\n\0), because you will probably have different types of clients which behave differently for reading data from sockets. Some need a \n to fire an event, some need \0.

For recieving data, sometimes you will get splitted data - this can hapen because the buffer is full (in my example 8192 bytes) or it just gets broken during transmission in lower levels.

Sometimes you can read two messages at once, but they have a zero character in between, so you can just use preg_split() to split the messages. The second message may not be complete, so you add it to your buffer.

 const message_delimiter = "\n\0";

 /*
  * Clear socket buffers older than 1 hour
  */
 function clear_buffer() {
  foreach($this->buffer as $key=>$val) {
   if(time() - $val['ts'] > 3600) {
    unset($this->buffer[$key]);
   }
  }
 }

 /*
  * Add data to a buffer
  */
 function buffer_add($sock,$data) {
  if(!isset($this->buffer[$sock])) {
   $this->buffer[$sock]['data'] = '';
  }

  $this->buffer[$sock]['data'] .= $data;
  $this->buffer[$sock]['ts'] = time();
 }

 function buffer_get($sock) {
  // split buffer by the end of string
  $lines = preg_split('/\0/',$this->buffer[$sock]['data']);

  // reset buffer to the last line of input
  // if the buffer was sent completely, the last line of input should be
  // an empty string
  $this->buffer[$sock]['data'] = trim($lines[count($lines)-1]);

  if(!empty($this->buffer[$sock]['data'])) {
   debug("buffer is not empty for $sock, len: ".strlen($this->buffer[$sock]['data']));
  }

  // remove the last line of input (incomplete data)
  // parse any complete data
  unset($lines[count($lines)-1]);

  // return only the fully sent data
  return $lines;
 }

 function read(&$sock,$len=8192,$flag=MSG_DONTWAIT) {
  $lines = array();

  $this->clear_buffer();

  $bytes_read = @socket_recv($sock,$read_data,$len,$flag);

  if ($bytes_read === false || $bytes_read == 0) {
   return false;
  } else {
   debug("recv: $read_data");
   $this->buffer_add($sock,$read_data);
   return $this->buffer_get($sock);
  }
 }

 /*
  * Write to a socket
  * add a newline and null character at the end
  * some clients don't read until new line is recieved
  *
  * try to send the rest of the data if it gets truncated
  */
 function write(&$sock,$msg) {
  $msg = $msg.self::message_delimiter;
  $length = strlen($msg);
  while(true) {
   $sent = @socket_write($sock,$msg,$length);
   if($sent <= 0) {
    return false;
   }
   if($sent < $length) {
    $msg = substr($msg, $sent);
    $length -= $sent;
    debug("Message truncated: Resending: $msg");
   } else {
    return true;
   }
  }
  return false;
 }

Dec 29, 2010

PHP:: Facebook getLoginUrl iframe next parameter redirect issue

DUE TO FREQUENT FACEBOOK API CHANGES THIS ARTICLE IS OUTDATED.

The php-sdk from Facebook has some bugs in it.

http://stackoverflow.com/questions/3380876/how-to-authorize-facebook-app-using-redirect-in-canvas http://forum.developers.facebook.net/viewtopic.php?id=70575

These solutions didn't work for me, so I had to change the function getLoginUrl in class Facebook

  public function getLoginUrl($params=array()) {
 $currentUrl = $this->getCurrentUrl();
 $args = array(
        'api_key'         => $this->getAppId(),
        'cancel_url'      => 'http://www.facebook.com/',
        'display'         => 'page',
        'fbconnect'       => 0,
        'next'            => $currentUrl,
        'return_session'  => 1,
        'session_version' => 3,
  'canvas'          => 1,
        'v'               => '1.0',
      );
 foreach($params as $key=>$val) {
  $args[$key] = $val;
 }
 return $this->getUrl(
  'www',
  'login.php',
  $args
 );
  }
Example:
if($me) {
 $logoutUrl = $facebook->getLogoutUrl();
} else {
 $loginUrl = $facebook->getLoginUrl(array('next'=>'http://apps.facebook.com/xxxxxxx/'));
 ?>
 <script type="text/javascript">
 top.location.href = '<?=$loginUrl?>';
 </script>
 <?php 
 exit;
}

Dec 6, 2010

PHP:: relative paths in include() or require()

While this might seem fairly obvious, I'd still like to point out that PHP's functions include(), include_once(), require() and require_once() have problems including files in different directories where paths of the filenames are relative. The absolute path of the included files is generated from the filename of the first included file, so any relative path in the second nested include will not have relative paths starting from it's directory, but the directory of the first file. To avoid confusion I find it's best to use absolute paths with the magic constant __FILE__, which is always the current script's filename.

Example:
require_once(dirname(__FILE__).'/../../include_all.php');

Nov 21, 2010

Flash wmode (transparent/opaque/window) z-index

In flash player 9 and below there was a problem with the object's z-index if the wmode was set to window.

When this option is set to transparent, it fixes the z-index issue, but messes up the keyboard layout and mouse wheel events in flash object. Flash player 10.1 fixes the keyboard layout issue.

Workaround
Setting wmode to opaque keeps the mouse wheel functionality and z-index of absolute elements on the page, but only if you set the background color for the element!

Having a transparent background (or setting the opacity attribute in CSS) (still) does not solve the issue.