当前位置: 首页 > news >正文

解决 Redis 缓存与数据库一致性问题的技术指南

Redis 缓存与数据库一致性是分布式系统中常见的挑战,尤其在高并发场景下(如电商、用户管理、对账系统)。Redis 作为高性能缓存,常用于加速数据访问,但其与数据库(如 MySQL)之间的数据同步可能因并发更新、延迟或故障导致不一致。本文将深入分析 Redis 缓存与数据库一致性问题的原因、解决方案,并在 Spring Boot 中实现一个用户管理示例,集成 Redis 缓存分库分表动态线程池Spring BatchAOPActiveMQ 等,解决一致性问题并提供代码实现。本文目标是为开发者提供一份全面的中文技术指南,帮助在 2025 年的 Spring Boot 3.2 生态中高效处理缓存一致性。


一、Redis 缓存与数据库一致性问题的背景

1.1 一致性问题场景

Redis 缓存常用于存储热点数据,减少数据库压力,但在以下场景下可能出现不一致:

  1. 缓存未及时更新
    • 数据库更新后,Redis 缓存未同步,导致客户端读取到旧数据。
  2. 并发更新冲突
    • 多个线程同时更新数据库和缓存,操作顺序错乱。
  3. 缓存失效后穿透
    • 缓存失效后,高并发请求直接访问数据库,可能导致脏数据被缓存。
  4. 分布式事务复杂性
    • 数据库和 Redis 的操作非原子,可能因故障部分失败。
  5. 分库分表场景
    • 分片数据库(如 ShardingSphere)与 Redis 缓存的同步更复杂。

1.2 一致性要求

  • 强一致性:缓存和数据库数据实时同步,适合金融、对账等场景。
  • 最终一致性:允许短暂不一致,最终同步,适合用户管理、商品信息等场景。
  • 弱一致性:优先性能,接受较长时间不一致,适合日志、统计等场景。

本文针对 最终一致性,结合您之前的对账数据导入场景(100 万数据批处理),提供适用于高并发用户管理系统的解决方案。

1.3 常见解决方案

  1. Cache-Aside(旁路缓存)
    • 读:先查缓存,缓存未命中则查数据库并更新缓存。
    • 写:更新数据库后删除/更新缓存。
    • 适用:简单场景,需手动管理缓存。
  2. Write-Through(写穿)
    • 写:同时更新数据库和缓存。
    • 适用:强一致性场景,性能开销大。
  3. Write-Behind(写后)
    • 写:先更新缓存,异步更新数据库。
    • 适用:最终一致性,高并发场景。
  4. 异步更新(消息队列)
    • 使用消息队列(如 ActiveMQ、Kafka)异步同步数据库和缓存。
    • 适用:分布式系统,复杂场景。
  5. 分布式锁
    • 使用 Redis 分布式锁(如 Redisson)控制并发更新。
    • 适用:高并发写场景。
  6. Canal 同步
    • 通过 Canal 监听 MySQL binlog,异步更新 Redis。
    • 适用:复杂分库分表场景。

1.4 挑战

  • 并发控制:多线程更新可能导致脏数据。
  • 性能权衡:强一致性降低吞吐量。
  • 故障恢复:Redis 或数据库故障需回滚。
  • 分库分表复杂性:ShardingSphere 分片需统一缓存策略。
  • 监控与运维:需监控一致性问题,记录操作日志。

1.5 适用场景

  • 高并发读(如用户查询)。
  • 批量数据处理(如对账数据导入)。
  • 微服务架构中的缓存优化。

二、解决方案设计

结合您的需求(用户管理、批处理、分库分表、动态线程池),选择以下方案:

  • Cache-Aside + 异步更新(ActiveMQ) + 分布式锁
    • :先查 Redis,未命中则查 MySQL(分库分表),并缓存。
    • :更新 MySQL 后删除 Redis 缓存,通过 ActiveMQ 异步更新 Redis。
    • 并发控制:使用 Redisson 分布式锁避免并发写冲突。
    • 批处理:Spring Batch 集成动态线程池,优化 100 万数据导入。
    • 监控:AOP 记录操作,Actuator 监控线程池和 Redis。
  • 最终一致性:允许短暂不一致,异步消息确保最终同步。

2.1 技术栈

  • Spring Boot 3.2:核心框架。
  • Redis:缓存,Redisson 分布式锁。
  • MySQL + ShardingSphere:分库分表存储。
  • Dynamic TP:动态线程池优化批处理。
  • Spring Batch:批量数据导入。
  • ActiveMQ:异步更新缓存。
  • AOP:性能和一致性监控。
  • Actuator:系统和线程池监控。

2.2 流程

  1. 读数据
    • 查询 Redis,命中则返回。
    • 未命中则查询 MySQL(ShardingSphere 分片),写入 Redis(带 TTL)。
  2. 写数据
    • 获取 Redisson 分布式锁。
    • 更新 MySQL(分片表)。
    • 删除 Redis 缓存。
    • 发送 ActiveMQ 消息,异步更新 Redis。
  3. 批处理
    • Spring Batch 分 chunk 导入,动态线程池控制并发。
    • 每 chunk 更新 MySQL 后删除缓存,发送消息。
  4. 监控
    • AOP 记录操作耗时和异常。
    • Actuator 暴露 Redis 和线程池指标。

三、在 Spring Boot 中实现

以下是用户管理系统的实现,包含 Redis 缓存、MySQL 分库分表、批处理导入、异步更新和分布式锁,解决一致性问题。

3.1 环境搭建

