1. @typescript-eslint/adjacent-overload-signatures
  • 建议函数重载的签名保持连续
  1. @typescript-eslint/await-thenable
  • 不允许对不是“Thenable”对象的值使用await关键字,相反对“Thenable”对象必须使用await,例如对Promise对象。
  1. @typescript-eslint/array-type
  • 定义数组时,使用统一的样式,如都使用T[]或都使用Array
"@typescript-eslint/array-type": [
  "error",
  {
    //      array | array-simple | generic
    "default": "array"
  }
]
  • default的值设置为array时,统一使用T[];设置generic时,统一使用Array
    ,设置为array-simple时,简单类型使用T[],其它类型使用Array
  1. @typescript-eslint/ban-ts-comment
  • 不允许使用
    @ts-<directional>
    格式的注释,或要求在注释后进行补充说明
  1. @typescript-eslint/ban-tslint-comment
  • 不允许使用
    //tslint:<rule-flag>
    格式的注释
  1. @typescript-eslint/ban-types
  • 不允许使用某些类型,例如类型小写保持一致,使用string,boolean,number等等,而不是String,Boolean,Number。
  1. @typescript-eslint/brace-style
  • 要求代码块的左大括号与其对应的语句或声明位于同一行。
  1. @typescript-eslint/class-literal-property-style
  • 建议类中的字面量属性对外暴露时,保持一致的风格
  1. @typescript-eslint/comma-dangle
  • 允许或禁止使用尾随逗号,类的最后一个属性或者数组最后一个元素禁止尾随逗号
"@typescript-eslint/comma-dangle": [
  "error",
  {
    //      never | always
    "arrays": "never",
    "objects": "never",
    "imports": "never",
    "exports": "never",
    "functions": "never"
  }
]
  • 共有数组arrays,对象objects,导入imports,导出exports和函数functions五各类型支持配置,值设置为never则是禁止尾随逗号,设置为always则是允许尾随逗号。
  1. @typescript-eslint/comma-spacing
  • 强制逗号前后的空格风格保持一致,例如强制要求逗号前不加空格,逗号后必须添加空格
"@typescript-eslint/comma-spacing": [
  "error",
  {
    "before": false,
    "after": true
  }
]
  1. @typescript-eslint/consistent-type-assertions
  • 强制使用一致的类型断言
  1. @typescript-eslint/default-param-last
  • 强制默认参数位于参数列表的最后一个
  1. @typescript-eslint/explicit-member-accessibility
  • 在类属性和方法上需要显式定义访问修饰符
  1. @typescript-eslint/func-call-spacing
  • 禁止或者要求函数名与函数名后面的括号之间加空格
"@typescript-eslint/func-call-spacing": [
  "error",
  "never"
]
  • 设置为never时,函数名后面禁止添加空格,设置为always时,函数名后面允许添加空格
  1. @typescript-eslint/init-declarations
  • 禁止或者要求在变量声明中进行初始化
"@typescript-eslint/init-declarations": [
  "error",
  "always"
]
  • 设置为always时,声明变量必须初始化,设置为never时,声明变量可以不初始化。
  1. @typescript-eslint/keyword-spacing
  • 强制在关键字之前和关键字之后保持一致的空格风格,例如在关键字前后都添加空格
"@typescript-eslint/keyword-spacing": [
  "error",
  {
    "before": true,
    "after": true
  }
]
  1. @typescript-eslint/lines-between-class-members
  • 禁止或者要求类成员之间有空行分隔,always为允许有空行,never为不允许有空行,如下设置空行后不加空行,属性和方法之前添加空行。
"@typescript-eslint/lines-between-class-members": [
  "error",
  {
    enforce: [
      {
        blankLine: "never",
        prev: "field",
        next: "method"
      }
    ]
  }
]
  1. @typescript-eslint/member-delimiter-style
  • 要求接口和类型别名中的成员之间使用特定的分隔符,支持定义的分隔符有三种:分号、逗号、无分隔符
  1. @typescript-eslint/member-ordering
  • 要求类、接口和类型字面量中成员的排序方式保持一致的风格
  1. @typescript-eslint/naming-convention
  • 强制标识符使用一致的命名风格。例如类名使用大驼峰,函数使用小驼峰。
  1. @typescript-eslint/no-array-constructor
  • 不允许使用“Array”构造函数。
  1. @typescript-eslint/no-base-to-string
  • 要求当一个对象在字符串化时提供了有用的信息,才能调用“toString()”方法
  1. @typescript-eslint/no-confusing-non-null-assertion
  • 不允许在可能产生混淆的位置使用非空断言
  1. @typescript-eslint/no-confusing-void-expression
  • 要求void类型的表达式出现在合适的位置
  1. @typescript-eslint/no-dupe-class-members
  • 不允许重复的类成员,即已经声明的成员属性,不允许重复再声明一次。
  1. @typescript-eslint/no-duplicate-imports
  • 禁止重复的模块导入,即已经导入的模块,不允许再再次导入。
  1. @typescript-eslint/no-empty-function
  • 不允许使用空函数,支持的白名单配置包括函数,箭头函数,方法,构造方法等等,配置如下
