民航项目


[TOC]

民航发动机数据管理系统

首先这个数据管理系统主要是干什么的

  1. 数据管理:收集、存储和处理来自飞机发动机的传感器数据。这些数据可能包括发动机性能指标、飞行参数和环境条件。

  2. 数据分析:利用机器学习和其他数据分析技术来分析这些数据,以识别模式、预测潜在故障或优化发动机性能。你提到过使用 MATLAB 和 Weka 进行进行减推比例和排气裕度的计算与分析。。

  3. 数据可视化:使用工具如 Element Plus 和 ECharts 来展示分析结果,帮助用户理解数据和分析结果。

  4. 权限管理:利用JWT令牌技术,通过自定义拦截器实现用户认证,并使用Sa-Token进行权限控制。

  5. 集成与接口:项目涉及前后端的分离开发,并使用技术 Apifox 来管理 API 接口文档和测试。

  6. 系统功能:实现用户管理、权限管理、操作日志记录和文件存储等功能,通过 EasyExcel 进行数据的导入导出。

  7. 部署与优化:本地部署Jar包,使用Nginx作为反向代理服务器,增强系统的安全性和性能。

    业务价值

    • 提高民航发动机监控数据的可视化水平,便于运营人员进行快速决策。
    • 通过数据分析与机器学习,优化发动机性能,降低维护成本。

ACARS数据(航空器通信寻址与报告系统)

ACARS用于传输有关飞行操作的信息,如起飞和降落时间、飞行高度、速度等。

比如说:A00:起飞地面重量,起飞高度,起飞马赫数,发动机的转速,进口温度,离心机的转速

B00:排气气体温度,推力衰减,风扇转速,发动机风扇和核心的振动,航空器总重

QAR数据(快速访问记录器)

QAR系统记录详细的飞行数据,这些数据通常以高频率采集,提供非常精细的飞行记录。

马赫,高度,气压,温度,静态空气温度,排气温度, 燃油空气比, 燃油流量

B6105数据(故障代码)

不同时刻发动机的不同传感器的, 静态空气温度,气压、无线电高度,马赫数

减推比例模型

减退比例:可以延长发动机的寿命,可以降低发动机在起飞和爬升阶段的推力输出,从而减少发动机部件的磨损和疲劳,延长发动机的使用寿命。在不需要最大推力的情况下使用减推比例,可以减少燃油消耗,提高燃油效率。这对于航空公司来说可以显著降低运营成本,尤其是在燃油价格波动较大的情况下。

排气裕度模型

是用于评估和优化喷气发动机性能的一种模型。它主要用于确定发动机在特定飞行条件下的排气性能,以及如何保持发动机在设计的性能范围内。排气裕度指的是发动机在特定操作条件下实际排气性能与设计目标性能之间的差距。它通常衡量发动机的实际排气温度、压力或速度与设计规定值之间的差异。

通过排气裕度模型,可以优化发动机的排气系统设计,以确保发动机在各种飞行条件下都能保持最佳性能。

数据处理

ACARS数据不用处理,但是QAR数据需要处理

ACARS数据不涉及到时间,所以数据量少,但是QAR的数据量涉及到时刻,数据量大

QAR数据需要进行补全、去噪和归一化,查看减推比例

数据库

数据库里面的数据包括两种,一种是用来计算减推比例的,一种是用来计算排气裕度的

其中减推比例计算 在计算过程当中 最大值为26%

排气裕度 的数据量 最大是 大连飞往海南省和乌鲁木齐 大概5个小时

其中 一张表的数据量有多少呢 60×60×5×40 = 720000

我们有很多张表了 其实说 大概的也就 2 - 4小时的比较多

但是数据有很多 计算起来有很慢怎么解决呢 :

1.数据压缩,降采样。去掉无关的数据列,然后是 按照固定的时间间隔来抽取数据点而不是处理每一个时间点的数据。

2.将大数据集按某个维度(如时间或空间)切分为多个小的子集进行并行处理

可以说是一次旅程就可以有一张表出来:

那么就是 加入

飞机信息表,记录飞机 航班号,起飞时间、降落时间、飞机号、飞机信息、发动机类型

发动机数据表,里面使用外键引用航班表的信息

数据库优化

