0%

FastDFS 踩坑记录

环境

fastdfs 3台服务器集群

问题

  1. nginx + fastdfs 下载文件偶现502异常

问题原因:
Storage同步延迟的结果,大概是这样的Tracker将文件放在StorageA上,但是访问的时候,Tracker发现StorageB也是可用的,就返回了StorageB的IP与端口。但是此时StorageB还没有此文件,所以一直等待,直到StorageB同步StorageA的数据。

  1. 下载fastdfs中文件上传阿里云oss

问题原因:
从fastdfs下载的文件流大小不全。可能原因由于 Storeage 同步未完成。

前言

CAS(Campare and Swap),即比较并替换,实现并发算法时常用的一种技术,Douglea大神在java同步器中大量使用CAS技术,鬼斧神工的实现了多线程执行的安全性。

CAS的思想恨简单: 三个参数,一个当前内存值V、旧的预期值A、即将更新的值B,当且仅当预期值A和内存值V相同时,将内存值修改为B并返回true,否则什么都不做,并返回false。

序列化

我们的Java对象并不只是存在内存中,优势还需要在网络中传输,或保存到硬盘中再下次需要时再加载出来,所有我们需要使用到Java序列化技术。

Java 序列化正是讲对象变成一串由二进制字符组成的数组,可以通过将二进制数据保存到磁盘或者传输网络,磁盘或者网络接收者可以在对象的属类的模版上来反序列化类的对象,以达到对象持久化的目的。

Java 对象序列化方式

在Java中有两种序列化的方式,Serializable 和 Externalizable,可能大部分人值知道Serializable而不知道Externalizable.

这两种序列化方式的区别是:实现了Serializable接口是自动序列化的,实现Externalizable则需要手动序列化,通过 writeExternal 和readExternal 方式手动进行。

transient 关键字总结

  1. transient修饰的变量不能被序列化。
  2. transient只作用域实现Serializable接口
  3. transient只能用来修饰普通成员变量字段
  4. 不管有没有transient修饰,静态变量都不能序列化

序列化

我们的Java对象并不只是存在内存中,优势还需要在网络中传输,或保存到硬盘中再下次需要时再加载出来,所有我们需要使用到Java序列化技术。

Java 序列化正是讲对象变成一串由二进制字符组成的数组,可以通过将二进制数据保存到磁盘或者传输网络,磁盘或者网络接收者可以在对象的属类的模版上来反序列化类的对象,以达到对象持久化的目的。

Java 对象序列化方式

在Java中有两种序列化的方式,Serializable 和 Externalizable,可能大部分人值知道Serializable而不知道Externalizable.

这两种序列化方式的区别是:实现了Serializable接口是自动序列化的,实现Externalizable则需要手动序列化,通过 writeExternal 和readExternal 方式手动进行。

transient 关键字总结

  1. transient修饰的变量不能被序列化。
  2. transient只作用域实现Serializable接口
  3. transient只能用来修饰普通成员变量字段
  4. 不管有没有transient修饰,静态变量都不能序列化

内存泄漏

问题现象

JVM 内存耗尽,后台日志抛出OutOfMemeryError异常。

问题原因

内存溢出问题可能的原因比较多,可能是全局的List、Map等对象不断被扩大,也可能是程序不慎将大量数据读到内存里;可能是循环操作导致,也可能后台线程触发加载数据导致。

解决方法

对于纯Java应用,在jvm启动时设置 -XX:+HeapDumpOnOutOfMemoryError参数,会在内存溢出时生成heapdump文件。使用 ha456.jar 工具打开headdump文件,分析大对象是如何产生的。

当然,在heapdump中对象类型可能只是List这种结构,看不出具体哪个业务代码创建的对象。此时要分析所有的全局对象,列出可疑的List或Map对象,排查其溢出原因。

全局对象、引用的初始化,修改要慎重。建议应用梳理所有可能存放全局对象的代码统一管控。

JVM 垃圾回收频繁

问题现象

top -H -p pid 命令查看,GC Slave 线程CPU占用排名始终为前三名,同时Jconsole查看jvm内存占用较高,垃圾回收频繁。使用ga456.jar 分析gc日志,查看gc频率、时长。