"@typescript-eslint/no-empty-function": [
  "error",
  {
    "allow": [
      "functions",
      "arrowFunctions",
      "generatorFunctions",
      "methods",
      "generatorMethods",
      "getters",
      "setters",
      "constructors",
      "asyncFunctions",
      "asyncMethods"
    ]
  }
]
  1. @typescript-eslint/no-empty-interface
  • 不允许声明空接口
  1. @typescript-eslint/no-extraneous-class
  • 不允许将类用作命名空间
  1. @typescript-eslint/no-extra-non-null-assertion
  • 不允许多余的非空断言
  1. @typescript-eslint/no-extra-parens
  • 禁止使用不必要的括号
  1. @typescript-eslint/no-extra-semi
  • 禁止使用不必要的分号
  1. @typescript-eslint/no-floating-promises
  • 要求正确处理Promise表达式,例如Promise一定要处理异常情况
  1. @typescript-eslint/no-implied-eval
  • 禁止使用类似“eval()”的方法
  1. @typescript-eslint/no-inferrable-types
  • 不允许对初始化为数字、字符串或布尔值的变量或参数进行显式类型声明
  1. @typescript-eslint/no-invalid-this
  • 禁止在this的值为undefined的上下文中使用this
  1. @typescript-eslint/no-invalid-void-type
  • 禁止在返回类型或者泛型类型之外使用void
  1. @typescript-eslint/no-loss-of-precision
  • 禁止使用失去精度的字面数字
  1. @typescript-eslint/no-magic-numbers
  • 禁止使用魔法数字。但有些情况下我们又需要直接使用数字,例如定义枚举时,在数组中根据索引取数据时,或者直接定义某些值不是魔法数字,示例如下
"@typescript-eslint/no-magic-numbers": [
  "off",
  {
    "ignoreEnums": true,
    "ignoreArrayIndexes": true,
    "ignoreNumericLiteralTypes": true,
    "ignore": [
      -1,
      0,
      1
    ]
  }
]
  1. @typescript-eslint/no-misused-new
  • 要求正确地定义“new”和“constructor”
  1. @typescript-eslint/no-misused-promises
  • 禁止在不正确的位置使用Promise
  1. @typescript-eslint/no-non-null-asserted-optional-chain
  • 禁止在可选链表达式之后使用非空断言
  1. @typescript-eslint/no-non-null-assertion
  • 禁止以感叹号作为后缀的方式使用非空断言
  1. @typescript-eslint/no-redeclare
  • 禁止变量重复声明,即前面声明过的变量,不允许再次声明。
  1. @typescript-eslint/no-require-imports
  • 禁止使用“require()”语法导入依赖
  1. @typescript-eslint/no-restricted-syntax
  • 不允许使用指定的(即用户在规则中定义的)语法。例如不允许直接使用console.log打印日志,而是使用我们封装好的LogUtil打印日志
"@typescript-eslint/no-restricted-syntax": [
  "error",
  {
    "selector": "CallExpression[callee.name='console.log']",
    "message": "不要直接使用console打印日志,请使用LogUtil"
  }
]
  1. @typescript-eslint/no-shadow
  • 禁止声明与外部作用域变量同名的变量
  1. @typescript-eslint/no-throw-literal
  • 禁止将字面量作为异常抛出
  1. @typescript-eslint/no-unnecessary-boolean-literal-compare"
  • 禁止将布尔值和布尔字面量直接进行比较
  1. @typescript-eslint/no-unnecessary-condition
  • 不允许使用类型始终为真或始终为假的表达式作为判断条件
  1. @typescript-eslint/no-unnecessary-qualifier
  • 禁止不必要的命名空间限定符

书接上回,我们继续来分享一些关于特殊时间获取的常用扩展方法。

01
、获取当前日期所在月的第一个指定星期几

该方法和前面介绍的获取当前日期所在周的第一天(周一)核心思想是一样的,只是把求周一改成求周几而已,当然其中有些小细节需要注意,比如求所在周的第一天则两天都在同一周,而求所在月第一个指定周则可能两天在不同周,具体代码如下:

//获取当前日期所在月的第一个指定星期几
public static DateTime GetFirstDayOfWeekDateTimeInMonth(this DateTime dateTime, DayOfWeek dayOfWeek)
{
    //获取当前日期所在月的第一天
    var firstDayOfMonth = dateTime.GetFirstDayDateTimeOfMonth();
    //计算目标日期与当月第一天相差天数
    var diff = ((int)dayOfWeek - (int)firstDayOfMonth.DayOfWeek + 7) % 7;
    return firstDayOfMonth.AddDays(diff);
}

下面我们还需要做详细的单元测试,我们分别测试指定周一和周日两个特殊日期,然后再分别测试三种特殊情况:

指定周一测试:

(1) 验证当前日期是周五,而周一在下一周的情况;

(2) 验证当前日期是本月第一个周一的情况;

(3) 验证当前日期是周日,并且在本月第一个周一之后的情况;

指定周日测试:

(1) 验证当前日期是周五,并且在本月第一个周日之前的情况;

(2) 验证当前日期是本月第一个周日的情况;

(3) 验证当前日期是周一,并且在本月第一个周日之后的情况;

具体代码如下:

