Vim 插件之 vim-easymotion

Vim 中已经提供了非常多移动的动作,从简单的字符间移动 (jkhl),到 word 间 (w/e/b),句子间 ((/)),段落间 ({/}),行首行尾 (0/^/$),文档开始 (gg),文档结尾 (G),还有搜索 (//np) 等等一系列的操作,但 easy-motion 将 Vim 中的移动又提升了一个高度。

继续往下阅读之前先确保阅读了 vim 文档中关于 motion 的内容。

想象一个场景,想要跳转到当前行下一个段落中的第二个句子的第三个单词开头,使用上面提到的方法,可能需要按下不同的按键,并且可能还需要组合使用,那有没有什么方法能降低这个移动(或者说跳转)操作的复杂度呢?答案就是 easy-motion。不知道有多少人用过 Chrome 下的 Vimium 插件,在网页中按下 f,页面中每一个能点击的地方都会显示几个字符,然后按下字符就会相当于在页面上点击,easy-motion 也使用相同的方式实现这一功能。

easy-motion 插件提供了更强的移动操作,在 easymotion 的官方文档中是这样定义 easymotion 的,easymotion 可以将 <number>w 或者 <number>f{char} 简化为几个按键的操作,举一个简单的例子,在 easymotion 下,如果按下 w,那么 easymotion 会高亮所有 w(下一个词首) 的结果,然后只需要按下一个键,就可以跳转到任何 w 按键按下后的目标地址。

Install

Plug 'easymotion/vim-easymotion'

Usage

:help easymotion

Easymotion 的触发需要按下两次 <leader><leader>,当然推荐熟悉之后使用 vim 的 map 配置更改一下 leader.

Default Mapping      | Details
---------------------|----------------------------------------------
<Leader>f{char}      | Find {char} to the right. See |f|.
<Leader>F{char}      | Find {char} to the left. See |F|.
<Leader>t{char}      | Till before the {char} to the right. See |t|.
<Leader>T{char}      | Till after the {char} to the left. See |T|.
<Leader>w            | Beginning of word forward. See |w|.
<Leader>W            | Beginning of WORD forward. See |W|.
<Leader>b            | Beginning of word backward. See |b|.
<Leader>B            | Beginning of WORD backward. See |B|.
<Leader>e            | End of word forward. See |e|.
<Leader>E            | End of WORD forward. See |E|.
<Leader>ge           | End of word backward. See |ge|.
<Leader>gE           | End of WORD backward. See |gE|.
<Leader>j            | Line downward. See |j|.
<Leader>k            | Line upward. See |k|.
<Leader>n            | Jump to latest "/" or "?" forward. See |n|.
<Leader>N            | Jump to latest "/" or "?" backward. See |N|.
<Leader>s            | Find(Search) {char} forward and backward.
                     | See |f| and |F|.

下面将 <leader><leader> 简写成 <ll>

向后向前跳转

<ll>w
<ll>b

双向跳转

<ll>s

向上向下跳转到行:

<ll>j
<ll>k

reference


2020-07-06 vim , vim-plugin

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

通过如下方式产生 KieServices:

KieServices ks = KieServices.Factory.get();

KieService 可以用来创建 KieContainer。

KieContainer 定义了规则的范围。

KieContainer

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

KieContainer 承载了 KieModule 和其依赖,一个层级的 KieModules 结构可以被加载到一个 KieContainer 实例中。

KieContainer 可以拥有一个或者多个 KieBases.

kie container hierarchical structure

KieContainer 可以通过 KieService 产生:

KieContainer kContainer = ks.newKieClasspathContainer();

KieModule

每一个 KieModule 包含了 business assets(包括了流程,规则,决策表等等)。

一个 KieModule 是一个标准的 Java-Maven 项目。

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

指定的文件 kmodule.xml 定义在 META-TNF/ 目录下,一定了内部的资源如何分组如何配置等等信息。

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.

KieSession 是否是线程安全的

KieContainer 是线程安全的。

KieContainer.newStatelessKieSession()

KieContainer.newKieSession() 方法是线程不安全的。

有状态 Session 和无状态 Session 区别

Drools 的 Session 分为有状态和无状态。

stateful KieSession 可以在多次和 Rule Engine 交互的过程中保持状态。 而无状态的 KieSession 只允许我们交互一次,并直接获取结果。

StatefulKnowledgeSession

  • 与[[规则引擎]]持久交互
  • 推理过程多次触发同一个数据集
  • 使用完后,要调用 dispose() 方法
  • 有状态会话可以随时添加 Fact

Stateful 可以通过 insert 方法插入 Fact,并取得 FactHandle,通过这个 Handle 可以多次更新 Fact 从而触发规则

        FactHandle handle = statefulKieSession.insert(factObject);
        factObject.setBalance(100.0);
        statefulKieSession.update(handle,factObject);

StatelessKnowledgeSession

  • 对 StatefulKnowledgeSession 做了包装
  • 不能重复插入 Fact
  • 执行规则使用 execute() 方法
  • insert, fireAllRules 和 dispose 方法

Stateless 类似一次函数调用,通过 execute 方法传入 Fact,匹配规则

session.execute(Arrays.asList(new Object[]{routeResult,featureManager.getFreeFeatures(),accessManager,this}));
// 又或者,执行完获得结果:
List<Command> cmds = new ArrayList<>();
cmds.add(CommandFactory.newInsert(routeResult,"routeResult"));        cmds.add(CommandFactory.newInsert(featureManager.getFreeFeatures(),"freeFeature"));
cmds.add(CommandFactory.newInsert(accessManager,"accessManager"));
cmds.add(CommandFactory.newInsert(this,"router"));
ExecutionResults results = statelessKieSession.execute( CommandFactory.newBatchExecution( cmds ) );

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

通过如下步骤创建 MergerFS 磁盘池:

  • 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 操作

在添加完硬盘之后,可以进行同步操作:

  • Sync, 同步数据,并更新校验,默认进行差量同步
  • Scrub,检查潜在的错误
  • Diff,列出和上一次存在的差别
  • Fix,尽可能恢复到上一次同步状态
  • Fix silent,修复潜在的错误

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

电子书

最近文章

  • 在日本生活必备的应用收集整理 在上一篇梳理了一下如何 注册日区的 Apple ID ,其实主要是为了这一篇整理做准备,有很多的应用,如果不是日区就安装不了,并且如果要在日本生活有一些应用还是非常重要的,所以就提前梳理一下需要用到的应用。
  • 如何注册日区 Apple ID 以及初次登录日区 App Store 之前一直使用美区的账号,但突然发现有一些 App 在美区也没有,比如去日本经常需要用到的「乗換案内」就搜不到,美区里面尽是一些冒牌的,还穿插各种广告的应用,还有很多日本本地的一些应用也几乎都没有上架美区。
  • 《工作、消费主义和新穷人》读书笔记 怎么知道的这一本书 前些日子去线下逛书店的时候,在书店刚进门的架子上一眼就看到了这一本,或许是这本书的名字起得太吸引眼球了,但是拿起来之后看到作者才意识到这本书是之前刚刚读过的 [[现代性与大屠杀]] 的作者 [[齐格蒙 鲍曼]] 的著作,这更让我提起了兴趣。
  • 《活法》读书笔记 怎么知道的这一本书 这本书一直非常出名,[[稻盛和夫]] 的名字一直非常响亮,但我似乎对其没有任何了解,这一本书也是非常机缘巧合地在楼下的图书馆里面看到了。
  • Mastodon 站点管理:导入自定义表情包 [[Mastodon]] 实例可以允许站点管理员自定义整站上的表情包,管理的地址在 首选项-管理(Administration)-自定义表情(custom emojis) , 具体的页面地址是 https://instance.domain/admin/custom_emojis。