Recently, I received a request to convert text into voice MP3 files, store them in fastdfs, and then store the file path into the database.
Our fastdfs is version 5.05, and the fastdfs tool class has been changed to support concurrency
public class FastClient<main> { private static Logger logger = Logger.getLogger(FastClient.class); /** * Load only once */ static { try { ClientGlobal.init("fdfs_client.properties"); TrackerClient trackerClient = new TrackerClient(ClientGlobal.getGtrackerGroup()); TrackerServer trackerServer = trackerClient.getConnection(); if (trackerServer == null) { logger.error("getConnection return null"); } StorageServer storageServer = trackerClient.getStoreStorage(trackerServer); if (storageServer == null) { logger.error("getStoreStorage return null"); } // There are pits here. For details, please refer to the article: http://www.ityouknow.com//fastdfs/2017/12/26/fastdfs-concurrent.html // Root cause: storageServer may be left blank when used with high concurrency, and storageClient1 will be reported to NPE when used // Solution: give up the shared storageClient1 mode, and create a new one each time // So note it out // storageClient1 = new StorageClientExtend(trackerServer, storageServer); } catch (Exception e) { logger.error(e); } } /** * description Get the StorageClient instance and use it concurrently * param * return * author * createTime 2019/4/27 13:12 **/ private static StorageClientExtend getStorageClientExtend() throws IOException { TrackerClient trackerClient = new TrackerClient(ClientGlobal.getGtrackerGroup()); TrackerServer trackerServer = trackerClient.getConnection(); if (trackerServer == null) { logger.error("getConnection return null"); } logger.info( "obtain socket connect===>"+trackerServer.getSocket() ); //Send a message to dfs ProtoCommon.activeTest(trackerServer.getSocket()); StorageServer storageServer = trackerClient.getStoreStorage(trackerServer); return new StorageClientExtend(trackerServer, storageServer); } /** * * @param fis * file * @param fileName * file name * @return If Null is returned, it is a failure */ public static synchronized String uploadFile(InputStream fis, String fileName) { try { NameValuePair[] metaList = null; byte[] fileBuff = null; if (fis != null) { int len = fis.available(); fileBuff = new byte[len]; int i = fis.read(fileBuff); } StorageClientExtend storageClientExtend = getStorageClientExtend(); String uploadFilepath = storageClientExtend.uploadFile1(fileBuff, getFileExt(fileName), metaList); logger.info("uploadFile File name uploadFilepath : " + uploadFilepath); return uploadFilepath; } catch (Exception ex) { logger.warn("uploadFile Upload file exception Exception:{}",ex); logger.error(ex); return null; }finally{ if (fis != null){ try { fis.close(); } catch (IOException e) { logger.warn("uploadFile IOException Upload file exception , Exception:{}",e); logger.error(e); } } } }
Post the business code and let's have a look
public void generateVoice(MessageNoticeDTO messageNoticeDTO, Integer type)throws NoSuchAlgorithmException, InvalidKeyException, MalformedURLException { Long messageNoticeId = messageNoticeDTO.getId(); String content = messageNoticeDTO.getContent(); Long messageNoticeCreateTime = messageNoticeDTO.getCreateTime(); OkHttpClient client = new OkHttpClient.Builder() //Timeout .connectTimeout(60, TimeUnit.SECONDS) //Read time .readTimeout(60, TimeUnit.SECONDS) .build(); //IFLYTEK builds authentication Request request = authentication(); // Open webSocket client.newWebSocket(request, new WebSocketListener() { //Call the interface of HKUST @Override public void onOpen(WebSocket webSocket, Response response) { super.onOpen(webSocket, response); //send data JSONObject frame = organizationRequestData(content); webSocket.send(frame.toString()); } //Get voice stream @SneakyThrows @Override public void onMessage(WebSocket webSocket, String text) { super.onMessage(webSocket, text); JSONObject object = JSONObject.parseObject(text); //Process return data ResponseData resp = JSONObject.toJavaObject(object, ResponseData.class); if (null == resp || resp.getCode() != 0 || resp.getData() == null) { log.error("Get audio data exception, send message to retry queue, express id: {},Response:{}", messageNoticeId, resp); throw new NullPointerException("Get audio data exception"); } //resp.data.status ==2 indicates that all data has been returned. You can close the connection and release resources if (resp.getData().getStatus() == 2) { //Upload to fastfds String uploadFilepath = uploadFile(resp); if (StringUtils.isBlank(uploadFilepath)) { log.error("Abnormal upload to file server, send message to retry queue, express id: {}", messageNoticeId); throw new NullPointerException("Upload to file server to get audio data abnormally url abnormal"); } log.info("Express voice data url Generation, id: {},voiceUrl:{}", messageNoticeId, uploadFilepath); //Update newsletter data information modifyMessageNotice(messageNoticeDTO, uploadFilepath); log.info("Express voice data update completed, id: {},voiceUrl:{}", messageNoticeId, uploadFilepath); webSocket.close(1000, "Get audio complete"); } } //websocker exception callback @Override public void onFailure(WebSocket webSocket, Throwable t, Response response) { super.onFailure(webSocket, t, response); webSocket.close(1000, "Get audio complete"); //Exception handling log.error("University of science and technology anomaly,news flash id Input:{},Throwable: {},Response: {}", messageNoticeId, t, response); voiceMessageNoticeAo.handleInfoMessageVoiceFail(messageNoticeId, messageNoticeCreateTime, type); } }); } /** * @param resp: * @Description: * @Author: * @Date: 2021/3/25 11:11 * @return: java.lang.String **/ private String uploadFile(ResponseData resp) { String result = resp.getData().getAudio(); byte[] audio = Base64.getDecoder().decode(result); //Upload to fastfds return FastClient.uploadFile(new ByteArrayInputStream(audio), ".mp3"); } /** * @param messageNoticeDTO: Express dto object * @Description: Update SMS table file path * @Author: * @Date: 2021/3/12 11:12 * @return: void **/ private void modifyMessageNotice(MessageNoticeDTO messageNoticeDTO, String uploadFile) throws MessageNoticeException { messageNoticeDTO.setVoiceUrl(FAST_DFS_DOMAIN + uploadFile); messageNoticeService.modifyMessageNotice(messageNoticeDTO); }
This is to obtain iFLYTEK and then transcode and upload the fastdfs code. At the beginning of the test, it is normal to develop to about 200500. However, in the online environment, when the peak reaches hundreds per second, I can't stand it. There are all kinds of exceptions when uploading
Various upload exceptions or sockertexception will be thrown, but these exceptions are accidental. For example, if you upload 1000 times, you may fail more than a dozen times or a hundred times.
At first, I thought it was a problem with the configured communication time. I modified the parameters and made them a little bigger. I found that I was in charge of some things, but I couldn't fundamentally solve the problem.
#Connection timeout, for socket socket function connect connect_timeout = 300 #Network communication timeout, the default is 60 seconds network_timeout = 600
I checked it on the Internet. They all said that the connection between the client and the server was disconnected and needed to be retried. The server can actively send messages to the client to maintain the connection
ProtoCommon.activeTest(trackerServer.getSocket());
After the test is found, it doesn't matter to add this. At this time, there's nothing to do. Let's have a look
In protocommon activeTest(trackerServer.getSocket()); Method, an empty packet will be sent to the server to keep the server disconnected, but the request throws an exception.
public static RecvHeaderInfo recvHeader(InputStream in, byte expect_cmd, long expect_body_len) throws IOException { byte[] header; int bytes; long pkg_len; header = new byte[FDFS_PROTO_PKG_LEN_SIZE + 2]; if ((bytes=in.read(header)) != header.length) { throw new IOException("recv package size " + bytes + " != " + header.length); } if (header[PROTO_HEADER_CMD_INDEX] != expect_cmd) { throw new IOException("recv cmd: " + header[PROTO_HEADER_CMD_INDEX] + " is not correct, expect cmd: " + expect_cmd); } if (header[PROTO_HEADER_STATUS_INDEX] != 0) { return new RecvHeaderInfo(header[PROTO_HEADER_STATUS_INDEX], 0); } pkg_len = ProtoCommon.buff2long(header, 0); if (pkg_len < 0) { throw new IOException("recv body length: " + pkg_len + " < 0!"); } if (expect_body_len >= 0 && pkg_len != expect_body_len) { throw new IOException("recv body length: " + pkg_len + " is not correct, expect length: " + expect_body_len); } return new RecvHeaderInfo((byte)0, pkg_len); }
This method will also be called in the uploaded code, and it will hang up as soon as the package is uploaded
Go to the Internet to see git related to fastdfs, and there is no solution. Let's go to the O & M to check the fastdfs log. As a result, the O & M said that there was no log and had to let him restart. The next day, he restarted and reported an exception. At this time, I asked the O & M to help look at the relevant parameters of fastdfs. Suddenly, I saw the maximum number of connections 256. I think I may have found the root cause of the problem.
Let the O & M change the maximum number of connections to 1024, and then test the upload. It is normal to upload 2000 at a time.
Looking back at the code, it should be to call iFLYTEK interface, the asynchronous method receives the response, and then directly process the upload, which is equivalent to how many connections will be opened as many requests come, and the maximum number of connections will soon be filled, resulting in the failure to obtain the connection of the subsequent re acquisition server. Only after the previous upload is completed, can the connection of the server be obtained, However, the asynchronous processing method is not blocked when it cannot be obtained. It directly uploads new, and then the server does not have a connection thread to handle this, and an empty path or connection exception will be returned.