目录
Seafile 是一款开源的企业云盘,注重可靠性和性能,支持全平台客户端。Seafile 内置协同文档 SeaDoc ,让协作撰写、管理和发布文档更便捷。它支持私有化部署,能够满足诸多文件协同的应用场景。
官网:https://www.seafile.com/home/
国内:https://bbs.seafile.com/
国外:http://forum.seafile.com/
seafile可靠性保证
事件驱动
通过分析seafile的日志发现它是通过事件驱动的。可以通过目录和日志去查看:
seafile.log
[08/05/24 09:36:32] Repo 'mesDir' sync state transition from 'committing' to 'synchronized'.
[08/05/24 09:37:03] Repo 'mesDir' sync state transition from 'initializing' to 'downloading'.
[08/05/24 09:37:03] Transfer repo '9319d653': ('normal', 'init') --> ('normal', 'check')
[08/05/24 09:37:03] Transfer repo '9319d653': ('normal', 'check') --> ('normal', 'commit')
[08/05/24 09:37:03] Transfer repo '9319d653': ('normal', 'commit') --> ('normal', 'fs')
[08/05/24 09:37:03] Transfer repo '9319d653': ('normal', 'fs') --> ('normal', 'data')
[08/05/24 09:37:03] Transfer repo '9319d653': ('normal', 'data') --> ('finished', 'finished')
[08/05/24 09:37:03] Repo 'mesDir' sync state transition from 'downloading' to 'synchronized'.
[08/05/24 09:37:05] Repo 'mesDir' sync state transition from 'synchronized' to 'committing'.
[08/05/24 09:37:05] All events are processed for repo 9319d653-ff60-4cf3-8f59-906697cf0c2d.
[08/05/24 09:37:05] Repo 'mesDir' sync state transition from 'committing' to 'synchronized'.
[08/05/24 09:37:31] Repo 'mesDir' sync state transition from 'synchronized' to 'committing'.
[08/05/24 09:37:31] All events are processed for repo 9319d653-ff60-4cf3-8f59-906697cf0c2d.
[08/05/24 09:37:31] Repo 'mesDir' sync state transition from 'committing' to 'synchronized'.
[08/05/24 09:38:02] [08/05/24 09:38:02] Repo 'mesDir' sync state transition from 'initializing' to 'downloading'.
Transfer repo '9319d653': ('normal', 'init') --> ('normal', 'check')
[08/05/24 09:38:02] Transfer repo '9319d653': ('normal', 'check') --> ('normal', 'commit')
[08/05/24 09:38:02] Transfer repo '9319d653': ('normal', 'commit') --> ('normal', 'fs')
[08/05/24 09:38:02] Transfer repo '9319d653': ('normal', 'fs') --> ('normal', 'data')
[08/05/24 09:38:04] Transfer repo '9319d653': ('normal', 'data') --> ('finished', 'finished')
[08/05/24 09:38:04] Repo 'mesDir' sync state transition from 'downloading' to 'synchronized'.
[08/05/24 09:38:06] Repo 'mesDir' sync state transition from 'synchronized' to 'committing'.
[08/05/24 09:38:06] All events are processed for repo 9319d653-ff60-4cf3-8f59-906697cf0c2d.
events.log
[08/05/24 09:33:05] Starting record seafile events.
[08/05/24 09:42:32] mesDir 9319d653-ff60-4cf3-8f59-906697cf0c2d 98ee680edca112626d1a4db7e961eef5a08457f5
[event 1] delete, attachment/订单Json/2024/8/2024-08-05/e669a2ec-5c06-4f44-a789-ba00f618822e/YYCYJJ240803001YG-13-03.json
[event 2] delete, attachment/IMOS炸单DWG文件/2024/8/2024-08-05/40a8bc7e-3a5b-48c2-b64c-247c059f9930/YYCYJJ240803001YG-13-03.DWG
[event 3] delete, attachment/IMOS炸单DWG文件/2024/8/2024-08-05/b5ef8d18-4660-49b9-8907-7235682b5ea0/AJ0SDS022407008YG-10-02.DWG
[event 4] delete, mp4/AJ0SCP012407006YG-08-03/AJ0SCP012407006YG-08-031001.mpr
[event 5] delete, mp4/AJ0SCP012407006YG-08-04/AJ0SCP012407006YG-08-041001.mpr
[event 6] delete, mp4/AJ0SCP012407006YG-08-03/AJ0SCP012407006YG-08-031002.mpr
[event 7] delete, mp4/AJ0SCP012407006YG-08-04/AJ0SCP012407006YG-08-041002.mpr
[event 8] delete, mp4/AJ0SCP012407006YG-08-03/AJ0SCP012407006YG-08-031003.mpr
[event 9] delete, mp4/AJ0SCP012407006YG-08-04/AJ0SCP012407006YG-08-041003.mpr
[event 10] delete, mp4/AJ0SCP012407006YG-08-03/AJ0SCP012407006YG-08-031004.mpr
[event 11] delete, mp4/AJ0SCP012407006YG-08-04/AJ0SCP012407006YG-08-041004.mpr
[event 12] delete, mp4/AJ0SCP012407006YG-08-04/AJ0SCP012407006YG-08-041005.mpr
[event 13] delete, mp4/AJ0SCP012407006YG-08-03/AJ0SCP012407006YG-08-031005.mpr
[event 14] delete, mp4/AJ0SCP012407006YG-08-04/AJ0SCP012407006YG-08-041006.mpr
[event 15] delete, mp4/AJ0SCP012407006YG-08-03/AJ0SCP012407006YG-08-031006.mpr
[event 16] delete, mp4/AJ0SCP012407006YG-08-04/AJ0SCP012407006YG-08-041007.mpr
[event 17] delete, mp4/AJ0SCP012407006YG-08-03/AJ0SCP012407006YG-08-031007.mpr
[event 18] delete, mp4/AJ0SCP012407006YG-08-03/AJ0SCP012407006YG-08-031008.mpr
[event 19] delete, mp4/AJ0SCP012407006YG-08-04/AJ0SCP012407006YG-08-041010.mpr
[event 20] delete, mp4/AJ0SCP012407006YG-08-03/AJ0SCP012407006YG-08-031009.mpr
[event 21] delete, mp4/AJ0SCP012407006YG-08-04/AJ0SCP012407006YG-08-041011.mpr
[event 22] delete, mp4/AJ0SCP012407006YG-08-03/AJ0SCP012407006YG-08-031010.mpr
[event 23] delete, mp4/AJ0SCP012407006YG-08-04/AJ0SCP012407006YG-08-041012.mpr
[event 24] delete, mp4/AJ0SCP012407006YG-08-03/AJ0SCP012407006YG-08-031011.mpr
事件驱动好处
逻辑解耦: 不需要关心太多的业务复杂度,只需要在需要的时候发起对应的业务逻辑调用。
时序性保证:通过事件发出就像将处理放入队列一样,能够保证时序性。
可靠性保证:由于事件的时序性很强,从而使数据一致性和可靠性得到保证。
本地联思文件同步云联思真实案例
先介绍下业务背景:由于工业软件的特殊性需要将文件进行不同磁盘目录的合并,通过seafile的同步中转能力实现文件的归档,使得在对外开放的服务器上能够正常访问到文件。
本地联思文件同步云联思架构
架构设计图如下:
由于seafile上传虽然宣称是可靠的,但是在实际操作中发现并不是百分之百的文件都上去了,还是有掉文件的情况。于是本地联思需要负责检查云联思seafile和云联思目录是否真实存在相应的文件。通过云联思部署客户端对外访问的API接口判定云联思seafile或MES目录是否真实存在上传文件,任意一个目录存在就表示同步完成,当然文件还要校验md5,不是名称一样就表示存在。
云联思客户端检查文件API
云联思客户端提供REST HTTP接口访问,提供心跳接口和相关的校验接口:
@RestController
@RequestMapping("/localMes/productionFiles")
@RequiredArgsConstructor
public class ProductionFileController extends BaseController {
private final SyncFileMappingConfig syncFileConfig;
/**
* 远端请求心跳测试
*
* @return
*/
@GetMapping("/heartbeat")
public Response<String> heartbeat(HttpServletRequest request) {
String ipAddress = request.getRemoteAddr();
String message = String.format("请求心跳:%s %s", ipAddress, DateUtil.formatDate(new Date().getTime(), "yyyy-MM-dd HH:mm:ss.SSS"));
logger.info(message);
return onSuccess(message);
}
/**
* 验证路径attachment文件是否存在
*
* @param syncFileModel
* @param bindingResult
* @return
*/
@RequestMapping(value = "/attachment/check/exists", method = RequestMethod.POST)
public Response<SyncFileModel> checkAttachmentPath(@Valid @RequestBody SyncFileModel syncFileModel, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return onBindingInvalidInput(bindingResult);
}
markFile(syncFileModel);
return onSuccess(syncFileModel);
}
/**
* 删除不存在的attachment文件
*
* @param syncFileModel
* @param bindingResult
* @return
*/
@RequestMapping(value = "/attachment/delete/notExists", method = RequestMethod.POST)
public Response<SyncFileModel> deleteAttachmentPath(@Valid @RequestBody SyncFileModel syncFileModel, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return onBindingInvalidInput(bindingResult);
}
deleteFile(syncFileModel);
return onSuccess(syncFileModel);
}
/**
* 验证路径mpr文件是否存在
*
* @param syncFileModelList
* @param bindingResult
* @return
*/
@RequestMapping(value = "/mpr/check/exists", method = RequestMethod.POST)
public Response<SyncFileModelList> checkMprPath(@Valid @RequestBody SyncFileModelList syncFileModelList, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return onBindingInvalidInput(bindingResult);
}
for (SyncFileModel syncFileModel : syncFileModelList.getSyncFileModelList()) {
markFile(syncFileModel);
}
// 删除云联思独有的mpr文件
deleteOnlyOnTargetMprFiles(syncFileModelList);
return onSuccess(syncFileModelList);
}
/**
* 删除不存在的mpr文件
*
* @param syncFileModelList
* @param bindingResult
* @return
*/
@RequestMapping(value = "/mpr/delete/notExists", method = RequestMethod.POST)
public Response<SyncFileModelList> deleteMprPath(@Valid @RequestBody SyncFileModelList syncFileModelList, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return onBindingInvalidInput(bindingResult);
}
if (CollectionUtils.isEmpty(syncFileModelList.getSyncFileModelList())) {
File file = new File(syncFileConfig.getMappingMp4(), syncFileModelList.getOrderNo());
if (file.exists()) {
FileUtil.del(file);
}
} el