Spring Boot 性能优化与最佳实践

一、应用层优化 (Spring Boot & Spring Framework)

1. 合理使用 Spring 核心特性

@Transactional 事务优化
  • 将事务范围控制在最小必要单元,避免在只读操作上开启事务。
  • 明确指定 readOnly = true 用于只读事务。
  • 避免在事务方法中执行耗时操作(如远程调用、复杂计算)。
java 复制代码
@Service
public class OrderService {
    
    private final OrderRepository orderRepository;
    
    // 构造函数注入
    public OrderService(OrderRepository orderRepository) {
        this.orderRepository = orderRepository;
    }
    
    // 只读事务优化
    @Transactional(readOnly = true)
    public List<Order> findAllOrders() {
        return orderRepository.findAll();
    }
    
    // 常规事务 - 最小化事务范围
    @Transactional
    public Order createOrder(Order order) {
        // 仅在必要操作上开启事务
        Order savedOrder = orderRepository.save(order);
        // 非事务性操作应放在事务外执行
        return savedOrder;
    }
    
    // 明确指定传播行为和隔离级别(根据实际需求)
    @Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_COMMITTED)
    public void updateOrderStatus(Long orderId, OrderStatus status) {
        Order order = orderRepository.findById(orderId)
            .orElseThrow(() -> new ResourceNotFoundException("Order not found"));
        order.setStatus(status);
        orderRepository.save(order);
    }
}
懒加载与Bean作用域优化
  • 对于启动时不立即需要的大对象或依赖,使用 @Lazy 延迟初始化,加速应用启动。
  • 正确使用作用域(singleton, prototype, request, session)。
java 复制代码
@Configuration
public class AppConfig {
    
    // 懒加载示例 - 仅在首次使用时初始化
    @Bean
    @Lazy
    public HeavyComponent heavyComponent() {
        return new HeavyComponent(); // 假设这是一个初始化耗时的组件
    }
    
    // 原型作用域Bean - 每次请求创建新实例
    @Bean
    @Scope("prototype")
    public RequestScopedComponent requestScopedComponent() {
        return new RequestScopedComponent();
    }
}
AOP切面优化
  • 精确指定切点表达式 (@Pointcut),避免拦截不必要的 JoinPoint。评估 AOP 带来的性能开销是否可接受。
java 复制代码
@Aspect
@Component
public class OptimizedAspect {
    
    // 精确的切点表达式,避免过度拦截
    @Pointcut("execution(* com.yourcompany.service.*Service.*(..)) && !execution(* com.yourcompany.service.*Service.get*(..))")
    public void serviceMethodPointcut() {}
    
    @Around("serviceMethodPointcut()")
    public Object optimizeServiceCalls(ProceedingJoinPoint joinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();
        try {
            return joinPoint.proceed();
        } finally {
            long duration = System.currentTimeMillis() - startTime;
            // 仅记录慢操作
            if (duration > 500) { // 超过500ms视为慢操作
                log.warn("Slow method: {} took {}ms", joinPoint.getSignature(), duration);
            }
        }
    }
}

2. 组件扫描

  • 使用 @SpringBootApplication(scanBasePackages = "包路径")@ComponentScan 明确指定扫描的基础包路径,避免扫描整个类路径 (classpath:*),减少启动时间和内存占用。
java 复制代码
// 优化组件扫描 - 明确指定基础包而非扫描整个根包
@SpringBootApplication(scanBasePackages = {
    "com.yourcompany.service", 
    "com.yourcompany.controller.api"
})
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

3. 启动优化配置

  • Spring Boot 2.2+ 支持。将所有 Bean 设置为懒加载,可以显著减少启动时间(尤其是有大量 Bean 时)。注意: 这可能导致首次请求延迟变高,需权衡。
  • 确保类路径干净,移除无用的 JAR 包。使用 Spring Boot 的 spring-boot-maven-plugin 打包为可执行 JAR(内嵌容器)或 WAR(部署到外部容器)时,依赖的组织方式会影响启动速度。
yaml 复制代码
# application.yml
spring:
  main:
    lazy-initialization: true  # 全局懒加载
    web-application-type: servlet  # 明确指定应用类型,避免自动检测耗时
    