定义外键关系:通过在发动机数据表中使用外键来引用航班表中的主键,将每条发动机数据关联到具体的航班。

确保唯一标识:确保每次航班(Flight)和每条发动机数据(EngineData)都有唯一的标识符,用来进行精确的关联。

索引优化:在查询频繁的字段(如 FlightIDTimestamp)上创建索引,以提高查询性能。

外键约束:使用外键约束保持数据完整性,确保每条发动机数据都关联到有效的航班。

对于历史航班和其发动机数据,可以考虑定期进行归档,以减少主数据库的负担。

数据库使用的设计模式

  1. Cache Aside Pattern(旁路缓存模式)
  • 目的:在数据访问时优先从缓存中读取数据,当缓存没有数据时,再从数据库中读取,并将结果写入缓存。
  • 应用场景:在需要频繁读取而数据变化不频繁的场景中,这种模式可以极大地提高数据访问效率。
  • 优点:
    • 减少了数据库的压力。
    • 提高了系统性能和响应速度。
  • 示例:在电商系统中,经常读取但不经常更新的商品信息可以使用旁路缓存模式进行加速。

2.Data Access Object Pattern(DAO 模式)

  • 目的:将数据访问操作封装到一个独立的对象中,提供数据的抽象接口。
  • 应用场景:在需要频繁进行数据库操作的场景下,使用 DAO 模式可以将数据访问层与业务逻辑层分离。
  • 优点:
    • 提供了一个统一的数据操作接口,隐藏了数据库访问的复杂性。
    • 提高了代码的可维护性和可测试性。
  • 示例:一个 UserDAO 对象可以负责所有与用户相关的数据库操作,如查询、插入、更新和删除。
  1. Factory Pattern(工厂模式)
  • 目的:为创建不同类型的数据对象提供统一的接口。
  • 应用场景:当需要根据不同条件动态创建不同类型的数据对象时,可以通过工厂模式生成这些对象。
  • 优点:
    • 提供灵活的数据对象创建机制,方便维护。
    • 隐藏具体数据类型的创建细节,符合开闭原则。
  • 示例:在一个金融系统中,工厂模式可以用于生成不同类型的交易记录对象(如股票交易、债券交易)。

权限验证

基于RDBC权限管理的一个平台,分为用户,角色,权限

用户(多对多)角色(多对多)菜单,基于RBAC设计的,通过将权限分配给角色,角色分配给用户,一般是多对多的关系

用户:平台上的个人账户,具有登录和使用系统的能力。

用户ID 用户名 密码(加密存储) 电子邮件 角色(一个或多个)

管理员:

​ 访问系统的所有功能

​ 管理用户账户(创建、编辑、删除)

​ 分配和修改角色

​ 发动机数据的上传和管理

操作员:

​ 和数据相关的一些工作

游客:

​ 使用相关功能

​ 导出数据,但不能上传

权限的话呢:增删改查肯定是要有的,包括数据和角色的表单管理

问题在于 是怎么样去实现权限验证的呢?

\1. 检测权限:用户访问资源时,我们首先要检查用户的令牌(SaToken)是否有访问该资源的权限。

\2. 返回权限拒绝:如果发现令牌不匹配或者用户无权限访问该资源,服务器会返回一个权限拒绝的状态码,比如403 Forbidden,和一条相关的错误信息,提示用户无权限。

\3. 前端处理:前端收到服务器返回的403状态码后,可以拦截请求并展示一条用户友好的错误信息,告知用户当前没有访问该资源的权限。

负责人

你作为负责人,你应该要做的是什么职责呢?

确定项目架构,搭建整体框架,基于 RBAC 权限模型设计用户数据库;

组员的工作:①前端页面的展示②机器学习算法集成③两个人做后端

前端到后端数据的传输

首先是前端的交互,然后通过vue3的代码进行数据传输,使用http协议向后端发送数据(使用axios发起请求)

DTO 接收前端表单实体,Entity 与数据库交互,VO 用于前端展示

然后是数据传输:数据传输请求包括下面的主要部分

请求方法:例如POST、PUT、GET等。

请求URL:后端API的端点地址。

请求头:包含Content-Type(如application/json)和其他元数据。

请求体:包含实际发送的数据(通常是JSON格式)。

然后后端接受到请求数据之后呢,controller层进行处理,做一些工作