3.1.1 配置步骤
  1. 创建 Spring Boot 项目

    • 使用 Spring Initializr 添加依赖:
      • spring-boot-starter-web
      • spring-boot-starter-data-jpa
      • spring-boot-starter-data-redis
      • mysql-connector-java
      • shardingsphere-jdbc-core
      • dynamic-tp-spring-boot-starter
      • spring-boot-starter-activemq
      • spring-boot-starter-batch
      • spring-boot-starter-aop
      • redisson-spring-boot-starter
    <project><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.2.0</version></parent><groupId>com.example</groupId><artifactId>cache-consistency-demo</artifactId><version>0.0.1-SNAPSHOT</version><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.33</version></dependency><dependency><groupId>org.apache.shardingsphere</groupId><artifactId>shardingsphere-jdbc-core</artifactId><version>5.4.0</version></dependency><dependency><groupId>cn.dynamictp</groupId><artifactId>dynamic-tp-spring-boot-starter</artifactId><version>1.1.5</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-activemq</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-batch</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency><dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId><version>3.23.2</version></dependency></dependencies>
    </project>
    
  2. 准备数据库和 Redis

    • MySQL
      • 创建两个数据库:user_db_0user_db_1
      • 每个数据库包含两个表:user_0user_1
      • 表结构:
        CREATE TABLE user_0 (id BIGINT PRIMARY KEY,name VARCHAR(255),age INT,INDEX idx_name (name)
        );
        CREATE TABLE user_1 (id BIGINT PRIMARY KEY,name VARCHAR(255),age INT,INDEX idx_name (name)
        );
        
    • Redis:启动 Redis 实例(默认端口 6379)。
  3. 配置 application.yml

    spring:profiles:active: devapplication:name: cache-consistency-demoshardingsphere:datasource:names: db0,db1db0:type: com.zaxxer.hikari.HikariDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverjdbc-url: jdbc:mysql://localhost:3306/user_db_0?useSSL=false&serverTimezone=UTCusername: rootpassword: rootdb1:type: com.zaxxer.hikari.HikariDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverjdbc-url: jdbc:mysql://localhost:3306/user_db_1?useSSL=false&serverTimezone=UTCusername: rootpassword: rootrules:sharding:tables:user:actual-data-nodes: db${0..1}.user_${0..1}table-strategy:standard:sharding-column: idsharding-algorithm-name: user-table-algodatabase-strategy:standard:sharding-column: idsharding-algorithm-name: user-db-algosharding-algorithms:user-table-algo:type: INLINEprops:algorithm-expression: user_${id % 2}user-db-algo:type: INLINEprops:algorithm-expression: db${id % 2}props:sql-show: truejpa:hibernate:ddl-auto: noneshow-sql: trueredis:host: localhostport: 6379activemq:broker-url: tcp://localhost:61616user: adminpassword: adminbatch:job:enabled: falseinitialize-schema: always
    server:port: 8081
    management:endpoints:web:exposure:include: health,metrics,threadpool
    dynamic-tp:enabled: trueexecutors:- thread-pool-name: batchImportPoolcore-pool-size: 4max-pool-size: 8queue-capacity: 1000queue-type: LinkedBlockingQueuerejected-handler-type: CallerRunsPolicykeep-alive-time: 60thread-name-prefix: batch-import-
    redisson:single-server-config:address: redis://localhost:6379
    logging:level:root: INFOcom.example.demo: DEBUG
    
  4. 运行并验证

    • 启动 MySQL、Redis 和 ActiveMQ。
    • 启动应用:mvn spring-boot:run
    • 检查日志,确认 ShardingSphere、Dynamic TP 和 Redisson 初始化。
3.1.2 原理
  • ShardingSphere:按 ID 哈希分片,分散数据压力。
  • Redis:缓存用户数据,TTL 控制失效。
  • Redisson:分布式锁控制并发写。
  • ActiveMQ:异步更新缓存,确保最终一致性。
  • Dynamic TP:优化批处理并发。
  • Spring Batch:分 chunk 导入数据。
3.1.3 优点
  • 高效缓存查询,降低数据库压力。
  • 异步更新确保最终一致性。
  • 分布式锁避免并发冲突。
3.1.4 缺点
  • 配置复杂,需协调多组件。
  • 异步更新可能有短暂不一致。
  • 分布式锁增加少量开销。
3.1.5 适用场景
  • 高并发用户查询。
  • 批量数据导入(如对账)。
  • 微服务缓存优化。

3.2 实现用户管理与缓存一致性

实现用户数据的增删改查和批量导入,集成 Redis 缓存和一致性保障。