# 仅在需要时激活相关自动配置
spring.autoconfigure.exclude:
  - org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration
  - org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration

# 启动时排除不需要的自动配置类

4.日志优化

  • 使用异步日志框架(如 Logback 的 AsyncAppender 或 Log4j2 的异步 Logger)减少 I/O 阻塞。
  • 合理设置日志级别,生产环境避免 DEBUG/TRACE
  • 控制日志输出量,避免过于冗长的日志格式或记录不必要的信息。

二、JVM 层优化

1. JVM参数优化示例

针对不同场景的JVM配置:

1. 常规Web应用(4核8G内存服务器):

bash 复制代码
java -jar app.jar \
  -Xms6g -Xmx6g \
  -XX:+UseG1GC \
  -XX:MaxGCPauseMillis=200 \
  -XX:InitiatingHeapOccupancyPercent=75 \
  -XX:+UseStringDeduplication \
  -XX:+UseContainerSupport \
  -XX:MaxRAMPercentage=75.0 \  # 容器环境中使用75%的可用内存
  -XX:+HeapDumpOnOutOfMemoryError \
  -XX:HeapDumpPath=/var/log/app/heapdump.hprof

2. 微服务应用(小内存环境):

bash 复制代码
java -jar app.jar \
  -Xms2g -Xmx2g \
  -XX:+UseSerialGC \  # 小内存环境下SerialGC可能更高效
  -XX:NewRatio=3 \    # 老年代与年轻代比例1:3
  -XX:SurvivorRatio=4 \ # Eden:Survivor = 4:1:1
  -XX:MaxTenuringThreshold=15 \ # 对象晋升老年代的年龄阈值
  -XX:+UseCompressedOops \ # 压缩对象指针
  -XX:+UseCompressedClassPointers

2. 虚拟线程配置(Java 21+)

Spring Boot 3.2+ 支持虚拟线程:

yaml 复制代码
# application.yml
server:
  tomcat:
    threads:
      virtual:
        enabled: true  # 启用虚拟线程

三、数据库层优化

1. HikariCP连接池配置

  • 选择合适的连接池: HikariCP (Spring Boot 默认,性能优异) 是首选。Druid 功能强大(监控、防御 SQL 注入等)也是好选择。
  • 关键参数:
    • maximum-pool-size:根据数据库处理能力和应用并发需求设置。不是越大越好! 过大会导致数据库连接耗尽或资源争抢。
    • minimum-idle:维持的最小空闲连接数。
    • connection-timeout:获取连接的超时时间,设置合理值避免长时间等待。
    • idle-timeout / max-lifetime:连接空闲超时和最大生命周期,防止连接泄漏或数据库端主动断开导致的问题。
    • 监控连接池指标(活跃连接、空闲连接、等待线程数) 是调优的关键。
yaml 复制代码
# application.yml
spring:
  datasource:
    type: com.zaxxer.hikari.HikariDataSource
    url: jdbc:mysql://${DB_HOST:localhost}:3306/mydb?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true
    username: ${DB_USERNAME:root}
    password: ${DB_PASSWORD:secret}
    driver-class-name: com.mysql.cj.jdbc.Driver
    hikari:
      # 核心配置
      maximum-pool-size: 15  # 最大连接数,根据CPU核心数和数据库性能调整
      minimum-idle: 5        # 最小空闲连接数
      idle-timeout: 300000   # 空闲超时时间(ms),默认600000(10分钟)
      connection-timeout: 20000  # 连接超时时间(ms)
      max-lifetime: 1800000  # 连接最大生命周期(ms),建议比数据库wait_timeout小30秒
      connection-test-query: SELECT 1  # 连接测试查询
      pool-name: MyAppHikariCP  # 线程池名称,便于监控
      auto-commit: false       # 禁用自动提交,在事务中手动控制
      # 高级配置
      leak-detection-threshold: 60000  # 连接泄漏检测阈值(ms)
      data-source-properties:
        cachePrepStmts: true           # 启用预处理语句缓存
        prepStmtCacheSize: 250         # 预处理语句缓存大小
        prepStmtCacheSqlLimit: 2048    # 预处理语句最大长度
        useServerPrepStmts: true       # 使用服务器端预处理

