2023年4月

1. 问题表现

经常出现进程崩溃,崩溃堆栈较为底层

image.png
原因基本上都是 read write memory 时触发了异常,盘查后初步怀疑是内存写坏了。

2. 排查期

UE 支持各种内存分配器:

  • TBB
  • Ansi
  • Jemalloc
  • Stomp
    还有自带的内存分配器:
  • Binned
  • Binned2
  • Binned3
    可以参考文章
    UE 中的内存分配器

    其中 Stomp 是引擎提供的排查内存写坏的工具之一,通过增加参数
    -stompmalloc
    可以让 UE 默认采用该内存分配器,启用了之后崩溃的第一现场就是内存写坏的代码地址。
    通过排查发现崩溃原因是遍历迭代器时删除元素后没有及时 continue,大致示例如下:
for(TArray<Actor*>::TIterator Iter(Actors); Iter; ++ Iter)
{
if (xxx)
{
Iter.RemoveCurrent(); //没有 continue
}

if (xxx)
{
Iter.RemoveCurrent();//没有 continue
}
}

当数组元素只剩一个时,如果触发了两次 RemoveCurrent,就会导致写到数组之外的内存空间,RemoveCurrent 的机制
会把后面的数组元素迁移到删除的位置上
,保证数据连贯。同时 RemoveCurrent 完毕后会自动把迭代器的下标前移一位。

image.png

3. Stomp 原理

3.1 内存覆盖

Stomp 其主要的功能是在写坏内存时可以马上捕获到第一现场。内存写坏了通常指程序在操作内存时写入了非法的数据或超出了内存分配的范围,导致程序出现错误或崩溃。这种情况通常被称为越界访问或非法访问内存。
大部分情况下有内存池的技术,且操作系统分配内存往往会向上按页对其分配,所以一时的内存越界读写有可能不会马上出现问题。而 Stomp 是在内存越界时就对其抛出异常。

3.2 实现原理

开启 Stomp 之后,内存分配基本上由
FMallocStomp::Malloc

FMallocStomp::Free
接管。

void* FMallocStomp::Malloc(SIZE_T Size, uint32 Alignment)
void FMallocStomp::Free(void* InPtr)

要做到写坏内存后能直接触发异常,需要在内存分配上做手脚,这里主要用到了两点:

  • 操作系统支持的 Pagefault 和 Page 权限控制
  • 哨兵机制
    Stomp 在给用户分配内存的时候会额外分配 2 个 Page 出来,分别在返回给用户的指针地址空间前后。当用户超出分配给他的内存上读写时,就会触发异常。其分配内存的流程大致如下:

image.png

这里有个问题是 FAllocationData 只有 32 个字节,但是其分配了一个 Page 给其使用,这里主要是由于分配内存都需要对齐 Page。到此内存分配完毕,接下来有 2 种情况:

  1. 从 Page2 写数据一直写到 Page3,由于 Page3 被标记为不可读不可写,因此一旦出现越界,就会直接抛出异常
  2. 从 Page1 写数据一直写到 Page0,由于 Page0 末端分配了一个 FAllocationData,因此一旦越界,哨兵值会被覆盖,当释放内存时
    FMallocStomp::Free
    就会对内存块的 FAllocationData 进行检查,一旦哨兵比对异常就抛出异常

image.png

LocalTime、LocalDate、LocalDateTime 区别

LocalTime、LocalDate、LocalDateTime是java8对日期、时间提供的新接口。
jdk1.8 之前的
SimpleDateFormat
是线程不安全的。
DateTimeFormatter
是线程安全的

  • LocalTime 用于
    时刻
    的计算(带有毫秒)
  • LocalDate 用于
    日期
    的计算
  • LocalDateTime 用于
    日期+时刻
    的计算
  • 额外补充 时间类型 默认格式:
    • Date
      Tue Feb 01 00:00:00 CST 2022
    • Timestamp
      2022-01-01 00:00:00.0
    • LocalDateTime
      2022-01-01T00:00:00.000

LocalTime、LocalDate、LocalDateTime 使用

now 获取当前 时刻、日期、时间

LocalTime now = LocalTime.now();
>>> 获取当前时刻: 10:20:00.856

LocalDate now = LocalDate.now();
>>> 获取当前日期: 2023-04-13

LocalDateTime now = LocalDateTime.now();
>>> 获取当前时间: 2023-04-13T17:29:29.357
  • LocalTime 获取当前时刻默认会带有毫秒,如果不需要毫秒的话,可以通过设置纳秒为0 保留秒
    1秒 = 十亿纳秒
    例如:
    LocalTime.now().withNano(0);
  • LocalDateTime 获取当前日期,默认
    toString
    会带有
    T
    用于区分
    日期

    时刻
    ,在项目中,可以通过
    全局序列化
    ,进行统一的时间格式输出为
    yyyy-MM-dd HH:mm:ss

of 获取指定 时刻、日期、时间

LocalTime ofTime = LocalTime.of(12, 0, 0);
>>> 获取指定时刻: 12:00

LocalDate ofTime = LocalDate.of(2023, 4, 13);
>>> 获取指定日期: 2023-04-13

LocalDateTime ofTime = LocalDateTime.of(2023,4,13,6,10,20,123);
>>> 获取指定时间: 2023-04-13T06:10:20.000000001
LocalDateTime ofTime = LocalDateTime.of(LocalDate.of(2023, 4, 13),LocalTime.of(12, 0, 0));
>>> 获取指定时间: 2023-04-13T12:00
  • LocalDateTime.of 的参数单位分别为 年、月、日、小时、分钟、秒、纳秒
  • LocaTime 常用常量
// 一天开始时的午夜时刻,“00:00”
LocalTime.MIDNIGHT
// 支持的最小时刻 “00:00” 这是一天开始时的时刻。
LocalTime.MIN
// 支持的最大时刻 “23:59:59.999999999” 这是一天结束时的时刻
LocalTime.MAX
// 中午的时刻 “12:00”
LocalTime.NOON
  • 一些比较特殊的获取方式,例如:
// 根据秒数 获取 时刻 例如:第 150 秒的时刻是 00:02:30 (相似方法同理)
LocalTime.ofSecondOfDay(150)
>>> 获取指定时刻: 00:02:30

// 获取指定年限 + 天数 得到日期,例如:获取2023年第120天的日期(相似方法同理)
LocalDate.ofYearDay(2023, 120);
>>> 获取2023年第120天的日期: 2023-04-30

plus || minus 增加或者减少

// 增加 1 星期(相似方法同理)
LocalDateTime.now().plusWeeks(1);
// 增加 1 天(相似方法同理)
LocalDate.now().plusDays(1);
// 增加 1 小时(相似方法同理)
LocalTime.now().plusHours(1);

