Spring Cloud第一性原理:从单体到微服务的架构演进

系列导航:本文是《Spring框架第一性原理》系列的第5篇 第1篇:为什么我们需要Spring框架? 第2篇:IoC容器:从手动new到自动装配的演进 第3篇:AOP:从代码重复到面向切面编程 第4篇:Spring Boot:约定优于配置的威力 第5篇:Spring Cloud:从单体到微服务的架构演进(本文) 引子:单体应用的困境 场景重现:一个电商系统的演进之路 让我们从一个真实的电商系统的成长历程说起。 第一阶段:创业初期(2015年) 团队规模:5人(2个后端、1个前端、1个产品、1个UI) 技术选型:单体架构 + Spring Boot 系统架构: ┌─────────────────────────────────────┐ │ 电商单体应用 │ │ (monolithic-ecommerce-app) │ ├─────────────────────────────────────┤ │ 用户模块 (UserModule) │ │ 商品模块 (ProductModule) │ │ 订单模块 (OrderModule) │ │ 库存模块 (InventoryModule) │ │ 支付模块 (PaymentModule) │ │ 物流模块 (LogisticsModule) │ │ 营销模块 (MarketingModule) │ ├─────────────────────────────────────┤ │ Spring Boot + MyBatis + MySQL │ └─────────────────────────────────────┘ ↓ MySQL数据库 代码结构: ecommerce-app/ ├── src/main/java/com/example/ │ ├── user/ # 用户模块 │ │ ├── UserController.java │ │ ├── UserService.java │ │ └── UserRepository.java │ ├── product/ # 商品模块 │ │ ├── ProductController.java │ │ ├── ProductService.java │ │ └── ProductRepository.java │ ├── order/ # 订单模块 │ │ ├── OrderController.java │ │ ├── OrderService.java │ │ └── OrderRepository.java │ ├── inventory/ # 库存模块 │ ├── payment/ # 支付模块 │ ├── logistics/ # 物流模块 │ └── marketing/ # 营销模块 └── pom.xml 典型业务流程(创建订单): ...

2025-11-03 · maneng

并发集合:从HashMap到ConcurrentHashMap的演进