3.2.1 配置步骤
  1. 实体类User.java):

    package com.example.demo.entity;import jakarta.persistence.Entity;
    import jakarta.persistence.Id;
    import java.io.Serializable;@Entity
    public class User implements Serializable {@Idprivate Long id;private String name;private int age;// Getters and Setterspublic Long getId() { return id; }public void setId(Long id) { this.id = id; }public String getName() { return name; }public void setName(String name) { this.name = name; }public int getAge() { return age; }public void setAge(int age) { this.age = age; }
    }
    
  2. RepositoryUserRepository.java):

    package com.example.demo.repository;import com.example.demo.entity.User;
    import org.springframework.data.jpa.repository.JpaRepository;public interface UserRepository extends JpaRepository<User, Long> {
    }
    
  3. 服务层UserService.java):

    package com.example.demo.service;import com.example.demo.entity.User;
    import com.example.demo.repository.UserRepository;
    import org.redisson.api.RLock;
    import org.redisson.api.RedissonClient;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.batch.core.Job;
    import org.springframework.batch.core.JobParametersBuilder;
    import org.springframework.batch.core.launch.JobLauncher;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.jms.core.JmsTemplate;
    import org.springframework.stereotype.Service;import java.util.concurrent.TimeUnit;@Service
    public class UserService {private static final Logger logger = LoggerFactory.getLogger(UserService.class);private static final ThreadLocal<String> CONTEXT = new ThreadLocal<>();private static final String CACHE_PREFIX = "user:";private static final String LOCK_PREFIX = "lock:user:";@Autowiredprivate UserRepository userRepository;@Autowiredprivate RedisTemplate<String, Object> redisTemplate;@Autowiredprivate RedissonClient redissonClient;@Autowiredprivate JmsTemplate jmsTemplate;@Autowiredprivate JobLauncher jobLauncher;@Autowiredprivate Job importUserJob;public User getUser(Long id) {try {CONTEXT.set("Get-" + Thread.currentThread().getName());String cacheKey = CACHE_PREFIX + id;User user = (User) redisTemplate.opsForValue().get(cacheKey);if (user != null) {logger.info("Cache hit for user: {}", id);return user;}user = userRepository.findById(id).orElse(null);if (user != null) {redisTemplate.opsForValue().set(cacheKey, user, 1, TimeUnit.HOURS);logger.info("Cache miss, loaded from DB: {}", id);}return user;} finally {CONTEXT.remove();}}public void saveUser(User user) {try {CONTEXT.set("Save-" + Thread.currentThread().getName());String lockKey = LOCK_PREFIX + user.getId();RLock lock = redissonClient.getLock(lockKey);try {if (lock.tryLock(10, 30, TimeUnit.SECONDS)) {userRepository.save(user);String cacheKey = CACHE_PREFIX + user.getId();redisTemplate.delete(cacheKey);jmsTemplate.convertAndSend("user-update-queue", user);logger.info("Saved user and deleted cache: {}", user.getId());} else {throw new RuntimeException("Failed to acquire lock for user: " + user.getId());}} finally {if (lock.isHeldByCurrentThread()) {lock.unlock();}}} catch (InterruptedException e) {Thread.currentThread().interrupt();throw new RuntimeException("Lock interrupted", e);} finally {CONTEXT.remove();}}public void startImportJob() {try {CONTEXT.set("Import-" + Thread.currentThread().getName());logger.info("Starting batch import job");JobParametersBuilder params = new JobParametersBuilder().addLong("timestamp", System.currentTimeMillis());jobLauncher.run(importUserJob, params.build());} catch (Exception e) {logger.error("Failed to start import job", e);} finally {CONTEXT.remove();}}
    }
    
  4. ActiveMQ 消费者UserCacheUpdater.java):

    package com.example.demo.service;import com.example.demo.entity.User;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.jms.annotation.JmsListener;
    import org.springframework.stereotype.Component;import java.util.concurrent.TimeUnit;@Component
    public class UserCacheUpdater {private static final Logger logger = LoggerFactory.getLogger(UserCacheUpdater.class);private static final String CACHE_PREFIX = "user:";@Autowiredprivate RedisTemplate<String, Object> redisTemplate;@JmsListener(destination = "user-update-queue")public void updateCache(User user) {try {String cacheKey = CACHE_PREFIX + user.getId();redisTemplate.opsForValue().set(cacheKey, user, 1, TimeUnit.HOURS);logger.info("Updated cache for user: {}", user.getId());} catch (Exception e) {logger.error("Failed to update cache for user: {}", user.getId(), e);}}
    }
    
  5. Spring Batch 配置BatchConfig.java):

    package com.example.demo.config;import com.example.demo.entity.User;
    import org.dynamictp.core.DtpRegistry;
    import org.dynamictp.core.executor.DtpExecutor;
    import org.springframework.batch.core.Job;
    import org.springframework.batch.core.Step;
    import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
    import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
    import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
    import org.springframework.batch.item.ItemProcessor;
    import org.springframework.batch.item.ItemReader;
    import org.springframework.batch.item.ItemWriter;
    import org.springframework.batch.item.database.JpaItemWriter;
    import org.springframework.batch.item.support.ListItemReader;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.jms.core.JmsTemplate;import jakarta.persistence.EntityManagerFactory;
    import java.util.ArrayList;
    import java.util.List;@Configuration
    @EnableBatchProcessing
    public class BatchConfig {@Autowiredprivate JobBuilderFactory jobBuilderFactory;@Autowiredprivate StepBuilderFactory stepBuilderFactory;@Autowiredprivate EntityManagerFactory entityManagerFactory;@Autowiredprivate JmsTemplate jmsTemplate;@Beanpublic ItemReader<User> reader() {// 模拟 100 万用户数据List<User> data = new ArrayList<>();for (long i = 1; i <= 1_000_000; i++) {User user = new User();user.setId(i);user.setName("User" + i);user.setAge(20 + (int) (i % 80));data.add(user);}return new ListItemReader<>(data);}@Beanpublic ItemProcessor<User, User> processor() {return item -> item; // 简单处理}@Beanpublic ItemWriter<User> writer() {JpaItemWriter<User> writer = new JpaItemWriter<>();writer.setEntityManagerFactory(entityManagerFactory);return items -> {writer.write(items);for (User user : items) {jmsTemplate.convertAndSend("user-update-queue", user);}};}@Beanpublic Step importUserStep() {DtpExecutor executor = DtpRegistry.getExecutor("batchImportPool");return stepBuilderFactory.get("importUserStep").<User, User>chunk(1000).reader(reader()).processor(processor()).writer(writer()).taskExecutor(executor).throttleLimit(4).build();}@Beanpublic Job importUserJob() {return jobBuilderFactory.get("importUserJob").start(importUserStep()).build();}
    }
    
  6. 控制器UserController.java):

    package com.example.demo.controller;import com.example.demo.entity.User;
    import com.example.demo.service.UserService;
    import io.swagger.v3.oas.annotations.Operation;
    import io.swagger.v3.oas.annotations.tags.Tag;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.*;@RestController
    @Tag(name = "用户管理", description = "用户数据操作")
    public class UserController {@Autowiredprivate UserService userService;@Operation(summary = "获取用户")@GetMapping("/users/{id}")public User getUser(@PathVariable Long id) {return userService.getUser(id);}@Operation(summary = "保存用户")@PostMapping("/users")public void saveUser(@RequestBody User user) {userService.saveUser(user);}@Operation(summary = "触发批量导入")@PostMapping("/import")public String startImport() {userService.startImportJob();return "Batch import started";}
    }
    
  7. AOP 切面CacheConsistencyAspect.java):

    package com.example.demo.aspect;import org.aspectj.lang.annotation.*;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Component;@Aspect
    @Component
    public class CacheConsistencyAspect {private static final Logger logger = LoggerFactory.getLogger(CacheConsistencyAspect.class);@Pointcut("execution(* com.example.demo.service.UserService.*(..))")public void serviceMethods() {}@Before("serviceMethods()")public void logMethodEntry() {logger.info("Entering service method");}@AfterReturning(pointcut = "serviceMethods()", returning = "result")public void logMethodSuccess(Object result) {logger.info("Method executed successfully, result: {}", result);}@AfterThrowing(pointcut = "serviceMethods()", throwing = "ex")public void logException(Exception ex) {logger.error("Service error: {}", ex.getMessage());}
    }
    
  8. 运行并验证

    • 启动应用:mvn spring-boot:run
    • 查询用户
      curl http://localhost:8081/users/1
      
      • 确认 Redis 缓存命中(第二次查询)。
    • 保存用户
      curl -X POST http://localhost:8081/users -H "Content-Type: application/json" -d '{"id":1,"name":"Alice","age":25}'
      
      • 确认 MySQL 保存、Redis 缓存删除、ActiveMQ 消息触发。
    • 批量导入
      curl -X POST http://localhost:8081/import
      
      • 确认 100 万数据分片存储,缓存异步更新。
    • 检查 /actuator/threadpool 和 ActiveMQ user-update-queue