处理完请求后,后端会生成响应并发送回前端。响应通常包括状态码(如200 OK)、响应头和响应体(如操作结果、成功消息、错误信息等)。

前端接收到后端的响应后,会根据响应的内容更新用户界面。例如,显示成功消息、更新视图、处理错误等。

操作日志记录

登录日志:雪花算法id,用户名,服务,ip,日期,登录信息

操作日志包括:操作用户名,用户服务,用户ip,用户地理位置, 操作, 操作具体内容,

自定义配置elasticsearch,实现长连接 AOP 调用 log服务 实现 + SLF4J

怎么使用的呢?主要是使用Elasticsearch 分布式搜索和分析引擎,来存储和分析日志数据

Spring Data Elasticsearch 封装了基本功能,

countLogin

  • 该方法统计符合特定条件的登录日志。默认情况下,它会统计最近一个月的登录情况。
  • 使用 BoolQueryBuilder 创建查询,过滤出状态为成功且操作类型为登录的记录。
  • 使用 AggregationBuilders 创建一个聚合,以用户名进行分组并统计每组的数量。
  • 查询通过 restTemplate.search 方法执行,结果在 termsbucket 中返回。

Websocket

在这里面websocket起到了什么作用呢?

WebSocket 是一种协议,它为客户端和服务器之间的实时、双向通信提供了一个持久化的连接。与传统的HTTP请求-响应模型不同,WebSocket 允许在单个连接上进行双向、全双工的数据交换。这使得WebSocket非常适合需要实时数据更新的应用场景,如在线聊天、实时通知、金融市场数据推送等。

连接建立:客户端通过发起一个WebSocket握手请求与服务器建立连接。握手请求是一个标准的HTTP请求,但带有 Upgrade 头,指示服务器将连接升级到WebSocket协议。

握手响应:服务器回应一个WebSocket握手响应,确认升级协议并建立连接。

数据传输:一旦连接建立,客户端和服务器可以通过WebSocket连接进行双向数据传输,而无需重新建立连接。数据可以是文本、二进制等格式。

连接关闭:当通信完成或出现错误时,任何一方可以主动关闭WebSocket连接。连接关闭的过程类似于普通的HTTP连接关闭。

优点在于下面几点:

实时性:WebSocket 提供了低延迟的实时数据传输。这使得应用程序能够即时更新数据,而不需要频繁地轮询服务器。

双向通信:WebSocket 允许服务器主动向客户端推送数据,而不需要客户端发起请求。这对于需要服务器推送实时更新的应用(如实时聊天、在线游戏等)非常有用。

减少开销:与传统的HTTP请求-响应模型相比,WebSocket 的握手过程只发生一次,之后的数据传输不需要重复的HTTP头。这减少了网络开销和带宽消耗。

持久连接:WebSocket 连接在建立后是持久的,直到被显式关闭。这意味着数据传输可以在多个消息中维持一个连接,而无需重新建立连接。

Satoken

Sa-Token 是一个轻量级的 Java 权限管理框架,提供了基于 Token 的认证和权限控制。它可以与 Spring Boot 等框架无缝集成,支持 JWT(JSON Web Tokens)用于身份验证和授权。

1.主要是 Sa-Token 整合 jwt

Sa-Token 可以与 JWT 集成,使用 JWT 作为认证 Token,确保请求的合法性和用户身份的验证。

2.注册 Sa-Token 拦截器,校验规则为 StpUtil.checkLogin() 登录校验

通过配置 Sa-Token 拦截器来拦截和处理请求,实现统一的权限管理和认证检查。

StpUtil.checkLogin() 方法检查用户是否已登录

3.SaCheckPermission

通过 SaCheckPermission 注解进行权限控制,确保用户有权限访问某些资源。

4.集成在全局异常处理当中

GlobalExceptionHandler处理在 Spring Boot 应用中出现的各种异常,并返回统一格式的错误响应。

AOP