LocalDateTime.now().plus(10L, ChronoUnit.DAYS);

// 与之相反的 minus 就是减少的意思 不再举例子说明

更改指定的 时间

// 直接改变 指定时刻
LocalTime.now().withHour(12);
>>> 09:57:23.505 -> 12:57:23.505

// 直接改变 指定日期
LocalDate.now().withDayOfMonth(2);
>>> 2023-04-14 -> 2023-04-02

// 直接改变 指定时间
LocalDateTime.now().withYear(2024);
>>> 2023-04-14T09:59:20.034 -> 2024-04-14T09:59:20.034
  • 其他
    with
    开头的方法大同小异,但要注意的是,如果改变的值是错误的时间,会报错的,例如:在2月份设置31天

isAfter || isBefore 比较大小

// 8:00
LocalTime time_8 = LocalTime.of(8, 0, 0);
// 9:00
LocalTime time_9 = time_8.plusHours(1);

boolean after = time_9.isAfter(time_8);
>>> 判断 9:00 是不是在 8:00 之后 >> true

boolean before = time_9.isBefore(time_8);
>>> 判断 9:00 是不是在 8:00 之前 >> false
  • isAfter || isBefore 是无法比较是否相等的,
    LocalDate

    LocalDateTime
    均有此方法,用法都一样

compareTo 时间比较

// 8:00
LocalTime time_8 = LocalTime.of(8, 0, 0);
// 9:00
LocalTime time_9 = time_8.plusHours(1);

int i = time_9.compareTo(time_8);
>>> i = 1

int i = time_8.compareTo(time_9);
>>> i = -1

int i = time_8.compareTo(LocalTime.of(8, 0, 0));
>>> i = 0
  • 此方法可对比出时间是否相等,假设 A.compareTo(B);
    • A > B = 1
    • A < B = -1
    • A = B = 0

LocalTime、LocalDate、LocalDateTime 相互转化

// LocalTime + LocalDate = LocalDateTime
LocalDateTime localDateTime = LocalTime.now().atDate(LocalDate.now());
LocalDateTime localDateTime = LocalDate.now().atTime(LocalTime.now());
LocalDateTime localDateTime = LocalDateTime.of(LocalTime.now(),LocalDate.now());

// LocalDateTime 转 LocalDate
LocalDate localDate = LocalDateTime.now().toLocalDate();

// LocalDateTime 转 LocalTime
LocalTime localTime = LocalDateTime.now().toLocalTime();

// 获取今日开始时间 2023-04-21T00:00
LocalDateTime localDateTime = LocalDate.now().atStartOfDay();
// 获取今日开始时间 2023-04-21T00:00
LocalDateTime startDateTime = LocalDateTime.of(LocalDate.now(), LocalTime.MIN);
// 获取今日结束时间 2023-04-21T23:59:59.999999999
LocalDateTime endDateTime = LocalDateTime.of(LocalDate.now(), LocalTime.MAX);

String 与 LocalTime、LocalDate、LocalDateTime 相互转化

主要使用
format

parse
进行转换,使用方法基本相同
使用
DateTimeFormatter.ofPattern()
定义时间格式,再进行转换
DateTimeFormatter
线程安全

format && parse

LocalTime.now().toString;
>>> 默认输出格式 10:50:25.323

LocalDate.now().toString()
>>> 默认输出格式 2023-04-14

LocalDateTime.now().toString();
>>> 默认输出格式 2023-04-14T15:59:40

// LocalTime 转 String 自定义输出格式,例如:**时**分**秒 该转化的 00 不会被省略
DateTimeFormatter localTimeFormat = DateTimeFormatter.ofPattern("HH时mm分ss秒");
String time = LocalTime.now().format(localTimeFormat);
>>> 09时11分00秒

// LocalDateTime 转 String
DateTimeFormatter localTimeFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String time = LocalDateTime.now().format(localTimeFormat);
>>> 2023-04-14 15:59:40

// String 转 LocalDateTime
LocalDateTime time = LocalDateTime.parse("2023-04-14 15:59:40", localTimeFormat);

Jackson 全局配置

在Stringboot 中,可自定义配置 Jackson 的序列化输出,使接口在 输入输出 统一规范
简单举个例子

    @Bean // 装载配置
    @Primary
    @ConditionalOnMissingBean
    public ObjectMapper objectMapper() {
        ObjectMapper mapper = create();
        log.info(">>>>> JackSon 全局设置成功,版本号:{}", mapper.version());
        return mapper;
    }

    private static ObjectMapper create() {
        ObjectMapper objectMapper = new ObjectMapper();
        // 创建自定义 时间转换 模板
        JavaTimeModule timeModule = new JavaTimeModule();
        
        // 定义统一的时间格式
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss", Locale.CHINA);
        // 序列化 添加 LocalDateTime 类 对应的时间格式 
        timeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(dateTimeFormatter));
        // 反序列化  支持时间戳
        timeModule.addDeserializer(LocalDateTime.class, new MillisOrLocalDateTimeDeserializer(dateTimeFormatter));
        
        // 定义统一的日期格式
        DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd", Locale.CHINA);
        // 序列化 添加 LocalDate 类 对应的日期格式
        timeModule.addSerializer(LocalDate.class, new LocalDateSerializer(dateFormatter));
        // 反序列化
        timeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(dateFormatter));
        
        // 注册自定义模板
        objectMapper.registerModules(createJavaTimeModules());
        return objectMapper;
    }

Date 与 LocalDate、LocalDateTime 相互转化

// Date 转 LocalDateTime
LocalDateTime localDateTime = new Date().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
// LocalDateTime 转 Date
Date date = Date.from(LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant());

// Date转LocalDate
LocalDate localDate = new Date().toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
// LocalDate 转 Date  需要先将 LocalDate 转 LocalDateTime
Date date= Date.from(LocalDate.now().atStartOfDay(ZoneId.systemDefault()).toInstant());

Long 与 LocalDate、LocalDateTime 相互转化

ZoneOffset.of("+8") 和 ZoneOffset.ofHours(8) 意义相同

long timeMillis = System.currentTimeMillis();

// 时间戳(Long) 转 LocalDateTime
LocalDateTime localDateTime = Instant.ofEpochMilli(timeMillis).atZone(ZoneOffset.ofHours(8)).toLocalDateTime();
// LocalDateTime 转 时间戳(Long) 秒级
Long second = LocalDateTime.now().toEpochSecond(ZoneOffset.ofHours(8));
// LocalDateTime 转 时间戳(Long) 毫秒级
Long milliSecond = LocalDateTime.now().toInstant(ZoneOffset.ofHours(8)).toEpochMilli();


