Java性能 -- 并发一致性
背景在并发编程中,Java是通过共享内存来实现共享变量操作的,所以在多线程编程中会涉及到数据一致性的问题 1234567public class Example { int x = 0; public void count() { x++; // 1 System.out.println(x) // 2 }} 有两个线程分别执行count方法,x是共享变量 可能出现3种结果:**<1,1>**,<2,1>,<1,2> Java内存模型 Java采用共享内存模型来实现多线程之间的信息交换和数据同步 程序运行时,局部变量将会存放在虚拟机栈中,而共享变量将会被保存在堆内存中 由于局部变量随线程的创建而创建,线程的销毁而销毁,Java栈数据并非线程共享,所以不需要关心数据的一致性 共享变量存储在堆内存或方法区中,堆内存和方法区的数据是线程共享的 堆内存中的共享变量在被不同线程操作时,会被加载到线程的工作内存中,即_CPU中的高速缓存_...
Java性能 -- 命令行工具
free12345$ free -m total used free shared buffers cachedMem: 15948 15261 687 304 37 6343-/+ buffers/cache: 8880 7068Swap: 0 0 0 Mem是从操作系统的角度来看的 总共有15948M物理内存,其中15261M被使用了,还有687可用,15948 = 15261 + 687 有若干线程共享了304M物理内存,已经被弃用(值总为0) buffer / cached :为了提高IO性能,由OS管理 A buffer is something that has yet to be “written” to disk. A cache is something that has been “read” from the disk and sto...
Kafka -- 提交位移
消费位移 Consumer的消费位移,记录了Consumer要消费的下一条消息的位移 假设一个分区中有10条消息,位移分别为0到9 某个Consumer消费了5条消息,实际消费了位移0到4的5条消息,此时Consumer的位移为5,指向下一条消息的位移 Consumer需要向Kafka汇报自己的位移数据,这个汇报过程就是提交位移 Consumer能够同时消费多个分区的数据,所以位移的提交实际上是在分区粒度上进行的 Consumer需要为分配给它的每个分区提交各自的位移数据 提交位移主要是为了表征Consumer的消费进度 当Consumer发生故障重启后,能够从Kafka中读取之前提交的位移值,然后从相应的位移处继续消费 位移提交的语义 如果提交了位移X,那么Kafka会认为位移值小于X的消息都已经被成功消费了 灵活 位移提交非常灵活,可以提交任何位移值,但要承担相应的后果 假设Consumer消费了位移为0~9的10条消息 如果提交的位移为20,位移位于10~19的消息可能会丢失 如果提交的位移为5,位移位于5~9的消息可能会被重复消费 位移提交的语义保障由应用程序保证,Ka...
Java性能 -- 协程
线程实现模型 轻量级进程和内核线程一对一相互映射实现的1:1线程模型 用户线程和内核线程实现的N:1线程模型 用户线程和轻量级进程混合实现的N:M线程模型 1:1线程模型 内核线程(Kernel-Level Thread)是由操作系统内核支持的线程,内核通过调度器对线程进行调度,负责完成线程的切换 在Linux中,往往通过fork函数创建一个子进程来代表一个内核中的线程 一个进程调用fork函数后,系统会先给新的子进程分配资源,然后复制主进程,只有少数值与主进程不一样 采用fork的方式,会产生大量的冗余数据,占用大量内存空间,也会消耗大量CPU时间来初始化内存空间和复制数据 如果是一模一样的数据,可以共享主进程的数据,于是轻量级进程(Light Weight Process,LWP)出现了 LWP使用clone系统调用创建线程 clone函数将部分父进程的资源的数据结构进行复制,复制内容可选,且没有被复制的资源可以通过指针共享给子进程 LWP运行单元更小,运行速度更快,LWP和内核线程一一映射,每个LWP都是由一个内核线程支持 N:1线程模型 1:1线程模型的缺陷 在线程创建、切换上都...
Kafka -- 避免重平衡
概念 Rebalance是让Consumer Group下所有的Consumer实例就如何消费订阅主题的所有分区达成共识的过程 在Rebalance过程中,所有Consumer实例共同参与,在协调者组件的帮助下,完成订阅主题分区的分配 整个Rebalance过程中,所有Consumer实例都不能消费任何消息,因此对Consumer的TPS影响很大 协调者 协调者,即Coordinator,负责为Consumer Group执行Rebalance以及提供位移管理和组成员管理等 Consumer端应用程序在提交位移时,其实是向Coordinator所在的Broker提交位移 Consumer应用启动时,也是向Coordinator所在的Broker发送各种请求 然后由Coordinator负责执行消费组的注册、成员管理记录等元数据管理操作 所有Broker在启动时,都会创建和开启相应的Coordinator组件,所有Broker都有各自的Coordinator组件 内部位移主题__consumer_offsets记录了为Consumer Group服务的Coordinator在哪一台Broker上...
Java性能 -- 线程池大小
线程池原理 在Hotspot JVM的线程模型中,Java线程被一对一映射为内核线程 Java使用线程执行程序时,需要创建一个内核线程,当该Java线程被终止时,这个内核线程也会被回收 Java线程的创建和销毁将会消耗一定的计算机资源,从而增加系统的性能开销 大量创建线程也会给系统带来性能问题,线程会抢占内存和CPU资源,可能会发生内存溢出、CPU超负载等问题 线程池:即可以提高线程复用,也可以固定最大线程数,防止无限制地创建线程 当程序提交一个任务需要一个线程时,会去线程池查找是否有空闲的线程 如果有,则直接使用线程池中的线程工作,如果没有,则判断当前已创建的线程数是否超过最大线程数 如果未超过,则创建新线程,如果已经超过,则进行排队等待或者直接抛出异常 线程池框架Executor Java最开始提供了ThreadPool来实现线程池,为了更好地实现用户级的线程调度,Java提供了一套Executor框架 Executor框架包括了ScheduledThreadPoolExecutor和ThreadPoolExecutor两个核心线程池,核心原理一样 ScheduledThreadPoo...
Java性能 -- 并发容器
并发场景下的Map容器 某电商系统需要统计销量TOP 10的商品,通常用哈希表来存储商品和销量的键值对,然后使用排序获取销量TOP 10的商品 并发场景下不能使用HashMap JDK 1.7,在并发场景下使用HashMap会出现死循环,导致CPU使用率居高不下,而扩容是导致死循环的主要原因 JDK 1.8,虽然修复了HashMap扩容导致的死循环问题,但在高并发场景下,依然会有数据丢失和不准确的情况 为了保证Map容器的线程安全,Java实现了HashTable、ConcurrentHashMap、ConcurrentSkipListMap HashTable、ConcurrentHashMap是基于HashMap实现的,适用于小数据量存取的场景 ConcurrentSkipListMap是基于TreeMap的设计原理实现的 ConcurrentSkipListMap是基于跳表实现的,而TreeMap是基于红黑树实现的 ConcurrentSkipListMap最大的特点是存取平均时间复杂度为O(log(n)),适用于大数据量存取的场景 HashTable / Concur...
Kafka -- 位移主题
ZooKeeper 老版本Consumer的位移管理依托于Apache ZooKeeper,自动或手动地将位移数据提交到ZK中保存 当Consumer重启后,能自动从ZK中读取位移数据,从而在上次消费截止的地方继续消费 这种设计使得Kafka Broker不需要保存位移数据,减少了Broker端需要持有的状态空间,有利于实现高伸缩性 但ZK并不适用于高频的写操作 位移主题 将Consumer的位移数据作为普通的Kafka消息,提交到__consumer_offsets(保存Consumer的位移信息) 提交过程需要实现高持久性,并需要支持高频的写操作 位移主题是普通的Kafka主题,同时也是一个内部主题,交由Kafka管理即可 位移主题的消息格式由Kafka定义,用户不能修改 因此不能随意向位移主题写消息,一旦写入的消息不能满足格式,那Kafka内部无法成功解析,会造成Broker崩溃 Kafka Consumer有API来提交位移(即向位移主题写消息) 消息格式 常用格式:Key-Value Key为消息键值,Value为消息体,在Kafka中都是字节数组 Key <Group ...
Java性能 -- 线程上下文切换
线程数量 在并发程序中,并不是启动更多的线程就能让程序最大限度地并发执行 线程数量设置太小,会导致程序不能充分地利用系统资源 线程数量设置太大,可能带来资源的过度竞争,导致上下文切换,带来的额外的系统开销 上下文切换 在单处理器时期,操作系统就能处理多线程并发任务,处理器给每个线程分配CPU时间片,线程在CPU时间片内执行任务 CPU时间片是CPU分配给每个线程执行的时间段,一般为几十毫秒 时间片决定了一个线程可以连续占用处理器运行的时长 当一个线程的时间片用完,或者因自身原因被迫暂停运行,此时另一个线程会被操作系统选中来占用处理器 上下文切换(Context Switch):一个线程被暂停剥夺使用权,另一个线程被选中开始或者继续运行的过程 切出:一个线程被剥夺处理器的使用权而被暂停运行 切入:一个线程被选中占用处理器开始运行或者继续运行 切出切入的过程中,操作系统需要保存和恢复相应的进度信息,这个进度信息就是_上下文_ 上下文的内容 寄存器的存储内容:CPU寄存器负责存储已经、正在和将要执行的任务 程序计数器存储的指令内容:程序计数器负责存储CPU正在执行的指令位置、即将执行的下一条...
Kafka -- 消费者组
消费者组 消费者组(Consumer Group)是Kafka提供的可扩展且具有容错性的消费者机制 一个消费者组内可以有多个消费者或消费者实例(进程/线程),它们共享一个Group ID(字符串) 组内的所有消费者协调在一起来消费订阅主题的所有分区 每个分区只能由同一个消费者组内的一个Consumer实例来消费,Consumer实例对分区有所有权 消息引擎模型 两种模型:点对点模型(消息队列)、发布订阅模型 点对点模型(传统的消息队列模型) 缺陷/特性:消息一旦被消费、就会从队列中被删除,而且只能被下游的一个Consumer消费 伸缩性很差,下游的多个Consumer需要抢占共享消息队列中的消息 发布订阅模型 缺陷:伸缩性不高,每个订阅者都必须订阅主题的所有分区(全量订阅) Consumer Group 当Consumer Group订阅了多个主题之后 组内的每个Consumer实例不要求一定要订阅主题的所有分区,只会消费部分分区的消息 Consumer Group之间彼此独立,互不影响,它们能够订阅相同主题而互不干涉 Kafka使用Consumer Group...















