前言 不正确的日志打印不但会降低程序运行性能,还会占用大量IO资源和硬盘存储空间。 本文主要总结一些能提高日志打印性能的手段。 一、通过AsyncAppender异步输出日志 我们通常使用的ConsoleAppender 和 RollingFileAppender都是同步输出日志,会阻塞程序运行。只有当日志打印完毕程序才会继续执行。 而通过AsyncAppender实现异步日志输出,会启用单独日志线程去记录日志,并且不会阻塞程序运行,可以极大的增加日志打印的吞吐量。 具体实现可以查看:logback异步输出日志详解 配置示例: 添加一个基于异步写日志的 appender,并指向原先配置的 appender即可。 <configuration> <!-- 同步输出 --> <appender name="FILE" class="ch.qos.logback.core.FileAppender"> <file>myapp.log</file> <encoder> <pattern>%logger{35} .... logback性能优化详解 java
背景 症状 排查 修复 背景 最近在陆续做机房升级相关工作,配合DBA对产线数据库链接方式做个调整,将原来直接链接读库的地址切换到统一的读负载均衡的代理 haproxy 上,方便机柜和服务器的搬迁。 切换之后线上时不时的会发生 discard connection 错误,导致程序报 500 错误,但不是每次都必现的。 开发框架: spring boot+mybatis+druid+shardingJDBC 网络架构: appserver->mysql(master) 写 appserver->haproxy->mysql(slave)/n 读 第一反应肯定是因为这次的读库地址的变动引起的问题,觉得问题应该是 druid 链接池中的 connection 保活策略没起作用,只要做下配置修改应该就可以了。结果这个问题让我们排查了好几天,我们竟然踩到了千年难遇的深坑。 这个问题排查的很坎坷,一次次的吐血,最终我们定位到问题并且优雅的修复了,我们一起来体验下这个一次一次让你绝望一次一次打脸的过程。 症状 先说故障症状,经常出现如下错误: discard connect.... 诡异的druid链接池链接断开故障经验总结 druid
实例 FROM java:8 VOLUME /tmp ADD app.jar app.jar RUN bash -c 'touch /app.jar' EXPOSE 9001 ENV JAVA_OPTS=" -server -Xmx4g -Xms4g -Xmn2g -XX:SurvivorRatio=8 -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m -XX:+UseParallelGC -XX:ParallelGCThreads=4 -XX:+UseParallelOldGC -XX:+UseAdaptiveSizePolicy -XX:+PrintGCDetails -XX:+PrintTenuringDistribution -XX:+PrintGCTimeStamps -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/ -Xloggc:/gc.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileS.... dockerfile指定jvm参数 Docker java jvm OOM内存溢出问题排查 诊断监控 java
在 webflux + druid 中使用druid的监控页面 使用tomcat作为web容器(druid版本1.2.8) 1.1 添加依赖 1.2 将filter赋值到tomcat的context 中 使用netty作为web容器. //TODO 2.1 添加依赖 避免报错 2.2 添加路由 使用tomcat作为web容器(druid版本1.2.8) 如果能选择tomcat作为容器的话, 就非常好解决了. 只要配置一下filter就行 注意: druid版本必须 大于1.2.5 1.1 添加依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> <exclusions> <!-- Exclude the reactor-netty dependency --> <exclusion> <groupId>or.... 在webflux+druid中 使用druid的监控页面 webflux
前言 CompletionStage是Java8新增得一个接口,用于异步执行中的阶段处理,其大量用在Lambda表达式计算过程中,目前只有CompletableFuture一个实现类,但我先从这个接口的方法开始介绍,为了举例说明这些接口方法的使用,会用到部分CompletableFuture的方法,下一步再详细的介绍CompletableFuture。 CompletionStage定义了一组接口用于在一个阶段执行结束之后,要么继续执行下一个阶段,要么对结果进行转换产生新的结果等等,一般来说要执行下一个阶段都需要上一个阶段正常完成,当然这个类也提供了对异常结果的处理接口。CompletionStage只定义了一组基本的接口,其实现类还可据此扩展出更丰富的方法。 方法概述 CompletionStage的接口方法可以从多种角度进行分类,从最宏观的横向划分,CompletionStage的接口主要分三类: 一、产出型或者函数型: 就是用上一个阶段的结果作为指定函数的参数执行函数产生新的结果。这一类接口方法名中基本都有apply 字样,接口的参数是**(Bi)Function**类型。 二、.... Java并发包之阶段执行之CompletionStage接口 java
/** * 创建多级目录文件 * * @param path 文件路径 * @throws IOException */ private void createFile(String path) throws IOException { if (StringUtils.isNotEmpty(path)) { File file = new File(path); if (!file.getParentFile().exists()) { file.getParentFile().mkdirs(); } file.createNewFile(); } } Java 在给定路径上创建文件,所在文件夹不存在时,如何正确创建。 java
用SchedulingConfigurer接口只能统一修改,要分开控制的话有多少个job就要有多少个实现。比较麻烦 配置线程池ThreadPoolTaskScheduler Copy@Configuration public class JobConfig { @Bean("taskExecutor") public ThreadPoolTaskScheduler taskExecutor() { ThreadPoolTaskScheduler executor = new ThreadPoolTaskScheduler(); executor.setPoolSize(20); executor.setThreadNamePrefix("taskExecutor-"); executor.setWaitForTasksToCompleteOnShutdown(true); executor.setAwaitTerminationSeconds(300); return executor; } } 封装实现 Copy@Component @Slf4j public class Job.... 使用ThreadPoolTaskScheduler动态修改调度时间 java
前言 项目中需要监听redis的一些事件比如键删除,修改,过期等。过期事件网上有很多例子可以参考,但修改或删除事件却很少。因为redis支持发布订阅所以其他的事件类型应该也是能实现的,通过过期事件监听结合上文键空间通知,我整理了相关代码,希望帮助需要的人快速解决问题。 代码实践 网上示例的失效事件监听代码 import com.alibaba.fastjson.JSONObject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.connection.Message; import org.springframework.data.redis.listener.KeyExpirationEventMessageListener; import org.springframework.data.redis.listener.RedisMessageListenerContainer; import org.springframewor.... Redis监听新增、修改、删除、过期事件 redis
最近一个粉丝朋友在面试时被面试官一个问题问懵了,看你简历上写着你深入研读过RocketMQ的源码,那请问一下:RocketMQ一个消费组内订阅同一个主题不同的TAG为什么会丢消息。 RocketMQ支持tag级别的消息过滤机制,其具体实现原理主要从存储、拉取模型两个部分进行展开。 MQ底层数据结构设计之巧妙 RocketMQ专门按照Topic为每一个topic建立索引,方便消费端按照topic进行消费,其具体实现为消息队列。 在RocketMQ中,ConsumeQueue的引入并不是为了提高消息写入的性能,而是为消费服务的。 消息消费队列中的每一个条目是一个定长的,设计极具技巧性,其每个条目使用固定长度(8字节commitlog物理偏移量、4字节消息长度、8字节tag hashcode),这里不是存储tag的原始字符串,而是存储hashcode。 目的就是确保每个条目的长度固定,可以使用访问类似数组下标的方式来快速定位条目,极大的提高了ConsumeQueue文件的读取性能,这样根据消费进度去访问消息的方法为使用逻辑偏移量logicOffset * 20即可找到该条目的起始偏移量.... RocketMQ一个消费组内订阅同一个主题不同的TAG为什么会丢消息 java
在springboot项目中增加邮件功能 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-mail</artifactId> </dependency> 在配置文件中配置为 spring.mail.host=smtp.mxhichina.com spring.mail.username=from@mail.com spring.mail.port=465 spring.mail.password=ENC(XmxK15odd+SF5VUGSFNUTse10lxijBIYz) spring.mail.properties.mail.smtp.auth=true spring.mail.properties.mail.smtp.starttls.enable=true spring.mail.properties.mail.smtp.starttls.required=true spring.... 有更新! Got bad greeting from SMTP host: smtp.yandex.ru, port: 465, response: [EOF]] java
swagger 2.X是springboot用于生成在线文档的工具,基于OpenApi2 springdoc-openapi-ui 则是基于OpenApi3,可以看成是swagger3,因为导入之后一些注解的包名都是 io.swagger.v3.oas.annotations baidu出来的都被swagger 2占领了,基本搜不到文档,想要查阅请自行谷歌 文档上说springdoc-openapi-ui能够在webflux的项目中使用,尚未尝试,留几个文档地址请自行查阅 https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Annotations https://github.com/springdoc-openapi https://springdoc.github.io/springdoc-openapi-demos/ 依赖 implementation 'org.springdoc:springdoc-openapi-ui:1.2.3' 使用 springdoc-openapi-ui作为spri.... springboot集成springdoc-openapi-ui java
在使用springboot开发业务应用程序的过程中,使用多数据源的场景很常见。目前,业界常用的spring boot整合多数据源的方案主要有两个,一个是Mapper分包方式,另一个是AOP切片方式。针对这种多数据源使用场景的痛点,MyBatis-plus团队开源了一个更为方便的解决方案(https://mp.baomidou.com/guide/dynamic-datasource.html),但是对于整合druid数据库连接池,却没有给出具体的使用示例。本篇文章就是使用springboot整合mybatis-plus、druid连接池和多数据源配置,给出一个可用的示例。 一、在SpringBoot项目pom文件中引入依赖 在SpringBoot项目pom文件中,引入如下依赖: org.apache.commons commons-collections4 4.1 com.baomidou mybatis-plus-boot-starter 3.4.1 com.alibaba druid 1.2.4 com.baomidou dynamic-datas.... springboot整合mybatis-plus、druid连接池和多数据源配置 springboot
这两天客户这边有一台服务器一到下午3点左右就开始卡住,页面无法访问,服务器CPU占用达到300%多 开始以为只是可能只是意外事件,重启一下就好,但是发现重启之后没几分钟服务器马上又反应无法访问,我就开始检查自己写的代码是不是有什么非常消耗CPU资源的逻辑,但是找了一段时间之后还是一无所获,不过马上反应的就是先把最新提交发布的代码还原到上一个版本。但是没过多久还是反应服务器开始又开始无法访问了。 于是就 第一步: 通过 top命令查找到这个消耗CPU的进程号PID 8958 top 第二步:使用 top -Hp pid( shift+p 按cpu排序,shift+m 按内存排序 ) top -Hp 8958 获取到这个进程下面所有线程,通过查看%CPU找到最耗费CPU的是线程PID 第三步:使用 printf '%x\n' PID (PID为上一步中获取到的线程号)转换成对应的16进制PID 5c7e 第四步:使用jstack 获取对应的线程信息 jstack 8958 | grep 5c7e 注意:8958是一开始获取的进程号,而5c7e则是这个进程下面最最耗费CPU的线程号 .... JVM找出占用CPU最高的线程 jvm
1. 问题 spring-cloud-gateway 网关新增了一个限流功能,使用的是模块自带的限流过滤器 RequestRateLimiterGatewayFilterFactory,基于令牌桶算法,通过 redis 实现。 其原理是 redis 中针对每个限流要素(比如针对接口限流),保存 2 个 key:tokenKey(令牌数量),timeKey(调用时间)。每次接口调用时,更新 tokenKey 的值为:原先的值 + (当前时间 - 原先时间)* 加入令牌的速度,如果新的 tokenKey 的值大于 1,那么允许调用,否则不允许;同时更新 redis 中 tokenKey,timeKey 的值。整个过程通过 lua 脚本实现。 在加入限流功能之前,500 客户端并发访问,tps 为 6800 req/s,50% 时延为 70ms;加入限流功能之后,tps 为 2300 req/s,50% 时延为 205ms,同时,原先 cpu 占用率几乎 600%(6 核) 变成不到 400%(cpu 跑不满了)。 2. 排查和解决过程 2.1 单个 CPU 跑满 查看单个线程的 cpu .... java性能调优记录 java
在java程序运行时,如果替换classpath下的某个jar包文件,可能会导致程序出现ClassNotFoundException**。 具体场景 我们要升级线上服务时,可能经常只需要替换其中一两个jar包即可完成升级。有时我们为了方便,经常会先替换完jar包再进行重启。其实这样的做法会有一个隐患,如果在你重启之前程序需要从这个jar包加载某个类的话,即使这个类在这个jar包中,也会导致程序出现ClassNotFoundException。 我们可以写个demo来验证一下这个问题 : public class A { public static void main(String[] args) throws InterruptedException { System.out.println("begin"); //在sleep期间jar包发生了改变 Thread.sleep(10000L); B b = new B(); b.print(); } } public class B { void print() { System.out.println("print B"); } }.... 有更新! 不要替换运行中JVM的相关jar包 jvm