// 时间戳(Long) 转 LocalDate
LocalDate localDate = Instant.ofEpochMilli(timeMillis).atZone(ZoneOffset.ofHours(8)).toLocalDate();
// LocalDate 转 时间戳(Long) 秒级
Long second =  LocalDate.now().atStartOfDay().toEpochSecond(ZoneOffset.ofHours(8));
// LocalDate 转 时间戳(Long) 毫秒级
Long milliSecond = LocalDate.now().atStartOfDay().toInstant(ZoneOffset.ofHours(8)).toEpochMilli();

常用时间操作方法

两个时间相差时间

Period.between(star,end)

统计 相差几年几个月几天
获得一个由两个日期之间的年数、月数和天数组成的周期,如果结束在开始之前,则此方法的结果可能是一个负周期。负号在每一年、每一个月和每一天都是一样的。

LocalDateTime star = LocalDateTime.of(2022, 3, 15, 16, 37, 10);
LocalDateTime end = LocalDateTime.of(2024, 4, 20, 16, 37, 10);
Period period = Period.between(star.toLocalDate(), end.toLocalDate());
System.out.println(">>> 两个时间相差:" + period.getYears() + "年" + period.getMonths() + "月" + period.getDays() + "日");

>>> 两个时间相差:2年1月5日

LocalDate.until

  • 方法1:
    Period until(ChronoLocalDate endDateExclusive);

    Period.between()
    的使用相同类似,统计 相差几年几个月几天,可参考上一个案例
  • 方法2:
    long until(Temporal endExclusive, TemporalUnit unit);
    支持指定单位(按年或按月或按天)分别统计
// ChronoUnit 可选择要计算的日期单位 年、月、日、小时、分钟 等等
LocalDateTime star = LocalDateTime.of(2024, 3, 20, 16, 37, 10);
LocalDateTime end = LocalDateTime.of(2024, 3, 21, 16, 37, 10);
long until = star.until(end, ChronoUnit.DAYS);
System.out.println(">>> 两个时间相差:" + until + "天");
>>> 两个时间相差:1天

// 使用 LocalDateTime 计算相隔日期,即使差了 1分钟 1毫秒 也不会计算1天
LocalDateTime star = LocalDateTime.of(2024, 3, 20, 16, 37, 10);
LocalDateTime end = LocalDateTime.of(2024, 3, 21, 16, 37, 9);
long until = star.until(end, ChronoUnit.DAYS);
System.out.println(">>> 两个时间相差:" + until + "天");
>>> 两个时间相差:0天

// 使用 LocalDate 计算相隔时差
LocalDate star = LocalDate.of(2024, 3, 20);
LocalDate end = LocalDate.of(2024, 3, 21);
long until = star.until(end, ChronoUnit.DAYS);
System.out.println(">>> 两个时间相差:" + until + "天");
>>> 两个时间相差:1天

Duration.between(star,end)

专业计算相隔时差,支持指定单位转化,天 到 纳秒 单位都支持
只能用 LocalDateTime

LocalDateTime star = LocalDateTime.of(2024, 3, 20, 16, 37, 10);
LocalDateTime end = LocalDateTime.of(2024, 3, 21, 16, 37, 10);
Duration duration = Duration.between(star, end);
System.out.println(">>> 两个时间相差:" + duration.toDays() + "天");
System.out.println(">>> 两个时间相差:" + duration.toHours() + "小时");
System.out.println(">>> 两个时间相差:" + duration.toMinutes() + "分钟");
System.out.println(">>> 两个时间相差:" + duration.toMillis() + "毫秒");
System.out.println(">>> 两个时间相差:" + duration.toNanos() + "纳秒");

>>> 两个时间相差:1天
>>> 两个时间相差:24小时
>>> 两个时间相差:1440分钟
>>> 两个时间相差:86400000毫秒
>>> 两个时间相差:86400000000000纳秒

TemporalAdjuster

TemporalAdjuster 是函数接口,在TemporalAdjusters 类中有很多预定义的实现。TemporalAdjuster仅有一个带Temporal对象参数的抽象方法adjustInto()。

TemporalAdjuster可以执行复杂的日期操作,例如,可以获得下一个星期日对于日期、当月的最后一天、下一年的第一天。当然也可以通过旧的java.util.Calendar api实现。不同的是,新api使用预定义的实现抽象出底层逻辑。

TemporalAdjusters 类中预定义实现

TemporalAdjusters类有很多预定义的static方法返回TemporalAdjuster对象,使用不同方式调节Temporal对象而与Temporal实现无关。

// 也可以使用 LocalDateTime 带时刻
LocalDate localDate = LocalDate.now(); 
// 当月第一天
localDate.with(TemporalAdjusters.firstDayOfMonth());
// 当月最后一天
localDate.with(TemporalAdjusters.lastDayOfMonth());
// 今年的第一天
localDate.with(TemporalAdjusters.firstDayOfYear());
// 今年的最后一天
localDate.with(TemporalAdjusters.lastDayOfYear());
// 下个月的第一天
localDate.with(TemporalAdjusters.firstDayOfNextMonth());
// 下一年的第一天
localDate.with(TemporalAdjusters.firstDayOfNextYear());

// 这个月的最后一个星期日
localDate.with(TemporalAdjusters.dayOfWeekInMonth(-1,DayOfWeek.SUNDAY));
// 这个月的倒数第二个星期日
localDate.with(TemporalAdjusters.dayOfWeekInMonth(-2,DayOfWeek.SUNDAY));
// 这个月的第一个星期日
localDate.with(TemporalAdjusters.dayOfWeekInMonth(-1,DayOfWeek.SUNDAY));
// 这个月的第二个星期日
localDate.with(TemporalAdjusters.dayOfWeekInMonth(-1,DayOfWeek.SUNDAY));

// 下个月的最后一个星期日
localDate.with(TemporalAdjusters.lastInMonth(DayOfWeek.SUNDAY));
// 上个星期五
localDate.with(TemporalAdjusters.previous(DayOfWeek.FRIDAY));
// 上个最近的星期五,包含今天的判断 如果今天星期五 则会返回今天日期
localDate.with(TemporalAdjusters.previousOrSame(DayOfWeek.FRIDAY));
// 下个星期一
localDate.with(TemporalAdjusters.next(DayOfWeek.MONDAY));
// 下个最近的星期一,包含今天的判断 如果今天是星期一 则会返回今天日期
localDate.with(TemporalAdjusters.nextOrSame(DayOfWeek.MONDAY));

