Actually, in the microservice world, there are different ways in which we can introduce caching into an application architecture. The databases, caches, or the use cases, which I have explained here in this blog, are just for reference, but you have to be wise in choosing the right cache and the right database which fits your use case. The most common cache which we all use is the embedded cache.
The embedded cache is used in applications where we need to do some mappings, or reference lookups, which could be adding more load to the database by interacting with the database frequently.
To reduce the latency between the application and the database, we have to get a copy of the data from the database and put it in the application in the form of a cache. In an embedded cache, a copy of the data from the database is taken, and then we keep it in the application and use it for reference mappings or private information.
There could be a case where we want to have an application-specific cache within that particular instance you can use embedded cache in that particular scenario.
Client-Server cache, as the name suggests it follows a client-server architecture where you will have an application, you will have a database and the cache is between the application and the database. It acts as a server for the client or the application. If you compare this with the embedded cache the cache has now moved out of the application, and it is placed as a separate instance, which we can connect from our applications.
In many scenarios, we use these kinds of caches when we want to have multiple applications interact with the same cache so that we can reduce the memory used in terms of embedding these caches inside each application. That's why we have taken the cache from inside the application and kept it as a server where clients can interact and retrieve data from the cache.
Distributed cache as the name suggests, when we have multiple applications retrieve data from a server cache, we need to have multiple instances of the cache to serve multiple clients. That's when we go for a distributed cache, and there could be multiple nodes, where different instances of the cache are deployed into the cloud or servers, and they are served for our applications. So, where can we use distributed cache?
Distributed cash is popular when you want to have low latency and high-volume transactions to be processed and retrieved using a cache mainly in Microservices based applications. One other type or variant of a distributed cache is embedding the cache within an application and interacting with the other application, which just got deployed or which needs to be auto-discovered.
So, the cache is distributed, and it is shared between two different applications and is embedded. It's a flavor of embedded cache and a distributed cache within the ecosystem. The distributed cache again leads us to a challenging conversation on how we can add scalability when we want to dynamically scale up and scale down our caches.
Microservices need a place to store hot data, and it is always best not to have a state in a microservice so that it can be scaled up easily. For a cloud-native application as you know, we store state in a separate service, which in turn can be scaled to handle many microservices talking to a cluster.
We can also provide the easy provision. Self-managed and distributed cache, which helps the application store data in a key-value format.
We use distributed cache in a distributed system. The main purpose of using distributed cache is to minimize operation latency by avoiding unnecessary network calls and avoiding the computation logic. Distributed cache increases the responsiveness of the service in Java application development.
We normally use cache in microservice applications in front of datastore to fetch some frequently accessed data to reduce the latency and to avoid unnecessary load on the data store. The distributed cache will minimize the computational load on the datastore. Also, we use cache to store user sessions or user states.
I have mentioned, also the read/write policies on the Caches. Let’s say we have some arbitrary microservice that has multiple application servers. Also, assume that we have a database and a cache. In Cache Aside, all the writing happens to the database. So, let’s say when write happens then all the data from app servers are written into the database directly.
Then when the first time read happens then the read request will come to the app server and then the app server will call the cache and the cache server will return not found in this case as there is no data in the cache server as of now.
Then app server invokes the database service and it fetches the required data from the database and posts that the app server calls the cache server API to store the data into the cache. So, the app server finally returns the response to the user.
It is Cache Aside Architecture, and it is used where the data is not updated in the cache first time. In this architecture, the app server invokes the cache server twice as you can see from the above diagram. So, we can minimize the two calls to cache here by using Read through Architecture.
Redis cluster is a data sharding and replication solution with automatic management failover. Data sharding is the practice of dividing up a large database into many smaller portions and distributing it across numerous machines to improve an application's manageability, availability, performance, and load balancing.
The reason for data sharding is because after a certain scalability point, scaling horizontally by adding more machines is cheaper and more reachable than scaling vertically by adding more servers. Cluster mode, like Sentinel mode, provides high availability, but sharding enables for more data to be kept in the cluster.
Cluster improves the scalability of your application as data continued to grow.
In a cluster mode, nodes communicate with each other all the time and they are aware of each other not similar to Sentinel as there is no dedicated monitoring facility. Every Redis cluster node has two TCP open connections. The standard Redis TCP port is used to serve clients for example 4579 plus the port obtained by adding 10000 to the data port.
So, 14,579 in this example, the port 14,579 is used for a cluster bus which allows a node to node communication. Actually, for failure detection, Nodes take the help of cluster bus and for updating configuration, failover authorization and so forth. The cluster keeps this state stored in memory and maintained by itself.
In my example, here is a cluster of three masters and each Messer has one slave, which is used to replicate the master. All nodes communicate with each other through the cluster bus. Once you set up a cluster the cluster will have a sharding which means once you add a key to Redis, a hash slot gets calculated.
Primarily Redis calculate the slot where the key is going to land by taking the CRC 16 of the key module 16384 as CRC is a cyclic redundancy check function, by finding the hash slot for the keys, your data will automatically be spread across all those three slaves nodes.
It is easy Setting up a Redis cluster in AWS for scalability and high availability is fairly simple. AWS ElastiCache for Redis can be used to set up a Redis cluster. With cluster mode enabled, Redis cluster offers increased scalability, allowing you to easily scale your Redis cache data as your application grows by setting up replicas in multiple availability zones.
Spring caching provides an abstraction around the different caching mechanism, and it makes easy to change your implementation through configuration. You have to make sure that the Redis server is available for you. Spring boot only provides Redis integration, but you have to download the Redis server and configure it so that we can access it through our Java spring boot application development.
I have crated my controller class as below:
Don’t worry about how complex is your server layer, but in the end, we have to deal with requests and responses only. Our caching mechanism works on top of it. If the app server request finds the data inside the cache based upon the key, then it gets the information from the Cache.
If it does not find the data, then let the service layer do the complex functions to save the data into the cache so that next time the data gets retrieved from the cache.
First, you need to include @EnableCaching annotation in your spring boot main class. Spring boot will automatically scan your classpath and will try to find out what kind of cache you are using in the application. We have to add the Redis starter dependency in the pom file as below:
Spring will identify the Redis as your underlying caching mechanism and then it provides the autoconfiguration to connect to the Redis server. You don’t have to write Redis client code separately.
All you have to do is to add few additional configurations into your application.yml file. Now, I am explaining some generic annotations.
Here I’ve used @CacheConfig annotation for defining the cache name and this is a class-level configuration. Another annotation I have used for the method level i.e. @Cacheable to enable the caching in the method level. Let’s say if your service class is having 10 methods and you want to have the caching for 5 methods and to do that you have to add the @Cacheable annotation to those 5 methods only.
Here I have used key = “#id” which is optional and if you don’t method then spring will automatically provide the default key. As I am using Employee Cache here and usually, we identify employees through employee ID only, so I am using the expression language here as key = “#id”.
These are the configuration for the Redis server host port and cache name. This Redis time-to-live is for specifying the time in milliseconds up to which the key should be active.
Now, instead of storing all the data into the cache, we can load the data into the cache based on some conditions. Let’s say we want to cache those employee details whose age is less than 30 years. It is our condition and based on this we will populate the cache. And the rest of the data will be fetched from the database. We have to write the below annotation for this.
Now next scenario is we want to delete all the records from the DB, and once the records get deleted from DB and then all those data should be deleted from our cache as well. In this case, we have to use @CacheEvict annotation, and we have to pass the key and value to this. So, now if you delete any record from the database, the same record will now get deleted from the cache.
Here in the below example, I have added @CachePut annotation for updating or adding the particular entity in the cache. Mainly I have put in the repository layer just before the DB entry so that the required data should be stored in the Redis Cache.
Here I am using an incrementing timestamp to determine the order operations that occurred and it would require Redis to write, sync message publishes, and sync messages handling within a few hundred nanoseconds. Updating Redis consists of two Redis commands where one is to update the value and the other is to send a publish/subscribe message to notify the other client of the change.
Distributed cash is popular when you want to have low latency and high-volume transactions to be processed and retrieved using a cache mainly in Microservices based applications.
Embedded cache is used in applications where we need to do some mappings. Client-Server cache as the name suggests it follows a client-server architecture and then distributed cache as the name suggests when we have multiple applications retrieve data from a server cache.
Microservices need a place to store hot data and it is always best not to have a state in a microservice so that it can be scaled up easily. For a cloud-native application, as you know we store state in a separate service
Redis cluster is a data sharding and replication solution with automatic management failover. Data sharding is the practice of dividing up a large database into many smaller portions and distributing it across numerous machines to improve an application's manageability.
Please refer to this entire blog to understand how to implement Redis in your spring boot application, and you will get an idea about all these annotations.