Using Azure Functions Timer Trigger for Seamless Cache Auto-Refresh in Real-World Applications

11 minute read

What is Azure Functions? Azure Functions is a serverless compute service that allows you to run event-driven code without provisioning or managing servers. You can use Azure Functions to run code in response to events such as HTTP requests, timer triggers, blob storage, queue messages, and more. Azure Functions is a great way to build serverless applications that scale automatically and only pay for the resources you use. Azure Functions supports multiple programming languages including C#, JavaScript, Python, and PowerShell. It is called serverless because you don’t have to worry about the underlying infrastructure. Azure Functions takes care of scaling, monitoring, and managing the infrastructure for you. You can focus on writing code and building applications without worrying about the infrastructure.

Different Types of Functions

  • Timer Trigger: It is a function that runs on a schedule. You can specify the schedule using a cron expression. The function will run at the specified time.
  • HTTP Trigger: It is a function that runs when an HTTP request is received. You can specify the HTTP method (GET, POST, PUT, DELETE) and the route. The function will run when an HTTP request is received at the specified route.
  • Blob Trigger: It is a function that runs when a blob is added or updated in a storage account. You can specify the storage account and the container. The function will run when a blob is added or updated in the specified container.
  • Queue Trigger: It is a function that runs when a message is added to a queue in a storage account. You can specify the storage account and the queue. The function will run when a message is added to the specified queue.
  • Event Grid Trigger: It is a function that runs when an event is published to an event grid topic. You can specify the event grid topic. The function will run when an event is published to the specified topic.
  • Event Hub Trigger: It is a function that runs when an event is received from an event hub. You can specify the event hub. The function will run when an event is received from the specified event hub.
  • RabbitMQ Trigger: It is a function that runs when a message is received from a RabbitMQ queue. You can specify the RabbitMQ queue. The function will run when a message is received from the specified queue.
  • Service Bus Queue Trigger: It is a function that runs when a message is added to a queue in a service bus namespace. You can specify the service bus namespace and the queue. The function will run when a message is added to the specified queue.
  • Service Bus Topic Trigger: It is a function that runs when a message is added to a topic in a service bus namespace. You can specify the service bus namespace and the topic. The function will run when a message is added to the specified topic.
  • Durable Functions: It is a function that runs a workflow. You can specify the workflow using code or an orchestration. The function will run the workflow.
  • Cosmos DB Trigger: It is a function that runs when a document is added or updated in a Cosmos DB database. You can specify the Cosmos DB database and the collection. The function will run when a document is added or updated in the specified collection.
  • SignalR Service Trigger: It is a function that runs when a message is received from a SignalR service. You can specify the SignalR service. The function will run when a message is received from the specified service.

Some real-world examples of Azure Functions are:

  • A function that runs on a schedule to update exchange rates in a cache
  • A function that sends an email when a new record is added to a database
  • A function that runs when a message is added to a queue
  • A function that runs when an event is published to an event grid topic
  • A function that runs when a document is added or updated in a Cosmos DB database
  • A function that runs when a message is received from a RabbitMQ queue
  • A function that processes images uploaded to a blob storage account

What is Redis? Redis is an in-memory data store that can be used as a database, message broker, or cache in your applications. Redis is a key-value store that stores data in memory for fast access. Redis is a popular choice for caching because it is fast, scalable, and reliable. Redis supports multiple data structures including strings, lists, sets, sorted sets, and hashes.

Some real-world examples of Redis are:

  • A cache for storing frequently accessed data i.e. user sessions, product catalog, etc.
  • A database for storing data that needs to be accessed quickly i.e. user profiles, product details, etc.
  • A leader board for storing scores and rankings i.e. game scores, user rankings, etc.

Implementation

Step 1: Create a azure timer function

  • Open Visual Studio
  • Select File -> New -> Project
  • Select “Azure Functions” template
  • Enter the project name, i.e, “CacheRefreshAzureFunction”, specify the Location and Solution name, i.e, “CacheRefreshFunction”, and click Next
  • Select Functions worker as .NET Framework Isolated v4. I will discuss the different Functions later.
  • Select Function as Timer Trigger
  • Uncheck the “Use Azureite for runtime storage account” and “Enable container support” checkbox. I will discuss about these later.
  • Keep the default Schedule value as “0 */5 * * * *” which means the function will run every 5 minutes. You can change the value later.
  • Click Create

Note: Different Types of Functions workers

  • .NET Framework Isolated v4: This is the default option. It is a .NET 5 based worker. It is a new worker and it is recommended to use this worker for new projects. The isolated worker is a new worker that runs in a separate process from the runtime. This means that the worker can run on a different version of .NET than the runtime. This allows you to use the latest version of .NET with your functions.
  • .NET Framework v1: This is the old worker. It is based on .NET Core 3.1. It is recommended to use this worker for old projects.

