项目在生产环境部署,出现大批量文件并发上传,运行环境偶发性报错:
java.io.IOException: recv cmd: 0 is not correct, expect cmd: 100
at org.csource.fastdfs.ProtoCommon.recvHeader(ProtoCommon.java:173)
at org.csource.fastdfs.ProtoCommon.recvPackage(ProtoCommon.java:201)
at org.csource.fastdfs.StorageClient.do_upload_file(StorageClient.java:714)
at org.csource.fastdfs.StorageClient.upload_file(StorageClient.java:162)
at org.csource.fastdfs.StorageClient.upload_file(StorageClient.java:180)
at org.csource.fastdfs.StorageClient1.upload_file1(StorageClient1.java:103)
at com.innoking.coding.utils.FastDfsClient.uploadFile(FastDfsClient.java:102)
at com.innoking.coding.utils.FastDfsClient.uploadFile(FastDfsClient.java:111)
at com.innoking.coding.FileHelper.saveAndUploadImageToFds(FileHelper.java:111)
at com.innoking.coding.uniview.subscriptioncontroller.RedisMsgSubscriber1.downPhoto(RedisMsgSubscriber1.java:315)
at com.innoking.coding.uniview.subscriptioncontroller.RedisMsgSubscriber1.onMessage(RedisMsgSubscriber1.java:186)
at org.springframework.data.redis.connection.lettuce.LettuceMessageListener.message(LettuceMessageListener.java:43)
at org.springframework.data.redis.connection.lettuce.LettuceMessageListener.message(LettuceMessageListener.java:29)
at io.lettuce.core.pubsub.PubSubEndpoint.notifyListeners(PubSubEndpoint.java:219)
at io.lettuce.core.pubsub.PubSubEndpoint.notifyMessage(PubSubEndpoint.java:208)
at io.lettuce.core.pubsub.PubSubCommandHandler.doNotifyMessage(PubSubCommandHandler.java:292)
at io.lettuce.core.pubsub.PubSubCommandHandler.decode(PubSubCommandHandler.java:130)
at io.lettuce.core.protocol.CommandHandler.channelRead(CommandHandler.java:594)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919)
at io.netty.channel.epoll.AbstractEpollStreamChannel$EpollStreamUnsafe.epollInReady(AbstractEpollStreamChannel.java:795)
at io.netty.channel.epoll.EpollEventLoop.processReady(EpollEventLoop.java:480)
at io.netty.channel.epoll.EpollEventLoop.run(EpollEventLoop.java:378)
at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:986)
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.lang.Thread.run(Thread.java:748)
环境如下:服务器环境fastDFS做了主从备份
- 操作系统:centos7
- FastDFS :5.05
- libfastcommon:1.0.36
- nginx :1.7.9
- fastdfs-nginx-module:1.16
fastDFS的tracker服务器日志如下类似:
[2017-09-19 09:13:52] ERROR - file: tracker_nio.c, line: 306, client ip: 192.168.0.1, pkg length: 15150 > max pkg size: 8192
[2017-09-19 10:34:57] ERROR - file: tracker_nio.c, line: 306, client ip: 192.168.0.1, pkg length: 16843 > max pkg size: 8192
[2017-09-19 10:34:57] ERROR - file: tracker_nio.c, line: 306, client ip: 192.168.0.1, pkg length: 16843 > max pkg size: 8192
[2017-09-19 11:31:08] ERROR - file: tracker_nio.c, line: 306, client ip: 192.168.03, pkg length: 23955 > max pkg size: 8192
[2017-09-19 11:42:56] ERROR - file: tracker_nio.c, line: 306, client ip: 192.168.01, pkg length: 12284 > max pkg size: 8192
[2017-09-19 12:10:28] ERROR - file: tracker_service.c, line: 2452, cmd=103, client ip: 192.168.0.3, package size 6258 is too long, exceeds 144
storaged的日志如下类似:
[2017-09-25 14:22:38] WARNING - file: storage_service.c, line: 7135, client ip: 192.168.1.11, logic file: M00/D1/04/wKg5ZlnIoKWAAkNRAAAY86__WXA920.jpg-m not exist
[2017-09-25 14:22:39] WARNING - file: storage_service.c, line: 7135, client ip: 192.168.1.11, logic file: M00/D1/04/wKg5ZlnIoKuAUXeVAAAeASIvHGw673.jpg not exist
[2017-09-25 14:22:50] ERROR - file: storage_nio.c, line: 475, client ip: 192.168.1.13, recv failed, errno: 104, error info: Connection reset by peer
[2017-09-25 14:22:56] ERROR - file: tracker_proto.c, line: 48, server: 192.168.1.11:23001, response status 2 != 0
[2017-09-25 14:23:06] ERROR - file: tracker_proto.c, line: 48, server: 192.168.1.11:23001, response status 2 != 0
[2017-09-25 14:23:11] ERROR - file: storage_service.c, line: 3287, client ip:192.168.1.13, group_name: group2 not correct, should be: group1
我们项目中fastdfs在application.properties中配置:
clientConf=ip:22122
项目中获取fastDFS上传方法如下:
FastDfsClient fastDFSClient = FastDfsClient.getInstance(clientConf);
//获取到返回的fastdfs附件地址(不含服务器ip地址的附件地址)
String fastdfs_url = fastDfsClient.uploadFile(uploadFile.getBytes(), fileSuffix);
项目中引用fastdfs工具类如下:
package com.innoking.coding.utils;
import org.csource.common.MyException;
import org.csource.common.NameValuePair;
import org.csource.fastdfs.*;
import java.io.IOException;
/**
*
* @ClassName: FastDfsClient
* @Description: TODO
* @author
* @version
*
*/
public class FastDfsClient {
private TrackerClient trackerClient = null;
private TrackerServer trackerServer = null;
private StorageServer storageServer = null;
private StorageClient1 storageClient = null;
private static FastDfsClient fastDfsClient=null;
public static FastDfsClient getInstance(String conf) throws Exception {
if( fastDfsClient==null){
fastDfsClient=new FastDfsClient(conf);
}
return fastDfsClient;
}
public FastDfsClient(String conf) throws Exception {
if (conf.contains("classpath:")) {
conf = conf.replace("classpath:", this.getClass().getResource("/").getPath());
}
ClientGlobal.initByTrackers(conf);
ClientGlobal.init(conf);
trackerClient = new TrackerClient();
trackerServer = trackerClient.getConnection();
storageServer = null;
storageClient = new StorageClient1(trackerServer, storageServer);
}
/**
* 上传文件方法
* <p>Title: uploadFile</p>
* <p>Description: </p>
* @param fileName 文件全路径
* @param extName 文件扩展名,不包含(.)
* @param metas 文件扩展信息
* @return
* @throws Exception
*/
public String uploadFile(String fileName, String extName, NameValuePair[] metas) throws Exception {
String result = storageClient.upload_file1(fileName, extName, metas);
return result;
}
public String uploadFile(String fileName) throws Exception {
return uploadFile(fileName, null, null);
}
public String uploadFile(String fileName, String extName) throws Exception {
return uploadFile(fileName, extName, null);
}
/**
* 上传文件方法
* <p>Title: uploadFile</p>
* <p>Description: </p>
* @param fileContent 文件的内容,字节数组
* @param extName 文件扩展名
* @param metas 文件扩展信息
* @return
* @throws Exception
*/
public String uploadFile(byte[] fileContent, String extName, NameValuePair[] metas) throws Exception {
String result = storageClient.upload_file1(fileContent, extName, metas);
return result;
}
public String uploadFile(byte[] fileContent) throws Exception {
return uploadFile(fileContent, null, null);
}
public String uploadFile(byte[] fileContent, String extName) throws Exception {
return uploadFile(fileContent, extName, null);
}
/**
* 删除文件
* @param fileName 文件服务器保存的地址
* @return
* @throws IOException
* @throws MyException
*/
public int deleteFile(String fileName) {
try {
return storageClient.delete_file1(fileName);
} catch (IOException e) {
e.printStackTrace();
} catch (MyException e) {
e.printStackTrace();
}
return 1;
}
/**
* 下载文件
* @param fileName 文件服务器中的文件名
* @param local_filename 下载到本地的文件名
* @return
* @throws IOException
* @throws MyException
*/
public int downloadFile(String fileName,String local_filename) throws IOException, MyException{
return storageClient.download_file1(fileName, local_filename);
}
/**
* 下载文件
* @param fileName 文件的路径 如group1/M00/00/00/wKgRsVjtwpSAXGwkAAAweEAzRjw471.jpg
* @return
* @throws IOException
* @throws MyException
*/
public byte[] getBytedownloadFile(String fileName) throws IOException, MyException{
return storageClient.download_file1(fileName);
}
}
开发环境因并发量并不大未出现问题,上线至生产环境后上传文件速度过快造成了并发量增大。
问题原因:在每次上传时都获取了storageClient,如上传时定义了:
String fastdfs_url = fastDfsClient.uploadFile(uploadFile.getBytes(), fileSuffix);
但是大批量上传时,可以理解为第一次上传时storageClient创建了storageServer,但是第二次上传时,发现已经有storageServer就直接使用了。因此造成第一次上传结束后storageServer关闭,第二次上传借用的storageServer丢失。
排查问题中参考该文章得到解决:一次FastDFS并发问题的排查经历_luckykapok918的博客-CSDN博客
特别感谢该文章博主。
具体修改如下:
对FastDfsClient工具类进行修改,将上传方法uploadFile变更为如下:
public class FastDfsClient {
private TrackerClient trackerClient = null;
// private TrackerServer trackerServer = null;
private StorageServer storageServer = null;
// private StorageClient1 storageClient = null;
private static FastDfsClient fastDfsClient=null;
public static FastDfsClient getInstance(String conf) throws Exception {
if( fastDfsClient==null){
fastDfsClient=new FastDfsClient(conf);
}
return fastDfsClient;
}
public FastDfsClient(String conf) throws Exception {
if (conf.contains("classpath:")) {
conf = conf.replace("classpath:", this.getClass().getResource("/").getPath());
}
ClientGlobal.initByTrackers(conf);
ClientGlobal.init(conf);
trackerClient = new TrackerClient();
// trackerServer = trackerClient.getConnection();
storageServer = null;
// storageClient = new StorageClient1(trackerServer, storageServer);
}
/**
* 上传文件方法
* <p>Title: uploadFile</p>
* <p>Description: </p>
* @param fileName 文件全路径
* @param extName 文件扩展名,不包含(.)
* @param metas 文件扩展信息
* @return
* @throws Exception
*/
public String uploadFile(String fileName, String extName, NameValuePair[] metas) throws Exception {
TrackerServer trackerServer = trackerClient.getConnection();
StorageClient1 storageClient = new StorageClient1(trackerServer, storageServer);
String result = storageClient.upload_file1(fileName, extName, metas);
return result;
}
}
在每次上传时都new新的storageClient和storageServer。
进行一天测试后问题未复现,得到解决