[Fact]
public void GetFirstDayOfWeekDateTimeInMonth()
{
    //验证当前日期是周五,而周一在下一周的情况
    var friday_monday = new DateTime(2024, 11, 1, 14, 10, 10);
    var day_friday_monday = friday_monday.GetFirstDayOfWeekDateTimeInMonth(DayOfWeek.Monday);
    Assert.Equal(new DateTime(2024, 11, 4), day_friday_monday);
    //验证当前日期是本月第一个周一的情况
    var monday_monday = new DateTime(2024, 11, 4, 4, 10, 10);
    var day_monday_monday = monday_monday.GetFirstDayOfWeekDateTimeInMonth(DayOfWeek.Monday);
    Assert.Equal(new DateTime(2024, 11, 4), day_monday_monday);
    //验证当前日期是周日,并且在本月第一个周一之后的情况
    var sunday_monday = new DateTime(2024, 11, 30, 4, 10, 10);
    var day_sunday_monday = sunday_monday.GetFirstDayOfWeekDateTimeInMonth(DayOfWeek.Monday);
    Assert.Equal(new DateTime(2024, 11, 4), day_sunday_monday);
    //验证当前日期是周五,并且在本月第一个周日之前的情况
    var friday_sunday = new DateTime(2024, 11, 1, 14, 10, 10);
    var day_friday_sunday = friday_sunday.GetFirstDayOfWeekDateTimeInMonth(DayOfWeek.Sunday);
    Assert.Equal(new DateTime(2024, 11, 3), day_friday_sunday);
    //验证当前日期是本月第一个周日的情况
    var sunday_sunday = new DateTime(2024, 11, 30, 4, 10, 10);
    var day_sunday_sunday = sunday_sunday.GetFirstDayOfWeekDateTimeInMonth(DayOfWeek.Sunday);
    Assert.Equal(new DateTime(2024, 11, 3), day_sunday_sunday);
    //验证当前日期是周一,并且在本月第一个周日之后的情况
    var monday_sunday = new DateTime(2024, 11, 4, 4, 10, 10);
    var day_monday_sunday = monday_sunday.GetFirstDayOfWeekDateTimeInMonth(DayOfWeek.Sunday);
    Assert.Equal(new DateTime(2024, 11, 3), day_monday_sunday);
}

02
、获取当前日期所在月的最后一个指定星期几

该方法和上一个求第一个指定星期几核心思想是一样的,具体代码如下:

//获取当前日期所在月的最后一个指定星期几
public static DateTime GetLastDayOfWeekDateTimeInMonth(this DateTime dateTime, DayOfWeek dayOfWeek)
{
    //获取当前日期所在月的最后一天
    var lastDayOfMonth = dateTime.GetLastDayDateTimeOfMonth();
    //计算目标日期与当月最后一天相差天数
    var diff = ((int)lastDayOfMonth.DayOfWeek - (int)dayOfWeek + 7) % 7;
    return lastDayOfMonth.AddDays(-diff);
}

单元测试可以参考求第一个指定星期几,这里就不赘述了。

03
、获取当前日期上一个指定星期几

求上一个指定周几,其实也不复杂,首先计算出当前日期与目标星期几相差的天数,其中有个小细节需要注意,就是如果两个日期相同,则需要把相差天数改为7,具体代码如下:

//获取当前日期上一个指定星期几
public static DateTime GetPreviousDayDateTimeOfWeek(this DateTime dateTime, DayOfWeek dayOfWeek)
{
    //计算当前日期与目标星期几相差天数
    var diff = ((int)dateTime.DayOfWeek - (int)dayOfWeek + 7) % 7;
    //如果相差0天表示当前日期和目标星期几相同,需要改为7
    diff = diff == 0 ? 7 : diff;
    return dateTime.AddDays(-diff).Date;
}

我们分别对以下四种情况做单元测试:

(1) 验证当前日期是周一,而上一个周一在上一月的情况;

(2) 验证当前日期是周一,而上一个周一在当月的情况;

(3) 验证当前日期是周日,而上一个周一在当月的情况;

(4) 验证当前日期是周六,并且是当月最后一天的情况;

具体代码如下:

[Fact]
public void GetPreviousDayDateTimeOfWeek()
{
    //验证当前日期是周一,而上一个周一在上一月的情况
    var monday = new DateTime(2024, 11, 1, 14, 10, 10);
    var day_monday = monday.GetPreviousDayDateTimeOfWeek(DayOfWeek.Monday);
    Assert.Equal(new DateTime(2024, 10, 28), day_monday);
    //验证当前日期是周一,而上一个周一在当月的情况
    var monday1 = new DateTime(2024, 11, 25, 14, 10, 10);
    var day_monday1 = monday1.GetPreviousDayDateTimeOfWeek(DayOfWeek.Monday);
    Assert.Equal(new DateTime(2024, 11, 18), day_monday1);
    //验证当前日期是周日,而上一个周一在当月的情况
    var sunday = new DateTime(2024, 11, 24, 4, 10, 10);
    var day_sunday = sunday.GetPreviousDayDateTimeOfWeek(DayOfWeek.Monday);
    Assert.Equal(new DateTime(2024, 11, 18), day_sunday);
    //验证当前日期是周六,并且是当月最后一天的情况
    var saturday = new DateTime(2024, 11, 30, 4, 10, 10);
    var day_saturday = saturday.GetPreviousDayDateTimeOfWeek(DayOfWeek.Monday);
    Assert.Equal(new DateTime(2024, 11, 25), day_saturday);
}

04
、获取当前日期下一个指定星期几

该方法和上面获取上一个指定星期几核心思想相同,具体代码如下:

//获取当前日期下一个最近指定星期几
public static DateTime GetNextDayDateTimeOfWeek(this DateTime dateTime, DayOfWeek dayOfWeek)
{
    //计算目标日期与当月最后一天相差天数
    var diff = ((int)dayOfWeek - (int)dateTime.DayOfWeek + 7) % 7;
    //如果相差0天表示当前日期和目标星期几相同,需要改为7
    diff = diff == 0 ? 7 : diff;
    return dateTime.AddDays(diff).Date;
}

单元测试也可以参考求上一个指定星期几,这里就不再赘述了。

05
、获取当前日期是其所在月的第几周

该方法的核心思想是,获取当前日期和当月第一天相差多少天,然后用相差的天数除以7即可获得当前是第几周。

但是这里有个比较麻烦的事情是如果第一周不满一周呢,比如当月的第一周第一天是2024-11-01周五,而今天是2024-11-07周四,应该是当月的第二周,但是如果直接计算两天的差再除以7结果显然是不对的。