打印gc日志的方法:在jvm启动参数里增加 -verbose:gc -Xverbosegclog:/filepath/gcdetail.log

问题原因

高并发下内存对象较多,jvm堆内存不够用

解决方法

扩大堆内存大小 -Xmx2048m -Xms2048

CPU 高

问题现象

50并发压测,监控工具显示bp、前置CPU占用90%以上。

问题原因

业务处理中存在大量CPU计算操作。

解决方法

采用更高效的算法、数据结构替换原来消耗CPU的代码、或者采用新的设计绕过瓶颈代码,比如查找数据的逻辑,可以把List改为Map,以空间换时间;比如Json报文替换XML报文,提高传输、解析和打印日志的效率。

导致Cpu计算资源高消耗的代码

报文格式转换、加解密、正则表达式、低效的循环、低效的正则表达式。

排除方法

  1. 压测进行时,使用jvisualvm工具远程连接应用,点抽样器CPU,点快照生成线程快照。采样一段时间后,抽样器会显示各个方法占用cpu时间,可以针对cpu时间占用高的方法进行优化。
  2. 使用tprofiler,jprofiler,OracleDeveloperStudio 12.6-liunx-86工具分别分析消耗cup时间长的方法,以上工具分析结果可能有些差别。针对cpu计算耗时最长的方法进行优化。

批处理时间长、数据库逐笔插入缓慢

问题现象

大批量数据(10万条以上)更新或插入数据库、耗时较长。

问题原因

批量数据处理时,如果逐步更新数据库库,则会存在大量网络IO、磁盘io,耗时较长,而且对数据库资源消耗较大。

解决方法

  1. 采用java提供的batchUpdate方法批量更新数据库,每1000条commit一次,可大幅度提高数据更新效率。
  2. 单线程改成线程池,并行处理,充分利用多核CPU,通过数据库或者其他同步锁控制并行性;增加缓冲池,降低数据库或磁盘IO访问频率。

数据库CPU高

问题现象

后台指令发送满负荷工作时,数据库CPU高

问题原因

后台指令发送线程每次对全量查询结果排序,结果集很大,然后取一条记录;索引区分度不高,满负荷执行时;查询频率很高;压测显示,并行发送指令的后台线程越多,数据库CPU越高,效率越低。

解决方法

  1. 去掉Order by,增加索引后,效果不明显。因为结果集大和查询频繁两个问题没有解决,因此网络使用设计新的方案。
  2. 新方案:设计指令发送线程池,生产者线程后台任务服务器只有一个线程,负责查询待发送指令,每次查询50条指令。每条指令包装成一个Runable对象,放进ThreadPoolExecutor线程池,线程池大小参数设置为100或200.每当线程池满时,生产者停止生产指令,休息15秒后继续。消费者线程即线程池里的线程,参数设置为4,8或12(和不同指令类型的指令数据量成正比)。

改进后的方案,数据库CPU降到10%一下,发送效率单机提升6倍,且可线性扩展任务服务器。

压测TPS曲线剧烈下降或抖动

问题现象

50并发压测,TPS曲线正常应该是平缓的,波动不大,如果突然出现剧烈下降,并且短时间内无法恢复,则可能存在问题。

问题原因

一般是由于前置或者bp的jvm进行垃圾回收,或者日志记录磁盘满导致的,

解决方法

如果不是特别剧烈的波动或者TPS曲线下降后长时间不反弹,则可以忽略该问题。否则,需要分析曲线下降的时刻,系统当时正在发生的事情。可以通过top命令监控当时CPU占用比较高的线程,也可以kill -3 pid 杀javacode来查看线程堆栈。

Redis 集群搭建

  1. Redis 关闭

    1
    redis-cli -h ip -p port -a pwd shutdown
  2. 取消所有Redis的密码设置

  3. 配置 dir ,若不配置dir,Redis启动时会根据当前执行目录生成相关数据和配置信息,不方便管理

  4. 启动所有的Redis实例

    1
    redis-server /midware/redis/cluster/7000/redis.conf  &
  5. 查询Redis集群状态

    1
    2
    3
    CLUSTER INFO

    CLUSTER NODE
  6. 构建集群

    1
    ./redis/bin/redis-trib.rb  create --replicas 1 10.10.118.37:7000 10.10.118.37:7001 10.10.118.38:7000 10.10.118.38:7001 10.10.118.39:7000 10.10.118.39:7001
  7. 再次查询Redis集群状态

  8. 关闭所有Redis,重新设置密码,再启动

