• Jmeter系列 - test plan测试计划详细讲解
     测试计划的作用测试计划描述了 Jmeter 在执行时,一系列的步骤一个完整的测试计划包含了一个或多个【线程组、逻辑控制器、采样器、监听器、定时器、断言和配置元素】 测试计划添加or删除元件通过右键点击树中的元件,选中要添加的元件也可以通过合并(merge)或打开(open)从文件中加载和添加元件 配置树中的元件树中的每一个控件都能通过右边内容区显示树中的每一个控件都能在树中随意拖动 运行测试计划可以通过ctrl+r运行测试计划通过右侧的数字:活动线程数/线程总数,这仅适用于本地运行的测试;使用客户端-服务器模式时,它们不包括在远程系统上启动的任何线程【分布式压测时,master机不会显示所有远程salve机的线程总数】 特别注意仅在调试测试计划时,才应该使用上面的 GUI 模式【界面模式】,如果实际运行负载测试的时候,应该使用CLI模式【命令行模式、无界面模式】 暂停运行测试计划两种暂停方式停止线程(ctrl + .)【硬中断】关闭线程(ctrl + ,)【软中断】 stop threads许多采样器(Samplers)都是可中断的,这意味着可以提前终止活动采样stop命令将检查所有线程是否已在默认超时(即5000 ms = 5秒)内停止如果有线程还没被停止,则会发送一条信息;此时可以再发送一次 stop 命令,但如果还是失败的话,就得退出 Jmeter 来清理 特别注意上述说的默认超时可以通过Jmeter属性来改变 jmeterengine.threadstop.wait 【jmeter安装目录 / bin / jmeter.properties】 shutdown threads线程会在当前运行任务结束后停止,不会中断活动线程正在执行的任务会出现一个【正在停止测试】的窗口(如下图),直到所有线程都运行完成了才会关闭如果停止时间太久,也可以直接发stop命令 注意点在Linux CLI模式下,是没有快捷键来停止线程运行的,所以Jmeter 在 CLI模式下会监听特定端口上的命令(默认端口4445,可以通过 jmeterengine.nongui.port 修改)如果 4445 端口被占用了(比如,另一个Jmeter实例用了),Jmeter 会自动选择备用端口;Jmeter 将尝试监听下一个更高的端口,直到到达Jmeter属性 jmeterengine.nongui.maxport 为止,该属性默认为4455 在CLI模式下,如何停止线程执行在bin目录下,运行脚本stoptest.cmd / stoptest.sh 【硬中断】shutdown.cmd / shutdown.sh 【软中断】注意,只有在同一个 host 下运行的脚本,Jmeter 才会接受
  • Jmeter系列 - 基础线程组Thread Group
    Thread Group基础线程组介绍 Thread Group的简单理解线程组是一个测试计划的开始点在一个测试计划中的所有元件都必须在某个线程组下线程组决定 Jmeter 执行测试计划的线程数 Thread Group提供的主要作用设置线程数设置ramp-up period设置执行测试的次数 Thread Group的独立性每个线程都会独立的运行测试计划,互不干扰,多个线程用于模仿对服务器的并发访问。 Thread Group线程属性讲解 在取样器错误后要执行的动作默认:继续建议:继续假设一个HTTP Sampler报错了,后面还有其他请求,最好肯定是继续执行下去啦 线程属性值设置的线程属性值是【预期压力值】而聚合报告是【压力测试的实际结果】 线程数Jmeter java进程下启动的线程,用来模拟真实用户数,1线程数 = 1用户数windows下,2g的 java内存,1m 的栈空间,最大启动线程数=1000Linux下,2g的 java内存,1m 的栈空间,最大启动线程数=2000在Jmeter中,先启动线程,再运行线程,后释放线程【启动线程并运行,释放线程】线程数建议不超过1000 Ramp-Up时间(秒)预期线程组的所有线程从启动-运行-释放的总时间ramp up=0时,表示瞬时加压,启动线程的时间无限趋*于0特别注意:在负载测试的时候,尽量把ramp up设置大一些,让性能曲线*缓,容易找到瓶颈点 循环次数每个线程循环执行的次数,默认一次【便于理解:线程的迭代次数、重复发起请求的次数】如果设置为永远,那么 jmeter 将以最大的可能去发送请求,以此测试出最大并发数 线程属性值的简单栗子上图:10s 内启动并运行 10 个线程,一共启动 10 个线程,总共发送 20 个请求(10*2),每秒启动一个线程,然后运行两次备注:如果 ramp-up 时间内,所有线程不能启动运行完的话,时间则会顺延下去 Ramp-up 设置注意事项Ramp-up需要设置足够长的时间来避免在测试刚开始时工作量过大假如需要大量线程的话,不建议设置成0,0 属于瞬时加压【过小的 ramp-up period 】如果设置 0,Jmeter 将在测试开始时就启动全部线程并立即发送请求,这样很容易让服务器达到饱满状态,且瞬间会增加很大的负载量,容易让服务器超载,这样是不合理的;不合理的原因并不是因为*均压力值过高,而是因为所有线程都在初始状态时一起并发访问,从而引起不正常的初始访问峰值,可以通过 Jmeter 的聚合报告看到这种情况 Ramp-up还必须足够短,保证最后一个线程在第一个线程完成之前开始运行如果 Ramp-up 过大,则会降低访问峰值的负载,即没有达到预期的压力峰值,无法获取准确的服务器最大负载情况【过大的 ramp-up period 】具体的表现为:一些线程还没有启动,初期启动的部分线程已经结束了【导致实际并发量并会小于预期并发量】 如何确定一个合理的ramp-up period首先,让初始点击率接**均点击率,前提是确定合理的访问量初始的 ramp-up period = *均点击率= 总线程/点击率;假如线程数=100,点击率=10次/s,则ramp-up period = 100/10 = 10s 延迟创建线程直到需要延迟创建线程,直到线程被需要、采样器开始执行时才会被创建,避免资源浪费 官方英文When selected, JMeter will allocate threads according to the ramp up times. Otherwise all threads are allocated to the JVM process the moment the test is started, regardless of ramp up time. 译文选中后,JMeter将根据 Ramp-up 时间来分配线程。 否则,无论 Ramp-up 时间如何设定,所有线程都将在测试开始时分配给JVM进程。 调度器Specify Thread Lifetime【scheduler】调度器的作用:控制每个线程组运行的持续时间以及它在多少秒后再启动Duration (seconds) :持续时间;线程组运行的持续时间Startup Delay (seconds):启动延迟;测试计划开始后,线程组的线程将在多少秒后再启动运行 调度器和循环次数的关系循环次数有固定值且 ≠ -1,持续时间不会生效,以循环次数为准循环次数设置为永远或 -1 时,持续时间才会生效 调度器注意事项当线程组运行完持续时间后,会逐步释放线程,不会一下子把所有线程释放掉,而释放线程也是需要时间的~所以测试计划总的时间(右上角的时间)会 > 持续时间+启动延迟 预习TPS总的完成请求数 = 线程总数 * 循环次数*均TPS = 总请求数 / 线程运行总时间【上图,右上角黄色三角形的时间】*均TPS(即聚合报告的TPS)是仅供参考的实际的TPS是由服务器决定的,因为它是衡量服务器处理能力的性能指标 
  • Jmeter - Jmeter的详细介绍
    Jmeter支持哪些测试场景?Jmeter能够对许多不同的应用程序/服务器/协议类型进行负载测试和性能测试Web - HTTP, HTTPS (Java, NodeJS, PHP, ASP.NET, …)SOAP / REST WebservicesFTP通过JDBC连接数据库(支持各种数据库)LDAP通过JMS的面向消息的中间件(MOM)邮箱协议 - SMTP(S), POP3(S) and IMAP(S)本机命令或Shell脚本TCPJava Objects Jmeter有哪些特征?功能齐全的测试编辑界面,允许快速记录测试计划(来自浏览器或本机应用程序),构建和调试 【HTTP代理服务器,类似抓包】命令行模式,可以在任何装了Java环境的系统(win、linux、mac)上进行测试【移植性好】提供完整且随时可查看的HTML报告可以在大多数流行的响应格式(HTML、JSON、XML或任何文本格式)中提取数据,实现关联【常说的数据关联】多线程框架允许通过多个线程进行并发采样,并通过单独的线程组同时对不同的方法进行采样。可以对测试结果进行缓存和离线分析、离线重放 Jmetert的扩展性体现在哪里?脚本化的采样器【BeanShell、Groovy】随意增删的采样器负载统计信息可以增删定时器数据分析和可视化插件提供了出色的扩展性和个性化jmeter自带方法可以向测试计划提供动态输入或数据处理能力通过针对Maven,Gradle和Jenkins的第三方开源库轻松进行持续集成。 Jmeter实际使用场景接口测试压力测试分布式压力测试测试 Restful 风格的API
  • [技术干货] 40个问题让你快速掌握Java多线程的精髓(4)
    四、线程池Q: ThreadPoolExecutor线程池构造参数中,corePoolSize和maximumPoolSize有什么区别?A:当提交新线程到池中时如果当前线程数 < corePoolSize,则会创建新线程如果当前线程数=corePoolSize,则新线程被塞进一个队列中等待。如果队列也被塞满了,那么又会开始新建线程来运行任务,避免任务阻塞或者丢弃如果队列满了的情况下, 线程总数超过了maxinumPoolSize,那么就抛异常或者阻塞(取决于队列性质)。调用prestartCoreThread()可提前开启一个空闲的核心线程调用prestartAllCoreThreads(),可提前创建corePoolSize个核心线程。Q: 线程池的keepalive参数是干嘛的?A:当线程数量在corePoolSize到maxinumPoolSize之间时, 如果有线程已跑完,且空闲时间超过keepalive时,则会被清除(注意只限于corePoolSize到maxinumPoolsize之间的线程)Q: 线程池有哪三种队列策略?A:握手队列相当于不排队的队列。可能造成线程数量无限增长直到超过maxinumPoolSize(相当于corePoolSize没什么用了,只以maxinumPoolSize做上限)无界队列队列队长无限,即线程数量达到corePoolSize时,后面的线程只会在队列中等待。(相当于maxinumPoolSize没什么用了)缺陷: 可能造成队列无限增长以至于OOM有界队列Q: 线程池队列已满且maxinumPoolSize已满时,有哪些拒绝策略?A:AbortPolicy 默认策略:直接抛出RejectedExecutionException异常DiscardPolicy 丢弃策略: 直接丢了,什么错误也不报DiscardOldestPolicy 丢弃队头策略: 即把最先入队的人从队头扔出去,再尝试让该任务进入队尾(队头任务内心:不公平。。。。)CallerRunsPolicy 调用者处理策略: 交给调用者所在线程自己去跑任务(即谁调用的submit或者execute,他就自己去跑)也可以用实现自定义新的RejectedExecutionHandlerQ:有以下五种Executor提供的线程池,注意记忆一下他们的用途,就能理解内部的原理了。newCachedThreadPool: 缓存线程池corePoolSize=0, maxinumPoolSize=+∞,队列长度=0 ,因此线程数量会在corePoolSize到maxinumPoolSize之间一直灵活缓存和变动, 且不存在队列等待的情况,一来任务我就创建,用完了会释放。newFixedThreadPool :定长线程池corePoolSize= maxinumPoolSize=构造参数值, 队列长度=+∞。因此不存在线程不够时扩充的情况newScheduledThreadPool :定时器线程池提交定时任务用的,构造参数里会带定时器的间隔和单位。 其他和FixedThreadPool相同,属于定长线程池。newSingleThreadExecutor : 单线程池corePoolSize=maxinumPoolSize=1, 队列长度=+∞,只会跑一个任务, 所以其他的任务都会在队列中等待,因此会严格按照FIFO执行newWorkStealingPool(继承自ForkJoinPool ): 并行线程池如果你的任务执行时间很长,并且里面的任务运行并行跑的,那么他会把你的线程任务再细分到其他的线程来分治。ForkJoinPool介绍:https://blog.csdn.net/m0_37542889/article/details/92640903Q: submit和execute的区别是什么?A:execute只能接收Runnable类型的任务,而submit除了Runnable,还能接收Callable(Callable类型任务支持返回值)execute方法返回void, submit方法返回FutureTask。异常方面, submit方法因为返回了futureTask对象,而当进行future.get()时,会把线程中的异常抛出,因此调用者可以方便地处理异常。(如果是execute,只能用内部捕捉或者设置catchHandler)Q:线程池中, shutdown、 shutdownNow、awaitTermination的区别?A:shutdown: 停止接收新任务,等待所有池中已存在任务完成( 包括等待队列中的线程 )。异步方法,即调用后马上返回。shutdownNow: 停止接收新任务,并 停止所有正执行的task,返回还在队列中的task列表 。awaitTermination: 仅仅是一个判断方法,判断当前线程池任务是否全部结束。一般用在shutdown后面,因为shutdown是异步方法,你需要知道什么时候才真正结束。五、Thread状态转换Q: 线程的6种状态是:A:New: 新建了线程,但是还没调用startRUNNABLE: 运行, 就绪状态包括在运行态中BLOCKED: 阻塞,一般是因为想拿锁拿不到WAITING: 等待,一般是wait或者join之后TIMED_WAITING: 定时等待,即固定时间后可返回,一般是调用sleep或者wait(时间)的。TERMINATED: 终止状态。欣赏一幅好图,能了解调用哪些方法会进入哪些状态。Q: java线程什么时候会进入阻塞(可能按多选题考):A:sleepwati()挂起, 等待获得别的线程发送的Notify()消息等待IO等待锁六、Volatile用volatile修饰成员变量时, 一旦有线程修改了变量,其他线程可立即看到改变。Q: 不用volatile修饰成员变量时, 为什么其他线程会无法立即看到改变?A:线程可以把变量保存在本地内存(比如机器的寄存器)中,而不是直接在主存中进行读写。这就可能造成一个线程在主存中修改了一个变量的值,而另外一个线程还继续使用它在寄存器中的变量值。Q: 用了volatile是不是就可以不用加锁啦?A: 不行。锁并不是只保证1个变量的互斥, 有时候是要保证几个成员在连续变化时,让其他线程无法干扰、读取。而volatile保证1个变量可变, 保证不了几个变量同时变化时的原子性。Q:展示一段《Java并发编程实战》书里的一个经典例子,在科目二考试里也出现了,只是例子换了个皮。为什么下面这个例子可能会死循环,或者输出0?:首先理解一下java重排序,可以看一下这篇博文:https://www.cnblogs.com/coshaho/p/8093944.html然后分析后面那2个奇怪的情况是怎么发生的。永远不输出:经过程序的指令排序,出现了这种情况:ReaderThread在while里读取ready值, 此时是false, 于是存入了ReaderThread的寄存器。主线程修改ready和number。ReaderThread没有感知到ready的修改(对于ReaderThread线程,感知不到相关的指令,来让他更新ready寄存器的值),因此进入死循环。输出0经过程序的指令排序,出现了这种情况:1)主线程设置ready为true2)ReaderThread在while里读取ready值,是true,于是退出while循环ReaderThread读取到number值, 此时number还是初始化的值为0,于是输出0主线程这时候才修改number=42,此时ReaderThread已经结束了!上面这个问题,可以用volatile或者加锁。当你加了锁时, 如果变量被写了,会有指令去更新另一个寄存器的值,因此就可见了。七、线程群组为了方便管理一批线程,我们使用ThreadGroup来表示线程组,通过它对一批线程进行分类管理使用方法:Thread group = new ThreadGroup("group");Thread thread = new Thread(gourp, ()->{..});即thread除了Thread(Runable)这个构造方法外,还有个Thread(ThreadGroup, Runnable)构造方法Q:在线程A中创建线程B, 他们属于同一个线程组吗A:是的线程组的一大作用是对同一个组线程进行统一的异常捕捉处理,避免每次新建线程时都要重新去setUncaghtExceptionHandler。即线程组自身可以实现一个uncaughtException方法。ThreadGroup group = new ThreadGroup("group") {    @Override    public void uncaughtException(Thread thread, Throwable throwable) {        System.out.println(thread.getName() + throwable.getMessage());        }    };}线程如果抛出异常,且没有在线程内部被捕捉,那么此时线程异常的处理顺序是什么?相信很多人都看过下面这段话,好多讲线程组的博客里都这样写:(1)首先看看当前线程组(ThreadGroup)有没有父类的线程组,如果有,则使用父类的UncaughtException()方法。(2)如果没有,就看线程是不是调用setUncaughtExceptionHandler()方法建立Thread.setUncaughtExceptionHandler实例。如果建立,直接使用它的UncaughtException()方法处理异常。(3)如果上述都不成立就看这个异常是不是ThreadDead实例,如果是,什么都不做,如果不是,输出堆栈追踪信息(printStackTrace)。来源:https://blog.csdn.net/qq_43073128/article/details/90597006https://blog.csdn.net/qq_43073128/article/details/88280469好,别急着记,先看一下下面的题目,问输出什么:Q:// 父类线程组static class GroupFather extends ThreadGroup {    public GroupFather(String name) {        super(name);    }    @Override    public void uncaughtException(Thread thread, Throwable throwable) {        System.out.println("groupFather=" + throwable.getMessage());    }} public static void main(String[] args) {    // 子类线程组    GroupFather groupSon = new GroupFather("groupSon") {        @Override        public void uncaughtException(Thread thread, Throwable throwable) {            System.out.println("groupSon=" + throwable.getMessage());        }    };    Thread thread1 = new Thread(groupSon, ()->{        throw new RuntimeException("我异常了");    });    thread1.start();}A:一看(1),那是不是应该输出groupFather?错错错,输出的是groupSon这句话在很多地方能看到,但没有去实践过看过源码的人就会这句话被误导。实际上父线程组不是指类继承关系上的线程组,而是指下面这样的:即指的是构造关系的有父子关系。如果子类的threadGroup没有去实现uncaughtException方法,那么就会去构造参数里指定的父线程组去调用方法。Q: 那我改成构造关系上的父子关系,下面输出什么?public static void main(String[] args) {    // 父线程组    ThreadGroup groupFather = new ThreadGroup("groupFather") {        @Override        public void uncaughtException(Thread thread, Throwable throwable) {            System.out.println("groupFather=" + throwable.getMessage());        }    };     // 子线程组,把groupFather作为parent参数    ThreadGroup groupSon = new ThreadGroup(groupFather, "groupSon") {        @Override        public void uncaughtException(Thread thread, Throwable throwable) {            System.out.println("groupSon=" + throwable.getMessage());        }    };     Thread thread1 = new Thread(groupSon, ()->{        throw new RuntimeException("我异常了");    });     thread1.start();}A:答案输出即只要子线程组有实现过,则会用子线程组里的方法,而不是直接去找的父线程组!Q:如果我让自己做set捕捉器的操作呢?那下面这个输出什么?public static void main(String[] args) {    // 父线程组    ThreadGroup group = new ThreadGroup("group") {        @Override        public void uncaughtException(Thread thread, Throwable throwable) {            System.out.println("group=" + throwable.getMessage());        }    };     // 建一个线程,在线程组内    Thread thread1 = new Thread(group, () -> {        throw new RuntimeException("我异常了");    });     // 自己设置setUncaughtExceptionHandler方法    thread1.setUncaughtExceptionHandler((t, e) -> {        System.out.println("no gourp:" + e.getMessage());    });     thread1.start();}A:看之前的结论里,似乎是应该输出线程组的异常?但是结果却输出的是:也就是说,如果线程对自己特地执行过setUncaughtExceptionHandler,那么有优先对自己设置过的UncaughtExceptionHandler做处理。那难道第(2)点这个是错的吗?确实错了,实际上第二点应该指的是全局Thread的默认捕捉器,注意是全局的。实际上那段话出自ThreadGroup里uncaughtException的源码:这里就解释了之前的那三点,但是该代码中没考虑线程自身设置了捕捉器所以修改一下之前的总结一下线程的实际异常抛出判断逻辑:如果线程自身有进行过setUncaughtExceptionHandler,则使用自己设置的按个。如果没设置过,则看一下没有线程组。并按照以下逻辑判断:如果线程组有覆写过uncaughtException,则用覆写过的uncaughtException如果线程组没有覆写过,则去找父线程组(注意是构造体上的概念)的uncaughtException方法。如果线程组以及父类都没覆写过uncaughtException, 则判断是否用Thread.setDefaultUncaughtExceptionHandler(xxx)去设置全局的默认捕捉器,有的话则用全局默认如果不是ThreadDeath线程, 则只打印堆栈。如果是ThreadDeath线程,那么就什么也不处理。
  • [技术干货] 40个问题让你快速掌握Java多线程的精髓(3)
    二、synchronized关键字即可作为方法的修饰符,也可以作为代码块的修饰符注意修饰方法时,并不是这个方法上有锁, 而是调用该方法时,需要取该方法所在对象上的锁。class A{     synchroized f(){     }   }即调用这个f(), 并不是说f同一时刻只能进入一次,而是说进入f时,需要取到A上的锁。Q: 调用下面的f()时,会出现死锁吗?class A{     synchroized f(){        t()     }      synchroized t(){     }}A:不会。1个线程内, 可以重复进入1个对象的synchroized 块。原理:当线程请求自己的锁时。JVM会记下锁的持有者,并且给这个锁计数为1。如果该线程再次请求自己的锁,则可以再次进入,计数为2。退出时计数-1,直到全部退出时才会释放锁。Q:2个线程同时调用f1和f2会产生同步吗?class A{    private static synchronized void f1(){};    private synchronized void f2(){};}A:不会产生同步。二者不是1个锁。f1是类锁,等同于synchronized(A.class)f2是对象锁。三、其他的同步工具CountDownLatchfinal CountDownLatch latch = new CountDownLatch(2);2是计数器初始值。然后执行latch.await()时, 就会阻塞,直到其他线程中把这个latch进行latch.countDown(),并且计数器降低至0。和join的区别:join阻塞时,是只等待单个线程的完成而CountDownLatch可能是为了等待多个线程Q: countDownLatch的内部计数值能被重置吗?A:不能重置了。如果要重新计数必须重新new一个。毕竟他的类名就叫DownLatchFutureTask可以理解为一个支持有返回值的线程FutureTask<Integer> task = new FutureTask<>(runable);当调用task.get()时,就能能达到线程里的返回值Q:调用futrueTask.get()时,这个是阻塞方法吗?如果是阻塞,什么时候会结束?A:是阻塞方法。线程跑完并返回结果阻塞时间达到futrueTask.get(xxx)里设定的xxx时间线程出现异常InterruptedException或者ExecutionException线程被取消,抛出CancellationExceptionSemaphore信号量:就是操作系统里常见的那个概念,java实现,用于各线程间进行资源协调。用Semaphore(permits)构造一个包含permits个资源的信号量,然后某线程做了消费动作, 则执行semaphore.acquire(),则会消费一个资源,如果某线程做了生产动作,则执行semaphore.release(),则会释放一个资源(即新增一个资源)更详细的信号量方法说明:https://blog.csdn.net/hanchao5272/article/details/79780045Q: 信号量中,公平模式和非公平模式的区别?下面设成true就是公平模式//new Semaphore(permits,fair):初始化许可证数量和是否公平模式的构造函数semaphore = new Semaphore(5, true);A:其实就是使用哪种公平锁还是非公平锁。Java并发中的fairSync和NonfairSync主要区别为:如果当前线程不是锁的占有者,则NonfairSync并不判断是否有等待队列,直接使用compareAndSwap去进行锁的占用,即谁正好抢到,就给谁用!如果当前线程不是锁的占有者,则FairSync则会判断当前是否有等待队列,如果有则将自己加到等待队列尾,即严格的先到先得!CyclicBarrier栅栏,一般是在线程中去调用的。它的构造需要指定1个线程数量,和栅栏被破坏前要执行的操作,每当有1个线程调用barrier.await(),就会进入阻塞,同时barrier里的线程计数-1。当线程计数为0时, 调用栅栏里指定的那个操作后,然后破坏栅栏, 所有被阻塞在await上的线程继续往下走。Exchanger我理解为两方栅栏,用于交换数据。简单说就是一个线程在完成一定的事务后,想与另一个线程交换数据,则第一个先拿出数据的线程会一直等待第二个线程,直到第二个线程拿着数据到来时才能彼此交换对应数据。原子类AtomicXXX就是内部已实现了原子同步机制Q:下面输出什么?(考察getAndAdd的用法)AtomicInteger num = new AtomicInteger(1);System.out.println(num.getAndAdd(1));System.out.println(num.get());A:输出1、2顾名思义, getAndAdd(),那么就是先get,再加, 类似于num++。如果是addAndGet(),那么就是++numQ:AtomicReference和AtomicInteger的区别?A:AtomicInteger是对整数的封装,而AtomicReference则对应普通的对象引用。也就是它可以保证你在修改对象引用时的线程安全性。即可能会有多个线程修改atomicReference里包含的引用。经典用法:boolean exchanged = atomicStringReference.compareAndSet(initialReference, newReference)就是经典的CAS同步法compreAndSet它会将将引用与预期值(引用)进行比较,如果它们相等,则在AtomicReference对象内设置一个新的引用。类似于一个非负责的自旋锁。AtomicReferenceArray是原子数组, 可以进行一些原子的数组操作例如 set(index, value),java中已实现的全部原子类:注意,没有float,没有short和byte。
  • [技术干货] 40个问题让你快速掌握Java多线程的精髓(2)
    Q: 上题的suspend和resume可以怎么替换,来解决死锁问题?A: 可以用wait和noitfy来处理(不过尽量不要这样设计,一般都是用run内部带1个while循环的)public class Test {    public static Object lockObject = new Object(); //拿来做临时锁对象    public static void main(String[] args) throws InterruptedException {         Thread thread = new MyThread();        thread.start();        Thread.sleep(1000);         System.out.println("主线程试图占用lockObject锁资源");        synchronized (Test.lockObject) {            // 用Test.lockObject做一些事            System.out.println("做一些事");        }        System.out.println("恢复");         synchronized (Test.lockObject) {            Test.lockObject.notify();        }     }} class MyThread extends Thread {    public void run() {        try {            synchronized (Test.lockObject) {                System.out.println("占用Test.lockObject");                Test.lockObject.wait();            }            System.out.println("MyThread释放TestlockObject锁资源");        }        catch (Exception e){}    }}如此执行,结果正常:Q: 下面这例子为什么会运行异常,抛出IllegalMonitorStateException错误?    public static void main(String[] args) throws InterruptedException {        Thread thread = new MyThread();        thread.start();        thread.notify();    }A: notify和wait的使用前提是必须持有这个对象的锁, 即main代码块 需要先持有thread对象的锁,才能使用notify去唤醒(wait同理)。改成下面就行了:        Thread thread = new MyThread();        thread.start();        synchronized (thread) {            thread.notify();        }Q: Thread.sleep()和Object.wait()的区别A:sleep不会释放对象锁, 而wait会释放对象锁。Q:Runnable接口和Callable的区别。A: Callable可以和Futrue配合,并且启动线程时用的时call,能够拿到线程结束后的返回值,call方法还能抛出异常。Q:thread.alive()表示线程当前是否处于活跃/可用状态。活跃状态: 线程已经启动且尚未终止。线程处于正在运行或准备开始运行的状态,就认为线程是“存活的thread.start()后,是否alive()一定返回true?public class Main {    public static void main(String[] args) {        TestThread tt = new TestThread();        System.out.println("Begin == " + tt.isAlive());        tt.start();        System.out.println("end == " + tt.isAlive());    }}A:不一定,有可能在打印时,线程已经运行结束了,或者start后,还未真正启动起来(就是还没进入到run中)Q: 线程A如下:public class A extends Thread {    @Override    public void run() {        System.out.println("this.isAlive()=" + this.isAlive());    }}把线程A作为构造参数,传给线程BA a = new A();Thread b = new Thread(a);b.start()此时会打印什么?A:此时会打印false!因为把a作为构造参数传入b中, b执行start时, 实际上是在B线程中去调用了 A对象的run方法,而不是启用了A线程。如果改成A a = new A();a.start()那么就会打印true了Q:把FutureTask放进Thread中,并start后,会正常执行callable里的内容吗?public static void main(String[] args) throws Exception {    Callable<Integer> callable = () -> {    System.out.println("call 100");    return 100;    };     FutureTask<Integer> task = new FutureTask<>(callable);    Thread thread = new Thread(task);    thread.start();}A:能正常打印
  • [技术干货] 40个问题让你快速掌握Java多线程的精髓(1)
    多线程可以理解为在同一个程序中能够同时运行多个不同的线程来执行不同的任务,这些线程可以同时利用CPU的多个核心运行。多线程编程能够最大限度的利用CPU的资源。本文将通过以下几个方向为大家讲解多线程的用法。1.Thread类基础2.synchronized关键字3.其他的同步工具CountDownLatchFutureTaskSemaphoreCyclicBarrierExchanger原子类AtomicXXX4.线程池5.Thread状态转换6.Volatile7.线程群组一、Thread类基础Q: Thread的deprecated过期方法是哪3个?作用是啥A:stop(), 终止线程的执行。suspend(), 暂停线程执行。resume(), 恢复线程执行。Q: 废弃stop的原因是啥?A:调用stop时,会直接终止线程并释放线程上已锁定的锁,线程内部无法感知,并且不会做线程内的catch操作!即线程内部不会处理stop后的烂摊子。如果其他线程等在等着上面的锁去取数据, 那么拿到的可能是1个半成品。变成题目的话应该是下面这样,问会输出什么?public class Test {     public static void main(String[] args) throws InterruptedException {         System.out.println("start");        Thread thread = new MyThread();        thread.start();        Thread.sleep(1000);        thread.stop();        // thread.interrupt();     }} class MyThread extends Thread {    public void run() {        try {            System.out.println("run");            Thread.sleep(5000);        } catch (Exception e) {            //处理烂摊子,清理资源            System.out.println("clear resource!");        }    }}Q: stop的替代方法是什么?A: interrupt()。调用thread.interrupt()终止时, 不会直接释放锁,可通过调用interrupt()或者捕捉sleep产生的中断异常,来判断是否被终止,并处理烂摊子。上题把thread.stop()改成thread.interrupt(),在Thread.sleep()过程中就会抛出interrupException(注意,InterrupExcetpion是sleep抛出的)因此就会输出clear resource。如果没有做sleep操作, 可以用isInterrupted()来判断自己这个线程是否被终止了,来做清理。另外注意一下interrupt和isInterrupted的区别:Q: suspend/resume的废弃原因是什么?A: :调用suspend不会释放锁。如果线程A暂停后,他的resume是由线程B来调用的,但是线程B又依赖A里的某个锁,那么就死锁了。例如下面这个例子,就要知道会引发死锁:public class Test {    public static Object lockObject = new Object();    public static void main(String[] args) throws InterruptedException {         System.out.println("start");        Thread thread = new MyThread();        thread.start();        Thread.sleep(1000);         System.out.println("主线程试图占用lockObject锁资源");        synchronized (Test.lockObject) {            // 用Test.lockObject做一些事            System.out.println("做一些事");        }        System.out.println("恢复");        thread.resume();     }} class MyThread extends Thread {    public void run() {        try {            synchronized (Test.lockObject) {                System.out.println("占用Test.lockObject");                suspend();            }            System.out.println("MyThread释放TestlockObject锁资源");        }        catch (Exception e){}    }}答案输出MyThread内部暂停后,外部的main因为没法拿到锁,所以无法执行后面的resume操作。
  • 当 CPU 摸鱼时,它到底在干什么?
    人空闲时会发呆会无聊,计算机呢?假设你正在用计算机浏览网页,当网页加载完成后你开始阅读,此时你没有移动鼠标,没有敲击键盘,也没有网络通信,那么你的计算机此时在干嘛?有的同学可能会觉得这个问题很简单,但实际上,这个问题涉及从硬件到软件、从 CPU 到操作系统等一系列环节,理解了这个问题你就能明白操作系统是如何工作的了。你的计算机 CPU 使用率是多少?如果此时你正在计算机旁,并且安装有 Windows 或者 Linux ,你可以立刻看到自己的计算机 CPU 使用率是多少。这是博主的一台安装有 Win10 的笔记本:可以看到大部分情况下 CPU 利用率很低,也就在 8% 左右,而且开启了 283 个进程,这么多进程基本上无所事事,都在等待某个特定事件来唤醒自己,就好比你写了一个打印用户输入的程序,如果用户一直不按键盘,那么你的进程就处于这种状态。有的同学可能会想也就你的比较空闲吧,实际上大部分个人计算机 CPU 使用率都差不多这样(排除掉看电影、玩游戏等场景),如果你的使用率总是很高,风扇一直在嗡嗡的转,那么不是软件 bug 就有可能是病毒。。。那么有的同学可能会问,剩下的 CPU 时间都去哪里了?剩下的 CPU 时间去哪里了?这个问题也很简单,还是以 Win10 为例,打开任务管理器,找到 “详细信息” 这一栏,你会发现有一个 “系统空闲进程”,其 CPU 使用率达到了 99%,正是这个进程消耗了几乎所有的 CPU 时间。那么为什么存在这样一个进程呢?以及这个进程什么时候开始运行呢?这就要从操作系统说起了。程序、进程与操作系统当你用最喜欢的代码编辑器编写代码时,这时的代码不过就是磁盘上的普通文件,此时的程序和操作系统没有半毛钱关系,操作系统也不认知这种文本文件。程序员写完代码后开始编译,这时编译器将普通的文本文件翻译成二进制可执行文件,此时的程序依然是保存在磁盘上的文件,和普通没有本质区别。但此时不一样的是,该文件是可执行文件,也就是说操作系统开始 “懂得” 这种文件,所谓 “懂得” 是指操作系统可以识别、解析、加载,因此必定有某种类似协议的规范,这样编译器按照这种协议生成可执行文件,操作系统就能加载了。在 Linux 下可执行文件格式为 ELF ,在 Windows 下是 EXE 。此时虽然操作系统可以识别可执行程序,但如果你不去双击一下(或者在Linux下运行相应命令)的依然和操作系统没有半毛钱关系。但是当你运行可执行程序时魔法就出现了。此时操作系统开始将可执行文件加载到内存,解析出代码段、数据段等,并为这个程序创建运行时需要的堆区栈区等内存区域,此时这个程序在内存中就是这样了:最后,根据可执行文件的内容,操作系统知道该程序应该执行的第一条机器指令是什么,并将其告诉 CPU ,CPU 从该程序的第一条指令开始执行,程序就这样运行起来了。一个在内存中运行起来的程序显然和保存在磁盘上的二进制文件是不一样的,总的有个名字吧,根据“弄不懂原则”,这个名字就叫进程,英文名叫做Process。我们把一个运行起来的程序叫做进程,这就是进程的由来。此时操作系统开始掌管进程,现在进程已经有了,那么操作系统是怎么管理进程的呢?调度器与进程管理银行想必大家都去过,实际上如果你仔细观察的话银行的办事大厅就能体现出操作系统最核心的进程管理与调度。首先大家去银行都要排队,类似的,进程在操作系统中也是通过队列来管理的。同时银行还按照客户的重要程度划分了优先级,大部分都是普通客户;但当你在这家银行存上几个亿时就能升级为 VIP 客户,优先级最高,每次去银行都不用排队,优先办理你的业务。类似的,操作系统也会为进程划分优先级,操作系统会根据进程优先级将其放到相应的队列中供调度器调度。这就是操作系统需要实现的最核心功能。现在准备工作已经就绪。接下来的问题就是操作系统如何确定是否还有进程需要运行。队列判空:一个更好的设计从上一节我们知道,实际上操作系统是用队列来管理进程的,那么很显然,如果队列已经为空,那么说明此时操作系统内部没有进程需要运行,这是 CPU 就空闲下来了,此时,我们需要做点什么,就像这样:if (queue.empty()) {  do_someting();}这些编写内核代码虽然简单,但内核中到处充斥着 if 这种异常处理的语句,这会让代码看起来一团糟,因此更好的设计是没有异常,那么怎样才能没有异常呢?很简单,那就是让队列永远不会空,这样调度器永远能从队列中找到一个可供运行的进程。而这也是为什么链表中通常会有哨兵节点的原因,就是为了避免各种判空,这样既容易出错也会让代码一团糟。就这样,内核设计者创建了一个叫做空闲任务的进程,这个进程就是Windows 下的我们最开始看到的“系统空闲进程”,在 Linux 下就是第 0号进程。当其它进程都处于不可运行状态时,调度器就从队列中取出空闲进程运行,显然,空闲进程永远处于就绪状态,且优先级最低。既然我们已经知道了,当系统无所事事后开始运行空闲进程,那么这个空闲进程到底在干嘛呢?这就需要硬件来帮忙了。一切都要归结到硬件在计算机系统中,一切最终都要靠 CPU 来驱动,CPU 才是那个真正干活的。原来,CPU 设计者早就考虑到系统会存在空闲的可能,因此设计了一条机器指令,这个机器指令就是 halt 指令,停止的意思。这条指令会让部分CPU进入休眠状态,从而极大减少对电力的消耗,通常这条指令也被放到循环中执行,原因也很简单,就是要维持这种休眠状态。值得注意的是,halt 指令是特权指令,也就是说只有在内核态下 CPU 才可以执行这条指令,程序员写的应用都运行在用户态,因此你没有办法在用户态让 CPU 去执行这条指令。此外,不要把进程挂起和 halt 指令混淆,当我们调用 sleep 之类函数时,暂停运行的只是进程,此时如果还有其它进程可以运行那么 CPU 是不会空闲下来的,当 CPU 开始执行halt指令时就意味着系统中所有进程都已经暂停运行。软件硬件结合现在我们有了 halt 机器指令,同时有一个循环来不停的执行 halt 指令,这样空闲任务进程的实际上就已经实现了,其本质上就是这个不断执行 halt 指令的循环,大功告成。这样,当调度器在没有其它进程可供调度时就开始运行空间进程,也就是在循环中不断的执行 halt 指令,此时 CPU 开始进入低功耗状态。在 Linux 内核中,这段代码是这样写的:while (1) {  while(!need_resched()) {      cpuidle_idle_call();  }}其中 cpuidle_idle_call函数最终会执行 halt 指令,注意,这里删掉了很多细节,只保留最核心代码,实际上 Linux 内核在实现空闲进程时还要考虑很多很多,不同类型的 CPU 可能会有深睡眠浅睡眠之类,操作系统必须要预测出系统可能的空闲时长并以此判断要进入哪种休眠等等,但这并不是我们关注的重点。总的来说,这就是计算机系统空闲时 CPU 在干嘛,就是在执行这一段代码,本质上就是 CPU 在执行 halt 指令。实际上,对于个人计算机来说,halt 可能是 CPU 执行最多的一条指令,全世界的 CPU 大部分时间都用在这条指令上了,是不是很奇怪。更奇怪的来了,有的同学可能已经注意到了,上面的循环可以是一个while(1) 死循环,而且这个循环里没有break语句,也没有return,那么操作系统是怎样跳出这个循环的呢?关于这个问题,我们将会在后续文章中讲解。总结CPU 空闲时执行特定的 halt 指令,这看上去是一个很简单的问题,但实际上由于 halt 是特权指令,只有操作系统才可以去执行,因此 CPU 空闲时执行 halt 指令就变成了软件和硬件相结合的问题。操作系统必须判断什么情况下系统是空闲的,这涉及到进程管理和进程调度,同时,halt 指令其实是放到了一个 while 死循环中,操作系统必须有办法能跳出循环,所以,CPU 空闲时执行 halt 指令并没有看上去那么简单。
  • [其他] 【参数】修改cms参数未能生效
    【版本信息】GaussDB 8.1.0之前版本【问题描述】某局点磁盘使用率100%,集群不可用,在清理数据后集群恢复正常,也将cms的配置文件里集群只读检测参数设置为on,但是过了几天后部分磁盘使用率超过90%后集群未只读【分析过程】查看cms进程目录下的cm_server.conf文件里只读检测参数enable_transaction_read_only为on查询cms进程如下3、咨询现场人员发现,集群在12号发生过磁盘满的情况,而且修改过只读检测参数,从cms进程的时间可以判断是在修改cms配置文件的只读检测参数后未重启cms进程导致只读检测未生效【修改cms参数流程】以只读检测参数enable_transaction_read_only值由off修改为on为例因生产环境一般无法停止集群,以下将采取kill进程的方式代替重启集群1、查询集群状态,确定出集群的cms主备节点2、分别将主备cms节点的/opt/huawei/Bigdata/mppdb/cm/cm_server/cm_server.conf文件中修改enable_transaction_read_only=on3、依次执行kill -9 cm_server备机和主机进程(kill前后分别观察进程号,进程号变化表示成功)
  • 服务器中常用的几个资源监控工具整理
          系统级性能优化通常包括两个阶段:性能剖析(performance profiling)和代码优化。性能剖析的目标是寻找性能瓶颈,查找引发性能问题的原因及热点代码。代码优化的目标是针对具体性能问题而优化代码或编译选项,以改善软件性能。以这台鲲鹏服务器为例,先查看自己操作系统的发行版本(1)nmon:支持收集一段时间内,整机的CPU、磁盘、网络、内存等各项资源的使用情况。(2)perf:Linux kernel自带的系统性能优化工具,获取指定进程内的调用情况、各线程调用的CPU资源消耗情况,并支持生成火焰图。火焰图的生成过程是: 先trace系统,获取系统的profiling数据 用脚本来绘制#脚本获取 git clone https://github.com/brendangregg/FlameGraph(3)top:监控进程和整机的CPU、内存资源消耗情况,并支持查看每个CPU核的使用情况。     1.负载:时间,登陆用户数,系统平均负载;     2.cpu:用户态,核心态,NICE,空闲,等待IO,中断等;     3.进程:运行,睡眠,停止,僵尸;     4.内存:总量,已用,空闲(系统角度),缓冲,缓存;     5.交换分区:总量,已用,空闲       任务区域默认显示:进程ID,有效用户,进程优先级,NICE值,进程使用的虚拟内存,物理内存和共享内存,进程状态,CPU占用率,内存占用率,累计CPU时间,进程命令行信息。(4)iostat:监控每块磁盘的读写次数、数据量大小、使用率。 iostat属于sysstat软件包,可以直接安装。 yum -y install sysstat(5)sar:(System Activity Reporter系统活动情况报告)目前LINUX上最为全面的系统性能分析工具之一,监控每张网卡的网络IO读写次数和数据量大小。先安装deltarpm再安装sar工具(sar也属于sysstat软件包,可以直接安装。)yum install deltarpm yum install sysstat(6)dstat:监控系统整体的性能信息,包括CPU、磁盘、网络、分页等。输出是彩色的,可读性较强安装dstatyum install dstat-0.7.2-12.el7 -y(7)htop:htop 是Linux系统中的一个互动的进程查看器,可以让用户交互式操作,支持颜色主题,可横向或纵向滚动浏览进程列表,并支持鼠标操作。 安装htopyum install -y htophtop优点: ▪ 在启动上,比top更快。 ▪ 可以横向或者纵向滚动浏览进程列表,以便看到所有的进程和完整的命令行。 ▪ 杀进程时不需要输入进程号。 ▪ htop支持鼠标操作。
  • [体验官] linux进程树方式查看进程
    ps axjf:linux以进程树的方式查看进程root@ecs-385f:~# docker ps CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMESroot@ecs-385f:~# ps axjf |grep docker    1  1698  1698  1698 ?           -1 Ssl      0  42:55 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock 5374  5578  5577  5374 pts/0     5577 S+       0   0:00          \_ grep docker进程树会显示进程之间的关系。也可以使用pstree显示
  • [其他] 【升级】升级停老集群阶段一个dn进程一直停不掉
    【问题描述】某局点在升级停老集群阶段一个dn进程一直停不掉,dn日志无刷新,dn堆栈中有75个线程未退,截取部分如下:【规避措施】1.找到gs_upgradectl执行节点;2.找到cm_ctl停止集群进程ps -ef |grep cm_ctl3.将cm_ctl进程kill掉4.再次查询cm_ctl停止集群命令,确认停止模式是否为-m i,如果不是,再次kill cm_ctl 停集群进程5.确认cm_ctl 停集群命令为-m i模式,则等待gs_upgradectl继续完成升级。
  • [赋能学习] 同一个进程中访问两个安全集群认证参考
    主进程参考: ``` public class HdfsTest { // static { // //日志配置文件 // PropertyConfigurator.configure(HdfsTest.class.getClassLoader().getResource(System.getProperty("user.dir")+"/log4j.properties").getPath()); // } private final static Log LOG = LogFactory.getLog(HdfsTest.class.getName()); public static void main(String[] args) throws LoginException, IOException { System.out.println(HdfsTest.class.getClassLoader().getResource("log4j.properties").getPath()); Configuration conf = new Configuration(); conf.addResource(new Path("D:\\fwc\\TowClusterHDFS\\src\\main\\resources\\config\\core-site.xml")); conf.addResource(new Path("D:\\fwc\\TowClusterHDFS\\src\\main\\resources\\config\\hdfs-site.xml")); Configuration conf1 = new Configuration(); conf1.addResource(new Path("D:\\fwc\\TowClusterHDFS\\src\\main\\resources\\configTwo\\core-site.xml")); conf1.addResource(new Path("D:\\fwc\\TowClusterHDFS\\src\\main\\resources\\configTwo\\hdfs-site.xml")); // PolluxJaasConfig.init(); // PolluxJaasConfig.getConfiguration(); // System.setProperty("java.security.krb5.conf", KRB5_FILE); String dir1 = HdfsTest.class.getClassLoader().getResource("config").getPath(); String dir4 = HdfsTest.class.getClassLoader().getResource("configTwo").getPath(); Krb5ConfigManager kcm = Krb5ConfigManager.getInstance(); kcm.mergeKrb5Config(dir4 + "/" + "krb5.conf");//d集群配置 kcm.mergeKrb5Config(dir1 + "/" + "krb5.conf");//a集群配置 // System.setProperty("java.security.auth.login.config", "D:\\fwc\\TowClusterHDFS\\src\\main\\resources\\jaas.conf"); UserGroupInformation.setConfiguration(conf); UserGroupInformation ugi = UserGroupInformation.loginUserFromKeytabAndReturnUGI("lyysxg@HADOOP1.COM", "D:\\fwc\\TowClusterHDFS\\src\\main\\resources\\config\\user.keytab"); ugi.doAs(new PrivilegedAction<Object>() { @Override public Object run() { try { FileSystem fSystem = FileSystem.get(conf); Path dirPath = new Path("/zwl/test11111"); fSystem.mkdirs(dirPath); LOG.info("success to mkdirs."); } catch (IOException e) { e.printStackTrace(); } return null; } }); UserGroupInformation.setConfiguration(conf1); UserGroupInformation ugi1 = UserGroupInformation.loginUserFromKeytabAndReturnUGI("lyysxg@HADOOP.COM", "D:\\fwc\\TowClusterHDFS\\src\\main\\resources\\configTwo\\user.keytab"); ugi1.doAs(new PrivilegedAction<Object>() { @Override public Object run() { try { FileSystem fSystem = FileSystem.get(conf1); Path dirPath = new Path("/zwl/test11111"); //清理环境,如果当前目录已经存在,先删除 // if (fSystem.exists(dirPath)) { // fSystem.delete(dirPath, true);//第二个参数用于指定是否递归删除 // } //******创建目录******** fSystem.mkdirs(dirPath); LOG.info("success to mkdirs."); } catch (IOException e) { e.printStackTrace(); } return null; } }); } } ``` 依赖的Krb5ConfigManager的实现参考 ``` public class Krb5ConfigManager { private static final Logger LOG = LoggerFactory.getLogger(Krb5ConfigManager.class); public static final String JAVA_SECURITY_KRB5_CONF_KEY = "java.security.krb5.conf"; public static final String KRB5CONF_PATH = new File("krb5.conf").getAbsolutePath(); private static boolean alreadyFreshKrb5Config = false; private static Krb5ConfigManager instance = new Krb5ConfigManager(); private Krb5Config krb5Config = null; private Map<String, String> md5ToFileMap = new ConcurrentHashMap<>(); private Map<String, Long> fileToModifyTime = new ConcurrentHashMap<>(); public static Krb5ConfigManager getInstance(){ return instance; } private Krb5ConfigManager(){ try { String file = System.getProperty(JAVA_SECURITY_KRB5_CONF_KEY); if (StringUtils.isNotEmpty(file)){ krb5Config = new Krb5Config(file); md5ToFileMap.put(calcMd5(file), file); fileToModifyTime.put(file, new File(file).lastModified()); krb5Config.writeKrb5ConfigToFile(KRB5CONF_PATH); System.setProperty(JAVA_SECURITY_KRB5_CONF_KEY, KRB5CONF_PATH); alreadyFreshKrb5Config = true; } } catch (IOException e) { LOG.warn("Krb5Config manager init main config file failed. File : " + KRB5CONF_PATH, e); } TimerTaskManager.schedule(new TimerTask(){ @Override public void run() { try{ checkKrb5Conf(); } catch (Exception e){ LOG.warn("Check frb5 conf failed. File: " + KRB5CONF_PATH, e); } } }, 60); } public void init(){ } public void mergeKrb5Config(String configPath){ String configPathSafe = PathCheck.getSafeFilePath(configPath); if (StringUtils.isEmpty(configPathSafe)){ return; } File configFile = new File(configPathSafe); if (!configFile.isFile()){ return; } //如果文件已经合并,且最后修改时间未改变,则不进行合并 Long lastModityTime = fileToModifyTime.get(configPathSafe); if (lastModityTime != null && lastModityTime == configFile.lastModified()){ return; } try { //如果md5值和已合并的文件相关,则不进行合并 String md5 = calcMd5(configPathSafe); if (StringUtils.isNotEmpty( md5ToFileMap.get(md5))){ return; } Krb5Config other = new Krb5Config(configPathSafe); synchronized (Krb5ConfigManager.class) { if (krb5Config == null){ krb5Config = other; }else{ //如果已经包含了对应的krb5配置,则不进行合并,直接返回 if(krb5Config.contains(other)){ return; }else{ //进行配置合并 krb5Config.mergeKrb5Config(other); } } md5ToFileMap.put(md5, configPathSafe); fileToModifyTime.put(configPathSafe, configFile.lastModified()); //防止更新配置文件过程中时,其他线程加载配置文件 synchronized (getConfigClass()){ krb5Config.writeKrb5ConfigToFile(KRB5CONF_PATH); } freshConfig(); } } catch (IOException e) { LOG.error("Merge Krb5Conf file failed. file:" + configPathSafe, e); return; } } private void freshConfig(){ String oldConfig = System.getProperty(JAVA_SECURITY_KRB5_CONF_KEY); //如果环境变量取值非默认值,先进行合并,防止配置丢失 if (oldConfig != null && !KRB5CONF_PATH.equals(oldConfig)){ System.setProperty(JAVA_SECURITY_KRB5_CONF_KEY, KRB5CONF_PATH); mergeKrb5Config(oldConfig); return; } try { Class<?> classRef = getConfigClass(); synchronized (classRef){ alreadyFreshKrb5Config = true; System.setProperty(JAVA_SECURITY_KRB5_CONF_KEY, KRB5CONF_PATH); Method refreshMethod; refreshMethod = classRef.getDeclaredMethod("refresh"); refreshMethod.invoke(null); } } catch (Exception ex) { LOG.warn("Refresh krb5.conf failed!" + ex); } } private Class<?> getConfigClass() { String javaVendor = System.getProperty("java.vendor"); try{ if (null != javaVendor && javaVendor.contains("IBM")) { return Class.forName("com.ibm.security.krb5.internal.Config"); } else { return Class.forName("sun.security.krb5.Config"); } } catch (ClassNotFoundException e) { return sun.security.krb5.Config.class; } } private String calcMd5(String file) throws IOException { String safePath = PathCheck.getSafeFilePath(file); InputStream in = new FileInputStream(safePath); return new String(Md5Utils.computeMd5ForFile(in), "utf-8"); } private void checkKrb5Conf() { String config = System.getProperty(JAVA_SECURITY_KRB5_CONF_KEY); String krb5ConfPath = PathCheck.getSafeFilePath(KRB5CONF_PATH); if (alreadyFreshKrb5Config && !krb5ConfPath.equals(config)){ LOG.warn("java.security.krb5.conf was changed to %s, will be changed to %s", config, krb5ConfPath); mergeKrb5Config(config); } } } ```
  • [维护宝典] Kafka CPU使用率高排查思路及解决方案
    一、          CPU使用率高的一般排查思路1) Top命令排查CPU使用率高的进程2) su - omm切换到omm用户,jstack -l 进程pid>>文件路径3) top -H -p 进程pid 查看CPU使用率高4) “printf "%x\n" 线程号”将上一步中CPU高的线程号转换成16进制5) 在jstack文件中搜索上一步中的16进制信息查看具体的线程二、          Kafka CPU使用率高的常见原因及解决方案1.     集群规划不合理a)    命令grep -c processor /proc/cpuinfo 查看CPU核数,df -h命令查看Kafka磁盘挂载情况,建议每台机器最大挂盘数量 <= processor / 2。挂载过多磁盘也可能导致CPU繁忙。b)    Kafka配置中查看num.io.threads、num.network.threads、num.replica.fetchers配置,num.io.threads和num.network.threads配置一般建议磁盘个数的倍数,但不能超过CPU核数。num.replica.fetchers配置一般建议不超过5,同步线程数过多也会造成CPU繁忙。c)    分区设置不合理:分区一般设置为节点个数的倍数,如果分区设置不合理,所有的生产和消费都集中在某个节点上,也可能导致CPU高。2.     操作系统句柄数过高a)    查看操作系统句柄数:cat /var/log/osinfo/statistics/file-nr.txtb)    统计各进程打开句柄数:lsof -n|awk '{print $2}'|sort|uniq -c|sort -nrc)    查看进程使用句柄数:lsof -p 进程pid | wc -l如果句柄数高,排查使用句柄数高的进程是否有句柄泄漏;排查操作系统日志,查看是否存在TCP内存溢出等。3.     根据CPU使用率高的线程具体排查a)    使用C++客户端,版本不兼容,kafka-request.log日志中大量获取全部topic元数据的信息:Jstack信息中查找CPU高的线程,线程都在获取元数据信息:Request日志开debug后,大量获取全部Topic元数据信息,且耗时很长。CPU长时间频繁处理元数据请求导致CPU升高。b)    使用Logstash版本过低,与Kafka高版本服务端不兼容:      Kafka服务端对部分topic加权限后,21005端口访问未加权限的topic,kafka-authorizer.log日志中大量请求Deny的日志           CPU频繁处理deny请求导致CPU异常升高。c)    Sssd异常使用id -Gn名称返回结果异常为sssd服务有问题kafka-authorizer.log日志为大量打印某用户不属于kafka组或者kafkaadmin组服务器的kafka无法正常鉴权,导致用户频繁访问此服务器,cpu异常升高
  • Jmeter常见函数使用方法汇总
    __intSumintSum 函数可用于计算两个或多个整数值的总和。引用名称是可选的, 但它不能是有效的整数。{__intSum(2,5,MYVAR)} 将返回 7 (2 5) 并将结果存储在 MYVAR 变量中。因此 {MYVAR} 将等于7。${__intSum(2,5,7)} 将返回 14 (2 5 7) 并将结果存储在 MYVAR 变量中。{__intSum(1,2,5,${MYVAR})} 如果 MYVAR 值等于8、1 2 5 {MYVAR}, 则返回16__longSumlongSum 函数可用于计算两个或多个长值的总和, 而当您知道值不会在间隔-2147483648 到2147483647中时, 请使用此方法而不是 __intSum。{__longSum(2,5,MYVAR)} 将返回 7 (2 5) 并将结果存储在 MYVAR 变量中。因此 {MYVAR} 将等于7。${__longSum(2,5,7)} 将返回 14 (2 5 7) 并将结果存储在 MYVAR 变量中。{__longSum(1,2,5,${MYVAR})} 如果 MYVAR 值等于8、1 2 5 {MYVAR}, 则返回16__StringFromFileStringFromFile 函数可用于从文本文件读取字符串。这对于运行需要大量可变数据的测试非常有用。例如, 在测试银行应用程序时, 可能需要使用不同帐号的100s 或1000s。另请参阅可更易于使用的 CSV 数据集配置测试元素。但是, 当前不支持多个输入文件。 每次被调用时, 它会从文件中读取下一行。所有线程共享相同的实例, 因此不同的线程将得到不同的行。当到达文件末尾时, 它将从一开始重新开始读取, 除非已达到最大循环计数。如果在测试脚本中有多个对该函数的引用, 则每个文件都将独立打开它, 即使文件名相同。[如果该值将再次用于其他位置, 请对每个函数调用使用不同的变量名。函数实例在线程之间共享, 并且该文件 (重新打开) 由任何线程碰巧需要下一行输入, 因此使用 threadNumber 作为文件名的一部分将导致不可预知的行为。如果打开或读取文件时出现错误, 则该函数将返回字符串 '** 错误 **'${__StringFromFile(PIN#'.'DAT,,1,2)}-读取 PIN1.DAT, PIN2.DAT${__StringFromFile(PIN.DAT,,,2)}-读取 PIN2.DAT 两次__machineNamemachineName 函数返回本地主机名。这将使用 Java 方法 InetAddress getLocalHost () 并将其传递到 getHostName ()Examples:${__machineName()} 将返回计算机的主机名${__machineName} 将返回计算机的主机名__machineIPmachineIP 函数返回本地 IP 地址。这将使用 Java 方法 InetAddress getLocalHost () 并将其传递到 getHostAddress ()${__machineIP()} 将返回计算机的 IP 地址${__machineIP} 将返回计算机的 IP 地址__javaScriptjavascript 函数执行一条 javascript (不是 Java) 代码并返回其值 JMeter javascript 函数调用独立的 javascript 解释器。Javascript 被用作脚本语言, 因此您可以进行计算等。javaScript 不是 JMeter 中性能最好的脚本语言。如果您的计划需要大量的线程, 建议使用 jexl3 或 __groovy 函数。${__javaScript('${sp}'.slice(7\,99999))} 7后的逗号被转义。Examples:${__javaScript(new Date())} 将返回 Sat 2016年1月09日 16:22:15 GMT 0100 (英语)${__javaScript(new Date(),MYDATE)} 将返回星期六 2016年1月09日 16:22:15 GMT 0100 (CET) 和存储在可变 MYDATE${__javaScript(Math.floor(Math.random()*(${maxRandom}+1)),MYRESULT)}将使用 maxRandom 变量, 返回一个介于0和 maxRandom 之间的随机值, 并将其存储在 MYRESULT__Random随机函数返回位于给定最小值和最大数值之间的随机数。${__Random(0,10)} 将返回介于0和10之间的随机数{__Random(0,10, MYVAR)} 将返回一个介于0和10之间的随机数字, 并将其存储在 MYVAR 中。 {MYVAR} 将包含随机数__RandomDateRandomDate 函数返回位于给定开始日期和结束日期值之间的随机日期。${__RandomDate(,,2050-07-08,,)}将返回到现在和2050-07-08 之间的随机日期。例如2039-06-21${__RandomDate(dd MM yyyy,,08 07 2050,,)}将返回一个自定义格式的随机日期, 如 04 03 2034__RandomStringRandomString 函数返回一个随机长度字符串, 使用字符在字符中使用。${__RandomString(5)}将返回一个随机字符串的5个字符, 可读或不${__RandomString(10,abcdefg)}将返回一个随机字符串10字符从 abcdefg 集, 如 cdbgdbeebd 或 adbfeggfad,..。${__RandomString(6,a12zeczclk, MYVAR)} 将返回从 a12zeczclk 集选取的随机字符串6个字符, 并将结果存储在 MYVAR 中, MYVAR 将包含字符串, 如2z22ak 或 z11kce..。__RandomFromMultipleVarsRandomFromMultipleVars 函数根据源变量提供的变量值返回一个随机值。${__RandomFromMultipleVars(val)}将返回一个基于变量的内容的随机字符串考虑到它们是否是多值或不${__RandomFromMultipleVars(val1|val2)} 将返回一个随机字符串基于变量的内容 val1 和 val2 考虑到它们是否是多值或不${__RandomFromMultipleVars(val1|val2, MYVAR)}将返回一个基于变量内容的随机字符串 val1 和 val2 考虑到它们是否是多值的, 并将结果存储在 MYVAR__UUIDUUID 函数返回一个伪随机类型4通用唯一标识符 (uuid)。${UUID()}将返回 UUIDs 的格式: c69e0dd1-ac6b-4f2b-8d59-5d4e8743eecd__CSVReadCSVRead 函数从 CSV 文件中返回一个字符串 (鸡毛 StringFromFile) 注意: JMeter 支持多个文件名。首次遇到文件名时, 文件将被打开并读取到内部数组中。如果检测到空行, 则将其视为文件的末尾-这允许使用尾随注释。 所有对同一文件名的后续引用都使用相同的内部数组。注意文件名大小写对函数很重要, 即使操作系统不关心, 因此 CSVRead (.txt, 0) 和 CSVRead (abc, 0) 将引用不同的内部数组。 '* 别名' 功能允许多次打开同一文件, 并且允许更短的文件名。 每个线程都有自己的内部指针到文件数组中的当前行。当线程第一次引用该文件时, 它将被分配到数组中的下一个可用行, 因此每个线程将从所有其他线程访问其他行。[除非有比数组中的行多的线程。默认情况下, 函数在每个逗号处拆分该行。如果要输入包含逗号的列, 则需要将分隔符更改为不出现在任何列数据中的字符, 通过设置属性: csvread. 分隔符例如, 可以按如下方式设置一些变量:COL1a ${__CSVRead(random.txt,0)}COL2a {__CSVRead(random.txt,1)}{__CSVRead(random.txt,next)}COL1b ${__CSVRead(random.txt,0)}COL2b {__CSVRead(random.txt,1)}{__CSVRead(random.txt,next)}这将读取一行中的两列, 以及下一个可用行中的两列。如果在相同的用户参数前处理器上定义了所有变量, 则这些行将是连续的。否则, 另一个线程可能会抓取下一行。该函数不适合用于大型文件, 因为整个文件存储在内存中。对于较大的文件, 请使用 CSV 数据集配置元素或 StringFromFile。__property属性函数返回 JMeter 属性的值。如果找不到属性值, 并且未提供默认设置, 则返回属性名称。提供默认值时, 不需要提供函数名-参数可以设置为 null, 并且将被忽略。${__property(user.dir)} - 返回值为 user.dir${__property(user.dir,UDIR)} - 返回值为user.dir 并保存在 UDIR${__property(abcd,ABCD,atod)} - 返回值属性为 abcd (或 "atod"如果没有定义) 并保存在ABCD中${__property(abcd,,atod)} - 返回值属性为 abcd (或 "atod"如果没有定义) 但不保存它__P这是一个简化的属性函数, 用于与命令行上定义的属性一起使用。与 __property 函数不同, 没有选项保存变量中的值, 如果没有提供默认值, 则假定为1。选择了1的值, 因为它对常见的测试变量 (如循环、线程计数、坡道等) 有效。
总条数:813 到第 页
上滑加载中