3.2.2 原理
  • Cache-Aside:读时优先查 Redis,未命中查 MySQL。
  • 分布式锁:Redisson 控制并发写,避免脏数据。
  • 异步更新:ActiveMQ 触发缓存更新,确保最终一致性。
  • 分库分表:ShardingSphere 分散数据压力。
  • 动态线程池:优化批处理并发。
3.2.3 优点
  • 高性能查询(Redis 缓存)。
  • 最终一致性,适合高并发。
  • 分布式锁确保写安全。
3.2.4 缺点
  • 异步更新有短暂不一致。
  • 配置复杂,需多组件协调。
  • 分布式锁增加开销。
3.2.5 适用场景
  • 高并发用户管理。
  • 批量数据导入。
  • 分布式缓存优化。

3.3 集成先前查询

结合分库分表、动态线程池、Spring Batch、AOP、ActiveMQ、Spring Profiles、Spring Security、FreeMarker、热加载、ThreadLocal、Actuator 安全性、CSRF、WebSockets、异常处理、Web 标准。

3.3.1 配置步骤
  1. 分库分表

    • 已集成 ShardingSphere。
  2. 动态线程池

    • 已使用 Dynamic TP 优化批处理。
  3. Spring Batch

    • 已实现批量导入。
  4. AOP

    • 已记录操作和异常。
  5. ActiveMQ

    • 已异步更新缓存。
  6. Spring Profiles

    • 配置 application-dev.ymlapplication-prod.yml
      # application-dev.yml
      spring:shardingsphere:props:sql-show: trueredis:host: localhostdynamic-tp:executors:- thread-pool-name: batchImportPoolcore-pool-size: 4max-pool-size: 8queue-capacity: 1000
      logging:level:root: DEBUG
      
      # application-prod.yml
      spring:shardingsphere:props:sql-show: falseredis:host: prod-redisdynamic-tp:executors:- thread-pool-name: batchImportPoolcore-pool-size: 8max-pool-size: 16queue-capacity: 2000
      logging:level:root: INFO
      
  7. Spring Security

    • 保护 API:
      package com.example.demo.config;import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;
      import org.springframework.security.config.annotation.web.builders.HttpSecurity;
      import org.springframework.security.core.userdetails.User;
      import org.springframework.security.core.userdetails.UserDetailsService;
      import org.springframework.security.provisioning.InMemoryUserDetailsManager;
      import org.springframework.security.web.SecurityFilterChain;@Configuration
      public class SecurityConfig {@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http.authorizeHttpRequests(auth -> auth.requestMatchers("/users/**", "/import").authenticated().requestMatchers("/actuator/health").permitAll().requestMatchers("/actuator/**").hasRole("ADMIN").anyRequest().permitAll()).httpBasic().and().csrf().ignoringRequestMatchers("/ws");return http.build();}@Beanpublic UserDetailsService userDetailsService() {var user = User.withDefaultPasswordEncoder().username("admin").password("admin").roles("ADMIN").build();return new InMemoryUserDetailsManager(user);}
      }
      
  8. FreeMarker

    • 用户管理页面:
      package com.example.demo.controller;import com.example.demo.entity.User;
      import com.example.demo.service.UserService;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.stereotype.Controller;
      import org.springframework.ui.Model;
      import org.springframework.web.bind.annotation.GetMapping;
      import org.springframework.web.bind.annotation.RequestParam;@Controller
      public class WebController {@Autowiredprivate UserService userService;@GetMapping("/web/users")public String getUser(@RequestParam Long id, Model model) {User user = userService.getUser(id);model.addAttribute("user", user);return "user";}
      }
      
      <!-- src/main/resources/templates/user.ftl -->
      <!DOCTYPE html>
      <html lang="zh-CN">
      <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>用户信息</title>
      </head>
      <body><h1>用户信息</h1><#if user??><p>ID: ${user.id}</p><p>姓名: ${user.name?html}</p><p>年龄: ${user.age}</p><#else><p>用户不存在</p></#if>
      </body>
      </html>
      
  9. 热加载

    • 启用 DevTools:
      <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><optional>true</optional>
      </dependency>
      
  10. ThreadLocal

    • 已清理 ThreadLocal(见 UserService)。
  11. Actuator 安全性

    • 已限制 /actuator/**
  12. CSRF

    • WebSocket 端点禁用 CSRF。
  13. WebSockets

    • 推送缓存更新状态:
      package com.example.demo.controller;import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.messaging.handler.annotation.MessageMapping;
      import org.springframework.messaging.simp.SimpMessagingTemplate;
      import org.springframework.stereotype.Controller;@Controller
      public class WebSocketController {@Autowiredprivate SimpMessagingTemplate messagingTemplate;@MessageMapping("/cache-status")public void sendCacheStatus() {messagingTemplate.convertAndSend("/topic/cache", "Cache updated");}
      }
      
  14. 异常处理

    • 处理一致性异常:
      package com.example.demo.config;import org.springframework.http.HttpStatus;
      import org.springframework.http.ProblemDetail;
      import org.springframework.http.ResponseEntity;
      import org.springframework.web.bind.annotation.ControllerAdvice;
      import org.springframework.web.bind.annotation.ExceptionHandler;@ControllerAdvice
      public class GlobalExceptionHandler {@ExceptionHandler(RuntimeException.class)public ResponseEntity<ProblemDetail> handleRuntimeException(RuntimeException ex) {ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(HttpStatus.INTERNAL_SERVER_ERROR, ex.getMessage());return new ResponseEntity<>(problemDetail, HttpStatus.INTERNAL_SERVER_ERROR);}
      }
      
  15. Web 标准

    • FreeMarker 模板遵循语义化 HTML。
  16. 运行并验证

    • 开发环境
      java -jar demo.jar --spring.profiles.active=dev
      
      • 查询用户,验证 Redis 缓存。
      • 保存用户,验证 MySQL 保存、缓存删除、异步更新。
      • 触发批量导入,验证一致性。
    • 生产环境
      java -jar demo.jar --spring.profiles.active=prod
      
      • 确认安全性、线程池和 Redis 配置。
3.3.2 原理
  • 缓存查询:Redis 优先,MySQL 兜底。
  • 写操作:分布式锁确保顺序,异步消息更新缓存。
  • 批处理:分 chunk 导入,动态线程池优化。
  • 监控:AOP 和 Actuator 记录一致性问题。
3.3.3 优点
  • 高效缓存,降低数据库压力。
  • 最终一致性,适合高并发。
  • 集成 Spring Boot 生态。
3.3.4 缺点
  • 短暂不一致窗口。
  • 配置复杂。
  • 分布式锁开销。
3.3.5 适用场景
  • 高并发读写。
  • 批量数据处理。
  • 分布式系统。

四、性能与适用性分析

4.1 性能影响

  • 查询:Redis 命中 <1ms,MySQL 10-50ms。
  • 保存:MySQL + 锁 20ms,异步更新 5ms。
  • 批处理:100 万数据约 5-10 分钟。
  • WebSocket 推送:2ms/消息。

4.2 性能测试

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class CacheConsistencyTest {@Autowiredprivate TestRestTemplate restTemplate;@Testpublic void testCacheConsistency() {long startTime = System.currentTimeMillis();restTemplate.getForEntity("/users/1", User.class);long duration = System.currentTimeMillis() - startTime;System.out.println("Query: " + duration + " ms");}
}

测试结果(Java 17,8 核 CPU,16GB 内存):

  • 查询:Redis 0.5ms,MySQL 20ms。
  • 保存:25ms。
  • 批处理:300,000ms(100 万数据)。

结论:Redis 缓存显著提升查询性能,异步更新确保一致性。

4.3 适用性对比

方法一致性性能适用场景
Cache-Aside最终简单高并发
Write-Through金融、对账
Write-Behind最终日志、统计
异步更新+锁最终分布式复杂场景

五、常见问题与解决方案

  1. 问题1:缓存未更新

    • 场景:ActiveMQ 消息丢失。
    • 解决方案
      • 配置消息持久化。
      • 添加重试机制。
  2. 问题2:并发写冲突

    • 场景:分布式锁失效。
    • 解决方案
      • 检查 Redisson 配置。
      • 延长锁超时。
  3. 问题3:ThreadLocal 泄漏

    • 场景/actuator/threaddump 显示泄漏。
    • 解决方案
      • 确认 ThreadLocal 清理。
  4. 问题4:批处理慢

    • 场景:100 万数据耗时长。
    • 解决方案
      • 增加分片库/表。
      • 调整 chunk 大小。

六、实际应用案例

  1. 案例1:用户查询

    • 场景:高并发查询用户数据。
    • 方案:Redis 缓存,异步更新。
    • 结果:查询性能提升 90%。
    • 经验:TTL 控制缓存失效。
  2. 案例2:批量导入

    • 场景:100 万用户数据导入。
    • 方案:Spring Batch + 动态线程池。
    • 结果:导入时间缩短 50%。
    • 经验:小 chunk 降低锁冲突。
  3. 案例3:并发写

    • 场景:多线程更新用户。
    • 方案:Redisson 分布式锁。
    • 结果:无脏数据。
    • 经验:锁超时需优化。

七、未来趋势

  1. 云原生缓存
    • Redis Cluster 动态扩展。
    • 准备:学习 Spring Cloud 和 K8s。
  2. AI 优化缓存
    • Spring AI 预测热点数据。
    • 准备:实验 Spring AI。
  3. 无服务器缓存
    • AWS ElastiCache 简化管理。
    • 准备:探索云服务。

八、总结

Redis 缓存与数据库一致性问题通过 Cache-Aside + 异步更新 + 分布式锁 方案解决,结合 ShardingSphere、Dynamic TP、Spring Batch 和 ActiveMQ,实现了高效用户管理和批量导入。示例展示查询(<1ms)、保存(25ms)和批处理(100 万数据 5-10 分钟),集成您之前的查询(分库分表、动态线程池、AOP 等)。未来可探索云原生和 AI 优化。

相关文章:

解决 Redis 缓存与数据库一致性问题的技术指南

Redis 缓存与数据库一致性是分布式系统中常见的挑战&#xff0c;尤其在高并发场景下&#xff08;如电商、用户管理、对账系统&#xff09;。Redis 作为高性能缓存&#xff0c;常用于加速数据访问&#xff0c;但其与数据库&#xff08;如 MySQL&#xff09;之间的数据同步可能因…...

LlamaIndex 第六篇 SimpleDirectoryReader

SimpleDirectoryReader 是将本地文件数据加载到 LlamaIndex 的最简单方式。虽然在实际生产场景中&#xff0c;您更可能需要使用 LlamaHub 提供的多种数据读取器&#xff08;Reader&#xff09;&#xff0c;但 SimpleDirectoryReader 无疑是快速入门的理想选择。 支持的文件类型…...

window 显示驱动开发-配置内存段类型

视频内存管理器&#xff08;VidMm&#xff09;和显示硬件仅支持某些类型的内存段。 因此&#xff0c;内核模式显示微型端口驱动程序&#xff08;KMD&#xff09;只能配置这些类型的段。 KMD 可以配置内存空间段和光圈空间段&#xff0c;其中不同&#xff1a; 内存空间段由保存…...

【人工智能学习之动作识别TSM训练与部署】

【人工智能学习之动作识别TSM训练与部署】 基于MMAction2动作识别项目的开发一、MMAction2的安装二、数据集制作三、模型训练1. 配置文件准备2. 关键参数修改3. 启动训练4. 启动成功 ONNX模型部署方案一、环境准备二、执行转换命令 基于MMAction2动作识别项目的开发 一、MMAct…...

PostgreSQL冻结过程

1.冻结过程 冻结过程有两种模式&#xff0c;依特定条件而择其一执行。为方便起见&#xff0c;将这两种模式分别称为惰性模式&#xff08;lazy mode&#xff09;和迫切模式&#xff08;eager mode&#xff09;。 并发清理&#xff08;Concurrent VACUUM&#xff09;通常在内部…...

SSHv2公钥认证示例-Paramiko复用 Transport 连接

在 Paramiko 中复用 Transport 连接时&#xff0c;若要通过 公钥认证&#xff08;而非密码&#xff09;建立连接&#xff0c;需手动加载私钥并与 Transport 关联。以下是详细操作步骤及完整代码示例&#xff1a; 步骤 1&#xff1a;加载私钥文件 使用 RSAKey 或 Ed25519Key 类…...

华为5.7机考-最小代价相遇的路径规划Java题解

题目内容 输入描述 输出描述 示例&#xff1a; 输入&#xff1a; 2 1 2 2 1 输出&#xff1a; 3 思路&#xff1a; Dijkstra 算法实现 dijkstra(int sx, int sy, int[][] dirs) 方法&#xff1a; 参数&#xff1a;起点坐标 (sx, sy) 和允许的移动方向 初始化&#xff1…...

element-ui分页的使用及修改样式

1.安装 npm install element-ui -S 2.在main.js中引入,这里是全部引入&#xff0c;也可以按需引入 import ElementUI from element-ui import element-ui/lib/theme-chalk/index.css Vue.use(ElementUI) 3.使用 layout"prev, pager, next, jumper" &#xff1a;jumpe…...

[Unity]-[UI]-[Image] 关于UI精灵图资源导入设置的详细解释

Unity UI Sprite UI资源导入详解图片导入项目Texture TypeTexture ShapeAdvanced Setting 高级设置 图片设置案例常见细节问题 知识点详解来源 UI资源导入详解 Unity中的UI资源有图片、矢量图、字体、预制体、图集、动画等等资源。 这其中图片是最重要以及最基础的资源组成&a…...

MLX-Audio:高效音频合成的新时代利器

MLX-Audio&#xff1a;高效音频合成的新时代利器 现代社会的快节奏生活中&#xff0c;对语音技术的需求越来越高。无论是个性化语音助手&#xff0c;还是内容创作者所需的高效音频生成工具&#xff0c;语音技术都发挥着不可或缺的作用。今天&#xff0c;我们将介绍一个创新的开…...

操作系统导论——第27章 插叙:线程API

关键问题&#xff1a;如何创建和控制线程&#xff1f; 操作系统应该提供哪些创建和控制线程的接口&#xff1f;这些接口如何设计得易用和实用&#xff1f; 一、线程创建 编写多线程程序的第一步就是创建新线程&#xff0c;因此必须存在某种线程创建接口。在 POSIX 中&#xff1…...

代采系统:定义、优势与未来趋势

一、代采系统的定义 代采系统是一种基于互联网的集中采购平台&#xff0c;它通过整合供应链资源&#xff0c;为中小企业或个人提供采购代理服务。商家可以在没有自己库存的情况下销售产品&#xff0c;当客户下单时&#xff0c;订单信息会自动或手动发送给供应商&#xff0c;由…...

后缀表达式+栈(详解)(c++)

前言 很抱歉&#xff0c;上一期没有介绍栈stack的用法&#xff0c;今天简要介绍一下&#xff0c;再讲讲后缀表达式&#xff0c;用stack栈做一些后缀表达式的练习。 栈 栈stack是c中系统给出的栈&#xff0c;有了它&#xff0c;就不用自己创建栈啦&#xff01; 头文件 栈sta…...

Kaggle图像分类竞赛实战总结详细代码解读

前言 我是跟着李沐的动手学深度学习v2视频学习深度学习的&#xff0c;光看不做假把式&#xff0c;所以在学习完第七章-现代卷积神经网络之后&#xff0c;参加了一次李沐发布的Kaggle竞赛。自己动手&#xff0c;从组织数据集开始&#xff0c;到训练&#xff0c;再到推理&#x…...

开源AI对比--dify、n8n

原文网址&#xff1a;开源AI对比--dify、n8n-CSDN博客 简介 本文介绍开源AI工作流工具的选型。 对比 项difyn8n占优者学习难度简单中等dify核心理念用LLM构建应用。“连接一切”。以工作流自动化连接各系统。平手工作模式 Chatflow&#xff1a;对话。支持用户意图识别、上下…...

【SQL系列】多表关联更新

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…...

软件设计师教程——第一章 计算机系统知识(下)

前言 在竞争激烈的就业市场中&#xff0c;证书是大学生求职的重要加分项。中级软件设计师证书专业性强、认可度高&#xff0c;是计算机相关专业学生考证的热门选择&#xff0c;既能检验专业知识&#xff0c;又有助于职业发展。本教程将聚焦核心重点&#xff0c;以点带面构建知…...

华为银河麒麟 V10(ARM)系统软件部署全攻略:Redis、RabbitMQ、MySQL 等集群搭建指南

一、Redis 集群部署&#xff08;主从 哨兵模式&#xff09; 1. 环境准备 系统&#xff1a;华为银河麒麟 V10&#xff08;ARM64&#xff09;节点&#xff1a;3 台服务器&#xff08;1 主 2 从 3 哨兵&#xff09; 2. 安装包下载 bash # 华为镜像站 wget https://update.c…...

World of Warcraft [CLASSIC][80][Deluyia] [Fragment of Val‘anyr]

瓦兰奈尔的碎片 [Fragment of Valanyr] 有时候下个班打个游戏&#xff0c;没想到套路也这么多&#xff0c;唉&#xff0c;何况现实生活&#xff0c;这一个片版本末期才1000G&#xff0c;30个&#xff0c;也就30000G&#xff0c;时光徽章等同月卡15000G&#xff0c;折合一下也就…...

C++:求分数序列和

【描述】 有一个分数序列 2/1,3/2,5/3,8/5,13/8,21/13,.... 求这个分数序列的前n项之和。 输入 输入有一行&#xff1a;正整数n。 输出 输出有一行&#xff1a;分数序列的和&#xff08;浮点数&#xff0c;精确到小数点后4位&#xff09;。 【样例输入】 99 【样例输出】 160.4…...

支付宝沙盒模式商家转账经常出现 响应异常: 解包错误

2025年5月9日16:27:08 php8.3 laravel11 octane swoole加速 测试时不时就出现 响应异常: 解包错误 错误信息&#xff1a; Yansongda\Artful\Exception\InvalidResponseException: 响应异常: 解包错误 in /opt/www/vendor/yansongda/artful/src/Direction/CollectionDirect…...

第04章—技术突击篇:如何根据求职意向进行快速提升与复盘

经过上一讲的内容阐述后&#xff0c;咱们定好了一个与自身最匹配的期望薪资&#xff0c;接着又该如何准备呢&#xff1f; 很多人在准备时&#xff0c;通常会选择背面试八股文&#xff0c;这种做法效率的确很高&#xff0c;毕竟能在“八股文”上出现的题&#xff0c;也绝对是面…...

数据统计的意义:钱包余额变动

钱包余额变动统计的核心意义在于通过数据可视化实现资金流动的透明化管理&#xff0c;其价值主要体现在以下五个维度&#xff1a; 一、财务健康诊断&#xff08;&#xff09; 资金流动可视化 通过期初/期末余额对比&#xff0c;可快速识别异常波动&#xff08;如连续3个月余额…...

单调栈模版型题目(3)

单调栈型题目贡献法 基本模版 这是数组a中的 首先我们要明白什么叫做贡献&#xff0c;在一个数组b{1,3,5}中&#xff0c;连续包含1的连续子数组为{1}&#xff0c;{1,3}&#xff0c;{1,3,5}&#xff0c;一共有三个&#xff0c;这三个数一共能组成6个连续子数组&#xff0c;而其…...

PostgreSQL 的 pg_advisory_lock 函数

PostgreSQL 的 pg_advisory_lock 函数 pg_advisory_lock 是 PostgreSQL 提供的一种应用级锁机制&#xff0c;它不锁定具体的数据库对象&#xff08;如表或行&#xff09;&#xff0c;而是通过数字键值来协调应用间的并发控制。 锁的基本概念 PostgreSQL 提供两种咨询锁(advi…...

NLP基础

1. 基本概念 自然语言处理&#xff08;Natural Language Processing&#xff0c;简称NLP&#xff09;是人工智能和语言学领域的一个分支&#xff0c;它涉及到计算机和人类&#xff08;自然&#xff09;语言之间的相互作用。它的主要目标是让计算机能够理解、解释和生成人类语言…...

[AI Tools] Dify 工具插件上传指南:如何将插件发布到官方市场

Dify 作为开源的 LLM 应用开发平台,不仅支持本地化插件开发,也提供了插件市场机制,让开发者能够将自己构建的插件发布并供他人使用。本文将详细介绍如何将你开发的 Dify Tools 插件上传至官方插件市场,包括 README 编写、插件打包、仓库 PR 等核心步骤。 一、准备 README 文…...

Qt读写XML文档

XML 结构与概念简介 XML&#xff08;可扩展标记语言&#xff09; 是一种用于存储和传输结构化数据的标记语言。其核心特性包括&#xff1a; 1、树状结构&#xff1a;XML 数据以层次化的树形结构组织&#xff0c;包含一个根元素&#xff08;Root Element&#xff09;&#xff…...

htmlUnit和Selenium的区别以及使用BrowserMobProxy捕获网络请求

1. Selenium&#xff1a;浏览器自动化之王 核心定位&#xff1a; 跨平台、跨语言的浏览器操控框架&#xff0c;通过驱动真实浏览器实现像素级用户行为模拟。 技术架构&#xff1a; 核心特性&#xff1a; 支持所有主流浏览器&#xff08;含移动端模拟&#xff09; 精…...

C#黑魔法:鸭子类型(Duck Typing)

C#黑魔法&#xff1a;鸭子类型(Duck Typing) 如果它走起路来像鸭子&#xff0c;叫起来像鸭子&#xff0c;那么它就是鸭子。 鸭子类型&#xff0c;主要应用于动态语言类型&#xff0c;比如JS、Python等&#xff0c;核心理念为&#xff1a;关注对象的行为&#xff08;方法或属性…...

2025 年数维杯数学建模B题完整论文代码模型

《2025 年数维杯数学建模B题完整论文代码模型》 B题完整论文 一、赛事背景与题目总览 2025 年第十届数维杯大学生数学建模挑战赛的 B 题聚焦于“马拉松经济的高质量发展思路探索”。近年来&#xff0c;我国马拉松赛事如同一颗颗璀璨的星星&#xff0c;在城市的天空中闪耀&am…...

C++23 中的 views::chunk:深入探索与应用

文章目录 一、views::chunk 的背景与动机二、views::chunk 的基本用法语法与参数示例代码 三、views::chunk 的高级用法处理不完整块与 views::drop 和 views::take 结合 四、性能分析五、应用场景1. 批量处理数据2. 分页显示3. 并行处理 六、与其他范围适配器的组合1. 与 view…...

库室指静脉人脸门禁机 LK-BM-S10C/JR

1、采用大于等于四核处理器&#xff0c;主频大于1G&#xff1b; 2、内存≥4G DDR3&#xff1b;存储≥8G 3、核心模块采用国产工业级处理芯片和嵌入式Android实时多任务系统&#xff0c;采用模块化设计,模块间通过标准接口相连&#xff1b; 4、大于等于10英寸电容屏&#xf…...

低成本自动化改造的18个技术锚点深度解析

执行摘要 本文旨在深入剖析四项关键的低成本自动化技术&#xff0c;这些技术为工业转型提供了显著的运营和经济效益。文章将提供实用且深入的指导&#xff0c;涵盖老旧设备联网、AGV车队优化、空压机系统智能能耗管控以及此类项目投资回报率&#xff08;ROI&#xff09;的严谨…...

线程中常用的方法

知识点详细说明 Java线程的核心方法集中在Thread类和Object类中,以下是新增整合后的常用方法分类解析: 1. 线程生命周期控制 方法作用注意事项start()启动新线程,JVM调用run()方法多次调用会抛出IllegalThreadStateException(线程状态不可逆)。run()线程的任务逻辑直接调…...

运维体系架构规划

运维体系架构规划是一个系统性工程&#xff0c;旨在构建高效、稳定、安全的运维体系&#xff0c;保障业务系统的持续运行。下面从规划目标、核心模块、实施步骤等方面进行详细阐述&#xff1a; 一、规划目标 高可用性&#xff1a;确保业务系统 724 小时不间断运行&#xff0c…...

C++结构体介绍

结构体的定义 在C中&#xff0c;结构体&#xff08;struct&#xff09;是一种用户定义的数据类型&#xff0c;允许将不同类型的数据组合在一起。结构体的定义使用struct关键字&#xff0c;后跟结构体名称和一对花括号{}&#xff0c;花括号内包含成员变量的声明。 struct Pers…...

RoPE长度外推:外插内插

RoPE:假定 α \alpha α是定值 其中一半位置是用cos表示的 cos ⁡ ( k α − 2 i d ) \cos(k\alpha^{-\frac{2i}{d}}) cos(kα−d2i​)(另一半是sin)(d是词嵌入维度) 当太长如何解决: 1 直接不管—外插 缺点:超过一定长度性能急剧下降。(较大时&#xff0c;对应的很多位置编码…...

牛客练习赛138-题解

牛客练习赛138-题解 https://ac.nowcoder.com/acm/contest/109081#question A-小s的签到题 题目描述 给定一个比赛榜单&#xff1a; 第一行是 n 个不同的大写字母&#xff0c;代表题号第二行是 n 个形如a/b的字符串&#xff0c;表示每道题的通过人数和提交人数 找到通过人…...

MySQL高可用方案全攻略:选型指南与AI运维实践

MySQL高可用方案全攻略:选型指南与AI运维实践 引言:当数据库成为业务生命线 在数字化时代,数据库就是企业的"心脏"。一次数据库宕机可能导致: 电商网站每秒损失上万元订单游戏公司遭遇玩家大规模流失金融系统引发连锁反应本文将为你揭秘: MySQL主流高可用方案…...

【库(Library)、包(Package)和模块(Module)解析】

在Python中&#xff0c;**库&#xff08;Library&#xff09;、包&#xff08;Package&#xff09;和模块&#xff08;Module&#xff09;**是代码组织的不同层级&#xff0c;而import语句的导入行为与它们密切相关。以下是详细对比和解释&#xff1a; &#x1f4e6; 1. 核心概…...

记录一次使用thinkphp使用PhpSpreadsheet扩展导出数据,解决身份证号码等信息科学计数法问题处理

PhpSpreadsheet官网 PhpSpreadsheet安装 composer require phpoffice/phpspreadsheet使用composer安装时一定要下载php对应的版本&#xff0c;下载之前使用php -v检查当前php版本 简单使用 <?php require vendor/autoload.php;use PhpOffice\PhpSpreadsheet\Spreadshee…...

为什么业务总是被攻击?使用游戏盾解决方案

业务频繁遭受攻击的核心原因在于攻防资源不对等&#xff0c;攻击者利用技术漏洞、利益驱动及企业防护短板发起攻击&#xff0c;而游戏盾通过针对性架构设计实现高效防御。以下是具体分析与解决方案&#xff1a; ​​一、业务被攻击的根源​​ ​​利益驱动攻击​​ ​​勒索与数…...

4.1【LLaMA-Factory 实战】医疗领域大模型:从数据到部署的全流程实践

【LLaMA-Factory实战】医疗领域大模型&#xff1a;从数据到部署的全流程实践 一、引言 在医疗AI领域&#xff0c;构建专业的疾病诊断助手需要解决数据稀缺、知识专业性强、安全合规等多重挑战。本文基于LLaMA-Factory框架&#xff0c;详细介绍如何从0到1打造一个垂直领域的医…...

二维旋转矩阵:让图形动起来的数学魔法 ✨

大家好&#xff01;今天我们要聊一个超酷的数学工具——旋转矩阵。它就像数学中的"旋转魔法"&#xff0c;能让图形在平面上优雅地转圈圈。别被"矩阵"这个词吓到&#xff0c;其实它就是一个数字表格&#xff0c;但功能超级强大&#xff01; 一、什么是旋转…...

go语言封装、继承与多态:

1.封装&#xff1a; 封装是通过将数据和操作数据的方法绑定在一起来实现的。在Go语言中&#xff0c;封装通过结构体&#xff08;struct&#xff09;和方法&#xff08;method&#xff09;来实现。结构体的字段可以通过大小写来控制访问权限。 package stutype Person struct …...

golang -- 如何获取变量类型

目录 前言获取变量类型一、fmt.Printf二、类型断言三、类型选择四、反射 reflect.TypeOf五、reflect.Value的Type()方法 前言 在学习反射的时候&#xff0c;对reflect包中获取变量类型的函数很迷惑 比如下面这个 用Type获取变量类型的方法&#xff08;在下面提到&#xff09; …...

Missashe考研日记-day36(改版说明)

Missashe考研日记-day36 改版说明 经过一天的思考、纠结和尝试&#xff0c;博主决定对更新内容进行改版&#xff0c;如下&#xff1a;1.不再每天都发一篇日记&#xff0c;改为一周发一篇包含一周七天学习进度的周记&#xff0c;但为了标题和以前相同&#xff08;强迫症&#…...

opencv中的图像特征提取

图像的特征&#xff0c;一般是指图像所表达出的该图像的特有属性&#xff0c;其实就是事物的图像特征&#xff0c;由于图像获得的多样性&#xff08;拍摄器材、角度等&#xff09;&#xff0c;事物的图像特征有时并不特别突出或与无关物体混杂在一起&#xff0c;因此图像的特征…...

一文了解氨基酸的分类、代谢和应用

氨基酸&#xff08;Amino acids&#xff09;是在分子中含有氨基和羧基的一类化合物。氨基酸是生命的基石&#xff0c;人类所有的疾病与健康状况都与氨基酸有直接或间接的关系。氨基酸失衡可引起肝硬化、神经系统感染性疾病、糖尿病、免疫性疾病、心血管疾病、肾病、肿瘤等各类疾…...