因此我们首先需要把第一周不满一周的天数补上,即前面还有4天。

如此就是(7+4)/7=1…4,即所在第二周,其中商表示完整的周,余数则表示不完整的周。如果转为公式则为:days/7 + (days%7 > 0 ? 1 : 0),我们对这个公式简化后得到:(days + 6)/7,具体实现代码如下:

//获取当前日期是其所在月的第几周
public static int GetWeekOfMonth(this DateTime dateTime)
{
    //获取当前日期所在月的第一天
    var firstDayOfMonth = dateTime.GetFirstDayDateTimeOfMonth();
    //首先设定周一为一周的开始
    //计算当前月第一天与周一相差天数
    //即第一周如果不满一周还差多少天
    var diff = ((int)firstDayOfMonth.DayOfWeek - (int)DayOfWeek.Monday + 7) % 7;
    //用第一周不满的差值加上当前日期的天数之和计算当前为当月第几周
    //然后计算 总天数/7的商,如果有余数则再加1
    //公式为:n/7 + (n%7 > 0 ? 1 : 0)
    //上面公式可以简化为 (n+6)/7
    return (diff + dateTime.Day + 6) / 7;
}

下面我们对其进行以下几种情况详细的单元测试:

(1) 验证当前日期是周五,且是当月第一天的情况;

(2) 验证当前日期是周日,且在当月第一周的情况;

(3) 验证当前日期是周一,且在当月第三周的情况;

(4) 验证当前日期是周日,且在当月第三周的情况;

(5) 验证当前日期是周六,且是当月最后一天的情况;

具体代码如下:

[Fact]
public void GetWeekOfMonth()
{
    //验证当前日期是周五,且是当月第一天的情况
    var friday = new DateTime(2024, 11, 1, 14, 10, 10);
    var day_friday = friday.GetWeekOfMonth();
    Assert.Equal(1, day_friday);
    //验证当前日期是周日,且在当月第一周的情况
    var sunday = new DateTime(2024, 11, 3, 14, 10, 10);
    var day_sunday = sunday.GetWeekOfMonth();
    Assert.Equal(1, day_sunday);
    //验证当前日期是周一,且在当月第三周的情况
    var monday = new DateTime(2024, 11, 11, 4, 10, 10);
    var day_monday = monday.GetWeekOfMonth();
    Assert.Equal(3, day_monday);
    //验证当前日期是周日,且在当月第三周的情况
    var date17 = new DateTime(2024, 11, 17, 4, 10, 10);
    var day17 = date17.GetWeekOfMonth();
    Assert.Equal(3, day17);
    //验证当前日期是周六,且是当月最后一天的情况
    var sunday1 = new DateTime(2024, 11, 30, 4, 10, 10);
    var day_sunday1 = sunday1.GetWeekOfMonth();
    Assert.Equal(5, day_sunday1);
}

06
、获取当前日期是其所在年的第几周(ISO 8601 标准)

在ISO 8601 标准规定中,每周从星期一开始,且每年最少有 52 周,每年的第一周是包含该年第一天的那一周,且该周必须至少有四天。

获取当然日期所在年的第几周可以通过调用C#中文化信息中日历组件中GetWeekOfYear方法,具体代码如下:

//获取当前日期是其所在年的第几周(ISO 8601 标准)
public static int GetWeekOfYear(this DateTime dateTime)
{
    var currentCulture = CultureInfo.CurrentCulture;
    return currentCulture.Calendar.GetWeekOfYear(dateTime, currentCulture.DateTimeFormat.CalendarWeekRule, currentCulture.DateTimeFormat.FirstDayOfWeek);
}

07
、获取当前日期所在月份的周数

该方法实现的核心思想是首先获取当前日期所在月份的第一天和最后一天,然后分别计算其所在当年第几周,最后相减即可得到,具体代码如下:

//获取当前日期所在月份的周数
public static int GetWeeksInMonth(this DateTime dateTime)
{
    //获取当前日期所在月的第一天
    var firstDayOfMonth = dateTime.GetFirstDayDateTimeOfMonth();
    //获取当前日期所在月的最后一天
    var lastDayOfMonth = dateTime.GetLastDayDateTimeOfMonth();
    //获取当月第一天在全年中的周数
    var firstWeek = firstDayOfMonth.GetWeekOfYear();
    //获取当月最后一天在全年中的周数
    var lastWeek = lastDayOfMonth.GetWeekOfYear();
    return lastWeek - firstWeek + 1;
}

08
、判断当前日期是否是周末

该方法比较简单,只是判断当前是否是否为周六或周日,具体代码如下:

//判断当前日期是否是周末
public static bool IsWeekend(this DateTime dateTime)
{
    return dateTime.DayOfWeek == DayOfWeek.Saturday || dateTime.DayOfWeek == DayOfWeek.Sunday;
}

09
、判断当前日期所在年是否是闰年

该方法调用了C#内置方法IsLeapYear,具体代码如下:

//判断当前日期所在年是否是闰年
public static bool IsLeapYear(this DateTime dateTime)
{
    return DateTime.IsLeapYear(dateTime.Year);
}

10
、获取当前日期所在季度

该方法也比较简单,只需要应用一个小公式即可求的,具体代码如下:

//获取当前日期所在季度
public static int GetQuarter(this DateTime dateTime)
{
    return (dateTime.Month - 1) / 3 + 1;
}

稍晚些时候我会把库上传至Nuget,大家可以直接使用Ideal.Core.Common。