@AfterReturning(pointcut = "@annotation(controllerLog)", returning = "commonResult")
public void doAfterReturning(JoinPoint joinPoint, Log controllerLog, Object commonResult) {
    if (Objects.isNull(commonResult)) {
        HttpServletResponse response = ServletUtil.getResponse();
        // 没有统一返回类则必须有相关response信息
        if (Objects.isNull(response)) {
            log.warn("LogAspect#doAfterReturning切面方法无返回值且未能获得Http响应信息,无法记录日志");
            return;
        }
        // response状态码信息转换为CommonResult格式
        commonResult = new CommonResult<>(response.getStatus(), response.getContentType(), null);
    }
    // 若返回值不是CommonResult类型,则根据返回值类型自动转换为CommonResult
    if (!(commonResult instanceof CommonResult)) {
        switch (commonResult) {
            case Integer num -> commonResult = CommonResult.of(num, "执行成功", "执行失败");
            case Boolean flag -> commonResult = CommonResult.of(flag, "执行成功", "执行失败");
            default -> commonResult = new CommonResult<>(HttpStatus.SUCCESS, "执行成功", commonResult);
        }
    }
    handleLog(joinPoint, controllerLog, null, (CommonResult<?>) commonResult);
}

/**
 * 请求出现异常后执行
 *
 * @param joinPoint 切点
 * @param e         异常
 */
@AfterThrowing(value = "@annotation(controllerLog)", throwing = "e")
public void doAfterThrowing(JoinPoint joinPoint, Log controllerLog, Exception e) {
    handleLog(joinPoint, controllerLog, e, null);
}

/**
 * 使用虚拟线程
 */
private final ExecutorService virtualThreadPerTaskExecutor = Executors.newThreadPerTaskExecutor(NamedThreadFactory.newVirtualThreadFactory("log"));

/**
 * 日志处理
 * 1.记录请求上下文信息
 * 2.开启虚拟线程异步处理
 * 3.判断日志类型,解析注解中的Spring EL表达式,获取日志内容
 * 4.调用日志服务保存日志
 *
 * @param joinPoint     切点
 * @param controllerLog 注解
 * @param e             异常
 * @param commonResult  统一返回值
 */
protected void handleLog(JoinPoint joinPoint, Log controllerLog, Exception e, CommonResult<?> commonResult) {
    // 无法异步获取request以及登录用户,先记录request相关信息
    String ip = IpUtil.getIpAddress(ServletUtil.getRequest());
    int userId = 0;
    String requestMethod = ServletUtil.getRequest().getMethod();
    // 操作日志时提前记录用户id
    if (controllerLog.businessType() != BusinessType.LOGIN &&
            controllerLog.businessType() != BusinessType.LOGOUT &&
            controllerLog.businessType() != BusinessType.ACCESS) {
        userId = userIdSearcher.getUserId();
    }
    int currentUserId = userId; // 满足lambda等效静态
    // 异步处理后续操作
    virtualThreadPerTaskExecutor.execute(() -> {
        try {
            SysLog sysLog = new SysLog();
            sysLog.setOperate(controllerLog.businessType().getKey());
            sysLog.setIp(ip);
            sysLog.setLocation(IpUtil.getRegionAndIsp(ip));
            // 获取方法入参,key为参数名,value为参数值
            LinkedHashMap<String, Object> params = ReflectUtil.resolveParams(joinPoint);
            StandardEvaluationContext context = ReflectUtil.paramsAlias(params);
            // 用自定义的Spring EL表达式解析字符串
            sysLog.setService(ReflectUtil.resolveValue(controllerLog.service(), context, String.class));
            // 是否抛出异常,若未抛出异常则通过状态码判断响应状态
            if (Objects.nonNull(e)) {
                sysLog.setStatus(BusinessStatus.FAIL.getKey());
                sysLog.setContent(StringUtil.handleExpMsg(e.getMessage()));
            } else if (commonResult.isSuccess()) {
                // 请求处理成功
                sysLog.setStatus(BusinessStatus.SUCCESS.getKey());
                sysLog.setContent(ReflectUtil.resolveValue(controllerLog.content(), context, String.class));
            } else {
                // 请求处理失败
                sysLog.setStatus(BusinessStatus.FAIL.getKey());
                sysLog.setContent(commonResult.getMsg());
            }
            String username;
            // 登录日志处理(含调用doAppLogin的登录)
            if (BusinessType.LOGIN == controllerLog.businessType()) {
                // 登录成功,直接从返回结果中获取用户信息
                username = Objects.isNull(e) && commonResult.isSuccess() ? ((UserVO) commonResult.getData()).getUsername()
                        // 否则用自定义的Spring EL表达式解析参数获取异常的用户名
                        : ReflectUtil.resolveValue(controllerLog.operator(), context, String.class);
            }
            // 注销日志处理,注销时会返回用户id
            else if (BusinessType.LOGOUT == controllerLog.businessType()) {
                Integer id = (Integer) commonResult.getData();
                username = usernameSearcher.getUsername(id);
            }
            // 其他操作日志处理,可直接通过请求获得用户id
            else {
                // 访问日志可通过返回结果获取用户信息或者日志注解中的operator获取用户名
                username = BusinessType.ACCESS == controllerLog.businessType() && Objects.isNull(e) && commonResult.isSuccess()
                        ? ((UserVO) commonResult.getData()).getUsername()
                        : StringUtil.isNotBlank(controllerLog.operator()) ? ReflectUtil.resolveValue(controllerLog.operator(), context, String.class)
                        : usernameSearcher.getUsername(currentUserId);
                // 获取请求方法类名
                String className = joinPoint.getTarget().getClass().getName();
                // 获取请求方法名
                String methodName = joinPoint.getSignature().getName();
                // 记录的方法格式为:类名#方法名格式
                sysLog.setMethod(STR."\{className}#\{methodName}");
                sysLog.setRequestMethod(requestMethod);
            }
            sysLog.setUsername(username);
            logFeignService.saveLog(sysLog);
        } catch (Exception exception) {
            log.error("日志存储失败", exception);
        }
    });
}