Step 2: Run Redis in Docker Let’s run Redis in a Docker container to run azure function locally. Assume you have Docker installed on your machine.

  • Run docker desktop on your machine
  • Open a powershell terminal
  • Run the following command to run the Redis container
docker run -d -p 6379:6379 --name redis-cache redis
  • Run the following command to check if the Redis container is
docker ps
  • Run the following command to connect to the Redis container
docker exec -it redis-cache redis-cli 
  • Run the following command to set a key-value pair in Redis
set key value
example: set name "John Doe"
  • Run the following command to get the value of a key in Redis
get key

example: get name
  • Run the following command to exit the Redis CLI
exit

Step 3: Set redis connection string in windows environment variable Let’s set the Redis connection string in the Windows environment variable to use it in the Azure Function App. I will use environment variable to get the Redis connection string in the Azure Function App.

  • Open the PowerShell
  • Run the following command to set the Redis connection string in the Windows environment variable
setx RedisConnectionString "localhost:6379" 
  • setx command is used to set the value of an environment variable. Since, Redis is running on localhost and the default port is 6379, I have set the value as “localhost:6379”.

  • Run the following command in Powershell Terminal to get the Redis connection string from the Windows environment variable

echo $env:RedisConnectionString
  • Or, run the following command in command prompt to get the Redis connection string from the Windows environment variable
echo %RedisConnectionString%

Step 4: Install Nuget Packages in CacheRefreshAzureFunction Project Install the following Nuget packages in the CacheRefreshAzureFunction project

Install-Package StackExchange.Redis
Install-Package Newtonsoft.Json

Step 5: Modify the Azure Function App in Local Machine

  • Click on the Solution file -> CacheRefreshFunction.sln to open the solution in Visual Studio
  • Rename the Function1.cs file to CacheRefreshTimerTriggerFunction.cs
  • Open the CacheRefreshTimerTriggerFunction.cs file
  • Modify the CacheRefreshTimerTriggerFunction class as follows
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json.Linq;
using StackExchange.Redis;
using System;
using System.Net.Http;
using System.Threading.Tasks;

namespace CacheRefreshAzureFunction
{
    public class CacheRefreshTimerTriggerFunction
    {
        private readonly ILogger _logger;
        private static readonly HttpClient httpClient = new HttpClient();

        public CacheRefreshTimerTriggerFunction(ILoggerFactory loggerFactory)
        {
            _logger = loggerFactory.CreateLogger<CacheRefreshTimerTriggerFunction>();
        }

        [Function("CacheRefreshTimerTriggerFunction")]
        public async Task RunAsync([TimerTrigger("*/5 * * * * *")] ScheduleTimerInfo timerInfo)
        {
            _logger.LogInformation($"Cache refresh function executed at: {DateTime.UtcNow}");

            try
            {
                // Fetch fresh data from API (e.g., Exchange Rates API)
                string apiUrl = "https://api.exchangerate-api.com/v4/latest/USD";
                string responseData = await httpClient.GetStringAsync(apiUrl);

                // Connect to Redis Cache
                // ConnectionMultiplexer object is used to connect to the Redis server
                // IDatabase object is used to interact with the Redis database
                string cacheConnectionString = Environment.GetEnvironmentVariable("RedisConnectionString")?.Trim();
  

                ConnectionMultiplexer redis = ConnectionMultiplexer.Connect(cacheConnectionString);
                IDatabase cache = redis.GetDatabase();

                // Store data in Redis Cache (Cache expiration: 5 minutes)
                // Cache expiration means that the stored data will be automatically removed from the cache after 5 minutes.
                // This helps ensure that the cache does not hold stale data and is refreshed periodically.
                cache.StringSet("ExchangeRates", responseData, TimeSpan.FromMinutes(5));

                // Retrieve data from cache
                _logger.LogInformation("Exchange rates updated successfully in Redis Cache.");

                // Retrieve data from cache
                string cachedData = cache.StringGet("ExchangeRates");


                // Show the cached data in the log

                if (!string.IsNullOrEmpty(cachedData))
                {
                    // Parse the JSON response
                    var exchangeRates = JObject.Parse(cachedData);

                    // Extract specific rates
                    var usdToBdt = exchangeRates["rates"]["BDT"].Value<decimal>();
                    var usdToCad = exchangeRates["rates"]["CAD"].Value<decimal>();

                    _logger.LogInformation("Current exchange rate:");
                    _logger.LogInformation($"USD to BDT: {usdToBdt}");
                    _logger.LogInformation($"USD to CAD: {usdToCad}");
                }
                else
                {
                    _logger.LogWarning("Cache is empty! Data retrieval failed.");
                }
            }
            catch (Exception ex)
            {
                _logger.LogError($"Error updating and retrieving cache: {ex.Message}");
            }

            if (timerInfo.IsPastDue)
            {
                _logger.LogWarning("The function is running later than scheduled.");
            }

            timerInfo.Last = DateTime.Now;
            timerInfo.Next = timerInfo.Last.AddSeconds(5);
        }
    }