:测试方法代码以及示例源码都已经上传至代码库,有兴趣的可以看看。
https://gitee.com/hugogoos/Ideal

转载请注明出处:

LVM(Logical Volume Manager,逻辑卷管理器)是一个用于Linux系统的磁盘管理工具。它提供了一种更加灵活的存储管理机制,可以方便地进行磁盘的扩容、缩减、快照以及迁移等操作。

基本概念

  1. 物理卷(PV):物理磁盘或分区,如
    /dev/sda1
  2. 卷组(VG):由一个或多个物理卷组成的集合。
  3. 逻辑卷(LV):从卷组中分配的逻辑磁盘,可以被文件系统格式化并用于存储数据。

安装LVM

在ubuntu系统可以通过下面的命令进行安装

# Ubuntu/Debian  
sudo apt
-get install lvm2

创建LVM

第一步:创建物理卷(PV)

假设有一个新的磁盘
/dev/sdb
,需要先将其初始化为物理卷:

sudo pvcreate /dev/sdb  

应用示例:

root@swan2:~# sudo pvcreate /dev/vdb
Physical volume
"/dev/vdb"successfully created.
root@sdwan2:
~# vgdisplay--- Volume group ---VG Name ubuntu-vg
System ID
Format lvm2
Metadata Areas
1Metadata Sequence No2VG Access read/write
VG Status resizable
MAX LV
0Cur LV1Open LV1Max PV0Cur PV1Act PV1VG Size<96.95GiB
PE Size
4.00MiB
Total PE
24818Alloc PE/ Size 12409 / 48.47GiB
Free PE
/ Size 12409 / 48.47GiB
VG UUID RCjkb6
-7ngM-9nss-OWOL-eqMR-9MDp-JCyLjk

第二步:创建卷组(VG)

创建一个名为
vg_data
的卷组,将新创建的物理卷加入其中:

sudo vgcreate vg_data /dev/sdb  

第三步:创建逻辑卷(LV)

创建一个名为
lv_data
的逻辑卷,大小为10G:

sudo lvcreate -n lv_data -L 10G vg_data  

第四步:格式化逻辑卷

对逻辑卷进行格式化,例如使用ext4文件系统:

sudo mkfs.ext4 /dev/vg_data/lv_data  

第五步:挂载逻辑卷

创建一个挂载点,然后将逻辑卷挂载到该挂载点:

mkdir /mnt/data  
sudo mount
/dev/vg_data/lv_data /mnt/data

扩容LVM

假设我们需要将逻辑卷
lv_data
扩展到20G,可以遵循以下步骤:

第一步:增加物理卷

假设在物理卷
/dev/sdb
上增加了空间(例如增加了第二个物理卷
/dev/sdc
),首先需要将新的物理卷初始化:

sudo pvcreate /dev/sdc  

然后,将其加入到卷组:

sudo vgextend vg_data /dev/sdc  

应用示例:

root@sdwan2:~# sudo vgextend ubuntu-vg /dev/vdb
Volume group
"ubuntu-vg"successfully extended
root@swan2:
~# vgdisplay--- Volume group ---VG Name ubuntu-vg
System ID
Format lvm2
Metadata Areas
2Metadata Sequence No3VG Access read/write
VG Status resizable
MAX LV
0Cur LV1Open LV1Max PV0Cur PV2Act PV2VG Size1.09TiB
PE Size
4.00MiB
Total PE
286961Alloc PE/ Size 12409 / 48.47GiB
Free PE
/ Size 274552 / <1.05TiB
VG UUID RCjkb6
-7ngM-9nss-OWOL-eqMR-9MDp-JCyLjk

第二步:扩展逻辑卷

使用以下命令将逻辑卷
lv_data
扩展到20G:

sudo lvextend -L 20G /dev/vg_data/lv_data  

或者,如果想使用所有可用的空间:

sudo lvextend -l +100%FREE /dev/vg_data/lv_data  

应用示例:

root@swan2:~# lvextend -l +100%FREE /dev/ubuntu-vg/ubuntu-lv
Size of logical volume ubuntu
-vg/ubuntu-lv changed from 48.47 GiB (12409 extents) to 1.09 TiB (286961extents).
Logical volume ubuntu
-vg/ubuntu-lv successfully resized.
root@sdwan2:
~#

第三步:扩展文件系统

扩展完逻辑卷后,需要扩展文件系统以利用新增的空间:

sudo resize2fs /dev/vg_data/lv_data  

应用示例:

root@swan2:~# sudo resize2fs /dev/ubuntu-vg/ubuntu-lv
resize2fs
1.45.5 (07-Jan-2020)
Filesystem at
/dev/ubuntu-vg/ubuntu-lv is mounted on /; on-line resizing required
old_desc_blocks
= 7, new_desc_blocks = 141The filesystem on/dev/ubuntu-vg/ubuntu-lv is now 293848064 (4k) blocks long.

root@swan2:
~#

查看LVM的信息

可以使用以下命令查看LVM的信息:

  • 查看所有物理卷:
    sudo pvdisplay  

  • 查看所有卷组:
    sudo vgdisplay  

  • 查看所有逻辑卷:
    sudo lvdisplay  

  • 查看详细的LVM状态:
    lvs

微软憋大招:SQL Server + Copilot = 地表最强AI数据库!

微软布局代码AI霸主地位

微软在人工智能领域的布局引人注目,尤其在代码生成领域,微软通过Copilot展现出了强大的竞争力。Copilot是基于人工智能的大模型代码助手工具,能够帮助开发者快速生成代码,大幅提升生产力。微软凭借其在AI技术领域的领先地位,正在不断推动Copilot的发展,并且早已没有任何对手。

