Implementing a High‑Frequency Like Feature with Spring Cloud, Redis Cache, and Periodic Persistence

This article demonstrates how to design and implement a high‑frequency like/unlike feature using Spring Cloud, Redis for caching, MySQL for persistence, and Quartz for scheduled data synchronization, covering cache design, data structures, service interfaces, database schema, and deployment scripts.

Top Architect
Top Architect
Top Architect
Implementing a High‑Frequency Like Feature with Spring Cloud, Redis Cache, and Periodic Persistence

Based on Spring Cloud, the article shows how to store user like and unlike actions in Redis first and then persist them to a relational database every two hours, reducing database load for high‑frequency operations.

1. Redis Cache Design and Implementation

Install Redis via Docker: docker run -d -p 6379:6379 redis:4.0.8 Start an existing Redis instance: redis-server Add the Redis starter dependency to pom.xml:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

Enable caching in the Spring Boot application: @EnableCaching Create a Redis configuration class ( RedisConfig) that defines RedisTemplate and StringRedisTemplate beans with JSON serialization:

public class RedisConfig {
    @Bean
    @ConditionalOnMissingBean(name = "redisTemplate")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        serializer.setObjectMapper(om);
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
        template.setKeySerializer(serializer);
        template.setValueSerializer(serializer);
        template.afterPropertiesSet();
        return template;
    }
    @Bean
    @ConditionalOnMissingBean(StringRedisTemplate.class)
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory factory) {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(factory);
        return template;
    }
}

Redis supports five data structures; the article chooses Hash to store each like record because it allows easy retrieval of all entries via a single key.

1.4 Like Data Storage Format in Redis

Key format: likedUserId::likedPostId, value: 1 for like, 0 for unlike. This enables quick extraction of both IDs by splitting on ::.

1.5 Redis Operations

Define a RedisService interface with methods for saving, unliking, deleting, incrementing/decrementing counts, and fetching all cached data.

public interface RedisService {
    void saveLiked2Redis(String likedUserId, String likedPostId);
    void unlikeFromRedis(String likedUserId, String likedPostId);
    void deleteLikedFromRedis(String likedUserId, String likedPostId);
    void incrementLikedCount(String likedUserId);
    void decrementLikedCount(String likedUserId);
    List<UserLike> getLikedDataFromRedis();
    List<LikedCountDTO> getLikedCountFromRedis();
}

Implement the interface in RedisServiceImpl using RedisTemplate to operate on the hash maps MAP_USER_LIKED and MAP_USER_LIKED_COUNT.

public class RedisServiceImpl implements RedisService {
    @Autowired
    RedisTemplate redisTemplate;
    @Override
    public void saveLiked2Redis(String likedUserId, String likedPostId) {
        String key = RedisKeyUtils.getLikedKey(likedUserId, likedPostId);
        redisTemplate.opsForHash().put(RedisKeyUtils.MAP_KEY_USER_LIKED, key, LikedStatusEnum.LIKE.getCode());
    }
    // other methods omitted for brevity
}

Utility class RedisKeyUtils builds the composite key:

public static String getLikedKey(String likedUserId, String likedPostId) {
    return likedUserId + "::" + likedPostId;
}

Enum LikedStatusEnum defines the status codes:

public enum LikedStatusEnum {
    LIKE(1, "点赞"),
    UNLIKE(0, "取消点赞/未点赞");
    private Integer code;
    private String msg;
    // constructor and getters
}

2. Database Design

Create a table user_like with fields for primary key, liked user ID, liked post ID, status, and timestamps.

create table `user_like`(
    `id` int not null auto_increment,
    `liked_user_id` varchar(32) not null comment '被点赞的用户id',
    `liked_post_id` varchar(32) not null comment '点赞的用户id',
    `status` tinyint default '1' comment '点赞状态,0取消,1点赞',
    `create_time` timestamp not null default current_timestamp comment '创建时间',
    `update_time` timestamp not null default current_timestamp on update current_timestamp comment '修改时间',
    primary key(`id`),
    index `liked_user_id`(`liked_user_id`),
    index `liked_post_id`(`liked_post_id`)
) comment '用户点赞表';

Corresponding JPA entity UserLike maps these columns.

3. Database Operations

Define LikedService with CRUD methods and a method to transfer cached data to the database.

public interface LikedService {
    UserLike save(UserLike userLike);
    List<UserLike> saveAll(List<UserLike> list);
    Page<UserLike> getLikedListByLikedUserId(String likedUserId, Pageable pageable);
    Page<UserLike> getLikedListByLikedPostId(String likedPostId, Pageable pageable);
    UserLike getByLikedUserIdAndLikedPostId(String likedUserId, String likedPostId);
    void transLikedFromRedis2DB();
    void transLikedCountFromRedis2DB();
}

Implementation LikedServiceImpl reads data from RedisService, merges it with existing records, and persists the result.

4. Periodic Persistence with Quartz

Add the Quartz starter dependency:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-quartz</artifactId>
</dependency>

Configure a job and trigger that runs every two hours:

@Configuration
public class QuartzConfig {
    private static final String LIKE_TASK_IDENTITY = "LikeTaskQuartz";
    @Bean
    public JobDetail quartzDetail() {
        return JobBuilder.newJob(LikeTask.class)
                .withIdentity(LIKE_TASK_IDENTITY)
                .storeDurably()
                .build();
    }
    @Bean
    public Trigger quartzTrigger() {
        SimpleScheduleBuilder schedule = SimpleScheduleBuilder.simpleSchedule()
                .withIntervalInHours(2)
                .repeatForever();
        return TriggerBuilder.newTrigger()
                .forJob(quartzDetail())
                .withIdentity(LIKE_TASK_IDENTITY)
                .withSchedule(schedule)
                .build();
    }
}

The job class LikeTask extends QuartzJobBean and calls the service methods to sync data.

public class LikeTask extends QuartzJobBean {
    @Autowired
    LikedService likedService;
    @Override
    protected void executeInternal(JobExecutionContext context) {
        log.info("LikeTask execution at {}", new Date());
        likedService.transLikedFromRedis2DB();
        likedService.transLikedCountFromRedis2DB();
    }
}

Finally, the article notes that the whole like/unlike operation should be atomic and suggests adding a shutdown hook to flush any remaining Redis data before the application exits.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

CacheredismysqlQuartzLikeFeature
Top Architect
Written by

Top Architect

Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.