Liunx 学习笔记

Liunx 目录介绍

根目录
/bin : 包含二进制文件,即可执行程序,这些程序是系统必需的文件
/sbin : 也用于存储二进制文件,只有超级用户root才可以使用
/etc : 存放配置文件,如passwd,inittab等
/boot : 系统引导时使用的文件,系统中非常重要的内核 vmlinux 就放在该目录下面
/dev : 存放设备文件,用户可以通过这些文件访问外部设备
/lib : 存放程序运行时所需要的库文件
/temp : 存放各种临时文件
/mnt : 安装软盘,光盘,U盘的挂载点
/root : 超级用户的个人主目录
/usr : 该目录的空间比较大,用于安装各种应用程序
/proc : 是一个虚拟目录,存放当前内存的印象,有内核自动产生
/var : 存放一些会随时改变的文件

常用命令

  1. 挂载
  • Mount /dev/fd0 /mnt/floppy
  1. 卸载挂载
  • Umount /mnt/floppy
  1. 启动加载
    1
    /etc/fstab

Spring Boot

Application事件和监听器

除了常见的Spring 框架事件,比如ContextRefreshedEvent,SpringApplication 也会发送其他的application事件。

有些事件实际是在ApplicationContext 创建前触发的,所以你不能在这些事件(处理类)中通过@Bean注册监听器,只能通过SpringApplication.addListeners(...) 或者 SpringApplicationBuilder.listeners(...) 方法注册。 如果想让监听器自动注册,而不关心应用的创建方法,你可以在工程中添加一个META-INF/spring.factories 文件,并使用org.springframework.context.ApplicationListener 作为key 指向那些监听器,如下:

1
org.springframework.context.ApplicationListener = com.example.project.MyListener

应用运行时,事件会以下面的次序发送:

  1. 在运行开始,但除了监听器注册或初始化以外的任务处理之前,会发送一个ApplicationStartedEvent.
  2. 在Environment将被用于已知的上下文,但在上下文被创建前,会发送一个ApplicationEnvironmentPreparedEvent
  3. 在refresh开始前,但在bean定义已被加载后,会发送一个ApplicationPreparedEvent
  4. 在refresh之后,相关的回调处理完成,会发送一个ApplicationReadyEvent表示应用准备好接受请求了。
  5. 启动过程如果出现异常,会发送一个ApplicationFailedEvent

spring.factories(学习整理一下)

官方start格式:spring-boot-start-xxx
个人start格式:xxx-spring-boot-start

编写自定义的spring-boot-start 需要借助与 spring.factories,具体的 start 编写方式下方有说明。

java:comp (学习整理一下)

@ConfigurationProperties

Relaxed绑定

Spring Boot 将Environment 属性绑定到@ConfigurationProperties beans 时会使用一些宽松的规格,所以Environment属性名和bean属性名不需要精确匹配。常见的示例中有用的包括虚线分割(比如:context-type 绑定到 contextPath),将environment属性转为大写字母(比如, PORT 绑定到 port)

