项目生命周期概要记录
项目背景
项目技术栈选型
项目初始化构建
项目环境
DB-Mysql
1. 本地Mysql 的安装
# 查看可安装的MySQL信息
brew search mysql
# 使用command 进行安装
brew install mysql@5.7
2.环境变量的配置
We've installed your MySQL database without a root password. To secure it run:
mysql_secure_installation
MySQL is configured to only allow connections from localhost by default
To connect run:
mysql -uroot
mysql@5.7 is keg-only, which means it was not symlinked into /usr/local,
because this is an alternate version of another formula.
If you need to have mysql@5.7 first in your PATH, run:
echo 'export PATH="/usr/local/opt/mysql@5.7/bin:$PATH"' >> ~/.zshrc
For compilers to find mysql@5.7 you may need to set:
export LDFLAGS="-L/usr/local/opt/mysql@5.7/lib"
export CPPFLAGS="-I/usr/local/opt/mysql@5.7/include"
To restart mysql@5.7 after an upgrade:
brew services restart mysql@5.7
Or, if you don't want/need a background service you can just run:
/usr/local/opt/mysql@5.7/bin/mysqld_safe --datadir=/usr/local/var/mysql
初始化数据库
- 创建用户
# 创建用户
create user if not exists xxxx identified by 'xxx'
# 查看是否成功创建
SELECT user, host FROM mysql.user
2. 创建数据库
CREATE {DATABASE | SCHEMA} [IF NOT EXISTS] database_name
[CHARACTER SET charset_name]
[COLLATE collation_name]
[ENCRYPTION {'Y' | 'N'}]
CREATE DATABASE IF NOT EXISTS dataaccess
CHARACTER SET utf8
COLLATE utf8_bin
# 查看创建的数据库是否成功
show databases;
3. 授权
GRANT privilege_type [,privilege_type],..
ON privilege_object
TO user_account;
grant all on dataaccess.* to xxxx;
# 查看授权对象是否成功
show grants for 'xxxx'
- 使用sql dump 初始化数据库
# 当存在多个database 时,use command 切换到指定的数据库
use databasexxx
# 使用source 命令加载sql dump
source xxxdump.sql
# 使用 show tables 查看所构建的表
show tables;
5.使用csv进行数据导入
LOAD DATA LOCAL INFILE 'data.csv'
INTO TABLE tablename
FIELDS TERMINATED BY ','
ENCLOSED BY '"'
LINES TERMINATED BY '\n'
IGNORE 1 ROWS;
#shell 脚本一括导入
#!/bin/bash
# 数据库配置
DB_HOST=localhost
DB_USERNAME=root
DB_PASSWORD=password
DB_NAME=mydb
# 要导入的目录
DATA_DIR=/path/to/csv/files
# 遍历目录,找到所有的 csv 文件
for file in "$DATA_DIR"/*.csv; do
if [[ -f "$file" ]]; then
# 获取表名,将文件名中的扩展名 .csv 去掉,其中的 basename 获取文件的名称
table=$(basename "$file" .csv)
# 构建 SQL 语句,注意替换字段名和表名
sql="LOAD DATA LOCAL INFILE '$file' INTO TABLE $table FIELDS TERMINATED BY ',' ENCLOSED BY '\"' IGNORE 1 LINES;"
# 执行 SQL 语句
mysql -h "$DB_HOST" -u "$DB_USERNAME" -p"$DB_PASSWORD" "$DB_NAME" -e "$sql"
# 输出已导入的文件名
echo "Imported $file to $table"
fi
done
MySql安装问题
问题1 mysql 初始化
1.事象:CREATE {DATABASE | SCHEMA} [IF NOT EXISTS] database_name [CHARACTER SET charset_name] [COLLATE collation_name] [ENCRYPTION {'Y' | 'N'}]
执行 mysql_secure_installation 时提示Can't connect to local MySQL server through socket '/tmp/mysql.sock
- 直接原因:在制定目录下没有找到对应的文件
mysql.sock - 根本原因:多次安装MySQL,卸载是,有部分文件为完全卸载
- 解决方案:
- 4.1 使用commond
find / -name 'mysql.sock'确定是否有该文件存在 - 4.2 若4.1实施后文件不存在文件
mysql.sock - 4.3 使用指令 mysql.server start mysql -h localhost -u root -p
- 4.1 使用commond
- 执行command
mysql.server start时显示success
问题2 使用source 命令同步表数据
- 事象: 在目录下存在多个表导出的对应的数据文件xxx.sql 使用命令
source /path/all.sql同步数据时,显示 Error Failed open the sql file - 直接原因:
all.sql不能被识别 - 根本原因: 指令中所描述的文件不存在
- 解决方案:
- 构建单独文件
all.sql - 在
all.sql文件中添加所有的要导入的sql 文件source /path/xxxx.sql - 重新执行
source /path/all.sql
- 构建单独文件
问题3 dbever 链接mysql database时报错
- 事象: 启动mysql服务后,使用dbever 链接数据库时,提示
Public key Retrival is not allowed - 直接原因:
- 根本原因:
- 解决方案:
- 修改数据库连接配置在数据库后添加
? allowPublicKeyRetrieval=true
- 修改数据库连接配置在数据库后添加
问题4 数据迁移
- 事象:表中设定了
auto increment字段其对应的数据导入后与导入前不一致。 - 直接原因:使用insert sql 的方式实施数据导入时其表定义中被设定了
auto increment的字段值依据其特性进行了自增 - 根本原因:insert 语句时触发其定义的属性特征
auto increment属性 - 解决方案: 通过csv 文件进行数据的导入可避免其自增属性字段的值导入前后不一致。
问题5 使用本地csv文件初始化数据库数据
-
事象:编辑shell 脚本导入csv时,显示
![图片转存失败,建议将图片保存下来直接上传 ERROR 3948 (42000) at line 1: Loading local data is disabled; this must be enabled on both the client and server sides (https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/fa8936bdf4ea46e182b9b2002d73519f~tplv-k3u1fbpfcp-zoom-1.image) (https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9f4cdd3bb3464a9f9e9e442bc5c19c79~tplv-k3u1fbpfcp-zoom-1.image)]() -
原因:错误提示是因为客户端和服务器端都没有启用本地数据加载(Local Data Loading)。为保障安全性,MySQL默认禁止从客户端加载本地数据。
-
解决方案:
1. 进入 MySQL 的配置文件my.cnf。 2. 在mysqld]段中增加以下配置:local-infile=1。 3. 保存并退出配置文件。 4. 重启 MySQL 服务 5. 运行定义的shell
#shell 脚本一括导入
#!/bin/bash
# 数据库配置
DB_HOST=localhost
DB_USERNAME=aiuser
DB_PASSWORD=Pcitc@Ai23$
DB_NAME=dataaccess
# 要导入的目录
DATA_DIR=./db-csv/ai-data
# 遍历目录,找到所有的 csv 文件
for file in "$DATA_DIR"/*.csv; do
if [[ -f "$file" ]]; then
# 获取表名,将文件名中的扩展名 .csv 去掉,其中的 basename 获取文件的名称
table=$(basename "$file" _202304281253.csv)
# 构建 SQL 语句,注意替换字段名和表名
sql="LOAD DATA LOCAL INFILE '$file' INTO TABLE $table FIELDS TERMINATED BY ',' ENCLOSED BY '"' IGNORE 1 LINES;"
# 执行 SQL 语句
mysql -h "$DB_HOST" -u "$DB_USERNAME" -p"$DB_PASSWORD" "$DB_NAME" -e "$sql"
# 输出已导入的文件名
echo "Imported $file to $table"
fi
done
DB-redis 本地
redis 中开启密码设定
- 查找redis.conf 文件所在目录
find / -name redis.conf - 修改requirepass的值 vim redis .conf 修改requirepass的值。
- redis 的常用客户端
Another redis Desktop Manager
本地FlyWay
项目集成
- 添加依赖
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-maven-plugin</artifactId>
</dependency>
2. 配置连接信息
flyway:
#开启
enabled: true
#当迁移时发现目标schema非空,而且带有没有元数据的表时,是否自动执行基准迁移,默认false.
baseline-on-migrate: true
#sql脚本位置
locations: db/migration
schemas: dataaccess
#编码
encoding: UTF-8
# 用户密码
password: Pcitc@Ai23$
# 用户名
user: aiuser
# 数据库连接url
url: jdbc:mysql://127.0.0.1:3306/dataaccess
3. 自定义FlywayConfig
@Configuration
@EnableConfigurationProperties(FlywayProperties.class)
public class FlywayConfig {
@Autowired
private FlywayProperties flywayProperties;
@Bean(initMethod = "migrate")
public Flyway flyway() {
System.out.println(flywayProperties.getLocations().get(0));
return new Flyway(Flyway.configure()
.dataSource(flywayProperties.getUrl(), flywayProperties.getUser(), flywayProperties.getPassword())
.locations(flywayProperties.getLocations().get(0)).baselineOnMigrate(true).encoding("UTF-8"));
}
}
5. 设定sql文件 * V-001_description.sql
集成问题
问题1 No migrations found. Are your locations set up correctly?
- 事象:flyway 集成后,项目成功启动后,log 中输出上述信息,对应的sql 文件未执行
- 直接原因:
- flyway 插件未发现对应的sql
- 根本原因:
- 自定义的FlywayConfig 中对于sql 所在路径未明确其配置
- 提供的sql文件的首字母默认应该为大写的
V。
- 解决方案 初始化自定义Flyway 的bean时,通过读取配置中的信息从而显式的配置locations的sql所在位置。
- 补充信息
flyway.baseline-description对执行迁移时基准版本的描述.
flyway.baseline-on-migrate当迁移时发现目标schema非空,而且带有没有元数据的表时,是否自动执行基准迁移,默认false.
flyway.baseline-version开始执行基准迁移时对现有的schema的版本打标签,默认值为1.
flyway.check-location检查迁移脚本的位置是否存在,默认false.
flyway.clean-on-validation-error当发现校验错误时是否自动调用clean,默认false.
flyway.enabled是否开启flywary,默认true.
flyway.encoding设置迁移时的编码,默认UTF-8.
flyway.ignore-failed-future-migration当读取元数据表时是否忽略错误的迁移,默认false.
flyway.init-sqls当初始化好连接时要执行的SQL.
flyway.locations迁移脚本的位置,默认db/migration.
flyway.out-of-order是否允许无序的迁移,默认false.
flyway.password目标数据库的密码.
flyway.placeholder-prefix设置每个placeholder的前缀,默认${.
flyway.placeholder-replacementplaceholders是否要被替换,默认true.
flyway.placeholder-suffix设置每个placeholder的后缀,默认}.
flyway.placeholders.[placeholder name]设置placeholder的value
flyway.schemas设定需要flywary迁移的schema,大小写敏感,默认为连接默认的schema.
flyway.sql-migration-prefix迁移文件的前缀,默认为V.
flyway.sql-migration-separator迁移脚本的文件名分隔符,默认__
flyway.sql-migration-suffix迁移脚本的后缀,默认为.sql
flyway.tableflyway使用的元数据表名,默认为schema_version
flyway.target迁移时使用的目标版本,默认为latest version
flyway.url迁移时使用的JDBC URL,如果没有指定的话,将使用配置的主数据源
flyway.user迁移数据库的用户名
flyway.validate-on-migrate迁移时是否校验,默认为true.
问题2 Unexpected error re. non-empty schemas
-
事象:启动项目后,程序异常终了,提示
non-empty schemas -
直接原因:
- flyway运行时,未找到对应的
flyway_schema_history表信息
- flyway运行时,未找到对应的
-
根本原因:
- flyway 首次运行时,通过配置本可以生成对应的版本信息表,但因是后续集成,未能自动构建
-
解决方案
- 手动创建起对应的版本信息表
CREATE TABLE `flyway_schema_history` (
`installed_rank` int NOT NULL,
`version` varchar(50) COLLATE utf8mb3_bin DEFAULT NULL,
`description` varchar(200) COLLATE utf8mb3_bin NOT NULL,
`type` varchar(20) COLLATE utf8mb3_bin NOT NULL,
`script` varchar(1000) COLLATE utf8mb3_bin NOT NULL,
`checksum` int DEFAULT NULL,
`installed_by` varchar(100) COLLATE utf8mb3_bin NOT NULL,
`installed_on` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`execution_time` int NOT NULL,
`success` tinyint(1) NOT NULL,
PRIMARY KEY (`installed_rank`),
KEY `flyway_schema_history_s_idx` (`success`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_bin;
# 构建应的索引
CREATE INDEX flyway_schema_history_s_idx ON flyway_schema_history (success)
使用docker构建项目环境
docker 准备基础
- 切换 docker镜像
- 使用command
docker info确认当前docker repository的信息 - 在docker destop 中的设置项中选中
docker engine补充镜像信息
"registry-mirrors": [
"https://docker.mirrors.ustc.edu.cn",
"https://hub-mirror.c.163.com"
]
- 使用command 确认是否镜像地址设定成功
- 创建docker-compose.yml 用于构建项目以来的mysql与redis环境
version: '3'
services:
mysql:
image: mysql:8.0.32
ports:
- "3306:3306"
volumes:
- ./mysql_data:/var/mysql_db_data
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: dataaccess
MYSQL_USER: aiuser
MYSQL_PASSWORD: Pcitc@Ai23$
redis:
image: redis:latest
ports:
- "6379:6379"
command:
--requirepass "Pcitc@22p"
## --requirepass 开通redis的校验
## volumes 关联绑定
3. 使用command运行docker compose 启动
* docker compose up -d 后台启动启动
4. 初始化DB
1. 使用command 进入mysql 命令窗体
docker-compose exec mysql mysql -u root -p
2. 创建DB,用户,并授权
3. 进入mysql的容器中,并使用指令source xxx.sql初始化db数据源
使用eureka进行服务注册
实现步骤
-
构建服务注册中心
- 添加项目依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>- 配置服务中心
server: port: 8761 eureka: client: registerWithEureka: false fetchRegistry: false3. 使用注解激活配置
@SpringBootApplication @EnableEurekaServer public class DataManagementEurekaApplication { public static void main(String[] args) { SpringApplication.run(DataManagementEurekaApplication.class, args); } } -
构建客户端
- 项目依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>2. 客户端配置
spring: application: name: multiple-data eureka: client: serviceUrl: defaultZone: ${EUREKA_URI:http://localhost:8761/eureka} instance: preferIpAddress: true server: port: 80815. 使用注解激活配置
@SpringBootApplication @EnableEurekaClient public class MultipleDataApplication { public static void main(String[] args) { SpringApplication.run(MultipleDataApplication.class, args); } }
测试
单体测试
-
Controller -MockMvc
MockMvc是SpringFramework的一部分,用于对MVC控制器进行单元测试。它提供了一个模拟的Servlet环境(MockServletContext),使开发人员可以方便地测试SpringMVC控制器的请求-响应流程。在应用程序开发中,MockMvc可用于测试SpringMVC的各种功能和特性,包括请求参数、session、cookie、查询字符串 -
测试基知识扩展.
2.1. Mockito.mock VS mock() VS mockbean
集成测试
性能测试
- JMeter:JMeter 是一个开源的性能测试工具,使用 Java 编写。它可以模拟多种负载类型,包括 HTTP、FTP、SMTP、SOAP、REST 等,并生成各种报告,从而帮助开发人员、测试人员和运维人员评估应用的性能、稳定性和可扩展性等各方面指标。
- Apache Bench:也称为 ab,是一个小型的 HTTP 负载测试工具,可以通过命令行运行。可以同时测试多个并发连接,生成简单的统计数据和基准测试结果。
- Gatling:Gatling 是另一个开源的性能测试工具,可以通过编写 Scala 代码来定义测试场景,并以 DSL 的方式模拟复杂的行为和场景。与 JMeter 不同的是,Gatling 使用 Akka 引擎来模拟并发用户,可以在运行时获取实时数据,并支持多种结果格式。
- VisualVM:VisualVM 是 Java Development Kit (JDK) 附带的一种性能监控和诊断工具。它可以实时监测正在运行的 Java 应用程序,并提供图形化的界面、堆栈跟踪、CPU 利用率、内存占用率等详细数据,用于分析应用程序的性能瓶颈。
DATAX
datax 的集成
问题一览
-
mysql的writer 与reader 的json 模版配置问题
-
事象:
- 运行job 时提示错误
ERROR Engine - 经DataX智能分析,该任务最可能的错误原因是: com.alibaba.datax.common.exception.DataXException: Code:[Framework-03], Description:[DataX引擎配置错误,该问题通常是由于DataX安装错误引起,请联系您的运维解决 .]. - Job运行速度必须设置
- 运行job 时提示错误
-
直接原因
配置的模版文件错误{ "job": { "content": [ { "reader": { "name": "mysqlreader", "parameter": { "username": "aiuser", "password": "Pcitc@Ai23", "column": [ "da_data_source_id", "da_data_source_name" ], "connection": [ { "table": [ "da_data_source" ], "jdbcUrl": [ "jdbc:mysql://localhost:3306/dataaccess?useSSL=false" ] } ] } }, "writer": { "name": "mysqlwriter", "parameter": { "username": "aiuser_target", "password": "Pcitc@Ai23", "column": [ "da_data_source_id", "da_data_source_name" ], "connection": [ { "jdbcUrl": "jdbc:mysql://127.0.0.1:3306/dataaccesstarget?useSSL=false", "table": [ "da_data_source" ] } ] } } } ] } } -
根本原因
模版文件中没有明确定义其job 的运行参数
{ "job": { "setting": { "speed": { "channel":1 }, "errorLimit": { "record": 0, "percentage": 0.02 } }, "content": [ { "reader": { "name": "mysqlreader", "parameter": { "username": "aiuser", "password": "Pcitc@Ai23$", "column": [ "da_data_source_id", "da_data_source_name" ], "connection": [ { "table": [ "da_data_source" ], "jdbcUrl": [ "jdbc:mysql://localhost:3306/dataaccess?useSSL=false" ] } ] } }, "writer": { "name": "mysqlwriter", "parameter": { "username": "aiuser_target", "password": "Pcitc@Ai23$", "column": [ "da_data_source_id", "da_data_source_name" ], "connection": [ { "jdbcUrl": "jdbc:mysql://127.0.0.1:3306/dataaccesstarget?useSSL=false", "table": [ "da_data_source" ] } ] } } } ] } }5. 解决方案
- 影响与总结
-
技术优化
问题
背景:前端接口调用后台datax,实现数据的导入
-
事象:接口提示响应超时
-
直接原因:后台逻辑处理较为复杂,处理时长大于前端接口默认定义的
10s -
根本原因:实现方案考虑不足,未针对数据导入过程中需要的时长进行预估,同时未考虑对应的解决方案
3.1 数据导入时耗时处理两大部分
🟥:数据导入脚本文件的构建
🟥:数据导入任务的运行 -
解决方案
4.1 拆分处理逻辑,分别将构建数据导入脚本文件与数据导入任务执行作为两个不同事件驱动
4.2 针对耗时处理的流程配置其作为新的线程运行,即就是异步执行 -
具体的实现:SpringBoot 中配置异步方法,以及使得异步方法生效
5.1. 项目中启用异步方法:@EnableAsync@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class }) // springboot 开启异步生效 @EnableAsync public class PcitcDataAccessAdminApplication { public static void main(String[] args) { // System.setProperty("spring.devtools.restart.enabled", "false"); SpringApplication.run(PcitcDataAccessAdminApplication.class, args); System.out.println("^V^ 人工智能数据接入服务平台-后台管理启动成功! ^V^ \n"); } }5.2 在异步方法上使用注解
@Ansy@Async public void dataImport(String jobJsonFile) { logger.info("dataImport is start"); // 依据传入的参数PYTHON3 构建commandLine 对象 CommandLine commandLine = new CommandLine(super.PYTHON); // 添加命令行的可执行文件参数 commandLine.addArgument(super.getRootPath().concat(super.PYTHON_BIN)); // 添加json 脚本文件 commandLine.addArgument(super.getRootPath().concat(super.JSON_FILE_DIR).concat(jobJsonFile)); // 初始化命令行执行器 Executor executor = new DefaultExecutor(); // 命令执行中的监视 ExecuteWatchdog watchdog = new ExecuteWatchdog(60000); executor.setWatchdog(watchdog); try { logger.info("dataImport is end"); executor.execute(commandLine); } catch (IOException e) { logger.error("数据任务导入失败{}",jobJsonFile); throw new RuntimeException(e); } }