2. JPA/Hibernate优化配置

yaml 复制代码
# application.yml
spring:
  jpa:
    hibernate:
      ddl-auto: validate  # 生产环境禁用update/create/drop
      naming:
        physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
    properties:
      hibernate:
        jdbc.batch_size: 30               # 批量操作大小
        order_inserts: true               # 启用插入排序优化批量操作
        order_updates: true               # 启用更新排序优化批量操作
        jdbc.batch_versioned_data: true   # 批量操作时处理版本控制
        show_sql: false                   # 生产环境禁用SQL显示
        format_sql: false
        generate_statistics: false         # 生产环境禁用统计
        cache:
          use_second_level_cache: true    # 启用二级缓存
          region.factory_class: org.hibernate.cache.jcache.JCacheRegionFactory
          use_query_cache: true           # 启用查询缓存
    open-in-view: false  # 关闭Open Session In View反模式!解决N+1问题和连接泄漏风险

3. SQL优化示例

JPA查询优化(避免N+1问题):

  • N+1 查询问题: 这是 ORM 最常见性能问题。使用 JOIN FETCH (JPA), @Fetch(FetchMode.JOIN) (Hibernate), 或 @NamedEntityGraph 来一次性加载关联数据。避免在循环中触发懒加载。
java 复制代码
@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
    
    // 正确:使用JOIN FETCH一次性加载关联
    @Query("SELECT o FROM Order o JOIN FETCH o.items WHERE o.id = :id")
    Optional<Order> findByIdWithItems(@Param("id") Long id);
    
    // 分页查询优化
    @Query("SELECT o FROM Order o WHERE o.status = :status ORDER BY o.createTime DESC")
    Page<Order> findByStatus(@Param("status") OrderStatus status, Pageable pageable);
    
    // 投影查询 - 只查询需要的字段
    @Query("SELECT new com.example.dto.OrderSummaryDTO(o.id, o.orderNo, o.createTime, o.status) " +
           "FROM Order o WHERE o.customerId = :customerId")
    List<OrderSummaryDTO> findOrderSummariesByCustomerId(@Param("customerId") Long customerId);
}

MyBatis批量操作示例:

  • MyBatis 优化: 合理使用一级/二级缓存;避免动态 SQL 过于复杂;使用 foreach 进行批量操作;注意 #{}${} 的区别(防注入)。
xml 复制代码
<!-- OrderMapper.xml -->
<insert id="batchInsertOrders">
    INSERT INTO orders (order_no, customer_id, status, create_time)
    VALUES
    <foreach collection="orders" item="order" separator=",">
        (#{order.orderNo}, #{order.customerId}, #{order.status}, #{order.createTime})
    </foreach>
</insert>

四、缓存优化

  • 本地缓存 (Caffeine, Ehcache): 速度快,适合缓存少量、不易变、对一致性要求不高的数据。注意单机缓存的一致性和内存限制。
  • 分布式缓存 (Redis, Memcached): 适合共享缓存、大量缓存数据、高可用场景。Redis 功能更丰富(数据结构、持久化等)。

1. Redis缓存集成与配置

java 复制代码
@Configuration
@EnableCaching
public class RedisCacheConfig {

    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
        // 默认配置
        RedisCacheConfiguration defaultConfig = RedisCacheConfiguration.defaultCacheConfig()
            .entryTtl(Duration.ofMinutes(30))  // 默认30分钟过期
            .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
            .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(
                new GenericJackson2JsonRedisSerializer()))
            .disableCachingNullValues();  // 不缓存null值
            
        // 特定缓存配置
        Map<String, RedisCacheConfiguration> configs = new HashMap<>();
        
        // 产品缓存时间较长
        configs.put("products", defaultConfig.entryTtl(Duration.ofHours(24)));
        
        // 用户缓存时间中等
        configs.put("users", defaultConfig.entryTtl(Duration.ofHours(1)));
        
        // 热点数据缓存时间短,但使用随机过期避免缓存雪崩
        configs.put("hotData", defaultConfig.entryTtl(Duration.ofMinutes(10)));
        
        return RedisCacheManager.builder(connectionFactory)
            .cacheDefaults(defaultConfig)
            .withInitialCacheConfigurations(configs)
            .transactionAware()  // 支持事务缓存
            .build();
    }
}