1
2
3
4
5
6
7
8
9
10
@ConfigurationProperties(prefix="person")
public class OwnerProperties {
private String firstName;
public String getFirstName() {
return this.firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
}

下面的属性名都能使用:

属性 说明
person.firstName 标准驼峰规则
person.first-name 虚线表示,推荐用于 .properties 和 .yml文件中
person.first_name 下划线表示,用于 .properties 和 .yml 文件的可选格式
PERSON_FIRST_NAME 大写形式,使用系统环境变量时推荐

@ConfigurationProperties VS @Value

@Value 是Spring 容器的一个核心特性,它没有提供跟 type-safe Configuration Properties 相同的特性。下面的表格总结了@ConfigurationProperties@Value 支持的特性:

特性 @Configuration @Value
Relaxd绑定 Yes NO
Meta-data支持 Yes NO
SpEL 表达式 NO Yes

如果为自己的组件定义了一些列的keys,那么建议将他们以 @ConfigurationProperties 注解的POJO进行分组。由于 @Value 不支持relaxed绑定,所有如果你使用环境变量属性提供值得话,它就不是很好的选中。最后,尽管 @Value 可以SpEL表达式,但这些表达式不会出来来自Application的属性。

自动配置 auto-configuration

从底层讲,自动配置(auto-configurtaion) 是通过标准的@Configuration类实现的。此外,@Conditional 注解用来约束自动配置生效的条件。通常自动配置类需要使用@conditionOnClass@ConditionOnMissingBean 注解,这是为了确保只有在相关的类被发现及没有声明自定义的 @Configuration 时才应用自动配置,具体查看 spring-boot-autofigure源码中的 @Configuration类(META-INF/spring.factories 文件)

Spring Boot 会自动检查你发布的jar是否存在 META-INF/spring.factories文件,该文件中以EnableAutoCOnfiguration 为空key的属性应该列出你的配置类

1
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\com.mycirp.libx.autoconfigure.LibXAutoConfiguration,\com.mycorp.libx.autoconfiguration.LibXWebAutoConfiguration

可以使用 @AutoConfigureAfter@AutoConfigureBefore注解为配置类指定特定的顺序。例如,你提供了web-specific 配置,你的类就需要应用在WebMvcAutoConfiguration后面。
也可以使用 @AutoConfigureOrder 注解为那些相互不知道存在的自动配置类提供排序,该注解语义跟常规的 @Order 注解相同,但专为自动配置提供顺序。

自动配置类在只能通过这种方式加载,确保它们定义在一个特殊的package中,特别不能成为组件扫描的目标。

条件注解

@ConditionOnMissingBean 注解是一个常见的示例,开发者可以用它覆盖自动配置类提供的默认行为。
Spring Boot 包含很多@Conditonal注解,你可以在自己的代码中通过注解@Configuration类或单独的@Bean 方法来重用它们。

Class 条件

@ConditionalOnClass@ConditionalOnMissingClass 注解可以根据特定类是否出现来决定配置的包含,由于注解元数据是使用 ASM 来解析的,所以你可以使用 value 属性来引用真正的类,即使该类没有出现在运行的应用的classpath下,也可以使用name属性如果你倾向于使用字符串作为类型。

Bean 条件

@ConditionalOnBean@ConditionalOnMissingBean 注解可以根据特定类是否存在决定bean的包含,你可以使用value属性指定beans(by type) ,也可以使用 name 定义beans (by name), search 属性用于限制搜索beans是需要考虑ApplicationContext层次。

需要注意bean自定义添加的顺序,因为这些条件的计算是基于目前处理内容的。出于这个原因,我们推荐在自动配置类中只使用 @ConditionOnBean@ConditionOnMissingBean 注解(即使保证他们在其他用户定义的beans后面加载)

@ConditionalOnBean@ConditionalOnMissingBean 不会阻止 @Configuration 类的创建,在类级别使用那些conditions跟使用注解标记每个@Bean方法是等价的。

Property 条件

@ConditionalOnProperty注解可以根据一个Spring Environment 属性来决定是否包含配置,使用 prefixname 属性指定要检查的配置。默认情况下,任何存在的主要不是 false 的属性都会匹配,你也可以使用havingValuematchIfMissing属性创建更高级的检测。

matchIfMissing 当配置文件不存在 prefix.name 值是默认为true还是false,true任务匹配成功
havingValue 是一个字符串 与 文件中配置的prefix.name 进行equal 比较

1
@ConditionalOnProperty(prefix = "example.service", value = "enabled", matchIfMissing = true),当配置文件中example.service.enabled=true时进行自动配置,如果没有设置此值就默认使用matchIfMissing对应的值

Resource条件

@ConditionalOnResource 注解只在特定资源出现时才会包含配置,可以使用常见的Spring约定命名资源,例如file://home/user/test.dat

Web Application条件

@ConditionalOnWebApplication@ConditonalOnNotWebApplication 注解可以根据应用是否为’web应用’来决定是否包含配置,web应用是任何使用Spring WebApplicationContext,定义一个 session 作用域,或有一个 StandardServeltEnvironment的应用。

SpEL表达式条件

@ConditionalOnExpression 注解可以根据SpEL表达式结果来决定是否包含配置。