Java 架构师主要需要做哪些工作呢?
- 负责设计和搭建软件系统架构(平台、数据库、接口和应用架构等),解决开发中各种系统架构问题。
- 优化现有系统的性能,解决软件系统平台关键技术问题攻关、核心功能模块设计、核心代码开发。
- 在项目需求不断细化的工程中校正整体的架构设计,以及详细模块拆分设计。
- 营造技术学习氛围,带领团队不断完善开发开发方法及流程,提升开发效率与质量,加强技术标准及规范。
- 带领团队攻克例如大数据量、高并发、高稳定性等带来的各种挑战及技术难关。
- 责任心强,有团队合作精神,工作认真负责高效并具有一定抗压能力。
- 参与讨论公司产品发展方向,完整的规划和把握产品研发架构。
Java架构师要学习哪些知识点呢?
并发编程
JAVA内存模型(JMM)
java 当中的线程通讯和消息传递
通信之间有两种传递机制:共享内存和消息传递。
共享内存:共享内存指多个线程共享一个内存区,发送者将内容写入内存区,接受者直接在内存区接受信息。从而实现了消息的传递。这种传递有个弊端:即需要程序员来控制线程的同步---线程的次序。然而这种方式并没有真正的实现消息传递,只是看起来是将消息从一个线程传到另一个线程中。
消息传递:指发送的线程直接将消息传递给接受线程,由于执行顺序的并发机制完成,不需要程序员来控制。
线程间的通信是用 volatile 和 synchronized 两个关键字实现同步完成的线程间的通信;但是在JAVA中的线程之间的通信其实就是共享内存,当一个变量被 volatile 修饰或者被同步块包括时,那么线程的操作会实时更新到共享内存,然后各个线程都会知道最新变量的值,也就是内存的可见性;看起来实现了线程间的通信,但是实际是共享内存。
管道流:管道流用于两个线程之间的字符流动或者字节流动,管道流主要:PipedOutputSTream,PipedInputStream,PipedWriter,PipedReader。他们和 io 的区别是:io 流是在硬盘,内存,socket之间流动,管道流是在线程之间流动。
什么是重排序和顺序一致性?Happens-Before?As-If-Serial?
重排序指编译器和处理器在不改变运算结果的情况下,重新排列指令的执行顺序,达到最大的执行效率。但是当时 volatile 修饰时,那么重排序的规则发生改变。
如果程序是正确同步的,程序的执行将具有顺序一致性(sequentially consistent)—-即程序的执行结果与该程序在顺序一致性内存模型中的执行结果相同(马上我们将会看到,这对于程序员来说是一个极强的保证)。这里的同步是指广义上的同步,包括对常用同步原语(lock,volatile和final)的正确使用。
在 JMM 中,如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须要存在 happens-before 关系。这里提到的两个操作既可以是在一个线程之内,也可以是在不同线程之间。
两个操作之间具有happens-before关系,并不意味着前一个操作必须要在后一个操作之前执行!happens-before仅仅要求前一个操作(执行的结果)对后一个操作可见,且前一个操作按顺序排在第二个操作之前(the first is visible to and ordered before the second)。
名称 | 代码示例 | 说明 |
---|---|---|
写后读 | a = 1;b = a; | 写一个变量之后,再读这个位置。 |
写后写 | a = 1;a = 2; | 写一个变量之后,再写这个变量。 |
读后写 | a = b;b = 1; | 读一个变量之后,再写这个变量。 |
as-if-serial语义的意思指:不管怎么重排序(编译器和处理器为了提高并行度),(单线程)程序的执行结果不能被改变。编译器,runtime 和处理器都必须遵守as-if-serial语义。
为了遵守as-if-serial语义,编译器和处理器不会对存在数据依赖关系的操作做重排序,因为这种重排序会改变执行结果。但是,如果操作之间不存在数据依赖关系,这些操作可能被编译器和处理器重排序。
延伸阅读:
JVM(十一)Java指令重排序Java指令重排序")
JVM(十二)Java顺序一致性模型Java顺序一致性模型")
Synchronized的概念和分析
同步、重量级锁以及 Synchronized 的原理分析
自旋锁、偏向锁、轻量级锁、重量级锁的概念、使用以及如何来优化他们
自旋锁
自旋锁原理非常简单,如果持有锁的线程能在很短时间内释放锁资源,那么那些等待竞争锁的线程就不需要做内核态和用户态之间的切换进入阻塞挂起状态,它们只需要等一等(自旋),等持有锁的线程释放锁后即可立即获取锁,这样就避免用户线程和内核的切换的消耗。
如果持有锁的线程执行的时间超过自旋等待的最大时间扔没有释放锁,就会导致其它争用锁的线程在最大等待时间内还是获取不到锁,这时争用线程会停止自旋进入阻塞状态,所以需要设定一个自旋等待的最大时间。
偏向锁
Java 偏向锁 (Biased Locking) 是 Java6 引入的一项多线程优化。偏向锁,顾名思义,它会偏向于第一个访问锁的线程,如果在运行过程中,同步锁只有一个线程访问,不存在多线程争用的情况,则线程是不需要触发同步的,这种情况下,就会给线程加一个偏向锁。
在有锁的竞争时,偏向锁会多做很多额外操作,尤其是撤销偏向所的时候会导致进入安全点,安全点会导致stw,导致性能下降,这种情况下应当禁用。
轻量级锁
轻量级锁是由偏向所升级来的,偏向锁运行在一个线程进入同步块的情况下,当第二个线程加入锁争用的时候,偏向锁就会升级为轻量级锁。
重量级锁
在 JDK1.5 之前都是使用 synchronized 关键字保证同步的,Synchronized 是非公平锁。 Synchronized 在线程进入 ContentionList 时,等待的线程会先尝试自旋获取锁,如果获取不到就进入 ContentionList,这明显对于已经进入队列的线程是不公平的,还有一个不公平的事情就是自旋获取锁的线程还可能直接抢占 OnDeck 线程的锁资源。
锁优化
以上介绍的锁不是我们代码中能够控制的,但是借鉴上面的思想,我们可以优化我们自己线程的加锁操作:
减少锁的时间:不需要同步执行的代码,能不放在同步快里面执行就不要放在同步快内,可以让锁尽快释放;
减少锁的粒度:它的思想是将物理上的一个锁,拆成逻辑上的多个锁,增加并行度,从而降低锁竞争;
锁粗化:假如有一个循环,循环内的操作需要加锁,我们应该把锁放到循环外面,否则每次进出循环,都进出一次临界区,效率是非常差的;
使用读写锁:ReentrantReadWriteLock 是一个读写锁,读操作加读锁,可以并发读,写操作使用写锁,只能单线程写;
读写分离:CopyOnWriteArrayList 、CopyOnWriteArraySet、CopyOnWrite 容器即写时复制的容器。CopyOnWrite 并发容器用于读多写少的并发场景,因为,读的时候没有锁,但是对其进行更改的时候是会加锁的,否则会导致多个线程同时复制出多个副本,各自修改各自的;
使用 cas:如果需要同步的操作执行速度非常快,并且线程竞争并不激烈,这时候使用 cas 效率会更高,因为加锁会导致线程的上下文切换,如果上下文切换的耗时比同步操作本身更耗时,且线程对资源的竞争不激烈,使用 volatiled+cas 操作会是非常高效的选择;
消除缓存行的伪共享:在多核 cpu 的处理器中,每个 cpu 都有自己独占的一级缓存、二级缓存,甚至还有一个共享的三级缓存,为了提高性能,cpu 读写数据是以缓存行为最小单元读写的;32 位的 cpu 缓存行为 32 字节,64 位 cpu 的缓存行为 64 字节,这就导致了一些问题。
延伸阅读:yeskery java 中的锁 - 偏向锁、轻量级锁、自旋锁、重量级锁
Volatile 和 DCL(双重检查) 的知识
Volatile 的使用场景和 Volatile 实现机制、内存语义、内存模型
Volatile 的使用场景:
synchronized 关键字是防止多个线程同时执行一段代码,那么就会很影响程序执行效率,而 volatile 关键字在某些情况下性能要优于 synchronized,但是要注意 volatile 关键字是无法替代 synchronized 关键字的,因为 volatile 关键字无法保证操作的原子性。通常来说,使用 volatile 必须具备以下 2 个条件:
- 对变量的写操作不依赖于当前值
- 该变量没有包含在具有其他变量的不变式中
实际上,这些条件表明,可以被写入 volatile 变量的这些有效值独立于任何程序的状态,包括变量的当前状态。
事实上,我的理解就是上面的2个条件需要保证操作是原子性操作,才能保证使用volatile关键字的程序在并发时能够正确执行。
场景:
- 状态标记量
- 双重检查锁
Volatile 实现机制:
volatile到底如何保证可见性和禁止指令重排序的。
下面这段话摘自《深入理解Java虚拟机》:
观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令
lock前缀指令实际上相当于一个内存屏障(也成内存栅栏),内存屏障会提供 3 个功能:
- 它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;
- 它会强制将对缓存的修改操作立即写入主存;
- 如果是写操作,它会导致其他CPU中对应的缓存行无效。
DCL 的单例模式,什么是 DCL?如何来解决 DCL 的问题
DCL 双重检查锁,用来解决创建安全的单例模式。
实例化对象的过程,实际上可以分解成以下三个步骤:
- 分配内存空间
- 初始化对象
- 将对象指向刚分配的内存空间
但是有些编译器为了性能的原因,可能会将第二步和第三步进行重排序,顺序就成了:
- 分配内存空间
- 将对象指向刚分配的内存空间
- 初始化对象
现在考虑重排序后,两个线程发生了以下调用:
Time | ThreadA | ThreadB |
---|---|---|
T1 | 检查到 uniqueSingleton 为空 | |
T2 | 获取锁 | |
T3 | 再次检查到 uniqueSingleton 为空 | |
T4 | 为 uniqueSingleton 分配内存空间 | |
T5 | 将 uniqueSingleton 指向内存空间 | |
T6 | 检查到 uniqueSingleton 不为空 | |
T7 | 访问 uniqueSingleton (此时对象还未完成初始化) | |
T8 | 初始化 uniqueSingleton |
在这种情况下,T7 时刻线程 B 对 uniqueSingleton
的访问,访问的是一个初始化未完成的对象。
延伸阅读:
并发基础之 AQS 的深度分析
- AbstractAueuedSynchronizer 同步器的概念、CLH 同步队列是什么?
在锁框架中,AbstractQueuedSynchronizer 抽象类可以毫不夸张的说,占据着核心地位,它提供了一个基于 FIFO 队列,可以用于构建锁或者其他相关同步装置的基础框架。
AbstractQueuedSynchronizer 类底层的数据结构是使用双向链表,是队列的一种实现,故也可看成是队列,其中 Sync queue,即同步队列,是双向链表,包括 head 结点和 tail 结点,head 结点主要用作后续的调度。而 Condition queue 不是必须的,其是一个单向链表,只有当使用 Condition 时,才会存在此单向链表。并且可能会有多个 Condition queue。
AQS 内部维护着一个 FIFO 队列,该队列就是 CLH同步队列。
CLH 同步队列是一个 FIFO 双向队列,AQS 依赖它来完成同步状态的管理,当前线程如果获取同步状态失败时,AQS 则会将当前线程已经等待状态等信息构造成一个节点(Node)并将其加入到 CLH 同步队列,同时会阻塞当前线程,当同步状态释放时,会把首节点唤醒(公平锁),使其再次尝试获取同步状态。
在CLH同步队列中,一个节点表示一个线程,它保存着线程的引用(thread)、状态(waitStatus)、前驱节点(prev)、后继节点(next)。
延伸阅读:
- 同步状态的获取和释放、线程阻塞和唤醒
同步状态的获取和释放:参考链接:Java中的AQS(二)同步状态的获取与释放
线程阻塞和唤醒:参考链接:Java中的AQS(二)同步状态的获取与释放
Lock和并发常用工具类
- java 当中的 Lock、ReentrantLock、ReentrantReadWriteLock、Condition
- java 当中的并发工具类 CyclicBarrier、CountDownLatch、Semphore
- java 当中的并发集合类 ConcurrentHashMap、ConcurrentLinkedQueue......
原子操作常用知识讲解
- 基本类型的原子操作比如经典的 AtomicBoolean、AtomicLnteger、AtomicLong
- 数组类型的原子操作代表几个类 AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray
- 引用类型的原子操作的典型 AtomicReference、AtomicReferenceFieldUpdater......
- CAS的概念和知识、Compare And Swap 以及他的缺陷
线程池和并发并行
- Executor、ThreadPoolExecutor、Callable &Future、ScheduledExecutorService
- ThreadLocal、Fork & Join?什么是并行?线程池如何保证核心线程不被销毁?
框架和源码应用
mybatis应用和源码解析
- mybatis 优缺点、spring 与mybatis 集成
- Config、Sql 配置、Mapper 配置、有几种注册 mapper 的方法,优先级如何?
- mybaits 的一级缓存、二级缓存、mybatis 的二级缓存为什么是鸡肋?
- 通用 mapper 的实现、mybaits 编写 sql 语句的三种方式
- @MapperScan 的源码分析?mapperScan 如何生效的?
- mybatis 如何扩展spring的扫描器的、mybatis 扫描完之后如何利用 FactoryBean 的?
- mybaits 底层如何把一个代理对象放到 spring 容器中?用到了 spring 的哪些知识?
- mybaits 和 spring 的核心接口 ImportBeanDefinitionRegistrar 之间千丝万缕的关系
- 从原来来说明 mybaits 的一级缓存为什么会失效?spring 为什么把他失效?有没有办法解决?
- 从 mybatis 来分析 mybatis 的执行流程、mybaits 的 sql 什么时候缓存的?缓存在哪里?
- mybaits 当中的方法名为什么需要和 mapper 当中的id一致?从源码来说明
tomcat 源码解析
- tomcat 的总体概述和tomcat的启动流程源码分析
- tomcat 当中 web 请求的源码分析?一个 http 如何请求到 tomcat 的?tomcat 如何处理的?
- tomcat 的协议分析,从源码来分析tomcat当中的各种详细配置的意义
- tomcat 和 apache、nginx 等等主流静态服务器的搭配使用
- tomcat 的性能调优?生成环境上如何让你的 tomcat 容器的性能达到最高
spring源码分析
- spring 的基本应用和spring源码的编译
- java 混乱的日志系统,Jul、jcl、log4j、slf4j.....
- spring4 和spring 在日志方面的源码对比
- AspectJ 和 springAop,aspectj 的静态织入
- JDK 动态代理的源码分析,JDK是如何操作字节码
- spring 通过 cglib 完成 AOP,cglib 如果完成方法拦截
- AnnotationAwareAspectJAutoProxyCreator 如何完成代理织入的
- BeanDefinition 是什么东西,sping 当中的各种 BeanDefinition 的作用
- BeanDefinition 有什么作用?如果来改变一个 bean 的行为
- BeanDefinitionRegistry 的作用,源码分析
- BeanNameGenerator 如何改变 beanName 的生成策略
- BeanPostProcessor 如何插手 bean 的实例化过程、经典的应用场景有哪些?spring 内部哪里用到了这个接口
- BeanFactoryPostProcessor 和 BeanPostProcessor 的区别、经典应用场景、spring 内部如何把他应用起来的
- BeanDefinitionRegistryPostProcessor 和 BeanFactoryPostProcessor 的关系已经区别,spring 底层如何调用他们
- ConfigurationClassPostProcessor 这个类如何完成bean的扫描,如何完成 @Bean 的扫描、如何完成对 @Import 的解析
- @Imoprt 的三种类型,普通类、配置类、ImportSelector
- 如何利用 ImportSelector 来完成对spring的扩展?
- @Configuration 这注解为什么可以不加?加了和不加的区别,底层为什么使用 cglib
- @Bean 的方法是如何保证单例的?如果不需要单例需要这么配置?为什么需要这么配置
- springFacoryBean 和 BeanFacory 的区别,有哪些经典应用场景?spring 的factoryMethod 的经典应用场景?
- ImportBeanDefinitionRegistrar 这个接口的作用,其他主流框架如何利用这个类来完成和spring的结合的?
- spring 是什么时候来执行后置处理器的?有哪些重要的后置处理器,比如 CommonAnnotationBeanPostProcessor
- CommonAnnotationBeanPostProcessor 如何来完成 spring 初始化方法的回调。spring 内部的各种 Procesor 的作用分别是什么
- spring 和 springBoot 当中的各种 @Enablexxxx 的原理是什么?如何自己实现一个?比如动态开启某某些自定义功能
- spring 如何来完成 bean 的循环依赖并且实例化的,什么是 spring 的 IOC 容器,怎么通过源码来理解?
- 其他,比如 Bean 的实例化过程,源码中的两次 gegetSingleton 的不同和相比如 SpringMvc 的源码分析等等......
Spring 微服务
Spring Cloud
- Eureka 的源码分析服务注册和服务发现以及心跳机制和保护机制,对比 eureka 与 zookeeper,什么是 CAP 原则?
- Ribbon 源码分析和客服端负载均衡,客户端负载均衡?服务端负载均衡? Ribbon 核心组件 IRule 以及重写 IRule
- Fegin 源码分析和声明式服务调用,Fegin 负载均衡,Fegin 如何与 Hystrix 结合使用? 有什么问题?
- Hystrix 实现服务限流、降级,大型分布式项目服务雪崩如何解决? 服务熔断到底是什么?一线公司的解决方案
- HystrixDoashboard 如何实现自定义接口降级、监控数据、数据聚合等等
- Zuul 统一网关详解、服务路由、过滤器使用等,从源头来拦截掉一些不良请求
- 分布式配置中心 Config 详解,如何与 github 或是其他自定义的 git 平台结合、比如 gitlab
- 分布式链路跟踪详解,串联调用链,,让Bug无处可藏,如何厘清微服务之间的依赖关系?如何跟踪业务流的处理顺序?
Spring Boot
- Spring Boot 的源码分析和基本应用、利用 springmvc 的知识模拟和手写一个 springboot
- springmvc 的零配置如何实现的?利用 servelt3.0 的哪些新知识?在 springmvc 中如何内嵌一个 tomcat,如何把 web.xml 去掉
- springboot 当中的监听器和设计模式中观察者模式的关系、模拟 java 当中的事件驱动编程模型
- springboot 的启动流程分析、springboot 如何初始化 spring 的 context?如何初始化 DispacterServlet 的、如何启动 tomcat 的
- springboot 的配置文件类型、配置文件的语法、配置文件的加载顺序、模拟 springboot 的自动配置
- springboot 的日志系统、springboot 如何设计他的日志系统的,有什么优势?如何做到统一日志的?
Docker
- 什么是 Docker、为什么要使用他、和开发有什么关系?能否带来便捷、Docker 简介、入门,Docker 的架构是怎样的?
- Docker 的三大核心概念:镜像(Images)、容器(Containers)、仓库服务注册器(Registry)他们分别是什么?
- Docker 的基础用法以及 Docker 镜像的基本操作
- 容器技术入门、Docker 容器基本操作、容器虚拟化网络概述以及 Docker 的容器网络是怎样的?
- 程序员如何利用 Dockerfile 格式、Dockerfile 命令以及 docker build 构建镜像
- Compose 和 Dockerfile 的区别是什么?Compose 的配置文件以及使用 Compose 运行容器、Docker 的实战应用
性能调优
mysql 性能调优
- mysql 中为什么不使用其他数据结构而就用B+树作为索引的数据结构
- mysql 执行计划详解 & mysql 查询优化器详解
- mysql 索引优化实战,包括普通查询、group by、order by
java数据结构算法
- hash 算法详解、java 当中 hashmap 源码解析、手写一个 hashmap
- 从源码理解 hashmapJDK7 和 JDK8 的变化、为什么有这样的变化,Java8 新特性
- 顺序存储、双向链表、单向链表、java 当中 linkedList 的源码分析
- java 当中线性结构、树形结构以及图形结构分析以及应用场景和经典使用
- 大数字运算和经典排序、二叉树红黑树排序、查找
JVM性能调优
- java 内存模型总体概述、类加载过程和 classloader、运行时数据区当中的总体内容、编译原理
- 内存区域与内存溢出异常、虚拟机对象、程序计数器、java 栈、本地方法栈、操作数、方法区、堆内存和元数据等等
- Classloader 的知识详细、默认全盘负责机制、从 JDK 源码来理解双亲委派模式、如何打破双亲委派?为什么需要打破?
- 虚拟机性能监控与故障处理、jvm 基本命令,jinfo 命令的使用 jmap 命令使用、jstak 命令的使用、使用 jvisualvm 分析
- 垃圾收集器与内存分配策略、垃圾回收算法与基础、串型收集器、并行收集器、内存分配与回收策略。
- 程序编译与代码优化、运行期优化、编译期优化、JVM 调优的本质是什么?什么是轻 gc ?什么是 Full gc?如何调优
- JVM 执行子系统、类文件结构、类加载机制、字节码执行引擎、字节码编译模式、如何改变字节码编译模式?
互联网工程
Maven
- 整体认知maven的体系结构
- maven核心命令
- maven的pom配置体系
- 搭建Nexus私服
Git
- 动手搭建 Git 客户端与服务端
- Git 的核心命令
- Git 企业应用
- Git 的原理,Git 底层指针介绍
Linux
- Linux 原理、启动、目录介绍
- Linux 运维常用命令、Linux 用户与权限介绍
- shell 脚本编写
分布式
分布式协调框架(Zookeeper)
- 什么是分布式系统?分布式系统有何挑战?Zookeeper 快速入门&集群搭建基本使用
- Zookeeper 有哪些常用命令以及注意事项、zkclient 客户端与 curator 框架有什么功能以及如何使用
- 手写 Zookeeper 常见应用场景:分布式配置中心、分布式锁、分布式定时任务
- Zookeeper 核心概念 znode、watch 机制、序列化、持久化机制讲解及其源码解析
- Zookeeper 怎么解决分布式中的一致性问题?领导选举流程讲解及其源码解析
RPC服务框架(Dubbo)
手写 RPC 框架以及为什么要使用 Dubbo? 传统应用系统如何演变成分布式系统详解
Dubbo 的六大特性是什么?对企业级开发有何好处?Dubbo 的作用简要说明、快速演示 Dubbo 调用示例
Dubbo 中协议、注册中心、动态代理机制是怎么达到可扩展的?Dubbo 的扩展机制源码解析
Dubbo 从服务提供者到注册中心到消费者调用服务中间的流程源码解析
Dubbo 的监控中心以及管理平台的使用,方便企业级开发与管理
分布式数据缓存(Redis)
- 关系型数据库瓶颈与优化、ehcache 和 redis 的对比?nosql 的使用场景
- Redis 基本数据类型、比如 map 的使用场景?有什么优缺点?什么时候用 map 等等
- Redis 高级特性、如何来理解 redis 的单线程但是高性能?如何理解 redis 和 epoll
- Redis 持久化、什么情况下需要持久化?方案是什么?有什么优缺点?如何优雅的选择持久化方案
- Redis 项目中应用、reids 的高级命令 mget、scan?为什么有 scan 这条命令,如何理解 redis 的游标?
- 单机版 redis 的安装以及 redis 生产环境启动方案
- redis 持久化机对于生产环境中的灾难恢复的意义
- redis 主从架构下如何才能做到 99.99% 的高可用性
- 在项目中重新搭建一套主从复制+高可用+多 master 的 redis cluster 集群
- redis 在实践中的一些常见问题以及优化思路(包含 linux 内核参数优化)
- redis 的 RDB 持久化配置以及数据恢复实验
- redis 的 RDB 和 AOF 两种持久化机制的优劣势对比
分布式数据存储(Mycat)
- 分库分表场景介绍
- Mycat原理解析
- 分库分表实战
分布式RabbitMQ
- RabbitMQ 环境安装 & RabbitMQ 整体架构与消息流转 & 交换机详解
- 消息如何保障 100% 的投递成功方案&企业消息幂等性概念及业界主流解决方案
- Confirm 确认消息详解 & Return 返回消息详解&消费端的限流策略&消费端 ACK 与重回队列机制
- SpringAMQP 用户管理组件-RabbitAdmin 应用 & SpringAMQP 消息模板组件-RabbitTemplate 实战
- SpringAMQP 消息容器-SimpleMessageListenerContainer 详解 & SpringAMQP 消息适配器-MessageListenerAdapter 使用
- RabbitMQ 与 SpringBoot2.0 整合实战 & RabbitMQ 与 Spring Cloud Stream 整合实战
- RabbitMQ 集群架构模式 & RabbitMQ 集群镜像队列构建实现可靠性存储 & RabbitMQ 集群整合负载均衡基础组件 HaProxy_
项目实战
- 大型互联网电商项目
- 面试题详解,offer选择
- 简历技术优化、项目优化
- 面试问题剖析
- 职业生涯规划