2. 缓存使用示例

java 复制代码
@Service
public class ProductService {
    
    private final ProductRepository productRepository;
    
    // 构造函数注入...
    
    // 基本缓存示例
    @Cacheable(value = "products", key = "#id", unless = "#result == null")
    public Product findById(Long id) {
        return productRepository.findById(id)
            .orElseThrow(() -> new ResourceNotFoundException("Product not found"));
    }
    
    // 条件缓存示例
    @Cacheable(value = "products", key = "#code", condition = "#code.length() > 3")
    public Product findByCode(String code) {
        return productRepository.findByCode(code)
            .orElseThrow(() -> new ResourceNotFoundException("Product not found"));
    }
    
    // 缓存更新
    @CachePut(value = "products", key = "#product.id")
    public Product updateProduct(Product product) {
        return productRepository.save(product);
    }
    
    // 缓存删除
    @CacheEvict(value = "products", key = "#id")
    public void deleteProduct(Long id) {
        productRepository.deleteById(id);
    }
    
    // 清空缓存
    @CacheEvict(value = "products", allEntries = true)
    @Scheduled(fixedRate = 86400000)  // 每天凌晨清空一次缓存
    public void clearProductCache() {
        log.info("Clearing product cache");
    }
}

3. 缓存问题解决方案示例

缓存穿透防护(空值缓存+布隆过滤器):

java 复制代码
@Service
public class ProductServiceImpl implements ProductService {
    
    private final ProductRepository productRepository;
    private final BloomFilter<Long> productIdBloomFilter;
    
    // 构造函数注入...
    
    @Override
    @Cacheable(value = "products", key = "#id", unless = "#result == null")
    public Product findById(Long id) {
        // 先检查布隆过滤器,如果不存在直接返回null
        if (!productIdBloomFilter.mightContain(id)) {
            return null;
        }
        
        Product product = productRepository.findById(id).orElse(null);
        
        // 缓存空值防止缓存穿透
        if (product == null) {
            // 缓存空值5分钟
            redisTemplate.opsForValue().set("products::" + id, null, 5, TimeUnit.MINUTES);
        }
        
        return product;
    }
}

缓存雪崩防护(添加随机过期时间):

java 复制代码
@Configuration
public class CacheConfig {
    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
        // 默认配置
        RedisCacheConfiguration defaultConfig = RedisCacheConfiguration.defaultCacheConfig()
            .entryTtl(Duration.ofMinutes(30))
            // 其他配置...
            
        return RedisCacheManager.builder(connectionFactory)
            .cacheDefaults(defaultConfig)
            .withCacheConfiguration("products", 
                defaultConfig.entryTtl(Duration.ofMinutes(30 + new Random().nextInt(10)))) // 30-40分钟随机过期
            .build();
    }
}

五、异步与并发优化

1. @Async异步方法与线程池配置

  • 将耗时且非结果依赖的操作(如发邮件、记录日志、调用外部系统)异步化,释放请求线程。
  • 配置自定义线程池 (ThreadPoolTaskExecutor) ,避免使用默认的 SimpleAsyncTaskExecutor(为每个任务新建线程)。
  • 设置合理的核心线程数、最大线程数、队列容量、拒绝策略。
java 复制代码
@Configuration
@EnableAsync
public class AsyncConfig {
    
    @Bean(name = "taskExecutor")
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 核心线程数 = CPU核心数 + 1
        int corePoolSize = Runtime.getRuntime().availableProcessors() + 1;
        executor.setCorePoolSize(corePoolSize);
        executor.setMaxPoolSize(corePoolSize * 2);  // 最大线程数
        executor.setQueueCapacity(100);              // 队列容量
        executor.setThreadNamePrefix("Async-");      // 线程名前缀
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); // 拒绝策略:调用者执行
        executor.setAwaitTerminationSeconds(60);     // 等待终止时间
        executor.setWaitForTasksToCompleteOnShutdown(true); // 关闭时等待任务完成
        executor.initialize();
        return executor;
    }
}

