感慨&概述
- 在使用minio(容器部署方式)的过程中,由于使用容器时间和本地时间不同,然后寻求解决方法,花费了很多时间,然后才有以下的收获和整理。整个过程耗费不少时间。
- 本文章对于
bitnami/minio:latest
和minio/minio
在问题二:minio修改时间和本地查询结果不一致上均适用。在问题一:minio容器和本地容器时间不一致上bitnami/minio:latest
在部署时,使用TZ=Asia/Shanghai
即可解决问题。
补充:MINIO_REGION和容器时间的关系
- region描述的是服务器的物理位置,默认是us-east-1(美国东区1),也是亚马逊S3的默认区域。可以通过
MINIO_REGION
环境变量进行修改 - 修改
MINIO_REGION
并不能解决,容器时间和本地时间问题,也不能解决文件最后修改时间在web控制台和java查询时间不一致的问题。这个可能是为了minio容器部署时,划分机器用的(如果错误,请大佬指导) - 当然,注意如果设置了
MINIO_REGION
请在java代码中配置同样的时区设置,否则导致代码运行失败,因为时区相差太大无法正常连接到minio - 当启动MinIO Docker容器时,可以通过设置环境变量
MINIO_REGION
来指定区域(如无需要,建议不要配置)
docker run -p 9000:9000 --name minio1 \
-e "MINIO_ROOT_USER=minioadmin" \
-e "MINIO_ROOT_PASSWORD=minioadmin" \
-e "MINIO_REGION=cn-north-1" \
-e "TZ=Asia/Shanghai" \
minio/minio server /data
@Configuration
public class Config {
//链式编程 构建MinioClient对象
@Bean
public MinioClient minioClient(){
return MinioClient.builder()
.region("cn-north-1") //注意一致
.endpoint("http://192.168.1.18:9000")
.credentials("minioadmin","minioadmin")
.build();
}
}
问题一:minio容器和本地容器时间不一致
问题说明
原因探究
- 这里不再详述探究的过程,简略地给出研究思路、方法。
- 研究思路:查看容器中使用的时区,将其修改为东八区。
- 研究过程和结果:在该版本容器内查看没有常见的linux时区设置的目录和文件,不过通过复制本地的文件到,minio容器中重新设置时区,可以成功解决容器时区和本地时区不一致的问题。
解决方法
- 查看容器信息
# 查看运行中minio容器的名称
docker ps
- 例如:运行中的MInio容器ID为 826ab07a0a42,名称为 minio
[root@yang ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
826ab07a0a42 minio/minio:RELEASE.2024-04-06T05-26-02Z "/usr/bin/docker-ent…" 14 minutes ago Up 14 minutes 0.0.0.0:9001->9000/tcp, :::9001->9000/tcp, 0.0.0.0:9000->9001/tcp, :::9000->9001/tcp minio
- 进入容器创建时区文件目录
# 进入容器 下面的两个命令任选其一即可
# 命令一
docker exec -it docker_Minio_name bash # 上一步查询的信息 minio
# 命令二
docker exec -it docker_Minio_ID bash # 上一步查询的信息 826ab07a0a42
# 在容器内执行命令:创建时区文件目录
mkdir -p /usr/share/zoneinfo/Asia/
# 推出容器命令
exit
- 复制本地时区文件到容器
# 复制本地的时区配置文件到容器中
[root@localhost ~]# docker cp /usr/share/zoneinfo/Asia/Shanghai docker_Minio_name:/usr/share/zoneinfo/Asia/
# 操作成功演示
[root@yang ~]# docker cp /usr/share/zoneinfo/Asia/Shanghai minio:/usr/share/zoneinfo/Asia/
Successfully copied 2.56kB to minio:/usr/share/zoneinfo/Asia/
- 再次进入容器内部,修改本地时间
# 再次进入容器 下面的两个命令任选其一即可
# 命令一
docker exec -it docker_Minio_name bash # 上一步查询的信息 minio
# 命令二
docker exec -it docker_Minio_ID bash # 上一步查询的信息 826ab07a0a42
# 定义本地的时区配置文件
ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
# 演示
bash-5.1# ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
结果验证
- 使用命令查看容器和本机时间是否一致
# 验证解决结果
bash-5.1# date
Sat May 4 23:58:36 CST 2024
bash-5.1# exit
exit
[root@yang ~]# date
2024年 05月 04日 星期六 23:58:43 CST
- Minio上传文件测试时间是否生效
- 在观察页面显示正常,但是遗憾的是,如果使用java minio客户端查询还是显示相差八小时。(这是第二个问题)
问题二:minio修改时间和本地查询结果不一致
具体问题
- minio在web管理显示的文件最后修改时间和本地一致,然后使用java代码查询出来的时间和本地时间差八个小时
- 查询代码和结果
//查询代码
@Test
void testObjectExists() throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
StatObjectResponse response = minioClient.statObject(
StatObjectArgs.builder()
.bucket("test02")
.object("image.jpg")
.build()
);
System.out.println(response);
}
//查询结果
ObjectStat{bucket=test02, object=image.jpg, last-modified=2024-05-04T06:53:55Z, size=563102}
原因探究
- MinIO存储文件的元数据时,通常会使用UTC(协调世界时间)来记录时间戳。这意味着无论实际的物理服务器位置如何,时间都会按照UTC来记录。但在Web管理界面中,MinIO会根据浏览器的时区设置自动调整显示时间,使其看起来与本地时间一致。
- 当使用Java代码通过MinIO的API查询文件的最后修改时间时,如果没有正确处理时区,Java可能会默认将UTC时间作为本地时间(即不进行时区转换)来处理。导致看到的时间比实际的本地时间少8小时(中国大陆时间为UTC+8)。
解决办法
- Java代码在处理时间时将UTC时间转换为本地时间(虽然麻烦,但只能这么解决问题)
时间转化工具类
import java.lang.annotation.Retention;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
public class TimeConverter {
// 使用自定义格式化模式
private static final DateTimeFormatter isoFormatter = new DateTimeFormatterBuilder()
.append(DateTimeFormatter.ISO_LOCAL_DATE)
.appendLiteral('T')
.append(DateTimeFormatter.ISO_LOCAL_TIME)
.optionalStart()
.appendOffsetId()
.optionalEnd()
.toFormatter();
private static final DateTimeFormatter customFormatter = new DateTimeFormatterBuilder()
.append(DateTimeFormatter.ISO_LOCAL_DATE)
.appendLiteral('T')
.append(DateTimeFormatter.ISO_LOCAL_TIME)
.appendLiteral('Z') // 添加字面值 'Z' 表示时区偏移量
.toFormatter();
/**
* 将UTC时间转换为指定时区的时间,格式保持ISO-8601 默认时区为东八区
* @param utcTimeString UTC时间字符串(ISO-8601格式)
* @return 指定时区的时间字符串(ISO-8601格式)
*/
public static String convertUtcToLocal(String utcTimeString) {
String zoneIdString="Asia/Shanghai";
return convertUtcToLocal(utcTimeString,zoneIdString);
}
/**
* 将UTC时间转换为指定时区的时间,格式保持ISO-8601
* @param utcTimeString UTC时间字符串(ISO-8601格式)
* @param zoneIdString 时区ID,如"Asia/Shanghai"
* @return 指定时区的时间字符串(ISO-8601格式)
*/
public static String convertUtcToLocal(String utcTimeString, String zoneIdString) {
Instant utcTime = Instant.parse(utcTimeString);
ZonedDateTime localTime = utcTime.atZone(ZoneId.of(zoneIdString));
DateTimeFormatter formatter = customFormatter.withZone(ZoneId.of(zoneIdString));
return formatter.format(localTime);
}
/**
* 将本地时间转换为UTC时间
* @param localTimeString 本地时间字符串(ISO-8601格式)
* @param zoneIdString 时区ID,如"Asia/Shanghai"
* @return UTC时间字符串(ISO-8601格式)
*/
public static String convertLocalToUtc(String localTimeString) {
String zoneIdString="Asia/Shanghai";
return convertLocalToUtc(localTimeString,zoneIdString);
}
/**
* 将本地时间转换为UTC时间
* @param localTimeString 本地时间字符串(ISO-8601格式)
* @param zoneIdString 时区ID,如"Asia/Shanghai"
* @return UTC时间字符串(ISO-8601格式)
*/
public static String convertLocalToUtc(String localTimeString, String zoneIdString) {
ZonedDateTime localTime = ZonedDateTime.parse(localTimeString, customFormatter.withZone(ZoneId.of(zoneIdString)));
Instant utcTime = localTime.toInstant();
return isoFormatter.format(utcTime.atZone(ZoneId.of("UTC")));
}
// 测试主方法
public static void main(String[] args) {
String utcTime = "2024-05-07T04:20:00Z";
System.out.println("原UTC时间: " + utcTime);
System.out.println("转换为北京时间: " + convertUtcToLocal(utcTime));
String shanghaiTime = "2024-05-20T20:20:00Z";
System.out.println("从北京时间转换回UTC时间: " + convertLocalToUtc(shanghaiTime));
}
}
调用测试和验证
- 测试类代码
@SpringBootTest
class MinioApplicationTests {
@Resource
private MinioClient minioClient;
/**
* 判断文件是否存在
*/
@Test
void testObjectExists() throws Exception {
StatObjectResponse response = minioClient.statObject(
StatObjectArgs.builder()
.bucket("test02")
.object("image.jpg")
.build()
);
System.out.println("修改前文件信息:"+response);
String localLastModifiedTime = TimeConverter.convertUtcToLocal(response.lastModified().toString());
ZonedDateTime localLastModified = ZonedDateTime.parse(localLastModifiedTime);
//通过反射 修改文件信息 不影响minio存储的文件信息
try {
Field lastModifiedField = response.getClass().getDeclaredField("lastModified");
lastModifiedField.setAccessible(true);
lastModifiedField.set(response, localLastModified);
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
System.out.println("修改后文件信息:"+response);
}
}
- 测试结果
修改前文件信息:ObjectStat{bucket=test02, object=image.jpg, last-modified=2024-05-04T06:53:55Z, size=563102}
修改后文件信息:ObjectStat{bucket=test02, object=image.jpg, last-modified=2024-05-04T14:53:55Z, size=563102}
上传文件说明
- 在查询文件信息时,需要将文件修改时间进行本地化转化,但是在上传文件的时候,我没找到直接设置文件修改时间的配置。所以,上传文件的时候,正常上传就行。
/**
* 上传文件
*/
@Test
void testObjectUpload() throws Exception {
ObjectWriteResponse response = minioClient.uploadObject(
UploadObjectArgs.builder()
.bucket("test02")
.object("image3.jpg")
.filename("src/main/resources/picture/image.jpg")
.build()
);
System.out.println(response);
}
- 上传成功后,在web控制台正常显示本地时间。查询时,使用工具类就可以了!