继上篇
Qt5.15.0 升级至 Qt5.15.9 遇到的一些错误

篇幅有点长,先说解决方法,在编译静态库时加上 -qt-libjpeg,编译出 libjpeg 库后,在项目中使用 #pragma comment 链接即可


在我以为升级到 Qt5.15.9 后,运行没有什么问题时,问题就来了

在加载 jpeg 格式的图片时,出现了“长跳转已经运行”的错误

这个错误一般是由 setjmp 函数触发的,出现的可能的原因有以下几种(听听 chatgpt 的回答)


longjmp 是 C 标准库中的一个函数,用于跳转到之前通过 setjmp 函数设置的跳转点,并在跳转时返回指定的错误码。如果在调用 longjmp 函数时出现错误,通常会表现为程序异常终止、崩溃或死锁等问题。

longjmp 函数可能出现错误的原因和解决方法如下:

  1. 调用 setjmp 的函数已经返回

使用 longjmp 函数必须要在之前调用过 setjmp 函数并且还没有从 setjmp 函数返回时才能使用。如果在 setjmp 函数返回后再调用 longjmp 函数,就会导致未定义行为,可能会导致程序崩溃或死锁。解决方法是检查 longjmp 函数的调用时机,并确保它是在 setjmp 函数之后而且还没有从 setjmp 函数返回时调用的。

  1. 跳转目标不存在

当 longjmp 函数尝试跳转到之前设置的跳转点时,如果该跳转点已经不存在,就会导致错误。这种情况通常发生在跳转点所在的函数已经返回或者被释放时。解决方法是检查跳转点是否存在,并确保跳转点所在的函数仍然有效。

  1. 跨线程调用

longjmp 函数通常不适用于多线程程序。如果在一个线程中设置了跳转点,然后在另一个线程中调用 longjmp 函数来跳转到该点,就会导致未定义行为。解决方法是使用线程安全的异常处理机制来代替 longjmp 和 setjmp 函数。

  1. 其他原因

除了上述几种情况外,还有可能是其他一些原因导致 longjmp 错误。例如,内存泄漏、非法指针访问、堆栈溢出等问题都可能导致 longjmp 函数失败。解决方法是根据具体错误信息进行排查和修复。


我这边是触发 ERREXIT2 后而跳出的错误

为何有这种丑陋的报错呢?官方应该可以把这种报错 catch 下来,而不是让程序闪退,看看 llbjpeg-turbo 作者给出的解释

原始链接:
Possibility of non-unwinding error handling

简单说来,ERREXIT 是官方的 libjpeg 遗留下来的,代码很古老,至今没人修,而 libjpeg-turbo 只是包装了 libjpeg 库,这样加载更快,对 libjpeg 中

的 api 没有任何改变,他也可以帮忙包装下这个报错,只是要加钱

话说回来,我为何遇到 ERREXIT 呢?

那就不得不说 Qt 对 5.15 后续版本做出的一些改动了

见:
https://doc.qt.io/qt-5/qtgui-attribution-libjpeg.html

就是说 jpeg 要你自己去链接,我们不再帮你集成到 qjpeg.lib 中了,应该是因为协议问题

Independent JPEG Group License and BSD 3-Clause "New" or "Revised" License and zlib License.

既然问题找到了,那解决方法“应该”也能浮出水面了,对,打上双引号的应该

是我低估了这个问题,在我以为自己加个 libjpeg-turbo 的库之后就能万事大吉时,结果往往给你一个大嘴巴子

我用 vcpkg 包管理器添加了 libjpeg-turbo:x86-windows-static,程序编译通过,再没有出现 ERROR2019 的错误,但是加载 jpeg 图片还是会报错

我以为是 libjpeg-turbo 的库版本太高了,就查阅低版本的库,想通过 vcpkg 新出的版本控制来实现的,奈何水平有限,没弄出来,就去官网下载 2.1.3 的压缩包自己编译

QT5.15.9 版本更新中包括将 libjpeg-turbo 更新至 2.1.3

见:
https://code.qt.io/cgit/qt/qtreleasenotes.git/about/qt/5.15.9/release-note.md

编译出一个 lib 库后,再链接到程序中,还是会报错,嗯,先排除 libjpeg 版本问题

那就从堆栈下手吧,一层一层的剥开问题,看本质

qt 里要求 libjpeg-turbo 的 version 为 80,而 vcpkg 提供的所有 libjpeg-turbo 版本都是 62,在 jconfig.h 中查看 version

嗯,80 的为 qt 专属,这就解释了为啥触发了 ERREXIT2 了,顺便说一句,vcpkg 提供的库其实就是官方的库,libjpeg-turbo 不管是 2.1.5 还是 2.1.3,JPEG_LIB_VERSION 都是 62

因此我们只要编译一个 libjpeg 的 qt 三方库就行了

借助
这篇教程

使用 Qt Creator(我使用的 Qt Creator 10.0.0) 打开 libjpeg.pro,再在 .pro 文件里改 lib 输出路径就行

顺便贴上构建设置

可能需要将 jom.exe 改成 nmake.exe(打开 pro 项目后,在构建和运行中选择)

这些都准备后,点击编译即可,在 lib 文件夹中就可以找到了

把库放到项目文件的库目录下,并静态绑定即可


是不是很复杂,直到上一步我也是这么以为的,在我全局搜索 qtlibjpeg.lib 时,我发现 qt 下已经给你编译好了

惊不惊喜意不意外(我都要骂娘了)

重新阅读了官方文档,上面说你可以选择在编译静态库时添加一些参数来一起编译你需要的三方库,比如 libjpeg

见:
https://doc.qt.io/archives/qt-6.0/configure-options.html#third-party-libraries

是我大意了,附上编译命令,

configure -static -static-runtime -debug-and-release -mp -prefix "..\msvc2019_x86_static" -opensource -confirm-license -optimize-size -qt-libjpeg -make libs -nomake examples -nomake tests -skip qtwebengine

编译出的库文件就在 lib 下

小结:还是对 Qt 的库配置不熟悉,导致花了大量工夫来解决这种问题;好在有了这次经验后,以后再遇到类似问题,就能手到擒来了

DRF限流组件(源码分析)

限流,限制用户访问频率,例如:用户1分钟最多访问100次 或者 短信验证码一天每天可以发送50次, 防止盗刷。

  • 对于匿名用户,使用用户IP作为唯一标识。
  • 对于登录用户,使用用户ID或名称作为唯一标识。
缓存={
	用户标识:[12:33,12:32,12:31,12:30,12,]    1小时/5次   12:34   11:34
{

1. 配置缓存

# settings.py
CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
            "PASSWORD": "qwe123",
        }
    }
}