这个AOP日志切面代码的主要功能是对标记了特定注解的方法进行日志记录,分别在方法正常返回和抛出异常的情况下执行不同的处理逻辑。以下是代码中关键部分的解释:

1. @AfterReturning注解

@AfterReturning表示该方法将在目标方法成功返回之后执行。

  • pointcut = "@annotation(controllerLog)":该切面会拦截所有被标注了自定义注解(比如@Log)的方法。
  • returning = "commonResult":表示捕获目标方法的返回值commonResult
  • 在方法返回后,doAfterReturning会检查返回值是否为空(commonResult),如果为空,则通过HttpServletResponse生成一个默认的返回对象。如果返回值不是CommonResult类型,则进行相应的转换为统一格式。
  • 最终调用handleLog方法来处理日志记录。

2. @AfterThrowing注解

@AfterThrowing表示该方法会在目标方法抛出异常时执行。

  • throwing = "e":捕获目标方法抛出的异常e
  • 方法体中通过handleLog处理异常信息并记录日志。

3. 日志异步处理

日志的核心处理逻辑位于handleLog方法中。通过虚拟线程池(ExecutorService)异步处理日志,以提高性能,避免阻塞主线程。

4. 日志处理逻辑

handleLog方法中,日志记录的详细逻辑包括:

  • 获取请求的上下文信息:包括客户端IP地址、请求方法、用户ID等。
  • 判断业务类型:通过controllerLog.businessType(),判断是否为登录、注销、访问等不同类型的业务操作,分别处理用户信息的获取逻辑。
  • 解析注解中的Spring EL表达式:用自定义的Spring EL表达式解析注解中定义的日志内容,这样可以动态获取方法的参数、返回值,形成灵活的日志内容。
  • 处理异常或成功情况:如果抛出了异常,日志将记录为失败;如果没有异常,则根据返回值的状态记录成功或失败。
  • 日志异步保存:通过调用日志服务(logFeignService.saveLog(sysLog))异步保存日志信息。

5. 方法的核心作用

  • doAfterReturning:在目标方法正常返回后进行日志记录,统一处理返回结果,并调用日志处理逻辑。
  • doAfterThrowing:在目标方法抛出异常时捕获异常信息,记录日志。
  • handleLog:负责处理所有日志信息的细节,包括请求上下文信息、方法参数、用户信息、执行结果等。通过虚拟线程池进行异步处理,提高日志处理的效率。

6. 虚拟线程

  • 使用virtualThreadPerTaskExecutor创建虚拟线程(JDK中的轻量级线程),通过异步执行日志的保存操作,避免日志处理对应用性能产生影响。

总结

该AOP切面通过@AfterReturning@AfterThrowing注解分别处理方法正常执行和抛出异常的情况,结合虚拟线程实现异步日志记录,解析方法参数、返回值及异常信息,并将这些信息保存为操作日志。这种实现方式减少了手动日志代码的冗余,并提升了日志记录的灵活性和性能。

