Backend Development 15 min read

Design and Implementation of a Friend Follow Microservice Using MySQL and Redis

This article explains how to design and implement a friend follow microservice by combining MySQL for persistent storage with Redis sets for efficient follow/unfollow operations and mutual follow queries, covering database schema, Maven dependencies, Spring Boot configuration, RedisTemplate setup, service and controller logic, and testing procedures.

Java Captain
Java Captain
Java Captain
Design and Implementation of a Friend Follow Microservice Using MySQL and Redis

Requirement Analysis

Friend (follow) functionality is a core feature in social applications, including follow/unfollow, followers, following, mutual follows, etc.

While a relational database can store simple follow lists, querying mutual follows among multiple users is inefficient; Redis sets provide fast intersection, union, and difference operations for this purpose.

Design Idea

The solution combines MySQL for persistent storage and Redis Sets for set operations. Each user maintains two Redis sets: one for the users they follow and another for their followers. Basic Redis commands such as SADD, SREM, SCARD, SISMEMBER, SMEMBERS and SINTER are used.

Database Table Design

CREATE TABLE `t_follow` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) DEFAULT NULL COMMENT '当前登录用户的id',
  `follow_user_id` int(11) DEFAULT NULL COMMENT '当前登录用户关注的用户的id',
  `is_valid` tinyint(1) DEFAULT NULL COMMENT '关注状态,0-没有关注,1-关注了',
  `create_date` datetime DEFAULT NULL,
  `update_date` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='用户和用户关注表';

Microservice Setup

Dependencies (pom.xml)

org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
org.springframework.boot
spring-boot-starter-web
mysql
mysql-connector-java
org.springframework.boot
spring-boot-starter-data-redis
org.mybatis.spring.boot
mybatis-spring-boot-starter
com.zjq
commons
1.0-SNAPSHOT
com.battcn
swagger-spring-boot-starter

Spring Boot Configuration (application.yml)

server:
  port: 7004
spring:
  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:
    port: 6379
    host: localhost
    timeout: 3000
    password: 123456
    database: 2
  swagger:
    base-package: com.zjq.follow
    title: 好用功能微服务API接口文档
eureka:
  client:
    service-url:
      defaultZone: http://localhost:7000/eureka/

RedisTemplate Configuration

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;

@Configuration
public class RedisTemplateConfiguration {
    @Bean
    public RedisTemplate
redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate
redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        Jackson2JsonRedisSerializer
jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
}

Follow/Unfollow Implementation

Service Layer

package com.zjq.seckill.service;

import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;

@Service
public class FollowService {
    @Value("${service.name.ms-oauth-server}")
    private String oauthServerName;
    @Value("${service.name.ms-diners-server}")
    private String dinersServerName;
    @Resource
    private RestTemplate restTemplate;
    @Resource
    private FollowMapper followMapper;
    @Resource
    private RedisTemplate redisTemplate;

    public ResultInfo follow(Integer followUserId, int isFollowed, String accessToken, String path) {
        // validation, load login user, check existing follow record
        // add, remove or re‑add follow in MySQL and Redis sets
    }
    private void addToRedisSet(Integer dinerId, Integer followUserId) {
        redisTemplate.opsForSet().add(RedisKeyConstant.following.getKey() + dinerId, followUserId);
        redisTemplate.opsForSet().add(RedisKeyConstant.followers.getKey() + followUserId, dinerId);
    }
    private void removeFromRedisSet(Integer dinerId, Integer followUserId) {
        redisTemplate.opsForSet().remove(RedisKeyConstant.following.getKey() + dinerId, followUserId);
        redisTemplate.opsForSet().remove(RedisKeyConstant.followers.getKey() + followUserId, dinerId);
    }
    private SignInUserInfo loadSignInDinerInfo(String accessToken) {
        // call oauth server to get user info
    }
}

Controller Layer

package com.zjq.seckill.controller;

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());
    }
}

Gateway Routing

spring:
  cloud:
    gateway:
      routes:
        - id: ms-follow
          uri: lb://ms-follow
          predicates:
            - Path=/follow/**
          filters:
            - StripPrefix=1

Common Follow List

The service reads the logged‑in user’s following set and another user’s following set from Redis, computes the intersection with SINTER, and then calls the user service to retrieve basic profile information.

public ResultInfo findCommonsFriends(Integer userId, String accessToken, String path) {
    Set
userIds = redisTemplate.opsForSet().intersect(loginUserKey, userKey);
    // remote call to user service, transform result
}

Testing

After starting the registry, gateway, authentication service, and follow microservice, the author performed follow operations for several users, verified the Redis sets and MySQL records, and confirmed that mutual follow queries returned the expected results.

JavaRedisSpring BootMySQLMicroserviceFollow Feature
Java Captain
Written by

Java Captain

Focused on Java technologies: SSM, the Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading; occasionally covers DevOps tools like Jenkins, Nexus, Docker, ELK; shares practical tech insights and is dedicated to full‑stack Java development.

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.