从github被收购说起

微软在2018年以75亿美元的价格收购了全球最大的代码托管平台GitHub,这一步棋可以说是微软AI布局的重要战略举措。GitHub上拥有超过3000万开发者,管理着超过9600万个代码仓库。这些代码仓库不仅是开发者们合作和分享代码的平台,更是海量数据的根据地。对于微软来说,这些数据正是训练Copilot所需的核心资源。

通过GitHub,微软能够接触到全球开发者的工作成果和代码实践,进而为Copilot提供更为丰富和精准的训练资源。随着Copilot不断优化和升级,微软已经逐渐将其转变为每个开发者的"智能助手"。不仅如此,微软还致力于让开发者能够定制属于自己的Copilot智能体(Agent),从而满足不同开发场景的个性化需求。

普通PC和手机本地大模型普及

随着硬件技术的提升,普通PC就能运行千亿参数的模型,而几台机器的集群甚至可以处理万亿级参数。微软在代码AI领域的布局,将极大地推动这一趋势的实现,代码生成工具将发生质的飞跃,进一步巩固微软在AI领域的霸主地位。

GitHub,这个曾被戏称为“世界上最大的同性交友网站”,如今早已脱胎换骨,成为了微软在代码AI领域的“数据引擎”。未来,或许每个开发者都会有一个属于自己的Copilot,在日常开发中如影随形,微软的AI生态也将在全球开发者的支持下愈发强大。

微软的终极布局

预计在下一个SQL Server版本中, Copilot 和 SQL Server 将会深度集成,到时候一系列 Text2SQL、RAG以及向量数据库等的 AI 基础功能会投喂给用户。微软Copilot 辅助SQL Server数据库管理员和开发者完成数据操作,自动生成优化的 SQL 语句,预测和处理数据库运行中的潜在问题,比如数据库自动故障自愈。微软正通过 SQL Server 和 Copilot 的深度结合,打造一个集成 AI 和数据库能力的全新生态系统,以应对 Oracle数据库 这个老对手推出的 AI 数据库
Oracle23AI

另外,Oracle 23c (c代表 Cloud)在2024年5月2日 改名为Oracle 23ai,意味着Oracle从

转为
AI
的战略,这个方向盘着实打的有点大,可以说,现在数据库AI大战一触即发。

Copilot 的引入也进一步降低了企业使用数据库的门槛,特别是PC集成了大模型之后,PC上运行的SQL Server可以帮助用户通过自然语言与数据库进行交互。 甚至在不了解 SQL 语法的情况下完成数据查询、分析等任务。 结合 Copilot,未来的 SQL Server 将不仅服务于数据专家,还能成为低代码/无代码用户的数据分析工具,赋能更多企业和开发者。

实际上,微软在SQL Server2016开始就已经提供了机器学习服务( Machine Learning Services),这个机器学习服务可以让你在数据库上使用Python、R、Java等语言
直接训练
机器学习
模型
!以后在每个大版本中都在完善机器学习服务这个功能,也就是说SQL Server在2016这个版本就已经有这种
遥遥领先

功能
了!

内置的编程语言如下

Python执行架构如下

安装界面截图如下

目前
SQL Server 2025
的CTP版本迟迟未出,估计微软是在憋大招,有跳票的可能性。

目前微软已向 OpenAI 投资近
140 亿
美元,此外,目前OpenAI的国内服务只有Azure公有云提供,除了Azure公有云并没有其他渠道。

按照这个发展趋势,未来微软也有可能把OpenAI 的核心技术
GPT v5.0
移植进去
SQL Server 2005
,目前就看微软怎么把
大模型
塞进SQL Server,所以
微软
会在
AI数据库上遥遥领先
吗,元芳,你怎么看?

本文版权归作者所有,未经作者同意不得转载。

为什么要有 Buffer Pool?

虽然说 MySQL 的数据是存储在磁盘里的,但是也不能每次都从磁盘里面读取数据,这样性能是极差的。

要想提升查询性能,那就加个缓存。所以,当数据从磁盘中取出后,缓存内存中,下次查询同样的数据的时候,直接从内存中读取。

为此,Innodb 存储引擎设计了一个缓冲池(Buffer Pool),来提高数据库的读写性能。

  • 当读取数据时,如果数据存在于 Buffer Pool 中,客户端就会直接读取 Buffer Pool 中的数据,否则再去磁盘中读取。
  • 当修改数据时,首先是修改 Buffer Pool 中数据所在的页,然后将其页设置为脏页,最后由后台线程将脏页写入到磁盘。

Buffer Pool里有什么

InnoDB 会把存储的数据划分为若干个页,以页作为磁盘和内存交互的基本单位,一个页的默认大小为 16KB。因此,Buffer Pool 同样需要按页来划分。

Buffer Pool里面包含很多个缓存页,同时每个缓存页还有一个描述数据,也可以叫做是控制数据,也可以叫做描述数据,或者缓存页的元数据。控制块数据,控制数据包括「缓存页的表空间、页号、缓存页地址、链表节点」等等,控制块数据就是为了更好的管理Buffer Pool中的缓存页的。

控制块也是占有内存空间的,它是放在 Buffer Pool 的最前面,接着才是缓存页,如下图:

Buffer Pool 除了缓存「索引页」和「数据页」,还包括了 undo 页,插入缓存、自适应哈希索引、锁信息等等。

数据库启动的时候,是如何初始化Buffer Pool的

数据库只要一启动,就会按照设置的Buffer Pool大小,稍微再加大一点,去找操作系统申请一块内存区域,作为Buffer Pool的内存区域。