Redis

Redis部署在docker里面,默认端口

整合的是 satoken和redis链接

redis整合的是 jackson序列化方式

存储的是 redis的信息:用户的角色信息与权限列表,jwt-token 请求时间

每次权限验证的时候重写satoken的接口,每次查权限登录信息的时候从redis缓存里面查

还有就是经常查询的数据库:比如说是 航班表的信息,但是实时数据可能不会存储到里面

在application.yml里面部署

使用Redis实现Session共享可以解决分布式应用程序中的Session一致性问题,同时提供高可用性和扩展性。Redis是一个快速、可靠且功能强大的缓存和数据存储系统,非常适合用于这种场景。

首先是用户信息,和权限信息,key/value形式,使用哈希的形式

然后是 用户会话登录形成的会话令牌即token,可以使用 string进行Jackson序列化存储

首先是

热点数据:

\1. 缓存热点数据:使用缓存系统(如Redis或Memcached)将热点数据缓存起来,加快访问速度,减少数据库压力。

\2. 数据分片:将数据分成多个分片,分布在不同的数据库或者服务器上,减少单一数据节点的压力。

\3. 负载均衡:在应用层增加负载均衡器,将请求分发到不同的服务器进行处理,以均衡负载,减少单个服务器的压力。

\4. 数据复制:创建热点数据的多个副本,放在不同的服务器上,从而分散读写压力。

\5. 异步处理:对于非实时性的数据更新,可以采用异步处理,减少瞬间高并发的压力。

\6. 热点数据分布策略:根据热点数据的分布规律,通过调整数据库表的结构或者查询逻辑,从根本上避免产生热点数据。

存储的数据类型,具体大小

如何保证缓存与数据库一致性

先更新数据库,然后再删除缓存

将更新缓存和删除缓存

异步重试。什么是异步重试?

其实就是把重试请求写到「消息队列」中,然后由专门的消费者来重试,直到成功。

或者更直接的做法,为了避免第二步执行失败,我们可以把操作缓存这一步,直接放到消息队列中,由消费者来操作缓存。

到这里你可能会问,写消息队列也有可能会失败啊?而且,引入消息队列,这又增加了更多的维护成本,这样做值得吗?

这个问题很好,但我们思考这样一个问题:如果在执行失败的线程中一直重试,还没等执行成功,此时如果项目「重启」了,那这次重试请求也就「丢失」了,那这条数据就一直不一致了。

所以,这里我们必须把重试或第二步操作放到另一个「服务」中,这个服务用「消息队列」最为合适。这是因为消息队列的特性,正好符合我们的需求:

  • 消息队列保证可靠性:写到队列中的消息,成功消费之前不会丢失(重启项目也不担心)
  • 消息队列保证消息成功投递:下游从队列拉取消息,成功消费后才会删除消息,否则还会继续投递消息给消费者(符合我们重试的场景)
  • image-20240911222304193

1.简单:适应并发量一致性都不是很高

写的时候删除缓存,然后再更新DB

读的时候先读缓存,然后读DB,异步将数据刷会缓存

问题在于 中间有一个步骤出错了怎么办,读写,写写并发会出现 不一致的问题

2.一般:

引入日志binlog,通过解析binlog来刷新缓存

写的时候第一步先删除缓存,删除之后再更新DB,我们监听从库(资源少的话主库也ok或者直接分析binlog也可以)的binlog,通过分析binlog我们解析出需要需要刷新的数据,然后读主库把最新的数据写入缓存。

读的时候读的时候先读缓存,然后读DB,异步将数据刷会缓存

3.困难

利用MQ集成分布式系统 将所有“读数据库” + “写数据库缓存”的步骤串行化

image-20240821141457071

写的时候第一步先删除缓存,删除之后再更新DB,我们监听从库(资源少的话主库也ok)的binlog,通过分析binlog我们解析出需要需要刷新的数据标识,然后将数据标识写入MQ,接下来就消费MQ,解析MQ消息来读库获取相应的数据刷新缓存。

读的时候第一步先读缓存,如果缓存没读到,则去读DB,之后再异步将数据标识写入MQ(这里MQ与写流程的MQ是同一个),接下来就消费MQ,解析MQ消息来读库获取相应的数据刷新缓存。