@Service
public class NotificationService {
    
    private final EmailClient emailClient;
    
    // 构造函数注入...
    
    @Async("taskExecutor")  // 指定线程池
    public CompletableFuture<Void> sendOrderConfirmationEmail(Order order) {
        try {
            // 模拟邮件发送耗时操作
            Thread.sleep(1000);
            emailClient.send(order.getCustomerEmail(), "Order Confirmation", 
                            "Thank you for your order #" + order.getId());
            return CompletableFuture.completedFuture(null);
        } catch (Exception e) {
            log.error("Failed to send email", e);
            return CompletableFuture.failedFuture(e);
        }
    }
}

2. 响应式编程示例(WebFlux)

  • 对于高并发、IO 密集型(如微服务间调用、数据库访问)应用,考虑使用 Spring WebFlux 和非阻塞式驱动(如 R2DBC, Reactive MongoDB)。
  • 利用少量线程处理大量并发连接,提高资源利用率。
  • 注意: 编程模型与 Servlet 不同,需要学习曲线。确保整个调用链都是非阻塞的。
java 复制代码
@RestController
@RequestMapping("/api/orders")
public class OrderController {

    private final OrderService orderService;
    
    // 构造函数注入...
    
    @GetMapping("/{id}")
    public Mono<ResponseEntity<OrderDTO>> getOrder(@PathVariable Long id) {
        return orderService.findById(id)
            .map(order -> ResponseEntity.ok(convertToDTO(order)))
            .defaultIfEmpty(ResponseEntity.notFound().build());
    }
    
    @GetMapping
    public Flux<OrderSummaryDTO> getOrdersByCustomer(@RequestParam Long customerId) {
        return orderService.findByCustomerId(customerId)
            .map(this::convertToSummaryDTO)
            .sort((o1, o2) -> o2.getCreateTime().compareTo(o1.getCreateTime()));
    }
}

@Service
public class OrderService {
    private final OrderRepository orderRepository;
    
    // 构造函数注入...
    
    public Mono<Order> findById(Long id) {
        return Mono.fromCallable(() -> orderRepository.findById(id)
            .orElseThrow(() -> new ResourceNotFoundException("Order not found")));
    }
    
    public Flux<Order> findByCustomerId(Long customerId) {
        return Flux.fromIterable(orderRepository.findByCustomerId(customerId));
    }
}

六、完整的性能优化实践案例

案例1:数据库查询优化前后对比

优化前(问题代码):

java 复制代码
// 问题:N+1查询和全表扫描
@GetMapping("/{orderId}")
public OrderDTO getOrder(@PathVariable Long orderId) {
    Order order = orderRepository.findById(orderId).orElseThrow();
    // 每次调用getItems()都会触发额外查询
    List<OrderItem> items = order.getItems(); 
    // ... 转换为DTO
}

优化后:

java 复制代码
// 优化:使用JOIN FETCH + 投影DTO
@GetMapping("/{orderId}")
public OrderSummaryDTO getOrderSummary(@PathVariable Long orderId) {
    return orderRepository.findOrderSummaryById(orderId)
        .orElseThrow(() -> new ResourceNotFoundException("Order not found"));
}

// Repository方法
@Query("SELECT new com.example.dto.OrderSummaryDTO(o.id, o.orderNo, o.createTime, " +
       "SUM(oi.quantity * oi.unitPrice) as totalAmount, " +
       "COUNT(oi) as itemCount) " +
       "FROM Order o JOIN o.items oi WHERE o.id = :id " +
       "GROUP BY o.id, o.orderNo, o.createTime")
Optional<OrderSummaryDTO> findOrderSummaryById(@Param("id") Long id);

案例2:缓存策略实现

多级缓存实现(Caffeine本地缓存 + Redis分布式缓存):

java 复制代码
@Configuration
@EnableCaching
public class MultiLevelCacheConfig {

    // Caffeine本地缓存配置
    @Bean
    public Cache<String, Object> caffeineCache() {
        return Caffeine.newBuilder()
            .maximumSize(10_000)          // 最大缓存项数
            .expireAfterWrite(5, TimeUnit.MINUTES)  // 写入后过期时间
            .recordStats()                // 开启统计
            .build();
    }
    