当内存区域申请完毕之后,数据库就会按照默认的缓存页的16KB的大小以及对应的800个字节左右的描述数据的大小,在Buffer Pool中划分出来一个一个的缓存页和一个一个的他们对应的描述数据。

只不过这个时候,Buffer Pool中的一个一个的
缓存页都是空的
,里面什么都没有,要等数据库运行起来之后,当对数据执行增删改查的操作的时候,才会把数据对应的页从磁盘文件里读取出来,放入Buffer Pool中的缓存页中。

管理Buffer Pool

管理空闲页-free链表

如何知道哪些缓存页是空的

当数据库运行起来之后,系统肯定会不停的执行增删改查的操作,此时就需要不停的从磁盘上读取一个一个的数据页放入Buffer Pool中的对应的缓存页里去,把数据缓存起来,那么以后就可以在内存里对这个数据执行增删改查了。

但是此时在从磁盘上读取数据页放入Buffer Pool中的缓存页的时候,必然涉及到一个问题,那就是哪些缓存页是空闲的?

因为默认情况下磁盘上的数据页和缓存页是一 一对应起来的,都是16KB,一个数据页对应一个缓存页。数据页只能加载到空闲的缓存页里,所以MySql必须要知道Buffer Pool中哪些缓存页是空闲的状态?

MySQL数据库会为Buffer Pool设计了一个
free链表
,是一个
双向链表
数据结构,这个free链表里,每个节点就是一个空闲的缓存页的描述数据块的地址,也就是说,只要你一个缓存页是空闲的,那么它的描述数据块就会被放入这个free链表中。

刚开始数据库启动的时候,所有的缓存页都是空闲的,因为此时可能是一个空的数据库,一条数据都没有,所以此时所有缓存页的描述数据块,都会被放入这个free链表中。

这个free链表里面就是各个缓存页的控制块,只要缓存页是空闲的,那么他们对应的控制块就会加入到这个free链表中,每个节点都会双向链接自己的前后节点,组成一个双向链表。

除此之外,这个free链表有一个基础节点,它会引用链表的头节点和尾节点,里面还存储了链表中当前有多少个节点,也就是
链表中有多少个控制块的节点
,也就是有多少个空闲的缓存页。

磁盘上的页如何读取到Buffer Pool的缓存页中去?

  • 首先,需要从free链表里获取一个控制块,然后就可以获取到这个控制块对应的空闲缓存页;
  • 接着就可以把磁盘上的数据页读取到对应的缓存页里去,同时把相关的一些数据写入控制块里去,比如这个数据页所属的表空间之类的信息
  • 最后把那个控制块从free链表里去除就可以了。

MySQL怎么知道某个数据页已经被缓存了

  • 在执行增删改查的时候,肯定是先看看这个数据页有没有被缓存,如果没被缓存就走上面的逻辑,从free链表中找到一个空闲的缓存页,从磁盘上读取数据页写入缓存页,写入控制数据,从free链表中移除这个控制块。
  • 但是如果数据页
    已经被缓存
    了,那么就会直接使用了。所以其实数据库还会有一个哈希表数据结构,他会用表空间号+ 数据页号,作为一个key,然后缓存页的地址作为value。当你要使用一个数据页的时候,通过“表空间号+数据页号”作为key去这个哈希表里查一下,如果没有就读取数据页,如果已经有了,就说明数据页已经被缓存了

MySQL引入了一个数据页缓存哈希表的结构,也就是说,每次你读取一个数据页到缓存之后,都会在这个哈希表中写入一个key-value对,key就是表空间号+数据页号,value就是缓存页的地址,那么下次如果你再使用这个数据页,就可以从哈希表里直接读取出来它已经被放入一个缓存页了。

管理脏页-flush链表

为什么会有脏页

如果你要更新的数据页都会在Buffer Pool的缓存页里,供你在内存中直接执行增删改的操作。mysql此时一旦更新了缓存页中的数据,那么缓存页里的数据和磁盘上的数据页里的数据,就不一致了,那么就说这个缓存页是脏页。

脏页怎么刷回磁盘

为了能快速知道哪些缓存页是脏的,于是就设计出 Flush 链表,它跟 Free 链表类似的,链表的节点也是控制块,区别在于 Flush 链表的元素都是脏页。

有了 Flush 链表后,后台线程就可以遍历 Flush 链表,将脏页写入到磁盘。

提高缓存命中率-LRU链表

Buffer Pool 的大小是有限的,对于一些频繁访问的数据希望可以一直留在 Buffer Pool 中,而一些很少访问的数据希望可以在某些时机可以淘汰掉,从而保证 Buffer Pool 不会因为满了而导致无法再缓存新的数据,同时还能保证常用数据留在 Buffer Pool 中。

缓存命中率是什么?

假设现在有两个缓存页,一个缓存页的数据,经常会被修改和查询,比如在100次请求中,有30次都是在查询和修改这个缓存页里的数据。那么此时我们可以说这种情况下,缓存命中率很高,为什么呢?因为100次请求中,30次都可以操作缓存,不需要从磁盘加载数据,这个缓存命中率就比较高了。

另外一个缓存页里的数据,就是刚从磁盘加载到缓存页之后,被修改和查询过1次,之后100次请求中没有一次是修改和查询这个缓存页的数据的,那么此时我们就说缓存命中率有点低,因为大部分请求可能还需要走磁盘查询数据,他们要操作的数据不在缓存中。

所以针对上述两个缓存页,当缓存页都满了的时候,第一个缓存页命中率很高,因此肯定是选择将第二个缓存页刷入磁盘中,从而释放缓存页。