4.困难进阶

阿里开源的cannl组件

基于bin log可以将数据库同步到其他各类数据库中,目标数据库支持mysql,postgresql,oracle,redis,MQ,ES等

canal分成服务端deployer和客户端adapter,我们可以部署多个,同时为了方便管理还提供了一个管理端admin

img

不同点就在于我们加一个缓存,将近期被修改的数据进行标记锁定。读的时候,标记锁定的数据强行走DB,没锁定的数据,先走缓存,加上标志 cache_0来规定

注册登录

使用邮箱注册,需要验证码,验证码存放在redis中,在redis中设置有效时间为一分钟

忘记密码:与注册部分同理,也是邮箱验证码

验证码:配置 SMTP 服务器地址+qq邮箱小号

用户登陆后后端会生成token与session存入redis中,同时token还会传送给前端,前端每次发送请求请求头中都会带着token。之后会与redis中存储的token进行对比验证,如果相同并且没有过期,就会放行。

cookie、session、token

cookie是客户端,浏览器。不安全 根据cookie登录不同的账号,容量有限。不能完全依赖cookie

session 响应头里面加入 set-cookie。 主要是session ID可以是 map存出用户信息,存储在服务端,安全。但是扩展性差,跨域限制。

多个服务器 => 集群session设置。

使用JWT token 进行传递字符串。

base64加密。

MINIO

服务地址,用户名,密码,存储桶

分布式对象存储

包括分片续传和单点上传

http协议

HTTP 是超⽂本传输协议,信息是明⽂传输

HTTPS 则解决 HTTP 不安全的缺陷,在TCP 和 HTTP ⽹络层之间加⼊了 SSL/TLS 安全协议

  1. HTTP1.1(用的最多)
    1. 相比于1.0,新增了那些功能?
      • 提出了⻓连接的通信⽅式,也叫持久连接。只要任意⼀端没有明确提出断开连接,则保持 TCP 连接状态。
      • ⻓连接的⽅式这使得管道(pipeline)⽹络传输成为了可能。在同⼀个 TCP 连接⾥⾯,客户端可以发起多个请求,只要第⼀个请求发出去了,不必等其回来,就可以发第⼆个请求出去,可以减少整体的响应时间。
      • 增加了host字段。1.0认为每台服务器都绑定一个唯一的IP地址,因此,请求消息中的URL并没有传递主机名(hostname)。而虚拟主机技术的发展,一台物理服务器上有多个虚拟主机,共享一个IP,于是增加host字段区分。
      • 没有请求优先级控制
  2. HTTP2.0
    1. HTTP/2 协议是基于 HTTPS 的,所以 HTTP/2 的安全性也是有保障的。
    2. 相比于1.1的性能改进:
      • 引入二进制分帧层。HTTP/2 不再像 HTTP/1.1 ⾥的纯⽂本形式的报⽂,⽽是全⾯采⽤了⼆进制格式,头信息和数据体都是⼆进制,并且统称为帧(frame):头信息帧和数据帧
      • HTTP/2 多个HTTP请求复⽤⼀个TCP连接,⼀旦发⽣丢包,就会阻塞住所有的 HTTP 请求。

nginx

电脑和前端页面访问的中介就是 nginx 高性能web

部署完打包之后 使用Nginx作为反向代理服务器,将请求转发给Spring Boot项目。

首先,安装Nginx,并编辑配置文件(一般位于/etc/nginx/nginx.conf),添加以下配置:

http {
    server {
        listen 80;
        server_name your_domain_name;

        location / {
            proxy_pass http://localhost:9090;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }
    }
}

将your_domain_name替换为你的域名,your_project_port替换为项目运行的端口。

保存并退出配置文件后,重启Nginx服务:

考虑为你的 Nginx 服务器配置 SSL/TLS 证书以实现 HTTPS。可以使用 Let's Encrypt 提供免费的 SSL 证书。

如果有多个后端服务器,可以使用 Nginx 来实现简单的负载均衡。只需在 proxy_pass 配置中定义多个上游服务器即可。

# HTTPS server
#
#server {
#    listen       443 ssl;
#    server_name  localhost;

#    ssl_certificate      cert.pem;
#    ssl_certificate_key  cert.key;

#    ssl_session_cache    shared:SSL:1m;
#    ssl_session_timeout  5m;

#    ssl_ciphers  HIGH:!aNULL:!MD5;
#    ssl_prefer_server_ciphers  on;

#    location / {
#        root   html;
#        index  index.html index.htm;
#    }
#}

使用nginx解决跨域问题。也是在conf里面修改配置问题

Access-Control-Allow-Origin:允许指定的源访问资源。* 表示允许所有源访问,你也可以指定具体的域名(如 https://example.com)。

Access-Control-Allow-Methods:指定允许的 HTTP 方法。

Access-Control-Allow-Headers:指定允许的请求头。

Access-Control-Max-Age:指定预检请求的缓存时间。

模型计算

ACARS数据那到底是怎么计算呢,主要是 包括单值预测和多值预测

其实和机器学习里面的东西是一样的。

基本上是调用matlab的模型,包括SCN和BP神经网络,和weka里面的机器学习模型

怎么调用matlab的模型呢,首先是要把模型给打包,然后放入到pom.xml文件中,在controller中就可以调用了

weka应该怎么使用呢,主要是应该首先下载weka,然后也是放到pom.xml文件,一般处理的是arff文件

部署问题

主要还是部署问题,

maven的package功能

得到相关的jar包,在linux或者其他系统通过java命令运行项目jar包

java -jar boot-0.0.1-SNAPSHOT.jar

然后前端是 下载 nginx作为前端代理

前端

前端打包的 Node.js和npm管理

npm run build

将打包完成的 dist文件夹放到 nginx的 html文件夹里面 ,然后修改nginx.conf文件

在命令行启动 start nginx

然后就可以进去,主要是在云服务器中 要下载 mysql和redis 这两个要启动

这就是前端静态代码的运行

项目难点

回答时,可以采用 STAR 法则

  • Situation(情境):简要介绍项目的背景和目标。
  • Task(任务):明确自己在项目中的职责和任务。
  • Action(行动):详细描述为实现目标所采取的具体行动和技术手段。
  • Result(结果):重点阐述项目取得的成果和带来的价值。

1.数据库设计

Situation(情境)

民航发动机在飞行过程中会产生大量的一秒一次(实时)的数据。这些数据用来计算减推比例和排气裕度,项目的目标是建立一个高效的数据存储和管理系统,以便对不同发动机和航班的数据进行存储、记录、分析和可视化。

Task(任务)

怎么样去建立数据库,可以灵活写入、快速查询的数据库架构。

Action(行动)

在mysql的数据库的设计中,包括

1.飞机信息表,记录航班号,起飞时间、降落时间、飞机号、飞机信息、发动机类型

2.发动机数据表,记录发动机每秒的数据,然后主键是ID,外键是航班号

在发动机数据表中使用flight_id作为外键,引用飞机信息表中的flight_id,以确保每条发动机数据都关联到特定的飞机。然后从特定的飞机信息表上可以得到 飞机信息

优化:

1.在写入的时候批量写入,而不是每秒直接写入数据库,1000条左右

2.对经常需要查询的字段 比如说航班号啊,时间加入索引,优化时间

3.上面分数据库的设计

4.定义外键约束。(缺点就是维护起来比较困难)

Result(结果)

执行起来更加迅速,数据库结构更加清晰。

2.数据库和redis缓存不一致的问题

Situation(情境)

民航发动机在飞行过程中会产生大量的一秒一次(实时)的数据。数据更新mysql数据库的时候和缓存不一致的情况。或者是mysql和redis的分布式事务的问题

Task(任务)

怎么保证数据和缓存的一致性

Action(行动)

image-20240817183541276

Result(结果)

保证了一致性

3.多线程插入民航发动机的数据

民航公司,会定时的 批量的 传输一部分的 发动机数据 上万条 数据库化持久化操作

提升速度 多线程:

事务失效:其他的怎么去快速感知,然后解决。

1.编程式事务:定义一个全局的事务管理器,让所有子线程都用这一个事务管理器。缺点:还是串行操作数据库

2.创建线程池:

每个线程处理 2000条数据

使用count down latch image-20240909141009461


文章作者: 索冀峰
文章链接: http://suojifeng.xyz
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 索冀峰 !
  目录