In the previous article, we described how to integrate EhCache in Spring Boot. Since we use EhCache, we should talk about some of its advanced features, otherwise we should use the default ConcurrentHashMap. This article does not cover the general details of how to drop files in EhCache and how to configure various expiration parameters. This part is left for readers to learn. If you don’t know how to do this, you can refer to the official documentation here.

So what are we going to do today? First think about a scene, when we use the EhCache, before the cache expiration can effectively reduce the access to the database, but usually we will application deployed in a production environment, in order to realize the application of high availability (a machine hang up and application needs to be available), is certainly will deploy a number of different processes to run, so in this case, The cache in each process is maintained independently when data is updated. If these processes cache synchronization mechanisms, then there will be a problem with the logic that the cache will always be returned to the user with an invalid cache because the cache is not updated. Therefore, when using EhCache, this article will discuss how to set up a cluster of in-process cache EnCache and configure their synchronization strategy.

Because the following is the process of building a cluster, you must use multi-machine debugging to avoid unnecessary errors.

Began to try

The implementation of this article will be based on the foundation engineering of the previous article. Let’s review the program elements from the previous article:

Definition of User entity

@Entity @Data @NoArgsConstructor public class User { @Id @GeneratedValue private Long id; private String name; private Integer age; public User(String name, Integer age) { this.name = name; this.age = age; }}Copy the code

Data access implementation for User entities (including caching annotations)

@CacheConfig(cacheNames = "users")
public interface UserRepository extends JpaRepository<User, Long> {

    @Cacheable
    User findByName(String name);

}Copy the code

Here’s how to transform the project:

Step 1: Implement the Serializable interface for cache objects that need to be synchronized

@Entity @Data @NoArgsConstructor public class User implements Serializable { @Id @GeneratedValue private Long id; private String name; private Integer age; public User(String name, Integer age) { this.name = name; this.age = age; }}Copy the code

Note: If this step is not done, subsequent cache cluster passes will result in serialization and deserialization related exceptions as User objects are transferred

Step 2: Reorganize the ehCache configuration file. We tried to set up the cluster manually. Different instances will have different configuration information in the network configuration, so we set up different configuration files for different instances. Like this:

For example 1, use ehcache-1.xml

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="ehcache.xsd"> <cache name="users" maxEntriesLocalHeap="200" timeToLiveSeconds="600"> <cacheEventListenerFactory class="net.sf.ehcache.distribution.RMICacheReplicatorFactory" properties="replicateAsynchronously=true, replicatePuts=true, replicateUpdates=true, replicateUpdatesViaCopy=false, replicateRemovals=true "/> </cache> <cacheManagerPeerProviderFactory Class = "net. Sf. Ehcache. Distribution. RMICacheManagerPeerProviderFactory" properties = "hostName = 10.10.0.100, port = 40001, SocketTimeoutMillis = 2000, peerDiscovery = manual, rmiUrls = / / 10.10.0.101:40001 / users "/ > < / ehcache >Copy the code

For example 2, use ehcache-2.xml

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="ehcache.xsd"> <cache name="users" maxEntriesLocalHeap="200" timeToLiveSeconds="600"> <cacheEventListenerFactory class="net.sf.ehcache.distribution.RMICacheReplicatorFactory" properties="replicateAsynchronously=true, replicatePuts=true, replicateUpdates=true, replicateUpdatesViaCopy=false, replicateRemovals=true "/> </cache> <cacheManagerPeerProviderFactory Class = "net. Sf. Ehcache. Distribution. RMICacheManagerPeerProviderFactory" properties = "hostName = 10.10.0.101, port = 40001, SocketTimeoutMillis = 2000, peerDiscovery = manual, rmiUrls = / / 10.10.0.100:40001 / users "/ > < / ehcache >Copy the code

Configuration description:

  • cacheThe tag defines a cache named Users, and here we add a subtag definitioncacheEventListenerFactoryThis tag is used to define the handling policy of the cache event listener. It takes the following parameters to set the synchronization policy of the cache:
    • ReplicatePuts: Whether a new element is copied to other peers when it is added to the cache. The default is true.
    • ReplicateUpdates: Whether to replicate an element that already exists in the cache when overwritten. The default is true.
    • ReplicateRemovals: Specifies whether the element is replicated when it is removed. The default is true.
    • ReplicateAsynchronously: specifies whether the replication mode is asynchronous or synchronous.  Specifies whether the replication mode is false. The default is true.
    • ReplicatePutsViaCopy: Specifies whether a new element is copied to another cache.  The value is replicated when specified as true. The default value is true.
    • ReplicateUpdatesViaCopy: Specifies whether an element is replicated when copied to another cache.  The value is replicated when specified as true.
  • There’s a new onecacheManagerPeerProviderFactoryTag to specify the cluster information and the cache information to be synchronized, where:
    • HostName: indicates the hostName of the current instance
    • Port: the port number used by the current instance to synchronize the cache
    • SocketTimeoutMillis: indicates the Socket timeout of the synchronization cache
    • PeerDiscovery: Indicates the discovery mode of a cluster node. The discovery mode can be manual or automatic. This mode is manually specified
    • RmiUrls: When peerDiscovery is set to Manual, this is used to specify which cache nodes need to be synchronized if there are more than one|The connection

Step 3: Package deployment and startup. Packaging is not a big problem, the main cache configuration content is somewhat different, so in the mode of the specified node, you need to separate it, and then use startup parameters to control the reading of different configuration files. Like this:

-Dspring.cache.ehcache.config=classpath:ehcache-1.xml
-Dspring.cache.ehcache.config=classpath:ehcache-2.xmlCopy the code

Step 4: Implement several interfaces to verify the synchronization of the cache

@RestController static class HelloController { @Autowired private UserRepository userRepository; @GetMapping("/create") public void create() { userRepository.save(new User("AAA", 10)); } @GetMapping("/find") public User find() { User u1 = userRepository.findByName("AAA"); System.out.println(" query AAA user: "+ u1.getage ()); return u1; }}Copy the code

Validation logic:

  1. Start Start both instances with the command parameters described in step 3
  2. Of instance 1/createInterface to create a piece of data
  3. Of instance 1/findInterface, instance 1 caches User, and synchronizes cached information to instance 2. There will be SQL query statements in instance 1
  4. Of instance 2/findInterface, because the cache cluster synchronizes the User’s information, there will be no SQL statement in this query in instance 2

Think further

At the time of the last post, there were comments on the official account asking, what should we do after the data update?

Once you’ve built the cache cluster, it’s easier. In this example, you need to do two things:

  1. saveOperation increase@CachePutAnnotations to put the results back into the cache after the update operation is complete
  2. Ensure that the cache event listens to replicateUpdates=true so that data is guaranteed to be replicated to other nodes after updates

This prevents dirty data from being cached, but this approach is not very good because the synchronization of the cache cluster still takes time and there are transient inconsistencies. At the same time, the in-process cache has to be consumed on every instance, and it is always not economical to store it in large quantities. As a result, in-process caching is often not the primary caching tool. Another more important use of caching will be discussed in the next article!

Welcome to Spring Boot 2.x Basics tutorial

The resources

  • EhCache Distributed cache/cache cluster
  • Java RMI: RMI Connection refused to host: 127.0.0.1 The exception is resolved

    Spring Boot 2.x basic tutorial: Using EhCache cluster. Welcome to pay attention to my official account: Program ape DD, for exclusive learning resources and daily dry goods push. Click through to the tutorial catalog for this series.