Backend Development 17 min read

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.

Architecture Digest
Architecture Digest
Architecture Digest
Design and Implementation of a Follow/Unfollow Microservice Using MySQL and Redis

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=1

Testing & 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.

JavaRedisSpring BootMySQLMicroserviceFollow Service
Architecture Digest
Written by

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.

0 followers
Reader feedback

How this landed with the community

login 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.