因此就引入LRU链表来判断哪些缓存页是不常用的。Least Recently Used,最近最少使用。整体思想就是,链表头部的节点是最近使用的,而链表末尾的节点是最久没被使用的。那么,当空间不够了,就淘汰最久没被使用的节点,从而腾出空间。

简单版的LRU链表

  • 当访问的页在 Buffer Pool 里,就直接把该页对应的 LRU 链表节点移动到链表的头部。
  • 当访问的页不在 Buffer Pool 里,除了要把页放入到 LRU 链表的头部,还要淘汰 LRU 链表末尾的节点。

比如下图,假设 LRU 链表长度为 5,LRU 链表从左到右有 1,2,3,4,5 的页。

如果访问了 3 号的页,因为 3 号页在 Buffer Pool 里,所以把 3 号页移动到头部即可。

而如果接下来,访问了 8 号页,因为 8 号页不在 Buffer Pool 里,所以需要先淘汰末尾的 5 号页,然后再将 8 号页加入到头部。

简单版的LRU链表存在两个问题

  • 预读失效
  • Buffer Pool 污染;

什么是预读失效?

MySQL 的预读机制:程序是有空间局部性的,靠近当前被访问数据的数据,在未来很大概率会被访问到。所以,MySQL 在加载数据页时,会提前把它相邻的数据页一并加载进来,目的是为了减少磁盘 IO。

但是可能这些被提前加载进来的数据页,并没有被访问,相当于这个预读是白做了,这个就是预读失效。

如果使用简单的 LRU 算法,就会把预读页放到 LRU 链表头部,而当 Buffer Pool空间不够的时候,还需要把末尾的页淘汰掉。

如果这些预读页如果一直不会被访问到,就会出现一个很奇怪的问题,不会被访问的预读页却占用了 LRU 链表前排的位置,而末尾淘汰的页,可能是频繁访问的页,这样就大大降低了缓存命中率。

如何解决

首先不能害怕预读失效就把预读机制去了,空间局部性原理在大部分场景下是成立且有效的

而要避免预读失效带来影响,最好就是
让预读的页停留在 Buffer Pool 里的时间要尽可能的短,让真正被访问的页才移动到 LRU 链表的头部,从而保证真正被读取的热数据留在 Buffer Pool 里的时间尽可能长

Mysql将LRU链表划分成了两个区域:old 区域 和 young 区域。
young 区域在 LRU 链表的前半部分,old 区域则是在后半部分

划分这两个区域后,
预读的页就只需要加入到 old 区域的头部
,当页被真正访问的时候,才将页插入 young 区域的头部。如果预读的页一直没有被访问,就会从 old 区域移除,这样
就不会影响 young 区域中的热点数据

什么是 Buffer Pool 污染?

即使有了以上划分young区的old区的链表也会存在这个问题。

当某一个 SQL 语句扫描了大量的数据时,因为被读取了,这些数据就都会放在young区的头部,那么由于 Buffer Pool 空间有限,就有可能会将 Buffer Pool 里的所有页都替换出去,导致LRU的young区域的大量热数据被淘汰,等这些热数据又被再次访问的时候,由于缓存未命中,就会产生大量的磁盘 IO,MySQL 性能就会急剧下降,这个过程被称为 Buffer Pool 污染。

如何解决

MySQL 将进入到 young 区域条件增加了一个
停留在 old 区域的时间判断

Mysql在对某个处在 old 区域的缓存页进行第一次访问时,就在它对应的控制块中记录下来这个访问时间:

  • 如果后续的访问时间与第一次访问的时间在某个时间间隔内,那么该缓存页就不会被从 old 区域移动到 young 区域的头部;
  • 如果后续的访问时间与第一次访问的时间不在某个时间间隔内,那么该缓存页移动到 young 区域的头部;

MYsql缓存能否替代Redis

  1. Redis缓存支持的场景更多。
    • 实际工作中缓存的结果不单单是Mysql Select语句返回的结果,有可能是在此基础上又加工的结果;而Mysql缓存的是Select语句的结果
    • Redis可以提供更丰富的数据类型的访问,如List、Set、Map、ZSet
  2. Redis缓存命中率要远高于Mysql缓存。
    • Mysql选择要缓存的语句的方式不是根据访问频率,主要是根据select语句里边是否包含动态变化的值,没有动态变化值的则缓存,比如用了now函数就不会缓存。Redis是由客户端自主根据访问频率高进行缓存。
    • Redis丰富的数据结构使得缓存复用率更高,比如缓存的是List,可以随意访问List中的部分元素,比如分页需求
    • Mysql缓存的失效粒度很粗,只要表有更新,涉及该表的所有缓存(不管更新是否会影响缓存)都失效,这样使得缓存的利用率会很低,只是适用更新很少的表
    • 当存在主从结点,并且会从多个结点读取数据时,各个结点的缓存不会同步3. 性能:Redis的查询性能要远高于Mysql缓存,最主要的原因是Redis是全部放在内存的,但是因为mysql缓存的命中率问题使得Mysql无法全部放到内存中。Redis性能好也还有一些其他原因
    • Redis的存储结构有利于读写性能Redis是IO多路复用,可以支持更大的吞吐,Mysql的数据特征使得做成IO多路复用绝大多数情况下也没有意义
    • 数据更新时会同时将该表的所有缓存失效,会使得数据更新的速度变慢。

面试题专栏

Java面试题专栏
已上线,欢迎访问。

  • 如果你不知道简历怎么写,简历项目不知道怎么包装;
  • 如果简历中有些内容你不知道该不该写上去;
  • 如果有些综合性问题你不知道怎么答;

那么可以私信我,我会尽我所能帮助你。