    // Redis分布式缓存配置
    @Bean
    public RedisCacheManager redisCacheManager(RedisConnectionFactory connectionFactory) {
        // 配置Redis缓存
        // ... 省略RedisCacheManager配置,同上一节
    }
    
    // 自定义缓存管理器,结合本地缓存和Redis缓存
    @Bean
    public CacheManager cacheManager(RedisCacheManager redisCacheManager, Cache<String, Object> caffeineCache) {
        return new MultiLevelCacheManager(redisCacheManager, caffeineCache);
    }
}

// 自定义多级缓存管理器实现
public class MultiLevelCacheManager implements CacheManager {
    private final RedisCacheManager redisCacheManager;
    private final Cache<String, Object> caffeineCache;
    
    // 构造函数注入...
    
    @Override
    public Cache getCache(String name) {
        return new MultiLevelCache(
            name, 
            caffeineCache, 
            redisCacheManager.getCache(name)
        );
    }
    
    // 其他方法实现...
}

// 多级缓存实现类
class MultiLevelCache implements Cache {
    private final String name;
    private final Cache<String, Object> localCache;
    private final Cache redisCache;
    
    // 实现get、put、evict等方法,先查本地缓存,再查Redis缓存...
}

七、监控与诊断

  1. 启用 Actuator: spring-boot-starter-actuator 提供丰富的生产就绪端点 (/actuator/metrics, /actuator/health, /actuator/prometheus 等)。
  2. 集成监控系统:
    • Prometheus + Grafana: 强大的指标采集和可视化组合。使用 micrometer-registry-prometheus
    • ELK Stack (Elasticsearch, Logstash, Kibana): 集中式日志管理和分析。
    • 分布式追踪: Zipkin, Jaeger。集成 Spring Cloud Sleuth 来追踪请求在微服务间的流转。
  3. Profiler 工具: 使用 VisualVM , YourKit , JProfiler , Arthas 等进行 CPU 采样、内存分析、线程分析,定位热点方法和内存泄漏。
  4. APM (应用性能监控): New Relic, Dynatrace, AppDynamics, SkyWalking, Pinpoint 等提供端到端的应用性能洞察。

总结

Spring Boot性能优化是一个系统性工程,需要从多个维度综合考虑:

  1. 应用层:合理使用Spring特性、优化Bean管理、AOP和事务
  2. JVM层:选择合适的JVM参数和垃圾收集器,根据应用特性调整
  3. 数据库层:优化连接池、SQL查询、ORM配置
  4. 缓存策略:多级缓存、缓存穿透/击穿/雪崩防护
  5. 并发处理:异步方法、响应式编程、线程池优化
  6. 监控与持续优化:建立完善的监控体系,持续收集数据并优化

关键建议:

  • 始终以数据为驱动进行优化决策
  • 优先解决最大的性能瓶颈("低垂的果实")
  • 避免过早优化和过度优化
  • 建立性能测试和基准测试体系
  • 持续监控生产环境性能指标
  • 关注Spring官方博客和文档,了解最新性能改进
相关推荐
Victor35641 分钟前
Redis(25)Redis的RDB持久化的优点和缺点是什么?
后端
Victor35642 分钟前
Redis(24)如何配置Redis的持久化?
后端
鼠鼠我捏,要死了捏8 小时前
深入解析Java NIO多路复用原理与性能优化实践指南
java·性能优化·nio
ningqw8 小时前
SpringBoot 常用跨域处理方案
java·后端·springboot
你的人类朋友8 小时前
vi编辑器命令常用操作整理(持续更新)
后端
胡gh8 小时前
简单又复杂,难道只能说一个有箭头一个没箭头?这种问题该怎么回答?
javascript·后端·面试
一只叫煤球的猫9 小时前
看到同事设计的表结构我人麻了!聊聊怎么更好去设计数据库表
后端·mysql·面试
uzong9 小时前
技术人如何对客做好沟通(上篇)
后端
颜如玉10 小时前
Redis scan高位进位加法机制浅析
redis·后端·开源
Moment10 小时前
毕业一年了,分享一下我的四个开源项目!😊😊😊
前端·后端·开源