18K Views

Redis pub-sub vs Cron job in symfony

Most Common requirement of a website is to send email notification whenever any task or some event occurs.

For example – Email notification to employee on his/her birthday.

Using Cron job : 

If we will do this thing using the crone job then we have to run crone job everyday, it will check employee data in database and find the people those b’day date coming on the same day and each day we have to filter data if someone b’day date is on the search day.

Means everyday we have to interact with database and filter all the employee data and check if someone b’day date is coming on current day. It will obviously increase the load on site as to filter employees data everyday.

Using Redis :

Instead we can use redis pub-sub in which we can set a key with employee email and time remain from his/her date of birth at the time of employee creation and expire that key just after that remaining time for his/her birthday.

A redis subscriber will listen the events and when it get a expire event we will get a expire key if expire key match with our pattern we will get employee detail from that key and instantly will send an email and again set a key with one year time for expire again and this cycle will be continue and we will get email notification for birthday without checking all data everyday.

Implementation:

First thing we need to ensure we have redis installed or not if not then install redis first and then proceed further.

We can create a service named with employee.bithday.notification in services.yaml.

EmailBirthdayNotificationService.php

<?php

namespace UVDesk\TicketBundle\Utils\EmailBirthdayNotificationService;

use Doctrine\ORM\EntityManager;
use Predis\Client as RedisClient;
use Symfony\Component\DependencyInjection\ContainerInterface;

class EmailBirthdayNotificationService
{
    private $container;
    private $redis_client;
    private $entityManager;

    public function __construct(ContainerInterface $container, EntityManager $entityManager)
    {
        $this->container = $container;
        $this->entityManager = $entityManager;

        // Instantiate redis client
        $this->redis_client = new RedisClient([
            'scheme' => $this->container->getParameter('redis.client.scheme'),
            'host' => $this->container->getParameter('redis.client.host'),
            'port' => $this->container->getParameter('redis.client.port'),
        ], [
            'profile' => '2.8',
            'prefix' => 'bday:email:notification:',
        ]);
    }

    public function getRedisClient()
    {
        return $this->redis_client;
    }

    /**
     * set keys and send mail after each retry
     */
    public function setThreadEmailKeys($email, $timeToExpire)
    {
        try {
            $this->redis_client->connect();

            if ((bool) $this->redis_client->isConnected() == false) {
                return false;
            }
        } catch (\Exception $e) {
            return false;
        }
        // Reset key with auto expiry
        $this->redis_client->pipeline(function ($pipe) use ($email, $timeToExpire) {
            $pipe->set("employee:$email", $timeToExpire);
            $pipe->expire("employee:$email", $timeToExpire);
        });
    }
}

?>

 

Now In controller we can call EmailBirthdayNotificationService just after employee created:

$setBdayNotificationKey = $this->container->get(’employee.bithday.notification’)->setBdayKey($email, $timeToExpire);

Here $timeToExpire will be remaining time form current year date of birth to next year date of birth time in seconds.

 

We have to make a symfony command file, which will check if any key get expire and it matches with our key pattern it will send an email and at the same time again set a key for next year with expire time of one year.

<?php

namespace UVDesk\TicketBundle\Command;

use Doctrine\ORM\Query;
use Predis\Client as RedisClient;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;

class BdayEmailNotificationCommand extends ContainerAwareCommand
{
    private $container;
	private $redis_client;
	private $entityManager;
	private $emailBirthdayNotificationService;

	protected function configure()
	{
		$this->setName('email:bithday:notificaton');
		$this->setDescription('Runs a pub-sub service to automate email to employee on his/her birthday.');
	}

    protected function initialize(InputInterface $input, OutputInterface $output)
	{
		$this->container = $this->getContainer();
		$this->entityManager = $this->container->get('doctrine.orm.entity_manager');
		$this->emailBirthdayNotificationService = $this->container->get('employee.birthday.notification');

		// Get default redis client
		$this->redis_client = $this->emailBirthdayNotificationService->getRedisClient();
		
		// Check if client is able to communicate with the server
		try {
			$this->redis_client->connect();
		
			if ((bool) $this->redis_client->isConnected() == false) {
				return false;
			}
		} catch (\Exception $e) {
			$output->writeln("\nUnable to establish a connection with the redis server.\n");
			exit(1);
		}
	}

    protected function execute(InputInterface $input, OutputInterface $output)
	{
	    // Subscribe to key-space event notification
	    $pubsub = $this->getPubSub();
	    $pubsub->subscribe('__keyevent@0__:expired');

        foreach ($pubsub as $message) {
			if ('subscribe' == $message->kind) {
				$output->write("\nSubscribed to channel <fg=yellow>" . $message->channel . "</>\n\n");
			} else if ('message' == $message->kind) {
				switch ($message->channel) {
					case '__keyevent@0__:expired':
						$expired_key = $message->payload;
						
						if ((bool) preg_match('/bday:email:notification:employee(.*)/', $expired_key, $matches) !== false) {
							$output->write("Key expired <fg=yellow>" . $expired_key . "</>\n");

							$email = $matches[1];
                            $from  = "uvdesk@example.com";
                            $subject = "Happy Birth day";
                            $content = "Wish you a very happy b'day.";

                            $mailer = $this->container->get('mailer');
							$message = $mailer->createMessage()
											->setSubject($subject)
											->setFrom($from))
											->setTo($email)
											->setBody($content, 'text/html');
							try {
								$mailer->send($message);

                                 // Reset key with auto expiry
                                $expireTime = 31536000; //(one year)

								$this->redis_client->pipeline(function ($pipe) use ($email, $expireTime) {
									$pipe->set("employee:$email", $expireTime);
									$pipe->expire("employee:$email", $expireTime);
								});

                            } catch(\Exception $e) {
								$logger = $this->container->get('logger');
								$logger->error('Email send error : '.$e->getMessage());
							}
						}
						break;
					default:
						break;
				}
			}
        }	
	    exit(0);
	}

    private function getPubSub()
	{
	    $pubsub_client = new RedisClient([
              'scheme' => $this->container->getParameter('redis.client.scheme'),
              'host' => $this->container->getParameter('redis.client.host'),
			'port' => $this->container->getParameter('redis.client.port'),
			'read_write_timeout' => '-1'
              ], [
			'profile' => '2.8',
	   ]);

	   $pubsub_client->config('set', 'notify-keyspace-events', 'KExe');
	   return $pubsub_client->pubSubLoop();
	}
}

I’ve explored this while contributing to Symfony based project UVdesk, there are a lot more things to learn and you could also contribute to an enterprise-level open source helpdesk.

Thanks for reading me. I hope you’ll get a idea how crone job can be replaced by using redis pub-sub as per requirement , please share your reviews on this, that will support me to write more.

 

UVdesk Forum!          Developer Visit!            Contact Us!          Live Demo!

Category(s) Symfony UVdesk
. . .

Comment

Add Your Comment

Be the first to comment.

css.php