JetBrains IntelliJ IDEA 中使用 vim 总结

虽然使用了很长时间的 Vim,也使用了很长时间的 IntelliJ IDEA,但总感觉没有充分利用,所以想再这里总结一下,系统的浏览一遍 Idea Vim 插件能提供的功能,看看能不能有所受益,Vim 和 IntelliJ IDEA 的基本操作和内容就省略了。

Introduction

首先 ideavim 这个插件是 JetBrains 官方提供的,基本上安装后即可。GitHub 的页面还提到 ideavim 插件提供了一些 Vim 插件的扩展功能,比如:

  • vim-easymotion
  • vim-surround
  • vim-multiple-cursors
  • vim-commentary
  • argtextobj.vim
  • vim-textobj-entire
  • ReplaceWithRegister

可以根据这个页面 上的方式配置和开启这个扩展功能。

为什么要用 IdeaVim

  • 既充分利用了 IntelliJ 提供的代码补全,重构,代码浏览等等功能,又可以充分利用 Vim 的多模式,以及 Vim 在编辑器中的高效
  • 利用 ~/.ideavimrc 来复用 Vim 的工作方式,以及充分利用 Idea 提供的 Action

vim-easymotion

Idea 中的 vim-easymotion 插件支持的配置,可以参考这里

AceJump

在了解 easymotion 时意外收获了 AceJump,IntelliJ IDEA 中的 easymotion 实际上是通过 AceJump 插件来实现的。 :q

默认情况下,使用 Ctrl + ; 来开启 AceJump 模式,不过我的 Ctrl + ; 已经作为输入法的多粘贴板来使用了,所以就改成 Alt + k

AceJump 的工作流程,按下 Alt + K 进入 AceJump ,此时按下任何按键就会在当前文件搜索,并给每一个结果一个 tag,按下回车,然后输入 tag 就可以快速跳转过去。

结合 action

在 IntelliJ IDEA 中,任何选项操作都会映射到一个 action 上,点击按钮,就执行对应的 action,所以记住 Ctrl + Shift + a 这个快捷键。

在编辑器模式下,可以输入如下命令查看 actionlist:

:actionlist

vim-surround

启用方式:

set surround

支持的 Commands: ys, cs, ds, S

下面的例子中,假设 *是当前光标的位置:

Old Text Command Text After command execute
“Hello *world!” ds” Hello world!
[123 + 4*56]/2 cs]) (123+456)/2
“Look ma, I’m *HTML!” cs” <q>Look ma, I'm HTML!</p>
if *x>3 ysW( if ( x>3 )
my $str = *www; vllS’ my $str = 'www';

vim-surround 在想要改变 surround 的时候非常方便。

vim-multiple-cursors

目前我的使用场景大部分通过 IDEA 自带的 rename 功能批量替换变量即可做到,所以目前还没有开启这个功能的需求,更多多光标的操作技巧可以参考这篇文章

IdeaVim 支持的所有快捷键:

总结

最后,这里 是我的 .ideavimrc 配置。

reference


2020-07-06 intellij , vim , editor , shortcut , summary

在树莓派上安装 Prometheus node-exporter

前些天正好在我的二代树莓派上安装了 AdGuard Home,这样一个基础服务必然不能少了监控,所以正好把 Prometheus node-exporter 安装一下。

Prerequisite

首先确认一下 CPU 型号,我的是二代,比较老,直接 lscpu 看一下就知道:

Model name:            ARMv7 Processor rev 5 (v7l)

这是一个 armv5 版本的,然后到 node-exporter 下载二进制:

注意选对版本。

Install

wget https://github.com/prometheus/node_exporter/releases/download/v1.0.1/node_exporter-1.0.1.linux-armv5.tar.gz

然后接着这篇文章 即可。

最后验证:

curl http://localhost:9100/metrics

添加到 Prometheus Server

  - job_name: 'raspberry pi node exporter'
	scrape_interval: 5s
	scrape_timeout: 5s
	static_configs:
	- targets: ['192.168.2.3:9100']

之后 Grafana 添加数据源,和之前的文章 一样。


2020-07-05 prometheus , raspberry-pi , linux , node-expoter , monitor

使用 Clonezilla 恢复 Windows 系统遇到的几个问题

之前也总结过两篇文章,我是如何使用 Clonezilla 进行全盘备份和恢复的 以及备份 Linux 过程中遇到的问题,今天这篇就记录一下恢复之前备份过的 Windows

我先来还原一下现在情况,原来我有两台小米的 Air 笔记本,所有的配置一样,不过一台我从之前的电脑上恢复了一个 Linux Mint 的系统,暂且叫这台 A1 笔记本,然后还有一台是默认的 Windows 系统,不过这一台用的比较少,暂且叫这台 A2 笔记本。前段时间我把 A2 笔记本使用 Clonezilla 备份了一下生成了一个从 device 到 image 的镜像,然后我把 A2 笔记本卖了,所以现在只剩下 A1 笔记本。

我在卖 A2 笔记本的时候,当时也做了系统的恢复,就是把当年 A1 原始的默认 Windows 系统恢复到了 A2 笔记本上,正好省去了我格式化硬盘,备份数据的苦恼,恢复上去之后 A2 没啥问题也可以直接启动。

但今天恢复 A1 笔记时,却遇到了一些问题,这里记录一下。

no bootable devices found

安装我之前的操作,恢复之前备份的 A2 的硬盘镜像文件到 A1 的整块硬盘上,官网的教程非常详细,这里就略过了,就安装默认的下一步下一步直接走了,可以所有操作完成后等等重启时,屏幕只出现了 “no bootable devices found”,我一想不应该呀,虽然用的是另一台机器的镜像恢复的数据,但是理论上应该还是能找到系统的。

之后还想着是不是引导坏了,还用着 Win PE 进去想修复一下,谁知道在 PE 里面根本找不到系统的硬盘,后来想想是不是恢复的时候把 MBR 搞坏了,还是说默认的 Windows 是安装在 GPT 分区表的硬盘上的。

然后使用如下的方法重新恢复了一次:

然后去 BIOS 中把之前引导 Linux 是的 Legacy 改成了 UEFI Mode,果然就能进入系统了。


2020-07-04 clonezilla , backup , restore

Albert launcher

Albert 是一个 Linux 上的启动器,使用 C++ 和 QT 实现,实现了如下的功能:

  • Run Applications
  • Open files
  • Open bookmarks
  • Search web
  • calculate things
  • GPL-licensed

Install

Official Build

通过下面的网站下载官方编译的版本。

从源代码编译安装

从源码编译安装:

git clone --recursive https://github.com/albertlauncher/albert.git
mkdir albert-build
cd albert-build
cmake ../albert -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Debug
make
sudo make install

顺利的话可以走到最后一步,不过大概率会出现各种依赖的问题。

➜ cmake ../albert -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Debug
CMake Error at /opt/qt59/lib/cmake/Qt5/Qt5Config.cmake:28 (find_package):
  Could not find a package configuration file provided by "Qt5X11Extras" with
  any of the following names:

	Qt5X11ExtrasConfig.cmake
	qt5x11extras-config.cmake

  Add the installation prefix of "Qt5X11Extras" to CMAKE_PREFIX_PATH or set
  "Qt5X11Extras_DIR" to a directory containing one of the above files.  If
  "Qt5X11Extras" provides a separate development package or SDK, be sure it
  has been installed.
Call Stack (most recent call first):
  lib/globalshortcut/CMakeLists.txt:16 (find_package)


-- Configuring incomplete, errors occurred!

安装缺失的依赖:

➜ sudo apt install libqt5x11extras5 libqt5x11extras5-dev



➜ cmake ../albert -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Debug
CMake Error at /usr/lib/x86_64-linux-gnu/cmake/Qt5/Qt5Config.cmake:28 (find_package):
  Could not find a package configuration file provided by "Qt5Svg" with any
  of the following names:

	Qt5SvgConfig.cmake
	qt5svg-config.cmake

  Add the installation prefix of "Qt5Svg" to CMAKE_PREFIX_PATH or set
  "Qt5Svg_DIR" to a directory containing one of the above files.  If "Qt5Svg"
  provides a separate development package or SDK, be sure it has been
  installed.
Call Stack (most recent call first):
  plugins/widgetboxmodel/CMakeLists.txt:7 (find_package)


-- Configuring incomplete, errors occurred!

安装缺失的依赖:

➜ sudo apt install libqt5svg5 libqt5svg5-dev

reference


2020-06-29 linux , albert , launcher , application

Moshi : 新一代的 Java 解析 JSON 工具

最近 FastJson 安全问题频发,所以 JSON 解析又被拉到台面上,而正好不久前看 Reddit 看到 Gson 的作者在推荐一个叫 Moshi 的库,这就花点时间看一下。1

Gson 存在的问题

序列化 Date 的时候不包含时区的信息

Date epoch = new Date(0);
String epochJson = new Gson().toJson(epoch);
// "Dec 31, 1969 7:00:00 PM"

RFC 3339 标准 里面规定的日期表示法:

2020-06-12T07:20:50.52Z

其中 T 用来分割前面的日期和后面的时间,而最后的 Z 表示这个时间是 UTC+0,其他人看到这个时间就可以根据自己的时区进行转换。2

Gson 在序列化 HTML 标签时,会进行 HTML escaping 成:

private String e = "12 & 5";
private String f = "12 > 6"

"e":"12 \u0026 5"
"f":"12 \u003e 6"

Moshi 优势

  • Moshi 库特别小,对于 Android 来说自然可以减小 APK 大小
  • Moshi 自带的 Adapter 可以满足大部分的需求,如果需要扩充也非常方便
  • 可以使用 @Json 来自定义 Field 名
  • Kotlin Support
  • 使用类似于 @HexColor int 的限定符可以让多个 JSON 映射到同一个 Java Type

Moshi 潜在的问题和 Gson 的差别

  • Moshi 只有少量的内置 Adapter,Moshi 不会去序列化平台相关的类型,比如 java.*, javax.*, android.* 等等,以防止被 Lock 在某一个特殊 JDK 或者 Android 版本
  • Moshi 只有少量的配置选项,没有 field naming strapegy, versioning, instance creator, long serialization policy.
  • Moshi 没有 JsonElement 模型
  • No HTML-safe escaping

使用

更加具体的使用方法可以参考源代码中的实现。

最基本的使用,序列化一个 Java Object 到 JSON,或者将 JSON 映射到 Java Object 里面。

将 JSON 字符串解析成 Java 对象

Moshi moshi = new Moshi.Builder().build();
JsonAdapter<BlackjackHand> jsonAdapter = moshi.adapter(BlackjackHand.class);

BlackjackHand blackjackHand = jsonAdapter.fromJson(json);
System.out.println(blackjackHand);

如果是 JsonArray:

Moshi moshi = new Moshi.Builder().build();

Type listOfCardsType = Types.newParameterizedType(List.class, Card.class);
JsonAdapter<List<Card>> jsonAdapter = moshi.adapter(listOfCardsType);

List<Card> cards = jsonAdapter.fromJson(json);

序列化 Java Object:

Moshi moshi = new Moshi.Builder().build();
JsonAdapter<BlackjackHand> jsonAdapter = moshi.adapter(BlackjackHand.class);

String json = jsonAdapter.toJson(blackjackHand);
System.out.println(json);

自定义 Field

比如有一个 JSON 字符串:

String json = ""
    + "{"
    + "  \"username\": \"jesse\","
    + "  \"lucky number\": 32"
    + "}\n";

其中的 lucky number 是带空格的,假如要解析到 Java Object

public final class Player {
  public final String username;
  public final @Json(name = "lucky number") int luckyNumber;
}

可以看到使用 @Json 注解即可。

源码解析

Moshi

Moshi 只能使用 Builder 模式创建,看其源码会发现,构造函数并不是 public 的。唯一一个带参数的

  Moshi(Builder builder) {
	List<JsonAdapter.Factory> factories = new ArrayList<>(
		builder.factories.size() + BUILT_IN_FACTORIES.size());
	factories.addAll(builder.factories);
	factories.addAll(BUILT_IN_FACTORIES);
	this.factories = Collections.unmodifiableList(factories);
  }

可以看到 Moshi 有一系列 adapter() 公开方法,通过 adapter() 方法可以返回一个 JsonAdapter<> 对象,之后的操作都在该 adapter 之上进行。

成员变量

private final List<JsonAdapter.Factory> factories;
private final ThreadLocal<LookupChain> lookupChainThreadLocal = new ThreadLocal<>();
private final Map<Object, JsonAdapter<?>> adapterCache = new LinkedHashMap<>();

说明:

  • factories: 是 JsonAdapter.Factory 数组,工厂模式产生 JsonAdapter
  • lookupChainThreadLocal 是一个 ThreadLocal 内部存放了 LookupChain
  • adapterCache 是一个 LinkedHashMap 用来保存 Object 到 JsonAdapter 的映射关系

Moshi 类中初始化的时候有 5 个内置的 Adapter Factory.

  static final List<JsonAdapter.Factory> BUILT_IN_FACTORIES = new ArrayList<>(5);

  static {
	BUILT_IN_FACTORIES.add(StandardJsonAdapters.FACTORY);
	BUILT_IN_FACTORIES.add(CollectionJsonAdapter.FACTORY);
	BUILT_IN_FACTORIES.add(MapJsonAdapter.FACTORY);
	BUILT_IN_FACTORIES.add(ArrayJsonAdapter.FACTORY);
	BUILT_IN_FACTORIES.add(ClassJsonAdapter.FACTORY);
  }
  • StandardJsonAdapters.FACTORY 包含了基本类型,包装类型,还有运行时才能决定的 ObjectJsonAdapter
  • CollectionJsonAdapter.FACTORY 包含 List, Collection, Set 类型
  • MapJsonAdapter.FACTORY 包含将 Map(Key 是 String) 的 JSON 转换成 Object 的 Adapter
  • ArrayJsonAdapter.FACTORY 处理包含了原始值或 Object 的 JSON Array
  • ClassJsonAdapter.FACTORY

Moshi.Builder

Moshi.Builder 是 Moshi 内部的一个类,用来创建 Moshi,它有一系列方法:

com.squareup.moshi.Moshi.Builder#add(java.lang.reflect.Type, com.squareup.moshi.JsonAdapter<T>)
com.squareup.moshi.Moshi.Builder#add(java.lang.reflect.Type, java.lang.Class<? extends java.lang.annotation.Annotation>, com.squareup.moshi.JsonAdapter<T>)
com.squareup.moshi.Moshi.Builder#add(com.squareup.moshi.JsonAdapter.Factory)
com.squareup.moshi.Moshi.Builder#add(java.lang.Object)
com.squareup.moshi.Moshi.Builder#addAll
com.squareup.moshi.Moshi.Builder#build

可以看到除了 overload 重载的 add() 方法外,就是 build 方法,而 add() 方法可以添加一系列类型。

JsonAdapter 抽象类

JsonAdapter 的公开方法:

com.squareup.moshi.JsonAdapter#fromJson(com.squareup.moshi.JsonReader)
com.squareup.moshi.JsonAdapter#fromJson(okio.BufferedSource)
com.squareup.moshi.JsonAdapter#fromJson(java.lang.String)
com.squareup.moshi.JsonAdapter#toJson(com.squareup.moshi.JsonWriter, T)
com.squareup.moshi.JsonAdapter#toJson(okio.BufferedSink, T)
com.squareup.moshi.JsonAdapter#toJson(T)
com.squareup.moshi.JsonAdapter#toJsonValue
com.squareup.moshi.JsonAdapter#fromJsonValue
com.squareup.moshi.JsonAdapter#serializeNulls
com.squareup.moshi.JsonAdapter#nullSafe
com.squareup.moshi.JsonAdapter#nonNull
com.squareup.moshi.JsonAdapter#lenient
com.squareup.moshi.JsonAdapter#failOnUnknown
com.squareup.moshi.JsonAdapter#indent

JsonAdapter 有两个抽象方法需要实现 fromJsontoJson

大致就能看出 JsonAdapter 的主要功能:

  • 一方面提供 fromJson to toJson的转换
  • 另一方面提供转换过程中的一些选项

    • serializeNulls serializes nulls when encoding JSON
    • nullSafe support for reading and writing
    • 比如 nonNull 调用时会返回一个不允许 null 值的 JsonAdapter
    • lenient 宽容处理 JSON
    • failOnUnknown 在遇到未知的 name 或 value 时抛出 JsonDataException 异常
    • indent 输出的 JSON 是格式化好的

JsonAdapter 主要去处理各个类型的转换,需要实现如下三个方法:

  static final JsonAdapter<Boolean> BOOLEAN_JSON_ADAPTER = new JsonAdapter<Boolean>() {
	@Override public Boolean fromJson(JsonReader reader) throws IOException {
	  return reader.nextBoolean();
	}

	@Override public void toJson(JsonWriter writer, Boolean value) throws IOException {
	  writer.value(value.booleanValue());
	}

	@Override public String toString() {
	  return "JsonAdapter(Boolean)";
	}
  };

JsonAdapter.Factory 接口

这个接口定义在抽象类 JsonAdapter 中,Factory 只需要实现一个方法:

  public interface Factory {
	@CheckReturnValue
	@Nullable JsonAdapter<?> create(Type type, Set<? extends Annotation> annotations, Moshi moshi);
  }

Factory 看名字也能猜到这是一个工厂方法,用来创造给定 Type 给定 Annotation 类型的 Adapter,如果无法创建则返回 null.

在 Factory 的实现中,可能会使用 moshi.adapter 来构建 Adapter.

Mosh.Builder

再看 Moshi.Builder 类成员

final List<JsonAdapter.Factory> factories = new ArrayList<>();

在源代码的基础上加了一些注释

reference


2020-06-23 java , json , gson , moshi

KIE API 学习笔记

Kie have these concepts which every user need to know.

KieService

KieService 允许创建 KieContainer

KieContainer

KieContainer 是所有给定 KieModule 的 KieBases 的集合。

KieContainer 可以加载 KieModule 和其依赖,KieContainer 可以拥有一个或者多个 KieBases.

KieModule

KieModule 在 org.kie.api.builder 包下,KieModule 是一个存放所有定义好的 KieBases 资源的容器,和 pom.xml 文件相似,定义了其 ReleaseId, kmodule.xml 文件定义了 KieBase 的名字,配置,以及其他用来创建 KieSession 的资源,以及其他用来 build KIEBases 的资源。

KieModule 用来定义多个 KieBases 和 KieSessions。KieModule 可以包含其他 KieModules.

KieBase

KieBase 是应用所有定义好的 Knowledge 合集,包括了 rules(规则), processes(流程), functions(方法), type models, KieBase 自身不包含任何运行时数据,sessions 可以从 KieBase 中创建,然后运行时数据可以被装入,并且通过 sessions 可以启动一个流程。

KieBase 代表着编译好的资源的版本,可以有 Stateless 和 Stateful Session,一个典型的使用场景是为一个 packages 使用一个 KieBase ,另一个 package 使用另一个 KieBase.

KieSession

KieSession 是和工作流引擎交互的最常用的方式,KieSession 允许应用建立和引擎的交互,session 的状态会在每一次调用的时候保存下来。而流程会在一组相同的变量上触发多次。当应用完成调用,必须调用 dispose() 来释放资源以及使用的内存。

KieServices kieServices = KieServices.Factory.get();
KieContainer kContainer = kieServices.getKieClasspathContainer();
KieSession kSession = kContainer.newKieSession();

for( Object fact : facts ) {
	kSession.insert( fact );
}
kSession.fireAllRules();
kSession.dispose();

每一个 KieBase 都可以有一个或者多个 KieSessions.

KieBuilder

KieBuilder is a builder for the resources contained in a KieModule

KieServices ks = KieServices.Factory.get();
KieRepository kr = ks.getRepository();
InputStream is = new ByteArrayInputStream(bytes);
KieModule kModule = kr.addKieModule(ks.getResources().newInputStreamResource(is));
KieContainer kContainer = ks.newKieContainer(kModule.getReleaseId());

KieResources

KieResources 可以从很多来源构造,字节流 (InputStream),文件系统 (File),ClassPath 等等。

KieModuleModel
KieRepository
KieContainerImpl
KieBase
KieSession

真正用来启动 Process 的类

ksession.startProcess()

reference


2020-06-17 kie , drools , jbpm , decision-table , score-card

JBoss 工作流相关 Docker 镜像整理

源代码地址:

三个可用的镜像:

  • business-central-workbench

JBoss Business-Central Workbench 基础镜像,可以根据该镜像来扩展创建自己的镜像。

  • business-central-workbench-showcase

继承自 JBoss Business-Central Workbench 镜像,可以直接使用的 Docker 镜像。提供了自定义的配置文件,默认的用户和角色。

  • jBPM Server Full distribution

提供了可以立即执行的全部 jBPM 功能的镜像,包含全部必须的配置文件。包括 jBPM Workbench, Kie Server and jBPM Case Management Showcase。可以访问 新手教程 来查看。


2020-06-12 jboss , kie , kie-workbench , drools

Java 查漏补缺:函数式接口

Functional interface is a new feature Java 8 introduced. Functional interfaces provides target types for lambda expressions and method references.

// Assignment context
Predicate<String> p = String::isEmpty;

// Method invocation context
stream.filter(e -> e.getSize() > 10)...

// Cast context
stream.map((ToIntFunction) e -> e.getSize())...

特性

  • 函数式接口都是表达一种行为
  • @FunctionalInterface 保证了函数式接口只有一个抽象方法,但是注解的使用是不必须的

java.util.function

相关的新的函数式接口定义在 java.util.function 包下 1

  • Consumer,一个参数,无返回,void accept(T t)
  • Function,一个参数,一个结果,R apply(T t)
  • Supplier,无参数,返回一个结果,T get()
  • Predicate 接收一个参数,返回一个 boolean,boolean test(T t)
  • BinaryOperator,接收两个相同类型参数,返回一个相同类型

实例

class Test
{
	public static void main(String args[])
	{
		// create anonymous inner class object
		new Thread(new Runnable()
		{
			@Override
			public void run()
			{
				System.out.println("New thread created");
			}
		}).start();
	}
}

使用函数式接口后

class Test
{
public static void main(String args[])
{

	// lambda expression to create the object
	new Thread(()->
	{System.out.println("New thread created");}).start();
}
}

定义和使用

@FunctionalInterface
interface Square
{
	int calculate(int x);
}

class Test
{
	public static void main(String args[])
	{
		int a = 5;

		// lambda expression to define the calculate method
		Square s = (int x)->x*x;

		// parameter passed and return type must be
		// same as defined in the prototype
		int ans = s.calculate(a);
		System.out.println(ans);
	}
}

reference


2020-06-09 java , linux , java-8

在 OpenMediaVault 上使用 SnapRAID 和 MergerFS

首先来介绍一下这两个软件,SnapRAID 和 MergerFS,不同于其他现有的 NAS 系统,可以把 OpenMediaVault 看成一个简单的带有 Web UI 的 Linux 系统,他使用最基本的文件系统,没有 ZFS 的实时文件冗余,所以需要 SnapRAID 提供的冗余来保护硬盘数据安全。SnapRAID 需要一块单独的硬盘来存放校验数据,这个盘的容量必须大于等于其他任何一个数据盘。SnapRAID 采用快照的方式来做数据冗余,这种设计避免了所有硬盘在没有数据操作情况下也要运转来实时数据备份的消耗。

MergerFS 则是一个联合文件系统,可以将多块硬盘挂载到一个挂载点,通过 MergerFS 来自动决定数据该存储在哪块硬盘上。

Prerequisite

先决条件:

  • 至少三块硬盘,两块硬盘用来演示 MergerFS 合并,一块硬盘用来作为 SnapRAID 冗余备份
  • 一个安装好的 OpenMediaVault 以及安装好 OMV-Extras 相关的插件
  • System - Plugin 下安装 openmediavault-snapraidopenmediavault-unionfilesystem 插件

MergerFS

MergerFS 是一个联合文件系统 (union file system),MergerFS 会将多块硬盘,或者多个文件夹合并到 MergerFS pool 中,这样一个系统就会有一个统一的文件入口,方便管理。

选用 MergerFS 另外一个理由就是,通过 MergerFS 合并的目录并不会对数据进行条带化的处理,每块硬盘上还是保存原来的文件目录和文件,任意一块硬盘单独拿出来放到其他系统上,不需要额外的逻辑卷的配置,就可以直接挂载读取这个硬盘的数据。

创建 MergerFS pool

  • Storage > Union Filesystems
  • Add
  • Give the pool a name
  • In the Branches 选项中,选择所有要合并的磁盘,这里不要选 parity 的磁盘
  • Create policy 中选择 Most free space
  • Minimum free space 中选择一个合适的大小,默认也可以
  • Option 中,默认
  • Save
  • Apply

这样以后在文件系统中就会看到新创建的联合目录。在创建共享文件夹的时候就可以在合并的联合文件系统上进行。

在创建了 MergerFS Pool 后,在 OpenMediaVault 的文件目录 /srv 目录下会多出一个文件夹,这个文件夹就会存放 MergerFS Pool 中的数据。

SnapRAID

SnapRAID 是一个磁盘阵列的冗余备份工具,它可以存储额外的奇偶校验信息用来恢复数据。

SnapRAID 适用于家庭媒体服务器,适合于存储多数不经常变动的大文件场景。

特性:

  • 所有的数据都经过哈希处理,以确保数据完整性来避免可能的磁盘损坏
  • 如果故障磁盘太多而无法恢复,则只会丢失故障磁盘上的数据。其他磁盘中的所有数据都是安全的
  • 如果意外的删除了某些文件,可以轻松的恢复
  • 可以在已经有数据的硬盘上使用
  • 硬盘可以有不同的大小
  • 可以在任何时候添加磁盘
  • 不会占用数据,可以在任何时候停用 SnapRAID 而不用重新格式化
  • 访问数据时,只有一块磁盘会转动,节省电源以及减少噪声

SnapRAID 作为一个备份工具非常强大,强烈推荐阅读官网上关于 SnapRAID 和 Unraid, ZFS 等系统或文件系统提供的备份的对比 1

在 OpenMediaVault 中使用 SnapRAID :

设置需要保护的磁盘

假设三块硬盘中前两块用来存储重要的数据,第三块用来存放奇偶校验信息。那么首先设置数据盘:

  • 在 Services > SnapRAID 菜单,click Drives 选项
  • 点击 Add
  • 选择第一块硬盘
  • 起一个友好的名字
  • 选择 Content
  • 选择 Data
  • 不需要选 Parity
  • 保存

重复上面的步骤将第二块磁盘也添加进来。

设置奇偶校验盘

添加奇偶校验信息盘的时候,和上面步骤相似。不过要注意的是在添加磁盘时

  • 不需要选择 Content
  • 不需要选择 Data
  • 选择 Parity

然后点击保存。

SnapRAID Scrub

最后设置 SnapRAID Scrub,scrubbing 的目的是检查数据盘和校验信息盘的错误。可以在 SnapRAID 的 settings 界面中启用 Scheduled diff,启用后会自动创建一个周期性 Crontab 任务。

SnapRAID Rules

对于一些不需要 SnapRAID 进行冗余校验的目录,可以在 Rules 选项中进行排除。比如说经常变动的 metadata 信息,Docker 容器配置,虚拟机等等。

SnapRAID Scheduled Jobs

周期性的执行这些命令。

sync 命令会更新 parity 信息,所有磁盘中修改的文件都会被读取,然后对应的 parity 信息都会更新。

touch 命令会将所有拥有 sub-second 时间戳的文件设置为 0,这样提高了 SnapRAID 识别移动和复制过的文件的能力,可以消除可能的重复。

scrub 命令会验证磁盘阵列中的数据和 sync 命令产生的 hash.

# Run this command for the first time
snapraid sync

# Run this command after the sync is completed
snapraid scrub

# Run this command for status
snapraid status

mergerfsfolders vs unionfilesystems

在安装完 OMV-Extras 后会在插件中看到两个相似的插件:

  • mergerfsfolders, 利用 mergerfs 将多个文件夹挂载到同一个挂载点
  • unionfilesystems,使用 union filesystem mergerfs 来将多块硬盘挂载到一个挂载点

其主要区别就在于一个是合并文件夹,一个是合并硬盘。所以如果对于全新的硬盘,没有任何数据,可以直接利用 unionfilesystems 来将多块硬盘组合成一个 pool.

reference


2020-06-07 openmediavault , snapraid , raid , backup , mergerfs , linux , debian

Linux 设备中的 major 和 minor 数字

Today, when I visit tldr issue and I saw a talk about the command lsblk, although I used a lot before, I really don’t understand the MAJ:MIN in the result. Most time, I use it to check the harddrive disk and partitions.

lsblk
NAME   MAJ:MIN RM   SIZE RO TYPE MOUNTPOINT
loop0    7:0    0 260.7M  1 loop /snap/kde-frameworks-5-core18/32
loop1    7:1    0 253.5M  1 loop /snap/electronic-wechat/7
loop2    7:2    0    69M  1 loop /snap/telegram-desktop/1634
loop3    7:3    0  21.3M  1 loop /snap/communitheme/1987
loop4    7:4    0    55M  1 loop /snap/core18/1754
loop5    7:5    0  93.9M  1 loop /snap/core/9066
loop6    7:6    0  54.8M  1 loop /snap/gtk-common-themes/1502
loop7    7:7    0  93.8M  1 loop /snap/core/8935
loop8    7:8    0 373.5M  1 loop /snap/anbox/158
loop10   7:10   0 397.1M  1 loop /snap/redis-desktop-manager/335
loop11   7:11   0 160.2M  1 loop /snap/gnome-3-28-1804/116
loop12   7:12   0    32M  1 loop /snap/git-fame/15
loop13   7:13   0 149.2M  1 loop /snap/postman/109
loop14   7:14   0    16M  1 loop /snap/communitheme/1768
loop15   7:15   0    55M  1 loop /snap/core18/1705
loop16   7:16   0 374.9M  1 loop /snap/redis-desktop-manager/400
loop17   7:17   0    69M  1 loop /snap/telegram-desktop/1627
loop18   7:18   0  62.1M  1 loop /snap/gtk-common-themes/1506
loop19   7:19   0  32.1M  1 loop /snap/git-fame/23
loop20   7:20   0 310.8M  1 loop
loop21   7:21   0 163.6M  1 loop /snap/postman/110
sda      8:0    0 931.5G  0 disk
├─sda1   8:1    0 214.9G  0 part
├─sda2   8:2    0  16.3G  0 part [SWAP]
└─sda3   8:3    0 700.4G  0 part /media/Backup
sdb      8:16   0 232.9G  0 disk
├─sdb1   8:17   0 232.9G  0 part /
└─sdb2   8:18   0     2M  0 part

However, when I take a close look at the output, I can see only the disk device output, but also see the snap package output. So I started to search informations about the MAJ:MIN.

Major and minor device number

We all know that under linux, all devices are managed under /dev folder. So lets check the special device first:

ls -al /dev/zero
crw-rw-rw- 1 root root 1, 5 May 29 19:40 /dev/zero

We can see that, ls output is a little bit different from normal output, /dev/zero device’s major number is 1 and minor is 5

Then let’s check /proc/devices:

cat /proc/devices

This file contains the list of device drivers configured into the current running kernal(block and character).1

We can see that under /proc/devices file, there are a list of number and strings. For example:

Character devices:
1 mem
5 /dev/tty
5 /dev/console
7 vcs

Block devices:
8 sd

Each device node’s type (block or character) and numbers serve as identifiers for the kernel.

On Linux, the canonical list of devices, with a brief explanation of their function, is maintained in the kernel.

  • major number: identify the driver associated with the device. For example /dev/null and /dev/zero are both managerd by driver 1, whereas virtual consoles and serial terminals are managed by driver 4. Kernal uses the major number at open time to dispatch execution to the appropriate driver.
  • minor number: refers to an instance, which is used by the driver itself, specified by the major number. Minor number is used for driver to identify the difference between devices.

After version 2.4, the kernel introduced a new feature, the device file system or devfs. But for now most distributions do not add these feature. Read more from here.

When devfs is not being used, adding a new driver to the system means assigning a major number to it. The assignment should be made at driver (module) initialization by calling the following function, defined in <linux.fs.h>:

int register_chrdev(unsigned int major, const char* name, struct file_operations* fops);

Once the driver has been registered in the kernel table, its operations are associated with the given major number. And a name must be inserted into the /dev directory and associated with your driver’s major and minor numbers.

The command to create a device node on the filesystem is called mknod:

mknod /dev/scull0 c 254 0

Explain:

  • c means: create a char device
  • with major nubmer 254
  • and minor number 0, minor number should be in the range 0 to 255

reference

  • 《Linux Device Drivers, Second Edition by Jonathan Corbet, Alessandro Rubini》

2020-06-01 linux , device , hardware , kernel

电子书

最近文章

  • 配置 Rime 在 Vim 下退出编辑模式时自动切换成英文输入法 半年以前在 Obisidian 的文章下面有人曾经问过我一个问题,如何在 Vim 或者其他使用 Vim 模式的编辑器,比如 IntelliJ,或者 Obisidian 开启 Vim 模式后方便地切换中英文输入法,因为在编辑模式和普通模式下,需要经常切换输入法,使得体验变得非常槽糕。
  • Asus RT-AC86U 设置 前些天给家里买手机正好凑单了一个 Asus RT-AC86U,正好可以代替出了两次故障的小米 3G。
  • 扩展 Proxmox 系统分区以及 Proxmox 文件系统初识 昨天想要扩展一下之前安装的 Proxmox 容量,对系统进行了一次关机,然而关机之后就悲剧的发现在 U 盘中的系统启动不了了,将 U 盘拔下检测之后发现 U 盘可能挂了,一个全新的 U 盘,在连续 192 天运行之后挂掉了。无奈之下只能想办法先恢复一下 Proxmox 系统以及安装在系统之上的 OpenMediaVault 了。
  • 『译』我最喜欢的命令行工具 偶然间看到一篇介绍 cli 的文章,感觉写得不错,正好借此机会也整理一下我之前使用过,以及觉得非常值得推荐的 CLI 工具。
  • 使用 Clonezilla 将硬盘中系统恢复到虚拟机中 今年陆陆续续将工作的环境迁移到了 macOS,虽然已经把日常的资料迁移到了 macOS,但是之前的 Linux 上还有一些配置,以及可以的一些测试还需要用到 Linux 虚拟机,所以我就想能不能用 Clonezilla 将磁盘中的系统备份然后恢复到虚拟机里面。因为我发现 macOS 下的 Fusion 还是很强大的。