Design and Implementation of a Follow/Unfollow Microservice Using MySQL and Redis
This article details the requirements analysis, design approach, database schema, and step‑by‑step implementation of a follow/unfollow microservice using MySQL for persistence and Redis Sets for efficient set operations, including Maven dependencies, Spring Boot configuration, RedisTemplate setup, service, controller, and testing of common‑follow queries.
Requirement Analysis – In social applications, a friend (follow) feature typically includes follow/unfollow, viewing followers/followings, mutual follows, and recommendations. While a relational database can store simple follower lists, querying mutual followers or common followings across multiple users is inefficient.
Redis Sets provide native set operations (intersection, union, difference) that make these queries fast and simple.
Design Idea – Combine MySQL for durable storage with Redis for fast set operations. Each user maintains two Redis Sets: one for the users they follow and another for their followers. Redis commands such as SADD , SREM , SCARD , SISMEMBER , SMEMBERS , and SINTER are used to manage and query these sets.
Database Table Design
CREATE TABLE `t_follow` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) DEFAULT NULL COMMENT 'Current logged‑in user ID',
`follow_user_id` int(11) DEFAULT NULL COMMENT 'ID of the user being followed',
`is_valid` tinyint(1) DEFAULT NULL COMMENT 'Follow status, 0‑unfollow, 1‑follow',
`create_date` datetime DEFAULT NULL,
`update_date` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='User follow relationship';Microservice Setup
Adding Maven Dependencies
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>redis-seckill</artifactId>
<groupId>com.zjq</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ms-follow</artifactId>
<dependencies>
<!-- Eureka client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- Spring Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- MySQL driver -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- Spring Data Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- MyBatis Spring Boot Starter -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<!-- Commons project (shared utilities) -->
<dependency>
<groupId>com.zjq</groupId>
<artifactId>commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- Swagger UI -->
<dependency>
<groupId>com.battcn</groupId>
<artifactId>swagger-spring-boot-starter</artifactId>
</dependency>
</dependencies>
</project>Spring Boot Configuration (application.yml)
server:
port: 7004 # service port
spring:
application:
name: ms-follow
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
url: jdbc:mysql://127.0.0.1:3306/seckill?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useUnicode=true&useSSL=false
redis:
host: localhost
port: 6379
password: 123456
database: 2
timeout: 3000
swagger:
base-package: com.zjq.follow
title: Follow Service API Documentation
eureka:
instance:
prefer-ip-address: true
instance-id: ${spring.cloud.client.ip-address}:${server.port}
client:
service-url:
defaultZone: http://localhost:7000/eureka/
mybatis:
configuration:
map-underscore-to-camel-case: true
logging:
pattern:
console: '%d{HH:mm:ss} [%thread] %-5level %logger{50} - %msg%n'Redis Configuration Class
package com.zjq.seckill.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* RedisTemplate configuration
*/
@Configuration
public class RedisTemplateConfiguration {
@Bean
public RedisTemplate
redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate
redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
// Use Jackson JSON serializer for values
Jackson2JsonRedisSerializer
jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
// Key and hash key use String serializer
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}Service Layer (FollowService) – Handles follow/unfollow logic, updates MySQL via MyBatis mapper, and synchronizes Redis Sets. It also provides a method to compute common followings using redisTemplate.opsForSet().intersect and calls the user service to fetch user details.
package com.zjq.seckill.service;
import com.zjq.commons.model.domain.ResultInfo;
import com.zjq.commons.model.pojo.Follow;
import com.zjq.seckill.mapper.FollowMapper;
import org.springframework.stereotype.Service;
import org.springframework.data.redis.core.RedisTemplate;
import javax.annotation.Resource;
import java.util.Set;
@Service
public class FollowService {
@Resource private FollowMapper followMapper;
@Resource private RedisTemplate
redisTemplate;
// ... follow/unfollow methods omitted for brevity ...
public ResultInfo findCommonsFriends(Integer userId, String accessToken, String path) {
// load logged‑in user, get Redis keys, intersect sets, call user service, return result
}
}Controller Layer (FollowController)
package com.zjq.seckill.controller;
import com.zjq.commons.model.domain.ResultInfo;
import com.zjq.seckill.service.FollowService;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
@RestController
public class FollowController {
@Resource private FollowService followService;
@Resource private HttpServletRequest request;
@PostMapping("/{followUserId}")
public ResultInfo follow(@PathVariable Integer followUserId,
@RequestParam int isFollowed,
String access_token) {
return followService.follow(followUserId, isFollowed, access_token, request.getServletPath());
}
@GetMapping("commons/{userId}")
public ResultInfo findCommonsFriends(@PathVariable Integer userId, String access_token) {
return followService.findCommonsFriends(userId, access_token, request.getServletPath());
}
}Gateway Routing – The Spring Cloud Gateway routes requests with the prefix /follow/** to the ms-follow service.
spring:
cloud:
gateway:
discovery:
locator:
enabled: true
lower-case-service-id: true
routes:
- id: ms-follow
uri: lb://ms-follow
predicates:
- Path=/follow/**
filters:
- StripPrefix=1Testing & Verification – After starting the Eureka server, gateway, authentication service, and the follow microservice, the author performed a series of follow actions (e.g., user 5 follows user 1, user 7 follows user 1, etc.) and verified the Redis Sets and MySQL records. Subsequent queries demonstrated correct computation of common followings (e.g., users 5 and 7 share follows of 1 and 2).
Overall, the article provides a complete end‑to‑end example of building a scalable follow feature using a hybrid MySQL‑Redis architecture within a Spring Cloud microservice ecosystem.
Architecture Digest
Focusing on Java backend development, covering application architecture from top-tier internet companies (high availability, high performance, high stability), big data, machine learning, Java architecture, and other popular fields.
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.