How to Build a Scalable Like System with Spring Cloud, Redis, and Quartz
This article walks through designing and implementing a high‑traffic like/unlike feature for a social platform using Spring Cloud, Redis caching, MySQL persistence, and a Quartz‑based scheduled job to sync data every two hours.
This article introduces the design of a large‑scale social platform like system based on Spring Cloud, using Redis for caching and persisting data to a database at regular intervals.
Because like and unlike are high‑frequency operations, writing directly to the database would degrade performance, so a Redis cache is introduced.
Data is flushed from Redis to the database every two hours (the interval can be adjusted).
The system must record the liker ID, the liked user ID, and the like status, not just a simple count.
The tutorial is divided into four parts:
Redis cache design and implementation
Database design
Database operations
Scheduled task for persistence
Redis cache design and implementation
Redis installation (Docker command): docker run -d -p 6379:6379 redis:4.0.8 Start Redis if already installed: redis-server Add the Spring Boot Redis starter dependency:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>Enable caching in the main application class:
@SpringBootApplication
@EnableDiscoveryClient
@EnableSwagger2
@EnableFeignClients(basePackages = "com.solo.coderiver.project.client")
@EnableCaching
public class UserApplication {
public static void main(String[] args) {
SpringApplication.run(UserApplication.class, args);
}
}Create a Redis configuration class:
public class RedisConfig {
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
template.setKeySerializer(jackson2JsonRedisSerializer);
template.setValueSerializer(jackson2JsonRedisSerializer);
template.setHashKeySerializer(jackson2JsonRedisSerializer);
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
@Bean
@ConditionalOnMissingBean(StringRedisTemplate.class)
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}Redis supports five data structures (String, List, Set, Hash, Zset). For the like feature, a Hash is chosen because all like records can be stored under a single key and retrieved efficiently.
Key format: likedUserId::likedPostId with value 1 for like and 0 for unlike.
Database design
The MySQL table user_like stores the like relationship:
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(1) 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 '用户点赞表';The corresponding JPA entity:
@Entity
@Data
public class UserLike {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String likedUserId;
private String likedPostId;
private Integer status = LikedStatusEnum.UNLIKE.getCode();
public UserLike() {}
public UserLike(String likedUserId, String likedPostId, Integer status) {
this.likedUserId = likedUserId;
this.likedPostId = likedPostId;
this.status = status;
}
}Database operations
Define a RedisService interface to encapsulate Redis actions:
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();
}Implementation uses RedisTemplate to operate on two 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 class RedisKeyUtils {
public static final String MAP_KEY_USER_LIKED = "MAP_USER_LIKED";
public static final String MAP_KEY_USER_LIKED_COUNT = "MAP_USER_LIKED_COUNT";
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;
LikedStatusEnum(Integer code, String msg) { this.code = code; this.msg = msg; }
public Integer getCode() { return code; }
}The LikedService provides CRUD operations and methods to transfer data from Redis to MySQL:
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();
}Scheduled task for persistence
Add Quartz dependency:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>Configure a Quartz job 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 scheduleBuilder = SimpleScheduleBuilder.simpleSchedule()
.withIntervalInHours(2)
.repeatForever();
return TriggerBuilder.newTrigger()
.forJob(quartzDetail())
.withIdentity(LIKE_TASK_IDENTITY)
.withSchedule(scheduleBuilder)
.build();
}
}Task implementation synchronizes Redis data to the database:
public class LikeTask extends QuartzJobBean {
@Autowired
LikedService likedService;
private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
log.info("LikeTask-------- {}", sdf.format(new Date()));
likedService.transLikedFromRedis2DB();
likedService.transLikedCountFromRedis2DB();
}
}Running the scheduled job ensures that like records and like counts stored in Redis are periodically flushed into the MySQL user_like table, providing durability while keeping the hot path fast.
The article concludes by inviting readers to share improvements or alternative implementations.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Programmer DD
A tinkering programmer and author of "Spring Cloud Microservices in Action"
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