    public class ScheduleTimerInfo
    {
        public DateTime Last { get; set; } = DateTime.Now;
        public DateTime Next { get; set; } = DateTime.Now.AddSeconds(5);

        // This property is used to determine if the timer is past due
        // It defines a method that returns true if the current time is greater than the Next property
        public bool IsPastDue => DateTime.Now > Next;
    }
}
  • Here, I have created a CacheRefreshTimerTriggerFunction class that contains a RunAsync method. The RunAsync method is decorated with the Function attribute and takes a ScheduleTimerInfo parameter. The RunAsync method is responsible for refreshing the cache at regular intervals. The method fetches fresh data from an API (e.g., Exchange Rates API), stores the data in a Redis cache, and retrieves the data from the cache. The method logs the exchange rates in the console.

  • [TimerTrigger(“*/5 * * * * *”)] attribute is used to specify the schedule of the function. In this case, the function will run every 5 seconds. */5 * * * * * is a cron expression.

  • You can use https://crontab.cronhub.io/ to generate the cron expression.

  • To know more about cron expression, read my article on Cron Expression

  • Now run the project and you will see the logs in the console as follows

[2025-02-23T01:42:52.477Z] Exchange rates updated successfully in Redis Cache.
[2025-02-23T01:42:52.503Z] Current exchange rate:
[2025-02-23T01:42:52.503Z] USD to CAD: 1.42
[2025-02-23T01:42:52.503Z] USD to BDT: 121.53

Step 6: Create Redis Cache in Azure

  • Log in to the Azure Portal (https://portal.azure.com/)
  • Click on Create a resource group and enter the resource group name, i.e., “myresourcegroup”, Location i.e., “West US” and click on Review + Create
  • Search “Azure Cache for Redis” and click on Create
  • Select Azure Cache for Redis
  • Select the Subscription, Resource Group i.e. myresourcegroup, DNS name i.e. exrateredis, Location i.e. West US, Cache type i.e. Standard, Pricing tier i.e. Basic C0 and click on Review + Create
  • Click on Create
  • Wait for the deployment to complete. It may take a few minutes.

Step 7: Deploy the Azure Function App to Azure

  • Right-click on the project in Visual Studio and select Publish
  • In the next screen, select Target as Azure and click on Next
  • Select Your Azure Subscription and click on Create New
  • Type the App Name, Subscription, Resource Group, Hosting Plan and click on Create as shown below
  • Here, I have created an Application Insights which is used to monitor the Azure Function App. I will use it later to see the logs as output.
  • Select Function Name and click on Next
  • Select Publish and click on Finish
  • Click on Publish button to deploy the Azure Function App to Azure
  • Wait for the deployment to complete
  • Click on the Browse button to open the Azure Function App in the browser

Step 8: Add Redis Connection String in Environment Variable in Azure Function App

  • Click on “Azure Cache for Redis” in the Azure Portal
  • Click on recent cache name i.e. exrateredis
  • Click Settings -> Authentication
  • Click on Access keys tab
  • Uncheck “Disable Access Keys Authentication” checkbox and click on Save
  • Copy the Primary connection string which is used to connect to the Redis Cache
  • Click on the Azure Function App i.e. “CacheRefreshAzureFunction198412” in the Azure Portal
  • Click on Settings -> Environment variables
  • Select App Settings and click on Add button
  • Enter the Name as “RedisConnectionString” and Value as the copied Primary connection string and click on Apply as shown below

Step 9: Set Firewall Rule in Azure Cache for Redis

  • Click on your deployed Azure Function i.e. “CacheRefreshAzureFunction198412” in the Azure Portal
  • Click on Properties tab of overview section, you will find the Outbound IP Addresses
  • Now, sort the Outbound IP Addresses and copy the first IP Address and last IP Address
  • Click on Redis Cache i.e. exrateredis in the Azure Portal
  • Click on Settings -> Firewall
  • Click on Add button
  • Enter the Name as “AzureFunctionApp” and Start IP as the copied first IP Address and End IP as the copied last IP Address.
  • Save the changes to apply the firewall rule.
  • Click on Settings -> Private Endpoint and click on Enable Public Network Access and click on Save as below

Step 10: Test the Azure Function App in Azure

  • Click on the Azure Function App i.e. “CacheRefreshAzureFunction198412” in the Azure Portal
  • Click on Functions -> CacheRefreshTimerTriggerFunction
  • Click on Logs to see the logs
  • You will see the logs as follows. You are actually seeing the logs in Application Insights.

Souce Code