第一、使用Spring Boot将数据库查询的数据存入Redis

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

Spring Boot会自动配置Redis连接,并提供RedisTemplate和StringRedisTemplate用于操作Redis。

数据库查询并存入Redis

首先,我们需要编写一个服务类,用于从数据库中查询数据并存入Redis中。假设我们有一个User实体类和一个UserRepository接口用于操作数据库:

@Entity
public class User {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String username;
    private String email;
    
    // getters and setters
}

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    // 自定义查询方法
    User findByUsername(String username);
}

接下来,编写一个服务类UserService,在Spring Boot启动时从数据库中查询用户数据并存入Redis中:

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;
    
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    
    @PostConstruct
    public void init() {
        List<User> userList = userRepository.findAll();
        for (User user : userList) {
            stringRedisTemplate.opsForValue().set("user:" + user.getId(), user.getUsername());
        }
    }
}

在上面的代码中,我们通过@PostConstruct注解标记的init方法在Spring容器初始化时执行,从数据库中查询所有用户数据并存入Redis中。我们使用StringRedisTemplate向Redis中存入数据,键值格式为user:{id},值为用户名。


第二、下面是一个基本的实现方案:示例提供了一个基本框架

  1. 配置Spring Boot应用
  • 配置数据源(DataSource)以连接到数据库。
  • 配置Redis客户端。
  1. 编写JDBC查询逻辑
  • 使用JdbcTemplateNamedParameterJdbcTemplate来执行SQL查询。
  • 采用分页或游标的方式获取数据,以便每次只加载一部分数据到内存中。
  1. 将数据写入Redis
  • 使用RedisTemplate或操作Redis的API来存储数据。
  1. 实现逻辑
  • 在一个循环中逐批次地读取数据,并将其写入Redis。

以下是一个示例代码片段,用于从JDBC数据源读取数据并将其写入Redis:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Map;

@Service
public class DataMigrationService {

    private static final int BATCH_SIZE = 1000; // 每次查询的数据量
    private static final String QUERY = "SELECT * FROM my_table LIMIT ?, ?"; // 分页查询语句

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 将数据从数据库迁移到Redis
     */
    @Async
    public void migrateDataToRedis() {
        int offset = 0;
        do {
            List<Map<String, Object>> rows = jdbcTemplate.queryForList(QUERY, offset, BATCH_SIZE);
            for (Map<String, Object> row : rows) {
                // 假设我们使用row中的"id"作为Redis的键
                Long id = (Long) row.get("id");
                // 存储到Redis
                redisTemplate.opsForValue().set("data:" + id, row);
            }
            offset += BATCH_SIZE;
        } while (rows.size() == BATCH_SIZE); // 如果查询结果等于BATCH_SIZE,则继续查询
    }
}

注意事项:

  • 异步处理:可以使用@Async注解来异步执行迁移任务,以减少对主应用程序的影响。
  • 错误处理:需要考虑异常处理机制,确保数据完整性和一致性。
  • 性能优化:根据实际情况调整BATCH_SIZE的大小,找到最佳的平衡点。
  • 资源管理:确保在处理大量数据时合理管理数据库连接和Redis连接,避免资源耗尽。

以上示例提供了一个基本框架,实际应用中可能需要根据具体的业务需求进行调整。


第三,一个真实的案例,查询数据库放入到布隆过滤器中。

通过runner来支持启动时把查询的数据放到了布隆过滤器。

在这个项目里,还有每天的定时任务来执行该动作。

NbigscreenBloom.java

package com.cqsym.nbigscreen.bloomfilter;

import com.cqsym.nbigscreen.config.DataSourceRepository;
import com.google.common.base.Charsets;
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.concurrent.TimeUnit;

@Component
public class NbigscreenBloom {
    private static final Logger log = LoggerFactory.getLogger(NbigscreenBloom.class);
    private static final int BATCH_SIZE = 10000; // 每次查询的数据量
    private static final int BloomFilter_init_size = 1000000; // 初始化布隆过滤数据量,100万。
    private static final int BloomFilter_size = BloomFilter_init_size * 2; // 布隆过滤数据量大小,200万。
    public static BloomFilter tddBloomFilter;
    public static BloomFilter toBloomFilter;
    @Autowired
    @Qualifier(value="secondDataSourceRepository")
    private DataSourceRepository secondDataSourceRepository;



    public void initForDay() {
        //tddBloomFilter = BloomFilter.create(Funnels.stringFunnel(Charsets.UTF_8), 10000000, 0.01);
        tddBloomFilter = BloomFilter.create(Funnels.longFunnel(), BloomFilter_size, 0.01);
        toBloomFilter = BloomFilter.create(Funnels.longFunnel(), BloomFilter_size, 0.01);

        try {
            log.info("initForDay ---------- 睡5秒 开始 ... ");
            TimeUnit.SECONDS.sleep(5);
            log.info("initForDay ---------- 睡5秒 完成。 ");
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        log.info("initForDay ---------- start initForTdd() ... ");
        new Thread(() -> {
            log.info("initForDay1 ---------- start initForTdd() ... ");
            initForTdd();
            log.info("initForDay1 ---------- end initForTdd(). ");
        },"initForTdd线程").start();
        log.info("initForDay ---------- end initForTdd(). ");

        log.info("initForDay ---------- start initForTo() ... ");
        new Thread(() -> {
            log.info("initForDay1 ---------- start initForTo() ... ");
            initForTo();
            log.info("initForDay1 ---------- end initForTo(). ");
        },"initForTo线程").start();
        log.info("initForDay ---------- end initForTo(). ");

    }

    public void initForTdd() {
        final String sql0 = "select count(1) from t_dispatch_detail ";
        final String sql = "select tdd_id from t_dispatch_detail limit ? , ? ";
        //int offset = 0;
        //int offset = 20000000;
        int offset = secondDataSourceRepository.queryForListSingleColumn(sql0, Integer.class).get(0)-BloomFilter_init_size;
        log.info("initForTdd ---------- First offset: " + offset);
        List<Long> rows;
        do {
            log.info("initForTdd ---------- offset: " + offset);
            rows = secondDataSourceRepository.queryForListSingleColumn(sql, Long.class, offset, BATCH_SIZE);
            for (Long row : rows) {
                tddBloomFilter.put(row);
            }
            offset += BATCH_SIZE;
        } while (rows.size() == BATCH_SIZE);
        log.info("initForTdd ---------- end");
    }

    public void initForTo() {
        final String sql0 = "select count(1) from t_order ";
        final String sql = "select to_id from t_order limit ? , ? ";
        //int offset = 0;
        //int offset = 12000000;
        int offset = secondDataSourceRepository.queryForListSingleColumn(sql0, Integer.class).get(0)-BloomFilter_init_size;
        log.info("initForTo ---------- First offset: " + offset);
        List<Long> rows;
        do {
            log.info("initForTo ---------- offset: " + offset);
            rows = secondDataSourceRepository.queryForListSingleColumn(sql, Long.class, offset, BATCH_SIZE);
            for (Long row : rows) {
                toBloomFilter.put(row);
            }
            offset += BATCH_SIZE;
        } while (rows.size() == BATCH_SIZE);
        log.info("initForTo ---------- end");
    }



}