2. 自定义限流类

# -*- encoding:utf-8 -*-
# @time: 2023/4/21 15:41
# @author: ifeng
from django.core.cache import cache as default_cache
from rest_framework import exceptions
from rest_framework import status
from rest_framework.throttling import SimpleRateThrottle


class ThrottledException(exceptions.APIException):
    status_code = status.HTTP_429_TOO_MANY_REQUESTS
    default_code = 'throttled'


class MyRateThrottle(SimpleRateThrottle):
    cache = default_cache  # 访问记录存放在django的缓存中(需设置缓存)
    scope = 'user'  # 构造缓存中的key
    cache_format = 'throttle_%(scope)s_%(ident)s'

    # 设置其他访问评率, 例如: 一分钟允许访问10次
    # 其他: 's': 'sec', 'm': 'min', 'h': 'hour', 'd': 'day'
    THROTTLE_RATES = {'user': '10/m'}

    def get_cache_key(self, request, view):
        if request.user:
            ident = request.user.id
        else:
            ident = self.get_ident(request)  # 获取请求用户IP(request中找请求头)

        # throttle_u # throttle_user_11.11.11.11ser_2

        return self.cache_format % {'scope': self.scope, 'ident': ident}

    def throttle_failure(self):
        wait = self.wait()
        detail = {
            'code': 1005,
            'data': '访问频率限制',
            'detail': '需要等待 %s s才能访问' % (int(wait))
        }
        raise ThrottledException(detail)

3. 使用限流类

  • 局部配置(views)
class UserView(APIView):
    throttle_classes = [MyRateThrottle, ]  # 限流
  • 全局配置(settings)
REST_FRAMEWORK = {
    # 限流
    "DEFAULT_THROTTLE_CLASSES": ["app01.throttle.MyRateThrottle", ],
    "DEFAULT_THROTTLE_RATES": {
        "user": "10/m",
        # "xx":"100/h"
    }
}

4. 多个限流类

本质,每个限流的类中都有一个
allow_request
方法,此方法内部可以有三种情况:

  • 返回True,表示当前限流类允许访问,继续执行后续的限流类。
  • 返回False,表示当前限流类不允许访问,继续执行后续的限流类。所有的限流类执行完毕后,读取所有不允许的限流,并计算还需等待的时间。
  • 抛出异常,表示当前限流类不允许访问,后续限流类不再执行。

5. 源码分析

  1. 这是限流大体的执行逻辑, 后面将对allow_reqeust中具体分析

  1. allow_request()在自定义的类里面没定义, 所以我们到父类SimpleRateThrottle执行allow_request()方法

进入主题之前,先了解 Nacos prometheus grafana。

image

nacos 是什么?

一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。

nacos 最新稳定版本更新到了 2.2.2 ,官方推荐使用 nacos 2.1.1 。

prometheus 是什么

From metrics to insight
Power your metrics and alerting with the leading
open-source monitoring solution.

大意是,从监控到洞察力。利用领先的开源监控解决方案,让你的监控和警报发挥作用。

prometheus 最新稳定版本更新到了 2.4.3,你可以选择使用长期支持版本 2.3.7(LTS).

grafana 是什么

Operational dashboards for your data here, there, or anywhere

大意是,在任何位置将你的数据以仪表板(控制面板)形式展现出来。

grafana 最新稳定版本更新到了 9.4.7 。

nacos系列文章第三篇:《运维篇:
Nacos prometheus grafana

当你再次看到这张流程图时,第三篇运维相关知识终于磨出来了:

image

整体思路,个人nacos系列博文一共分为三篇

  • 基础篇:《MySQL数据库与Nacos搭建监控服务》,Nacos与MySQL基本介绍。
  • 开发篇:《开发篇:springboot与微服务组件nacos》,从代码开始构建,集成微服务组件。
  • 运维篇:《运维篇:nacos prometheus grafana》,服务监控篇,主要以Linux发行版为主。

不同的场景有不同的解决方案,按需(调研)适配spring、springboot、springcloud集成nacos。

运维篇:springboot与微服务组件nacos

此处主要以Linux(centos-stream-9)环境为主。如果非要在Windows平台使用,也是可以的。本人也在Windows平台测试过,同样可以正常运行。只是官方目前并不推荐在Windows平台使用,可能是还不够稳定吧。

个人在测试环境下,用过三个版本:

  • nacos 2.0.3
  • nacos 2.1.1
  • nacos 2.2.0

目前,2.2.x 版本UI变化比较大,更加优美。

Linux服务器部署springboot项目

当你测试时,需要准备测试使用的环境。比如maven打包好的 jar 包,如何上传部署?

上传文件方法

  • 方式一:已知服务器用户名和密码(具有相关权限),可以使用 scp 命令上传。
  • 方式二:已知服务器用户名和密码(具有相关权限),可以使用工具 WinSCP、FileZilla等工具上传文件。

如下是WinSCP界面,两种方式:

  • 左右方式,Windows窗口在左边,Linux服务器窗口在右边,支持拖拽和复制粘贴。
  • 只显示远程目录形式,支持拖拽。

image

必备环境

  • Linux(centos-stream-9)
  • JDK17:支撑springboot服务启动
  • Nacos 2.1.1 :监控发现springboot服务

解压JDK & 配置环境变量
(如果仅仅为了测试可以不配置环境变量,采用指定服务路径形式)

RHEL体系shell环境变量调用顺序:

image

配置全局环境变量:vim /etc/profile,配置当前用户环境变量:vim .bash_profile 或者 .bashrc,加入配置全局变量我所列出的内容即可。什么时候配置全局用户,什么时候配置当前用户,各有各的应用场景。

比如配置当前用户环境变量,我只在 test 用户配置测试,就不给你 demo 用户进行测试,我们互不干扰。

比如配置全局用户环境变量,我既要 test 用户配置测试,我又要 demo 用户进行测试,还要在 root 用户下使用,我全都要。

如下分4步优化操作

  1. 解压jdk :tar -zxvf jdk-17.0.4.1_linux-x64_bin.tar.gz
  2. 简化应用名称:mv jdk-17.0.4.1_linux-x64_bin jdk17
  3. 新建部署目录:mkdir -p /usr/java
  4. 指定部署目录:mv jdk17 /usr/java/

介绍配置全局环境变量
:vim /etc/profile

JAVA_HOME=/usr/java/jdk17
CLASS_PATH=.:$JAVA_HOME/jre/lib/rt.jar:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
PATH=$PATH:$JAVA_HOME/bin
export JAVA_HOME CLASS_PATH PATH

执行 source 命令立即生效

source /etc/profile