引子:一个缓存系统的并发灾难 假设你正在开发一个高并发的缓存系统,使用HashMap存储缓存数据。看似简单的设计,却可能导致系统崩溃。 场景:本地缓存的并发问题 /** * 方案1:使用HashMap(线程不安全) */ public class UnsafeCache { private Map<String, String> cache = new HashMap<>(); public void put(String key, String value) { cache.put(key, value); } public String get(String key) { return cache.get(key); } public static void main(String[] args) throws InterruptedException { UnsafeCache cache = new UnsafeCache(); // 10个线程并发写入 Thread[] threads = new Thread[10]; for (int i = 0; i < 10; i++) { final int threadId = i; threads[i] = new Thread(() -> { for (int j = 0; j < 1000; j++) { cache.put("key-" + threadId + "-" + j, "value-" + j); } }); } for (Thread t : threads) t.start(); for (Thread t : threads) t.join(); System.out.println("缓存大小:" + cache.cache.size()); System.out.println("期望大小:10000"); } } /* 执行结果(多次运行): 第1次:缓存大小:9856 ❌ 数据丢失 第2次:缓存大小:9923 ❌ 数据丢失 第3次:程序卡死 ❌ 死循环(JDK 1.7) 第4次:抛出ConcurrentModificationException 问题列表: 1. 数据丢失(最常见) └─ 多个线程同时put,覆盖彼此的修改 2. 死循环(JDK 1.7的经典Bug) └─ 扩容时形成环形链表,get()陷入死循环 3. ConcurrentModificationException └─ 迭代时其他线程修改了HashMap 4. 数据不一致 └─ size()返回错误的值 真实案例: 某公司生产环境,使用HashMap作为本地缓存 高峰期CPU飙到100%,经排查是HashMap死循环 导致服务不可用,损失数百万 */ 解决方案演进 /** * 方案2:使用Hashtable(线程安全,但性能差) */ public class HashtableCache { private Map<String, String> cache = new Hashtable<>(); public void put(String key, String value) { cache.put(key, value); } public String get(String key) { return cache.get(key); } } /* Hashtable的问题: 1. 整个方法都加synchronized(粗粒度锁) 2. 读写都加锁(读也需要互斥) 3. 性能差(高并发场景) 性能测试: 并发读写10000次 ├─ HashMap(不安全):50ms ├─ Hashtable(安全):500ms └─ 性能下降10倍! */ /** * 方案3:使用Collections.synchronizedMap(性能同样差) */ public class SynchronizedMapCache { private Map<String, String> cache = Collections.synchronizedMap(new HashMap<>()); public void put(String key, String value) { cache.put(key, value); } public String get(String key) { return cache.get(key); } } /* Collections.synchronizedMap的实现: public V put(K key, V value) { synchronized (mutex) { // 全局锁 return m.put(key, value); } } 问题:与Hashtable相同,粗粒度锁 */ /** * 方案4:使用ConcurrentHashMap(最佳方案) */ public class ConcurrentHashMapCache { private Map<String, String> cache = new ConcurrentHashMap<>(); public void put(String key, String value) { cache.put(key, value); } public String get(String key) { return cache.get(key); } public static void main(String[] args) throws InterruptedException { ConcurrentHashMapCache cache = new ConcurrentHashMapCache(); // 10个线程并发写入 Thread[] threads = new Thread[10]; for (int i = 0; i < 10; i++) { final int threadId = i; threads[i] = new Thread(() -> { for (int j = 0; j < 1000; j++) { cache.put("key-" + threadId + "-" + j, "value-" + j); } }); } long start = System.currentTimeMillis(); for (Thread t : threads) t.start(); for (Thread t : threads) t.join(); long end = System.currentTimeMillis(); System.out.println("缓存大小:" + cache.cache.size()); System.out.println("期望大小:10000"); System.out.println("耗时:" + (end - start) + "ms"); } } /* 执行结果: 缓存大小:10000 ✅ 正确 期望大小:10000 耗时:65ms ConcurrentHashMap的优势: 1. 线程安全 2. 性能好(细粒度锁/无锁) 3. 读操作无锁(大部分情况) 4. 不会死循环 */ 性能对比总结 方案 线程安全 读性能 写性能 适用场景 HashMap ❌ 极快 极快 单线程 Hashtable ✅ 慢(加锁) 慢(加锁) 低并发 SynchronizedMap ✅ 慢(加锁) 慢(加锁) 低并发 ConcurrentHashMap ✅ 快(无锁) 较快(细粒度锁) 高并发 核心洞察: ...

2025-11-03 · maneng

服务治理:注册发现、负载均衡与熔断降级

引子:一次服务雪崩事故 2020年双11,某电商平台因评论服务故障导致整个系统瘫痪3小时,损失上亿。 故障链路: 用户下单 → 订单服务 → 评论服务(响应慢,20秒超时) → 订单服务线程池耗尽 → 用户服务调用订单服务失败 → 整个系统崩溃 问题根源:缺乏有效的服务治理机制 一、服务注册与发现 1.1 为什么需要服务注册中心? 问题:微服务架构下,服务IP动态变化 订单服务 → 库存服务(192.168.1.10:8080) 问题: 1. 库存服务重启,IP可能变化 2. 库存服务扩容,新增实例 3. 库存服务下线,需要摘除 解决方案:服务注册中心 订单服务 → 注册中心 → 获取库存服务列表 ↓ [192.168.1.10:8080, 192.168.1.11:8080, 192.168.1.12:8080] 1.2 Nacos服务注册与发现 服务提供者:注册服务 /** * 库存服务:自动注册到Nacos */ @SpringBootApplication @EnableDiscoveryClient public class InventoryServiceApplication { public static void main(String[] args) { SpringApplication.run(InventoryServiceApplication.class, args); } } # application.yml spring: application: name: inventory-service # 服务名 cloud: nacos: discovery: server-addr: 127.0.0.1:8848 namespace: dev group: DEFAULT_GROUP 服务消费者:发现服务 ...

2025-11-03 · maneng

Spring源码深度解析:从使用者到贡献者

系列导航:本文是《Spring框架第一性原理》系列的第6篇(最终篇) 第1篇:为什么我们需要Spring框架? 第2篇:IoC容器:从手动new到自动装配的演进 第3篇:AOP:从代码重复到面向切面编程 第4篇:Spring Boot:约定优于配置的威力 第5篇:Spring Cloud:从单体到微服务的架构演进 第6篇:Spring源码深度解析(本文) 引子:为什么要阅读Spring源码? 三个真实场景 场景1:面试被问倒 面试官:"请说说Spring容器的启动流程?" 候选人:"Spring容器启动时会扫描注解,创建Bean,然后注入依赖..." 面试官:"具体一点,refresh()方法做了哪些事情?" 候选人:"呃...不太清楚..." 面试官:"那@Transactional是如何实现的?" 候选人:"通过AOP代理..." 面试官:"什么时候创建代理?代理怎么拦截方法?" 候选人:"这个...没深入研究过..." 结果:错失Offer 场景2:生产问题无从下手 线上问题:Spring容器启动失败 异常信息: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'orderService': Unsatisfied dependency expressed through field 'userService' 问题: ├─ orderService依赖userService ├─ 但容器中没有userService的Bean ├─ 为什么没有创建? └─ 如何定位问题? 不懂源码: ❌ 只能Google搜索错误信息 ❌ 盲目尝试各种配置 ❌ 浪费大量时间 懂源码: ✅ 知道Bean的创建流程 ✅ 知道依赖注入的机制 ✅ 快速定位到扫描路径配置错误 场景3:性能优化无从优化 问题:Spring应用启动慢(需要30秒) 不懂源码: ├─ 不知道启动时间花在哪里 ├─ 盲目减少Bean数量 └─ 效果有限 懂源码: ├─ 知道refresh()方法的12个步骤 ├─ 知道Bean创建的生命周期 ├─ 定位到瓶颈:@ComponentScan扫描了大量不必要的类 ├─ 优化扫描范围 └─ 启动时间降到5秒 阅读源码的五大收益 1. 面试加分 └─ 深入理解Spring原理,回答问题有理有据 └─ 知其然也知其所以然 2. 问题定位 └─ 遇到问题时,能快速定位根因 └─ 不再盲目Google,节省时间 3. 性能优化 └─ 知道性能瓶颈在哪里 └─ 有针对性地优化 4. 技术提升 └─ 学习优秀的代码设计 └─ 学习设计模式的实战应用 5. 贡献开源 └─ 有能力向Spring提交PR └─ 提升技术影响力 源码阅读准备 开发环境搭建 1. 下载Spring源码 # 1. 克隆Spring Framework仓库 git clone https://github.com/spring-projects/spring-framework.git # 2. 切换到稳定版本(推荐5.3.x) cd spring-framework git checkout v5.3.25 # 3. 查看项目结构 tree -L 1 . ├── spring-aop # AOP模块 ├── spring-aspects # AspectJ集成 ├── spring-beans # Bean管理核心 ├── spring-context # 应用上下文 ├── spring-core # 核心工具类 ├── spring-expression # SpEL表达式 ├── spring-jdbc # JDBC支持 ├── spring-orm # ORM集成 ├── spring-tx # 事务管理 ├── spring-web # Web基础 ├── spring-webmvc # Spring MVC └── spring-webflux # 响应式Web 2. 导入IDEA 步骤: 1. 打开IDEA → Open → 选择spring-framework目录 2. 等待Gradle构建完成(首次需要10-30分钟) 3. 构建完成后,项目结构如下: spring-framework/ ├── spring-beans/ │ └── src/main/java/ │ └── org/springframework/beans/ │ ├── factory/ │ │ ├── BeanFactory.java # Bean工厂接口 │ │ ├── support/ │ │ │ ├── AbstractBeanFactory.java │ │ │ └── DefaultListableBeanFactory.java │ │ └── config/ │ │ └── BeanDefinition.java # Bean定义 │ └── BeanWrapper.java ├── spring-context/ │ └── src/main/java/ │ └── org/springframework/context/ │ ├── ApplicationContext.java # 应用上下文接口 │ ├── support/ │ │ └── AbstractApplicationContext.java # 启动核心 │ └── annotation/ │ └── AnnotationConfigApplicationContext.java └── spring-aop/ └── src/main/java/ └── org/springframework/aop/ └── framework/ ├── ProxyFactory.java └── autoproxy/ └── AbstractAutoProxyCreator.java 常用快捷键: ├─ Ctrl+N (Mac: Cmd+O):搜索类 ├─ Ctrl+Shift+N:搜索文件 ├─ Ctrl+Alt+B (Mac: Cmd+Alt+B):查看接口实现 ├─ Ctrl+H:查看类层次结构 └─ Ctrl+F12:查看类的方法列表 3. 编写测试用例 // 创建测试项目:spring-source-test // pom.xml <dependencies> <!-- Spring核心依赖 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.25</version> </dependency> <!-- 测试依赖 --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.2</version> <scope>test</scope> </dependency> </dependencies> // 测试代码 @Configuration @ComponentScan("com.example") public class AppConfig { } @Component public class UserService { public void sayHello() { System.out.println("Hello from UserService"); } } // 测试类 public class SpringSourceTest { public static void main(String[] args) { // 在这里打断点,开始调试 ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); UserService userService = context.getBean(UserService.class); userService.sayHello(); } } 调试技巧: 1. 在new AnnotationConfigApplicationContext(AppConfig.class)打断点 2. Step Into (F7),进入构造方法 3. 一步步跟踪执行流程 4. 关注关键方法:refresh()、invokeBeanFactoryPostProcessors()、finishBeanFactoryInitialization() 源码阅读方法论 方法1:从顶层接口入手 阅读顺序(IoC容器): 1. BeanFactory # 最顶层接口,定义基本功能 ├─ getBean() # 获取Bean ├─ containsBean() # 是否包含Bean └─ isSingleton() # 是否单例 2. ApplicationContext # 扩展接口,增强功能 ├─ 继承BeanFactory # Bean管理 ├─ 继承MessageSource # 国际化 ├─ 继承ApplicationEventPublisher # 事件发布 └─ 继承ResourceLoader # 资源加载 3. AbstractApplicationContext # 抽象实现类,定义模板 └─ refresh() # 核心方法,定义启动流程 4. AnnotationConfigApplicationContext # 具体实现类 └─ 使用注解配置 原则: ✅ 先看接口,理解设计意图 ✅ 再看抽象类,理解模板流程 ✅ 最后看具体实现,理解细节 ❌ 避免:直接看具体实现(容易迷失) 方法2:以功能为主线 功能主线1:Bean的创建流程 └─ 入口:ApplicationContext.getBean() └─ AbstractBeanFactory.doGetBean() └─ AbstractAutowireCapableBeanFactory.createBean() └─ AbstractAutowireCapableBeanFactory.doCreateBean() ├─ createBeanInstance() # 实例化 ├─ populateBean() # 属性填充 └─ initializeBean() # 初始化 功能主线2:AOP代理的创建 └─ 入口:@EnableAspectJAutoProxy └─ AspectJAutoProxyRegistrar └─ AnnotationAwareAspectJAutoProxyCreator └─ postProcessAfterInitialization() # Bean后置处理 └─ wrapIfNecessary() # 创建代理 功能主线3:事务的拦截 └─ 入口:@EnableTransactionManagement └─ TransactionInterceptor └─ invoke() # 拦截方法 ├─ createTransactionIfNecessary() # 开启事务 ├─ invokation.proceed() # 执行方法 └─ commitTransactionAfterReturning() # 提交事务 原则: ✅ 选择一个功能点,深入研究 ✅ 画出调用链路图 ✅ 理解每个方法的职责 ❌ 避免:同时看多个功能(容易混乱) 方法3:Debug + 日志 // 开启Spring Debug日志 logging.level.org.springframework=DEBUG // 在关键位置打断点 public class SpringSourceTest { public static void main(String[] args) { // 断点1:容器创建 ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); // 断点2:Bean获取 UserService userService = context.getBean(UserService.class); // 断点3:方法调用 userService.sayHello(); } } // 查看调用栈 断点1命中时,查看Call Stack: main() └─ AnnotationConfigApplicationContext.<init>() └─ AbstractApplicationContext.refresh() ├─ prepareRefresh() ├─ obtainFreshBeanFactory() ├─ prepareBeanFactory() ├─ postProcessBeanFactory() ├─ invokeBeanFactoryPostProcessors() ← 处理@Configuration、@Bean ├─ registerBeanPostProcessors() ← 注册BeanPostProcessor ├─ initMessageSource() ├─ initApplicationEventMulticaster() ├─ onRefresh() ├─ registerListeners() └─ finishBeanFactoryInitialization() ← 创建所有单例Bean // 使用条件断点 右键断点 → Condition: beanName.equals("userService") # 只在创建userService时暂停 // 使用日志断点 右键断点 → Evaluate and log: "Creating bean: " + beanName # 记录日志,不暂停程序 IoC容器启动流程:refresh()方法深度剖析 refresh()方法概览 // AbstractApplicationContext.refresh() // 这是Spring容器启动的核心方法,包含12个步骤 @Override public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // 1. 准备刷新上下文环境 prepareRefresh(); // 2. 获取BeanFactory(告诉子类刷新内部BeanFactory) ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // 3. 准备BeanFactory(配置BeanFactory的标准上下文) prepareBeanFactory(beanFactory); try { // 4. BeanFactory后置处理(允许子类对BeanFactory进行后置处理) postProcessBeanFactory(beanFactory); // 5. 调用BeanFactory后置处理器(执行所有BeanFactoryPostProcessor) invokeBeanFactoryPostProcessors(beanFactory); // 6. 注册Bean后置处理器(注册所有BeanPostProcessor) registerBeanPostProcessors(beanFactory); // 7. 初始化消息源(国际化) initMessageSource(); // 8. 初始化事件广播器 initApplicationEventMulticaster(); // 9. 刷新上下文(允许子类初始化特殊Bean,如内嵌Web容器) onRefresh(); // 10. 注册监听器 registerListeners(); // 11. 初始化所有单例Bean(非懒加载) finishBeanFactoryInitialization(beanFactory); // 12. 完成刷新(发布ContextRefreshedEvent事件) finishRefresh(); } catch (BeansException ex) { // 销毁已创建的Bean destroyBeans(); // 取消刷新 cancelRefresh(ex); throw ex; } finally { // 清理缓存 resetCommonCaches(); } } } 步骤1:prepareRefresh() - 准备刷新 protected void prepareRefresh() { // 1. 记录启动时间 this.startupDate = System.currentTimeMillis(); // 2. 设置容器状态 this.closed.set(false); // 未关闭 this.active.set(true); // 已激活 // 3. 初始化属性源(可由子类覆盖) initPropertySources(); // 4. 验证必需的属性 // 例如:getEnvironment().setRequiredProperties("JAVA_HOME"); getEnvironment().validateRequiredProperties(); // 5. 创建早期事件集合(在事件广播器准备好之前收集事件) if (this.earlyApplicationListeners == null) { this.earlyApplicationListeners = new LinkedHashSet<>(this.applicationListeners); } else { this.applicationListeners.clear(); this.applicationListeners.addAll(this.earlyApplicationListeners); } // 6. 创建早期事件集合 this.earlyApplicationEvents = new LinkedHashSet<>(); } 作用: ✅ 记录启动时间(用于统计启动耗时) ✅ 设置容器状态(标记为激活状态) ✅ 初始化和验证环境变量 ✅ 准备事件监听器 步骤2:obtainFreshBeanFactory() - 获取BeanFactory protected ConfigurableListableBeanFactory obtainFreshBeanFactory() { // 刷新BeanFactory(由子类实现) refreshBeanFactory(); // 返回BeanFactory return getBeanFactory(); } // AbstractRefreshableApplicationContext(XML配置) @Override protected final void refreshBeanFactory() throws BeansException { // 如果已有BeanFactory,先销毁 if (hasBeanFactory()) { destroyBeans(); closeBeanFactory(); } try { // 创建新的BeanFactory(DefaultListableBeanFactory) DefaultListableBeanFactory beanFactory = createBeanFactory(); // 设置序列化ID beanFactory.setSerializationId(getId()); // 定制BeanFactory(是否允许Bean覆盖、是否允许循环依赖) customizeBeanFactory(beanFactory); // 加载Bean定义(从XML文件) loadBeanDefinitions(beanFactory); // 保存BeanFactory引用 this.beanFactory = beanFactory; } catch (IOException ex) { throw new ApplicationContextException("I/O error parsing bean definition source", ex); } } // GenericApplicationContext(注解配置) @Override protected final void refreshBeanFactory() throws IllegalStateException { // 标记为已刷新 if (!this.refreshed.compareAndSet(false, true)) { throw new IllegalStateException("GenericApplicationContext does not support multiple refresh attempts"); } // 设置序列化ID this.beanFactory.setSerializationId(getId()); } 对比: XML配置(AbstractRefreshableApplicationContext): └─ 每次refresh都创建新的BeanFactory └─ 从XML文件加载Bean定义 注解配置(GenericApplicationContext): └─ BeanFactory在构造方法中创建(只创建一次) └─ Bean定义通过注解扫描加载(在步骤5) 步骤3:prepareBeanFactory() - 准备BeanFactory protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) { // 1. 设置类加载器 beanFactory.setBeanClassLoader(getClassLoader()); // 2. 设置SpEL表达式解析器 beanFactory.setBeanExpressionResolver( new StandardBeanExpressionResolver(beanFactory.getBeanClassLoader()) ); // 3. 添加属性编辑器注册器(用于类型转换) beanFactory.addPropertyEditorRegistrar( new ResourceEditorRegistrar(this, getEnvironment()) ); // 4. 添加BeanPostProcessor:ApplicationContextAwareProcessor // 作用:处理Aware接口回调(ApplicationContextAware、EnvironmentAware等) beanFactory.addBeanPostProcessor( new ApplicationContextAwareProcessor(this) ); // 5. 忽略以下Aware接口的依赖注入(由ApplicationContextAwareProcessor处理) beanFactory.ignoreDependencyInterface(EnvironmentAware.class); beanFactory.ignoreDependencyInterface(EmbeddedValueResolverAware.class); beanFactory.ignoreDependencyInterface(ResourceLoaderAware.class); beanFactory.ignoreDependencyInterface(ApplicationEventPublisherAware.class); beanFactory.ignoreDependencyInterface(MessageSourceAware.class); beanFactory.ignoreDependencyInterface(ApplicationContextAware.class); // 6. 注册可解析的依赖(自动装配时使用) beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory); beanFactory.registerResolvableDependency(ResourceLoader.class, this); beanFactory.registerResolvableDependency(ApplicationEventPublisher.class, this); beanFactory.registerResolvableDependency(ApplicationContext.class, this); // 7. 添加BeanPostProcessor:ApplicationListenerDetector // 作用:检测ApplicationListener类型的Bean并注册到事件广播器 beanFactory.addBeanPostProcessor( new ApplicationListenerDetector(this) ); // 8. 如果存在LoadTimeWeaver,添加编织支持 if (beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) { beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory)); beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader())); } // 9. 注册默认的环境Bean if (!beanFactory.containsLocalBean(ENVIRONMENT_BEAN_NAME)) { beanFactory.registerSingleton(ENVIRONMENT_BEAN_NAME, getEnvironment()); } if (!beanFactory.containsLocalBean(SYSTEM_PROPERTIES_BEAN_NAME)) { beanFactory.registerSingleton(SYSTEM_PROPERTIES_BEAN_NAME, getEnvironment().getSystemProperties()); } if (!beanFactory.containsLocalBean(SYSTEM_ENVIRONMENT_BEAN_NAME)) { beanFactory.registerSingleton(SYSTEM_ENVIRONMENT_BEAN_NAME, getEnvironment().getSystemEnvironment()); } } 作用: ✅ 配置类加载器、表达式解析器 ✅ 添加BeanPostProcessor(处理Aware接口回调) ✅ 注册可解析的依赖(BeanFactory、ApplicationContext等) ✅ 注册环境Bean(Environment、System Properties等) 步骤5:invokeBeanFactoryPostProcessors() - 执行BeanFactory后置处理器 这是最复杂也是最重要的步骤之一,负责处理@Configuration、@Bean、@ComponentScan等注解 ...

2025-11-03 · maneng

可观测性:监控、日志、链路追踪三位一体

引子:一次线上故障的排查噩梦 2021年某晚,某电商平台接口响应慢,用户投诉激增。 排查过程: 运维:哪个服务出问题了?(无监控) 开发:日志在哪?(分散在100台服务器) 架构师:调用链路是什么?(无链路追踪) 耗时:3小时才定位到问题(数据库连接池配置错误) 教训:微服务架构下,可观测性至关重要 一、可观测性的本质 1.1 什么是可观测性? **可观测性(Observability)**是指通过外部输出理解系统内部状态的能力。 三大支柱: Metrics(指标):数字化的度量(QPS、响应时间、错误率) Logs(日志):事件的记录(请求日志、错误日志) Traces(追踪):请求的全链路视图(调用链路) 1.2 监控 vs 可观测性 维度 监控 可观测性 目标 已知问题 未知问题 方式 预设指标 任意维度查询 例子 CPU > 80%告警 为什么这个请求慢? 二、监控体系:Prometheus + Grafana 2.1 监控指标的四个黄金信号 延迟(Latency):请求响应时间 流量(Traffic):QPS、TPS 错误(Errors):错误率 饱和度(Saturation):CPU、内存、磁盘使用率 2.2 Prometheus监控配置 1. 添加依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-registry-prometheus</artifactId> </dependency> 2. 配置application.yml management: endpoints: web: exposure: include: "*" # 暴露所有端点 metrics: export: prometheus: enabled: true tags: application: ${spring.application.name} 3. 自定义指标 ...

2025-11-03 · maneng

并发设计模式与最佳实践

一、不可变对象模式 1.1 不可变对象的设计 /** * 标准的不可变对象设计 */ public final class ImmutableUser { private final String name; private final int age; private final List<String> hobbies; public ImmutableUser(String name, int age, List<String> hobbies) { this.name = name; this.age = age; // 防御性复制 this.hobbies = new ArrayList<>(hobbies); } public String getName() { return name; } public int getAge() { return age; } public List<String> getHobbies() { // 返回不可变视图 return Collections.unmodifiableList(hobbies); } } /* 不可变对象的五大要求: 1. ✅ 类声明为final 2. ✅ 所有字段都是final 3. ✅ 所有字段都是private 4. ✅ 不提供setter方法 5. ✅ 可变字段防御性复制 优势: ├─ 天然线程安全 ├─ 可以安全共享 └─ 适合做缓存key 适用场景:String、Integer、LocalDate等 */ 二、ThreadLocal模式 2.1 基本使用 /** * ThreadLocal:线程本地存储 */ public class ThreadLocalExample { // 每个线程独立的SimpleDateFormat private static ThreadLocal<SimpleDateFormat> dateFormat = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd")); public static String formatDate(Date date) { return dateFormat.get().format(date); // 线程安全 } public static void main(String[] args) { // 10个线程并发格式化日期 for (int i = 0; i < 10; i++) { new Thread(() -> { System.out.println(formatDate(new Date())); }).start(); } } } /* 使用场景: 1. SimpleDateFormat(线程不安全) 2. 数据库连接(每个线程独立连接) 3. 用户上下文(Spring Security) */ 2.2 内存泄漏问题 /** * ThreadLocal内存泄漏问题 */ public class ThreadLocalMemoryLeak { private static ThreadLocal<byte[]> threadLocal = new ThreadLocal<>(); public static void main(String[] args) throws InterruptedException { // 线程池场景 ExecutorService executor = Executors.newFixedThreadPool(10); for (int i = 0; i < 100; i++) { executor.submit(() -> { // 存储大对象 threadLocal.set(new byte[1024 * 1024]); // 1MB // 业务逻辑 // ... // ❌ 忘记清理,导致内存泄漏 // threadLocal.remove(); // 正确做法 }); } executor.shutdown(); } /* 内存泄漏原因: 1. ThreadLocal存储在ThreadLocalMap中 2. key是弱引用(ThreadLocal) 3. value是强引用(实际对象) 4. 线程池线程复用,ThreadLocalMap一直存在 5. value无法被GC,导致内存泄漏 解决方案: try { threadLocal.set(value); // 业务逻辑 } finally { threadLocal.remove(); // 必须清理 } */ } 三、两阶段终止模式 3.1 优雅停止线程 /** * 正确的线程停止方式 */ public class GracefulShutdown { private volatile boolean running = true; public void start() { Thread thread = new Thread(() -> { while (running) { // 检查标志位 try { // 业务逻辑 Thread.sleep(1000); } catch (InterruptedException e) { // 响应中断 Thread.currentThread().interrupt(); break; } } // 清理资源 cleanup(); }); thread.start(); } public void stop() { running = false; // 第一阶段:设置标志位 } private void cleanup() { // 第二阶段:清理资源 System.out.println("清理资源"); } /* 错误做法: thread.stop(); // ❌ 已废弃,不安全 正确做法: 1. volatile标志位 2. interrupt()中断 3. 两阶段:设置标志 + 清理资源 */ } 四、读写锁模式 4.1 ReentrantReadWriteLock /** * 读写锁:读多写少场景 */ public class ReadWriteLockExample { private final ReadWriteLock rwLock = new ReentrantReadWriteLock(); private final Map<String, String> cache = new HashMap<>(); // 读操作:共享锁 public String get(String key) { rwLock.readLock().lock(); try { return cache.get(key); } finally { rwLock.readLock().unlock(); } } // 写操作:排他锁 public void put(String key, String value) { rwLock.writeLock().lock(); try { cache.put(key, value); } finally { rwLock.writeLock().unlock(); } } /* 读写锁特性: ├─ 读-读:可并发 ├─ 读-写:互斥 └─ 写-写:互斥 性能提升: 读多写少场景,性能比synchronized高3-5倍 */ } 4.2 StampedLock(JDK 8) /** * StampedLock:性能更好的读写锁 */ public class StampedLockExample { private final StampedLock lock = new StampedLock(); private double x, y; // 乐观读 public double distanceFromOrigin() { long stamp = lock.tryOptimisticRead(); // 乐观读 double currentX = x; double currentY = y; if (!lock.validate(stamp)) { // 验证是否被修改 stamp = lock.readLock(); // 升级为悲观读锁 try { currentX = x; currentY = y; } finally { lock.unlockRead(stamp); } } return Math.sqrt(currentX * currentX + currentY * currentY); } // 写锁 public void move(double deltaX, double deltaY) { long stamp = lock.writeLock(); try { x += deltaX; y += deltaY; } finally { lock.unlockWrite(stamp); } } /* StampedLock优势: 1. 乐观读不加锁(性能极高) 2. 读写性能比ReentrantReadWriteLock高 3. 适合读多写少场景 注意: ❌ 不支持重入 ❌ 不支持Condition */ } 五、并发编程最佳实践 5.1 核心原则 1. 优先使用不可变对象 └─ String、Integer、LocalDate 2. 减少锁的范围 └─ 只锁必要的代码 3. 避免锁嵌套 └─ 容易死锁 4. 使用并发工具类 ├─ AtomicInteger替代synchronized计数 ├─ ConcurrentHashMap替代Hashtable ├─ CountDownLatch/CyclicBarrier协调线程 └─ BlockingQueue实现生产者-消费者 5. 线程池管理 ├─ 自定义ThreadPoolExecutor(禁用Executors) ├─ 优雅关闭(shutdown + awaitTermination) └─ 线程命名(便于排查) 6. 异常处理 ├─ 捕获InterruptedException ├─ 设置UncaughtExceptionHandler └─ 避免异常导致线程死亡 5.2 常见陷阱 /** * 常见并发陷阱 */ public class ConcurrencyTraps { // ❌ 陷阱1:双重检查锁定错误 private static Singleton instance; public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); // 需要volatile } } } return instance; } // ✅ 正确:private static volatile Singleton instance; // ❌ 陷阱2:SimpleDateFormat共享 private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); // ✅ 正确:使用ThreadLocal或DateTimeFormatter // ❌ 陷阱3:复合操作不原子 if (!map.containsKey(key)) { map.put(key, value); } // ✅ 正确:map.putIfAbsent(key, value); // ❌ 陷阱4:忘记释放锁 lock.lock(); // 业务逻辑 lock.unlock(); // 异常时不会执行 // ✅ 正确:try-finally // ❌ 陷阱5:使用Executors工厂方法 Executors.newFixedThreadPool(10); // 无界队列 // ✅ 正确:自定义ThreadPoolExecutor static class Singleton { private static volatile Singleton instance; public static Singleton getInstance() { return instance; } } } 5.3 性能调优 1. 减少锁竞争 ├─ 缩小锁范围 ├─ 降低锁粒度(分段锁) └─ 使用无锁算法(CAS) 2. 减少上下文切换 ├─ 控制线程数(CPU核心数 × 2) ├─ 使用协程/虚拟线程(JDK 19+) └─ 避免频繁阻塞 3. 内存优化 ├─ 对象池(避免频繁创建) ├─ ThreadLocal注意清理 └─ 合理设置JVM参数 4. 监控与调优 ├─ JProfiler监控线程状态 ├─ Arthas诊断死锁 └─ 压测验证性能 六、总结:并发编程知识体系 6.1 核心知识点回顾 第1篇:并发三大问题 ├─ 可见性:CPU缓存导致 ├─ 原子性:指令交错执行 ├─ 有序性:指令重排序 └─ JMM:happens-before规则 第2篇:同步机制 ├─ synchronized:锁升级(偏向锁→轻量级锁→重量级锁) ├─ Lock:ReentrantLock、公平锁/非公平锁 ├─ AQS:CLH队列、独占/共享模式 └─ 死锁:四个必要条件、预防策略 第3篇:无锁编程 ├─ CAS:CPU的CMPXCHG指令 ├─ Atomic:AtomicInteger、LongAdder ├─ 并发工具:CountDownLatch、Semaphore └─ 无锁数据结构:无锁栈、无锁队列 第4篇:线程池与异步 ├─ ThreadPoolExecutor:七参数、四拒绝策略 ├─ ForkJoinPool:工作窃取算法 ├─ CompletableFuture:组合式异步编程 └─ 最佳实践:配置、监控、优雅关闭 第5篇:并发集合 ├─ ConcurrentHashMap:分段锁→CAS+synchronized ├─ BlockingQueue:生产者-消费者模式 ├─ CopyOnWriteArrayList:写时复制 └─ 选择指南:性能对比、适用场景 第6篇:设计模式与实践 ├─ 不可变对象:天然线程安全 ├─ ThreadLocal:线程本地存储 ├─ 读写锁:读多写少优化 └─ 最佳实践:避坑指南、性能调优 6.2 学习路径建议 阶段1:理解原理(1-2周) ├─ JMM、happens-before ├─ 三大问题 └─ volatile、final 阶段2:掌握同步(2-3周) ├─ synchronized原理 ├─ Lock接口 └─ AQS源码 阶段3:无锁编程(2-3周) ├─ CAS原理 ├─ Atomic类 └─ 并发工具类 阶段4:工程实践(4-6周) ├─ 线程池配置 ├─ 并发集合使用 ├─ 实际项目应用 └─ 性能调优 总计:约3个月系统学习 6.3 推荐资源 经典书籍: 1. 《Java并发编程实战》 ⭐⭐⭐⭐⭐ 2. 《Java并发编程的艺术》 ⭐⭐⭐⭐ 3. 《深入理解Java虚拟机》 ⭐⭐⭐⭐⭐ 官方文档: 1. JDK源码(java.util.concurrent包) 2. Java Language Specification - Chapter 17 3. JSR-133: Java Memory Model 开源项目: 1. Netty(高性能网络框架) 2. Disruptor(高性能队列) 3. Spring(DI容器、事务管理) 结语 Java并发编程系列完结 ...

2025-11-03 · maneng

Java技术生态全景图:从JVM到微服务的完整技术栈深度解析

引子:一个请求背后的Java技术栈全貌 2025年某天上午10点,用户小王在电商平台下了一个订单,点击"提交订单"的那一刻,背后的Java技术栈开始运转: 0.01秒内发生的事情: Nginx 接收HTTP请求 → 转发到 Spring Cloud Gateway 网关 Gateway 鉴权(JWT) → Nacos 服务发现 → 路由到订单服务 订单服务(Spring Boot): Caffeine 本地缓存检查库存 MyBatis 查询 MySQL 订单信息 Redis 分布式锁防止超卖 RabbitMQ 发送消息到库存服务 库存服务 消费消息 → 扣减库存 → Elasticsearch 更新商品索引 支付服务 调用第三方支付接口 → Sentinel 限流熔断 全链路日志通过 SkyWalking 追踪 → 存储到 ClickHouse 这背后,涉及50+核心技术组件,组成了现代Java应用的完整生态。 今天我们就来系统化梳理这张技术全景图。 一、Java技术栈分层架构 1.1 完整分层视图 ┌─────────────────────────────────────────────────────────┐ │ 业务应用层(Business Layer) │ │ 电商平台、金融系统、物流平台、内容管理系统... │ └────────────────────┬────────────────────────────────────┘ │ ┌────────────────────┴────────────────────────────────────┐ │ 微服务治理层(Microservice Governance) │ │ 服务注册、配置中心、API网关、链路追踪、限流熔断... │ │ Spring Cloud、Dubbo、Nacos、Sentinel、SkyWalking │ └────────────────────┬────────────────────────────────────┘ │ ┌────────────────────┴────────────────────────────────────┐ │ 应用框架层(Application Framework) │ │ Spring Boot、Spring MVC、Spring Data、Spring Security │ │ MyBatis、Netty、Quartz、Shiro... │ └────────────────────┬────────────────────────────────────┘ │ ┌────────────────────┴────────────────────────────────────┐ │ 中间件与存储层(Middleware & Storage) │ │ MySQL、Redis、MongoDB、Elasticsearch、RabbitMQ、Kafka │ └────────────────────┬────────────────────────────────────┘ │ ┌────────────────────┴────────────────────────────────────┐ │ Java核心层(Core Java) │ │ JVM、并发包(JUC)、集合框架、IO/NIO、网络编程、反射... │ └─────────────────────────────────────────────────────────┘ 二、Java核心层:基石技术 2.1 JVM:Java虚拟机 JVM是Java生态的基石,理解JVM是从初级到高级的分水岭。 ...

2025-10-21 · maneng

三单对碰技术实现方案:从业务逻辑到代码实现的完整攻略

文章概述 适用场景:跨境电商技术团队、系统架构师、后端开发工程师、关务技术人员 阅读收获: 深入理解三单对碰的业务逻辑和技术要求 掌握三单对碰系统的架构设计思路 学会处理各种异常场景和边界条件 获得生产环境验证的代码实现方案 难度等级:进阶/高级 引子:一个看似简单却暗藏玄机的需求 产品经理:“三单对碰很简单啊,就是把订单、支付单、运单三个数据对一下嘛,有什么难的?” 技术老鸟:(苦笑)“你知道每天有多少订单因为差了1分钱、多了一个空格、时间戳不对而被海关拒绝吗?” 真实数据: 某企业日均订单量5万单 初期三单对碰失败率:15% 优化后失败率:0.3% 这14.7个百分点的差距,意味着: 每天少7350单失败订单 客服工作量减少80% 用户体验显著提升 这篇文章就是要告诉你,这14.7%是怎么优化出来的。 一、三单对碰的本质:海关的"测谎仪" 1.1 为什么要三单对碰? 海关不傻,他们要防止这些问题: 虚假交易:没人买,企业自己刷单洗钱 低报价格:商品值100元,申报10元偷税 身份盗用:用别人身份证下单避税 三单对碰的逻辑: 订单系统(企业) → "张三买了一瓶面霜,99元" 支付系统(第三方) → "张三确实付了99元" 物流系统(第三方) → "确实给张三发了一件货" 海关:三方数据一致 → 放行 ✅ 海关:三方数据不一致 → 拒绝 ❌ 1.2 三单到底"单"在哪里? 订单(Order): <Order> <订单编号>CB20250118001</订单编号> <购买人姓名>张三</购买人姓名> <身份证号>320106199001011234</身份证号> <电话>13800138000</电话> <收货地址>江苏省南京市鼓楼区XX路XX号</收货地址> <商品清单> <商品1> <商品名称>雅诗兰黛小棕瓶精华</商品名称> <规格型号>50ml</规格型号> <数量>1</数量> <单价>650.00</单价> </商品1> </商品清单> <订单金额>650.00</订单金额> <运费>10.00</运费> <税费>85.15</税费> <订单总额>745.15</订单总额> <下单时间>2025-01-18 10:30:00</下单时间> </Order> 支付单(Payment): <Payment> <支付流水号>PAY20250118001</支付流水号> <对应订单号>CB20250118001</对应订单号> <付款人姓名>张三</付款人姓名> <身份证号>320106199001011234</身份证号> <支付金额>745.15</支付金额> <支付时间>2025-01-18 10:30:15</支付时间> <支付方式>微信支付</支付方式> </Payment> 运单(Logistics): ...

2025-10-16 · maneng

如约数科科技工作室

浙ICP备2025203501号

👀 本站总访问量 ...| 👤 访客数 ...| 📅 今日访问 ...