关于Kafka
最新版本 kafka_2.13-4.0.0 其中 2.13 是 scala 版本
0.7, 0.8, 0.9, 0.10, 0.11, 1.0, 1.1, 2.0.X, 2.1.X, 2.2.X, 2.3.X, 2.4.X, 2.5.X, 2.6.X, 2.7.X, 2.8.X, 3.0.X, 3.1.X, 3.2.X, 3.3.X, 3.4.X, 3.5.X, 3.6.X, 3.7.X, 3.8.X, 3.9.X.
- 概念
Topic 主题
Partition 分区 :
- 每个 Topic 可以分为多个分区,可以提供消息的并发量;
- 每个分区都可以有多个副本
- 消息在每个分区里是有序的
生产者组
消费者组
- 消息的状态被保存在consumer中,broker不会关心哪个消息被消费了被谁消费了,只记录一个offset值(指向partition中下一个要被消费的消息位置)
- Producer只管向broker push消息,consumer只管从broker pull消息
- 思考点
- 高性能实现
- rebalance 机制
- 消息幂等性处理
- 顺序消息的实现
wait() notify() 和 notifyAll()
- 如果线程调用了对象的 wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁。
- 当有线程调用了对象的 notifyAll()方法(唤醒所有 wait 线程)或 notify()方法(只随机唤醒一个 wait 线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。也就是说,调用了notify后只要一个线程会由等待池进入锁池,而notifyAll会将该对象等待池内的所有线程移动到锁池中,等待锁竞争。
- 优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中,唯有线程再次调用 wait()方法,它才会重新回到等待池中。而竞争到对象锁的线程则继续往下执行,直到执行完了 synchronized 代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁。
命令式编程(Imperative) vs声明式编程( Declarative)
- 命令式编程(Imperative):详细的命令机器怎么(How)去处理一件事情以达到你想要的结果(What);
- 声明式编程( Declarative):只告诉你想要的结果(What),机器自己摸索过程(How)
SQL语言就是很典型的声明式编程语言
正则表达式(regular expressions)或者逻辑语言(Prolog)为声明式语言
常见的设计模式
创建型: 创建对象时,不再由我们直接实例化对象;而是根据特定场景,由程序来确定创建对象的方式,从而保证更大的性能、更好的架构优势。创建型模式主要有工厂方法、抽象工厂模式、单例模式、生成器模式和原型模式
结构型: 用于帮助将多个对象组织成更大的结构。结构型模式主要有适配器模式adapter、桥接模式bridge、组合器模式component、装饰器模式decorator、门面模式、亨元模式flyweight和代理模式proxy
行为型: 用于帮助系统间各对象的通信,以及如何控制复杂系统中流程。行为型模式主要有命令模式command、解释器模式、迭代器模式、中介者模式、备忘录模式、观察者模式、状态模式state、策略模式、模板模式和访问者模式
类模式:用于处理类与子类之间的关系,这些关系通过继承来建立,是静态的,在编译时便确定下来了;工厂方法、适配器、模板方法、解释器
对象模式:用户处理对象之间关系的,这些关系可以通过组合或聚合来实现,在运行时刻是可以变化的,更具动态性。
happens-before
happens-before 是JMM最核心的概念。对应Java程序员来说,理解happens-before是理解JMM的关键。
happens-before(先行发生)是Java中一个重要的多线程概念,用于描述不同线程之间操作的执行顺序。它是Java内存模型(Java Memory Model)中的一部分
在Java中,如果一个操作happens-before另一个操作,那么第一个操作的结果将对第二个操作可见,即第二个操作可以看到第一个操作的影响。
死锁的四个必要条件
- 互斥条件:进程对所分配到的资源不允许其他进程进行访问,若其他进程访问该资源,只能等待,直至占有该资源的进程使用完成后释放该资源
- 请求和保持条件:进程获得一定的资源之后,又对其他资源发出请求,但是该资源可能被其他进程占有,此事请求阻塞,但又对自己获得的资源保持不放
- 不可剥夺条件:是指进程已获得的资源,在未完成使用之前,不可被剥夺,只能在使用完后自己释放
- 环路等待条件:是指进程发生死锁后,若干进程之间形成一种头尾相接的循环等待资源关系
在Java开发中,死锁常发生在多线程资源竞争的场景中,以下是几种典型场景及示例:
一、锁顺序不当引发的死锁
当多个线程以不同的顺序请求多个锁时,容易形成循环等待。例如:
转账场景:线程A先锁账户A再请求账户B,线程B先锁账户B再请求账户A,导致互相等待。
动态锁顺序:若锁顺序由外部输入决定(如随机选择转账账户),可能打破固定顺序规则,导致死锁。
二、循环资源竞争
典型场景是哲学家就餐问题,多个线程(哲学家)同时竞争相邻资源(筷子),且每个线程持有资源并等待下一个资源释放。例如:
5个线程各自持有1个资源并等待下一个,形成环形依赖。
三、事务中的资源竞争
在数据库操作中,若多个事务以不同顺序获取表锁或行锁,可能触发死锁:
订单与库存场景:事务A先锁库存表再锁订单表,事务B先锁订单表再锁库存表,导致互等。
长时间事务:事务持有锁时执行复杂业务逻辑,其他事务被迫长时间等待。
四、协作对象间的锁嵌套
当调用外部方法时未释放当前锁,可能与其他线程形成嵌套死锁:
车队调度案例:线程A持有出租车锁并调用调度方法(需获取调度锁),而线程B持有调度锁并尝试获取出租车锁。
五、线程通信机制错误
若线程间互相等待通知,例如线程A等待线程B的信号,同时线程B也在等待线程A的信号,会导致双方永久阻塞。
如何避免这些场景
统一锁顺序:强制所有线程按固定顺序获取锁(如按资源ID排序)。
超时机制:使用
tryLock()
设置超时,放弃等待后重试。
减少锁粒度:拆分大锁为多个小锁,降低竞争概率。
事务优化:缩短事务持有锁的时间,避免跨表操作的顺序冲突。
具体案例分析可参考:转账死锁示例 、哲学家问题代码 。
atomic 的原理
AtomicX -> private static final Unsafe U = Unsafe.getUnsafe();
Unsafe 类是 Java 中一个提供低级别操作能力的工具类,它允许开发者直接对内存进行读写,以及执行其他一些类似于 C 语言的底层操作。这些功能使得 Java 在运行效率和底层资源操作能力方面得到了增强。然而,由于 Unsafe 类提供了指针操作内存空间的能力,使用不当可能会带来程序错误的风险,因此在使用时需要格外小心。
CAS 操作 避免了锁竞争
动态代理
JDK动态代理是Java原生的代理实现方式,它要求被代理的目标类必须实现接口
。JDK动态代理通过反射机制
,利用InvocationHandler和Proxy类,为目标接口生成一个实现该接口的代理类。
CGLIB动态代理则不同,它不要求目标类实现任何接口,因为它是通过继承目标类
的方式来创建代理对象。CGLIB(Code Generation Library)是一个第三方代码生成库,它通过底层的字节码技术
,动态生成目标类的子类,并在子类中拦截所有方法的调用。
缓存穿透
缓存穿透是指客户端请求的数据既不在缓存中,也不在数据库中
,导致每次请求都直接到达数据库。这种情况通常发生在恶意用户构造无效请求时,可能会导致数据库压力过大,甚至崩溃。解决缓存穿透的方法包括:
- 使用布隆过滤器来过滤无效请求。
- 对请求参数进行合法性校验,确保请求的数据在数据库中存在。
- 对频繁请求的无效数据进行限流,减少对数据库的压力
G1(Garbage-First)
G1自JDK9起成为默认GC
- 基于 “标记-整理” 算法,收集后不会产生内存碎片。
- 可以非常精确控制停顿时间,在不牺牲吞吐量前提下,实现低停顿垃圾回收。
G1 垃圾收集器将堆内存划分为若干个 Region,每个 Region 分区只能是一种角色,Eden区、S区、老年代O区的其中一个,空白区域代表的是未分配的内存,最后还有个特殊的区域H区(Humongous),专门用于存放巨型对象,如果一个对象的大小超过Region容量的50%以上,G1 就认为这是个巨型对象。
每个 Region 分区 分配了一个 RSet(Remembered Set),它内部类似于一个反向指针,记录了其它 Region 对当前 Region 的引用情况,这样就带来一个极大的好处:回收某个Region时,不需要执行全堆扫描,只需扫描它的 RSet 就可以找到外部引用,来确定引用本。G1 每次 GC 时 所有的新生代都会被扫描,因此引用源是年轻代的对象不需要在RSet中记录;所以最终只需要记录老年代到新生代之间的引用即可
MySQL 事务、锁、索引
Spring
Arthas
Arthas 是一款线上监控诊断产品,通过全局视角实时查看应用 load、内存、gc、线程的状态信息,并能在不修改应用代码的情况下,对业务问题进行诊断,包括查看方法调用的出入参、异常,监测方法执行耗时,类加载信息等,大大提升线上问题排查效率
关于代码质量
Sonar代码质量扫描