关于环境变量更多配置可参考个人公众号关于JDK17的介绍。

验证Java版本

配置了环境变量:

java -version

未配置环境变量:

/usr/java/jdk-17.0.4.1/bin/java -version

解压Nacos Server 并剪切到 /usr/local/ 目录

tar -zxvf nacos-server-2.1.1.tar.gz
mv nacos /usr/local/

启动nacos服务(非集群模式)

cd /usr/local/nacos/bin/
sh startup.sh -m standalone

新建测试目录

mkdir -p /opt/test  /opt/sh

执行命令脱离终端运行,并输出日志到指定文件 /opt/test/springboot.log

[root@Centos9-Stream ~]# nohup /usr/java/jdk-17.0.4.1/bin/java -jar /opt/test/springboot-test-0.0.1-SNAPSHOT.jar  >> /opt/test/springboot.log 2>&1 &

编辑脚本

vim /opt/sh/start_springboot_demo.sh

写入到脚本

#!/bin/bash
nohup /usr/java/jdk-17.0.4.1/bin/java -jar /opt/test/springboot-test-0.0.1-SNAPSHOT.jar  >> /opt/test/springboot.log 2>&1 &

curl命令测试接口

PS D:\work> curl http://192.168.245.132:8081/demo/getStu
{"code":0,"msg":"成功","data":{"id":"d5e52f71-67f0-499a-acf9-574ec14ffbe6","name":"梦梦","sex":"女","age":16}}

使用 cat 命令查看日志

cat  /opt/test/springboot.log 

使用 vim 查看

vim  /opt/test/springboot.log

使用 tail 命令查看

tail -f /opt/test/springboot.log
tail -n 5 /opt/test/springboot.log

tail 参数
:-n代表查看多少条日志信息,我只想查看最近的5条数据,使用 tail -n 5。

Springboot启动服务指定参数

注意
:带参运行方式,开发或者测试时,可以运用上,便于调试。

参数一:--server.port=9999 代表指定服务端口运行,不添加则使用默认设置服务端口
参数二:-Dspring.profiles.active=prod 指定环境(eg:dev:开发环境、prod:生产环境、test:测试环境)

Windows平台

指定服务API端口:--server.port=9999

java -jar springboot-test-nacos.jar --server.port=9999

指定服务运行(生产)环境:-Dspring.profiles.active=prod

java -jar "-Dspring.profiles.active=prod" demo-0.0.1-SNAPSHOT.jar

Linux(RHEL系列)平台

采用默认形式指定运行(生产)环境,输出日志到 test.log 文件:

nouhup java -jar -Dspring.profiles.active=prod demo-0.0.1-SNAPSHOT.jar > test.log 2>&1 & 

采用默认形式运行服务并指定服务端口,输出日志到 test.log 文件:

nouhup java -jar springboot-test-nacos.jar --server.port=9999 > test.log 2>&1 & 

采用指定 jdk 路径,指定运行(生产)环境,输出日志到 test.log 文件:

nohup /usr/java/jdk-17.0.4.1/bin/java -jar /opt/test/springboot-test-0.0.1-SNAPSHOT.jar  > /opt/test/springboot.log 2>&1 &

Linux & Win 监控运行中的服务

无论是 Linux 平台还是 Windows 平台,netstat 命令基本用法还是需要掌握的,对于开发、测试、运维都比较重要。

使用 netstat 监控如下服务:

  • nacos:监控springboot服务
  • prometheus:采集数据 ,个人使用的版本为 2.37.1
  • grafana:图形化(仪表板)展示数据 ,个人使用版本为 9.3.0,目前9.3.x版本已经更新到 9.3.11

关于 nacos 服务部署以及设置MySQL数据源,此篇不再赘述。可以参考前两篇博文,有介绍,可在公众号内搜索到。

Linux (REHL系列) 平台查询运行中的nacos服务

netstat -tlunp | grep 8848;netstat -tlunp | grep 9848;netstat -tlunp | grep 9849

Windows 平台查询运行中的nacos服务

通过Windows terminal打开powershell或者CMD,使用netstat命令配合findstr查询nacos使用到的端口:

netstat -ano | findstr 8848;netstat -ano | findstr 9848;netstat -ano | findstr 9849

image

查阅官方文档,发现Nacos2.0版本相比1.X新增了gRPC的通信方式,因此需要增加2个端口。新增端口是在配置的主端口(server.port)基础上,进行一定偏移量自动生成。偏移量分别为:+1000,+1001,所以监控到端口多出了9848和9849。

友情提示
:通常着重关注
ESTABLISHED
,表示已经确立联系。

通过查找到的PID,以图片上PID值8220为示例,使用命令tasklist去查找使用服务以及占用内存

tasklist | findstr 8220
java.exe                      8220 Console                    1    449,472 K

Linux (RHEL系列)查看运行中的springboot服务,已经引入微服务组件 nacos

[root@Centos9-Stream test]# netstat -tlunp | grep 8081; netstat -tlunp | grep 8082;netstat -tlunp | grep 8083;
tcp6       0      0 :::8081                 :::*                    LISTEN      2690/java
tcp6       0      0 :::8082                 :::*                    LISTEN      3104/java
tcp6       0      0 :::8083                 :::*                    LISTEN      3213/java

image

在Linux平台我习惯使用参数 -tlunp 去查询监听服务。

参数含义

  • -t:--tcp,显示监听tcp协议。
  • -l:--listening ,用于展示监听服务的sockets。
  • -u:--udp,显示监听udp协议。
  • -n:--numeric,不解析服务(主机、端口、用户)名称。
  • -p:--programs,显示使用了sockets 应用程序的PID号。

上面图片以及代码展示的tcp6(tcp协议),8081 表示ip绑定端口号,LISTEN 代表正在监听,2690/java 表示监听到java进程pid号是2690。通常我们比较关心的是端口(port),监听状态,服务名进程号。

更多参数使用,请查看帮助命令::

netstat -h

Windows 平台查看运行中的springboot服务

netstat -ano | findstr 8081;netstat -ano | findstr 8082;netstat -ano | findstr 8083

参数含义

  • -a: 显示所有连接和侦听端口。
  • -n:以数字形式显示地址和端口号。
  • -o:显示拥有的与每个连接关联的进程 ID。

更多参数使用,请查看帮助命令:

netstat help

RHEL 系列查看nacos、prometheus、grafana服务进程

ps命令监控服务三种方式:

  1. 使用 ps 命令参数 -aux:ps -aux | grep nacos| grep -v "grep"
  2. 使用 ps 命令参数 -ef:ps -ef | grep nacos| grep -v "grep"
  3. 使用 ps 命令参数 -le:ps -le | grep nacos| grep -v "grep"

如下使用参数 -aux 进行监控 nacos、prometheus、grafana 服务

[root@Centos9-Stream prometheus-2.37.1]# ps -aux | grep nacos;ps -aux | grep prometheus;ps -aux | grep grafana

image

可以看到,监控服务命令将 nacos、prometheus、grafana 以红色字体标记出来了。将当前使用用户root、nacos 进程(pid)号2334、服务所在路径、配置文件指定路径等等展现在屏幕上。

这里教大家一个小技巧,如果你不想看到 grep --color=auto 这一行,可以通过 grep 取反命令过滤。

示例 grep 取反过滤:

ps -aux | grep nacos| grep -v "grep"

Windows 平台查看nacos、prometheus、grafana服务进程

tasklist | findstr nacos;tasklist | findstr prometheus;tasklist | findstr grafana;

RHEL 系列查看nacos、prometheus、grafana服务端口

[root@Centos9-Stream prometheus-2.37.1]# netstat -tlunp | grep 3000;netstat -tlunp | grep 8848;netstat -tlunp | grep 9090
tcp6       0      0 :::3000                 :::*                    LISTEN      2194/grafana-server
tcp6       0      0 :::8848                 :::*                    LISTEN      2234/java
tcp6       0      0 :::9090                 :::*                    LISTEN      2509/prometheus

image

Windows 平台查看nacos、prometheus、grafana服务端口

netstat -ano | findstr 3000;netstat -ano | findstr 8848;netstat -ano | findstr 9090

Prometheus采集数据

简单科普

Prometheus是一个开源的系统监控和报警系统,它可以从多个来源收集数据,并对数据进行多维度的数据模型分析,可视化展现,及时报警。它已经加入到CNCF基金会,成为继k8s之后第二个在CNCF托管的项目。

Prometheus的基本原理是通过HTTP协议周期性抓取被监控组件的状态,任意组件只要提供对应的HTTP接口就可以接入监控。 不需要任何SDK或者其他的集成过程。 这样做非常适合做虚拟化环境监控系统,比如VM、Docker、Kubernetes等。 输出被监控组件信息的HTTP接口被叫做exporter。

Prometheus支持多种数据源,比如:

  1. 系统监控数据:例如cpu,内存,磁盘IO等。
  2. 网络数据:例如网络吞吐量,延迟等。
  3. 进程和线程数据:例如goroutines,processes,threads等。
  4. 存储数据:例如数据库的插入,更新,删除等。

Prometheus支持多种数据模型,例如:

  1. 指标模型:可以对系统的指标进行建模。
  2. 时间模型:可以对系统的时间序列数据进行建模。
  3. 事件模型:可以对系统的事件进行建模。
  4. 图模型:可以对系统的状态或流程进行可视化展现。

Prometheus有多种可视化图像界面,例如:

  1. Grafana:一个开源的、基于Web的可视化平台。
  2. VTiger:一个开源的、基于Web的仪表盘系统。

Prometheus还支持高可用,可以对数据做异地备份,联邦集群,部署多套prometheus,pushgateway上报数据等功能。总的来说,Prometheus是一个功能强大、易于使用的系统监控和报警系统,可以广泛应用于各种类型的系统监控和性能分析场景。

必备环境

  • prometheus:采集数据
  • nacos:监控发现服务
  • JDK:支撑服务启动

prometheus 官方文档地址:
https://prometheus.io/docs/prometheus/2.37/getting_started/

prometheus 下载地址:
https://prometheus.io/download/

1、安装prometheus

请前往上面列出的下载地址准备好安装包,简化安装步骤如下。

tar -zxvf prometheus-2.37.1.linux-amd64.tar.gz
mv prometheus-2.37.1.linux-amd64 prometheus-2.37.1
mv prometheus-2.37.1 /usr/local/

2、启动prometheus服务

指定配置文件路径,执行prometheus脚本。示例:

/usr/local/prometheus-2.37.1/prometheus --config=/usr/local/prometheus-2.37.1/prometheus.yml 

如果想脱离终端在后台运行,需要在行首加上 nohup 命令,在行尾加上& 符号。示例:

nohup /usr/local/prometheus-2.37.1/prometheus --config=/usr/local/prometheus-2.37.1/prometheus.yml &

参数含义

  1. prometheus:普罗米修斯服务脚本。
  2. --config:用于指定普罗米修斯脚本 yml 格式配置文件。

3、修改配置文件,监控nacos暴露的metrics数据
说明:metrics_path和static_configs不要设置多个,否则检测会出现语法错误,提示已存在

# metrics_path defaults to '/metrics'
# scheme defaults to 'http'.
#监控nacos服务
    metrics_path: '/nacos/actuator/prometheus'
    static_configs:
#设置监控nacos远程服务地址
      - targets: ['192.168.245.132:8848']

4、检测配置文件

[root@Centos9-Stream nacos]# /usr/local/prometheus-2.37.1/promtool check config /usr/local/prometheus-2.37.1/prometheus.yml
Checking /usr/local/prometheus-2.37.1/prometheus.yml
 SUCCESS: /usr/local/prometheus-2.37.1/prometheus.yml is valid prometheus config file syntax

通过 kill 命令暴力停掉进程,再次启动 prometheus 服务:

netstat -tlunp | grep 9090
kill -9 进程号
nohup /usr/local/prometheus-2.37.1/prometheus --config=/usr/local/prometheus-2.37.1/prometheus.yml &

关于 netstat 命令上面有简单介绍过,不再赘述。

5、访问prometheus服务

prometheus 运行默认端口是9090:
http://192.168.245.132:9090/

tips
:需要将端口9090开放,或者通过防火墙管理工具firewalld临时关闭防火墙服务进行测试。

Linux(RHEL系列):

# 开放9090端口
firewall-cmd --zone=public --add-port=9090/tcp --permanent
firewall-cmd --reload

# 临时关闭
systemctl stop firewalld.service

6、查看prometheus监控status
依次选择status---> Targets---> show more

7、验证数据

搜索nacos_monitor,得到如下结果(截取部分):

nacos_monitor{instance="192.168.245.132:8848", job="prometheus", module="config", name="configCount"}
1
nacos_monitor{instance="192.168.245.132:8848", job="prometheus", module="config", name="dumpTask"}
0
nacos_monitor{instance="192.168.245.132:8848", job="prometheus", module="config", name="getConfig"}
9
...
nacos_monitor{instance="192.168.245.132:8848", job="prometheus", module="naming", name="ipCount"}
2

Grafana图形化展示数据

简单科普

Grafana是一款开源的数据可视化工具,它可以在Web浏览器中运行,提供了一个易于使用的界面,使用户可以轻松地创建各种类型的图表和仪表板。

Grafana拥有快速灵活的客户端图表,面板插件有许多不同方式的可视化指标和日志,官方库中具有丰富的仪表盘插件,比如热图、折线图、图表等多种展示方式,让我们复杂的数据展示的美观而优雅。它支持许多不同的时间序列数据(数据源)存储后端,每个数据源都有一个特定查询编辑器,官方支持以下数据源:Graphite、infloxdb、opensdb、prometheus、elasticsearch、cloudwatch。每个数据源的查询语言和功能明显不同,你可以将来自多个数据源的数据组合到一个仪表板上,但每个面板都要绑定到属于特定组织的特定数据源。它还支持警报功能,允许用户将规则附加到仪表板面板上,但目前只支持graph面板的报警。

总的来说,Grafana是一款功能强大的数据可视化工具,可以帮助用户轻松地创建漂亮的图表和仪表板,同时也支持各种数据源的组合和报警功能,是数据分析和可视化的必备工具之一。

必备环境

  • grafana:图形化(仪表板)展示数据。
  • prometheus:采集数据
  • nacos:监控发现服务
  • JDK:支撑服务启动

tips
:初次登录,用户名:admin,密码:admin。

grafana下载地址:
https://grafana.com/grafana/download

1、搭建grafana,图形化展示metrics数据

RHEL 系列快速安装grafana:

sudo yum install grafana

在RHEL7以及以上使用yum或者dnf命令安装完后,可以使用如下命令启动或者查看服务状态。

启动grafana服务:

systemctl start grafana-server.service

查看grafana服务:

systemctl status grafana-server.service

详细grafana安装指南,请参考官方文档
https://grafana.com/docs/grafana/v9.3/setup-grafana/installation/

Linux平台安装grafana服务
Standalone Linux Binaries(64 Bit)

oss版本获取地址,基于AGPLv3 开源协议。

wget https://dl.grafana.com/oss/release/grafana-9.3.0.linux-amd64.tar.gz
tar -zxvf grafana-9.3.0.linux-amd64.tar.gz

企业版enterprise获取地址

wget https://dl.grafana.com/enterprise/release/grafana-enterprise-9.3.0.linux-amd64.tar.gz
tar -zxvf grafana-enterprise-9.3.0.linux-amd64.tar.gz

Red Hat, CentOS, RHEL, and Fedora(64 Bit)

可以根据自己的需求选择 oss 版本或者 enterprise 企业版本。企业版本包含oss版本所有功能,同时也是免费使用,拥有更多插件功能。

wget https://dl.grafana.com/oss/release/grafana-9.3.0-1.x86_64.rpm
sudo yum install grafana-9.3.0-1.x86_64.rpm

Ubuntu and Debian(64 Bit)

sudo apt-get install -y adduser libfontconfig1
wget https://dl.grafana.com/oss/release/grafana_9.3.0_amd64.deb
sudo dpkg -i grafana_9.3.0_amd64.deb

grafana配置文件

如果使用yum或者dnf命令安装,安装后的grafana配置文件路径。主要配置文件在/etc/grafana/目录下,文件收集以及报警信息相关在/var/lib/grafana/目录下。

[root@Centos9-Stream local]# ls /etc/grafana/
grafana.ini  ldap.toml  provisioning
[root@Centos9-Stream local]# ls /var/lib/grafana/
alerting  csv  file-collections  grafana.db  png
[root@Centos9-Stream local]# du -sk -h /var/run/grafana/grafana-server.pid
4.0K    /var/run/grafana/grafana-server.pid

使用
du
命令查看
grafana-server
服务所占存储空间

[root@Centos9-Stream local]# du -sk -h /usr/sbin/grafana-*
4.0K    /usr/sbin/grafana-cli
102M    /usr/sbin/grafana-server

以上这些信息,在RHEL7以上环境中,你可以使用
systemctl
命令查看到所在路径

[root@Centos9-Stream local]# systemctl status grafana-server.service
● grafana-server.service - Grafana instance
	...
     CGroup: /system.slice/grafana-server.service
             └─2821 /usr/sbin/grafana-server --config=/etc/grafana/grafana.ini 
             --pidfile=/var/run/grafana/grafana-server.pid

2、配置数据源prometheus
获取nacos json模板:
https://github.com/nacos-group/nacos-template/blob/master/nacos-grafana.json

加入http地址:
http://192.168.245.132:9090/

注意
:将ip地址替换成你自己的。

如果遇到问题(注意排查导入json文件配置以及grafana数据源名称配置)

Failed to upgrade legacy queries Datasource prometheus was not found

检查配置json文件数据源名称:

"datasource": "prometheus",
"format": "none",
"gauge": {
   "maxValue": 100,
   "minValue": 0,
   "show": false,
   "thresholdLabels": false,
   "thresholdMarkers": true

可以看到默认配置的datasource是:prometheus

解决方法:
修改默认数据源名称Prometheus为prometheus
,注意匹配名称。

配置prometheus数据源

grafana运行默认端口是3000,访问:
http://192.168.245.132:3000/

同样需要开放相应端口,参考上面介绍prometheus使用的方法。

image

引入nacos json template

注意:熟悉的情况下,这个配置模板,可以根据自己的需求去修改,不一定要照搬,可以灵活运用。

image

引入nacos json配置文件,看到如下三项代表配置成功

  • nacos monitor:展示nacos服务监控信息,包含上线服务、IP个数、cpu、内存、线程负载等等。
  • nacos detail:展示nacos服务详细信息。
  • nacos alert:展示nacos服务报警相关信息。

image

nacos monitor 展示效果

image

配置语言中文 & 时区
值得注意的是,在9.3.x之前还没实装设置语言这一项功能。

image

如果不出意外,配置完成,可以正常访问配置的nacos监控服务。

3、访问grafana服务

访问地址,默认端口3000,可以根据你的工作场景进行调整:
http://192.168.245.132:3000/,如果在本地搭建,将ip替换为
127.0.0.1 或者 localhost。

如果在服务器搭建,将ip替换为你的解析域名或者远程ip地址。

image

如果想深入学习nacos,可以参考官方用户指南、运维指南和《Nacos架构&原理》

《Nacos架构&原理》:
https://developer.aliyun.com/ebook/36?spm=a2c6h.20345107.ebook-index.18.152c2984fsi5ST

到此为止,是本篇的全部内容。感谢你的阅读和建议,我们下期再见。

参考资料

END----

静下心来,才发现原来不会的还有很多。

一分耕耘,一分收获。

多总结,你会发现,自己的知识宝库越来越丰富。