2019年11月

在以不同语言编写并在不同平台上运行的应用程序之间交换数据时,Protobuf 编码可提高效率。

协议缓冲区 Protocol Buffers Protobufs)像 XML 和 JSON 一样,可以让用不同语言编写并在不同平台上运行的应用程序交换数据。例如,用 Go 编写的发送程序可以在 Protobuf 中对以 Go 表示的销售订单数据进行编码,然后用 Java 编写的接收方可以对它进行解码,以获取所接收订单数据的 Java 表示方式。这是在网络连接上的结构示意图:

Go 销售订单 —> Pbuf 编码 —> 网络 —> Pbuf 界面 —> Java 销售订单

与 XML 和 JSON 相比,Protobuf 编码是二进制而不是文本,这会使调试复杂化。但是,正如本文中的代码示例所确认的那样,Protobuf 编码在大小上比 XML 或 JSON 编码要有效得多。

Protobuf 以另一种方式提供了这种有效性。在实现级别,Protobuf 和其他编码系统对结构化数据进行 序列化 serialize 反序列化 deserialize 。序列化将特定语言的数据结构转换为字节流,反序列化是将字节流转换回特定语言的数据结构的逆运算。序列化和反序列化可能成为数据交换的瓶颈,因为这些操作会占用大量 CPU。高效的序列化和反序列化是 Protobuf 的另一个设计目标。

最近的编码技术,例如 Protobuf 和 FlatBuffers,源自 1990 年代初期的 DCE/RPC 分布式计算环境/远程过程调用 Distributed Computing Environment/Remote Procedure Call )计划。与 DCE/RPC 一样,Protobuf 在数据交换中为 IDL(接口定义语言)和编码层做出了贡献。

本文将着眼于这两层,然后提供 Go 和 Java 中的代码示例以充实 Protobuf 的细节,并表明 Protobuf 是易于使用的。

Protobuf 作为一个 IDL 和编码层

像 Protobuf 一样,DCE/RPC 被设计为与语言和平台无关。适当的库和实用程序允许任何语言和平台用于 DCE/RPC 领域。此外,DCE/RPC 体系结构非常优雅。IDL 文档是一侧的远程过程与另一侧的调用者之间的协定。Protobuf 也是以 IDL 文档为中心的。

IDL 文档是文本,在 DCE/RPC 中,使用基本 C 语法以及元数据的语法扩展(方括号)和一些新的关键字,例如 interface。这是一个例子:

[uuid (2d6ead46-05e3-11ca-7dd1-426909beabcd), version(1.0)]
interface echo {
   const long int ECHO_SIZE = 512;
   void echo(
      [in]          handle_t h,
      [in, string]  idl_char from_client[ ],
      [out, string] idl_char from_service[ECHO_SIZE]
   );
}

该 IDL 文档声明了一个名为 echo 的过程,该过程带有三个参数:类型为 handle_t(实现指针)和 idl_char(ASCII 字符数组)的 [in] 参数被传递给远程过程,而 [out] 参数(也是一个字符串)从该过程中传回。在此示例中,echo 过程不会显式返回值(echo 左侧的 void),但也可以返回值。返回值,以及一个或多个 [out] 参数,允许远程过程任意返回许多值。下一节将介绍 Protobuf IDL,它的语法不同,但同样用作数据交换中的协定。

DCE/RPC 和 Protobuf 中的 IDL 文档是创建用于交换数据的基础结构代码的实用程序的输入:

IDL 文档 —> DCE/PRC 或 Protobuf 实用程序 —> 数据交换的支持代码

作为相对简单的文本,IDL 是同样便于人类阅读的关于数据交换细节的文档(特别是交换的数据项的数量和每个项的数据类型)。

Protobuf 可用于现代 RPC 系统,例如 gRPC;但是 Protobuf 本身仅提供 IDL 层和编码层,用于从发送者传递到接收者的消息。与原本的 DCE/RPC 一样,Protobuf 编码是二进制的,但效率更高。

目前,XML 和 JSON 编码仍在通过 Web 服务等技术进行的数据交换中占主导地位,这些技术利用 Web 服务器、传输协议(例如 TCP、HTTP)以及标准库和实用程序等原有的基础设施来处理 XML 和 JSON 文档。 此外,各种类型的数据库系统可以存储 XML 和 JSON 文档,甚至旧式关系型系统也可以轻松生成查询结果的 XML 编码。现在,每种通用编程语言都具有支持 XML 和 JSON 的库。那么,是什么让我们回到 Protobuf 之类的二进制编码系统呢?

让我们看一下负十进制值 -128。以 2 的补码二进制表示形式(在系统和语言中占主导地位)中,此值可以存储在单个 8 位字节中:10000000。此整数值在 XML 或 JSON 中的文本编码需要多个字节。例如,UTF-8 编码需要四个字节的字符串,即 -128,即每个字符一个字节(十六进制,值为 0x2d0x310x320x38)。XML 和 JSON 还添加了标记字符,例如尖括号和大括号。有关 Protobuf 编码的详细信息下面就会介绍,但现在的关注点是一个通用点:文本编码的压缩性明显低于二进制编码。

在 Go 中使用 Protobuf 的示例

我的代码示例着重于 Protobuf 而不是 RPC。以下是第一个示例的概述:

  • 名为 dataitem.proto 的 IDL 文件定义了一个 Protobuf 消息,它具有六个不同类型的字段:具有不同范围的整数值、固定大小的浮点值以及两个不同长度的字符串。
  • Protobuf 编译器使用 IDL 文件生成 Go 版本(以及后面的 Java 版本)的 Protobuf 消息及支持函数。
  • Go 应用程序使用随机生成的值填充原生的 Go 数据结构,然后将结果序列化为本地文件。为了进行比较, XML 和 JSON 编码也被序列化为本地文件。
  • 作为测试,Go 应用程序通过反序列化 Protobuf 文件的内容来重建其原生数据结构的实例。
  • 作为语言中立性测试,Java 应用程序还会对 Protobuf 文件的内容进行反序列化以获取原生数据结构的实例。

我的网站上提供了该 IDL 文件以及两个 Go 和一个 Java 源文件,打包为 ZIP 文件。

最重要的 Protobuf IDL 文档如下所示。该文档存储在文件 dataitem.proto 中,并具有常规的.proto 扩展名。

示例 1、Protobuf IDL 文档

syntax = "proto3";

package main;

message DataItem {
  int64  oddA  = 1;
  int64  evenA = 2;
  int32  oddB  = 3;
  int32  evenB = 4;
  float  small = 5;
  float  big   = 6;
  string short = 7;
  string long  = 8;
}

该 IDL 使用当前的 proto3 而不是较早的 proto2 语法。软件包名称(在本例中为 main)是可选的,但是惯例使用它以避免名称冲突。这个结构化的消息包含八个字段,每个字段都有一个 Protobuf 数据类型(例如,int64string)、名称(例如,oddAshort)和一个等号 = 之后的数字标签(即键)。标签(在此示例中为 1 到 8)是唯一的整数标识符,用于确定字段序列化的顺序。

Protobuf 消息可以嵌套到任意级别,而一个消息可以是另外一个消息的字段类型。这是一个使用 DataItem 消息作为字段类型的示例:

message DataItems {
  repeated DataItem item = 1;
}

单个 DataItems 消息由重复的(零个或多个)DataItem 消息组成。

为了清晰起见,Protobuf 还支持枚举类型:

enum PartnershipStatus {
  reserved "FREE", "CONSTRAINED", "OTHER";
}

reserved 限定符确保用于实现这三个符号名的数值不能重复使用。

为了生成一个或多个声明 Protobuf 消息结构的特定于语言的版本,包含这些结构的 IDL 文件被传递到protoc 编译器(可在 Protobuf GitHub 存储库中找到)。对于 Go 代码,可以以通常的方式安装支持的 Protobuf 库(这里以 作为命令行提示符):

% go get github.com/golang/protobuf/proto

将 Protobuf IDL 文件 dataitem.proto 编译为 Go 源代码的命令是:

% protoc --go_out=. dataitem.proto

标志 --go_out 指示编译器生成 Go 源代码。其他语言也有类似的标志。在这种情况下,结果是一个名为 dataitem.pb.go 的文件,该文件足够小,可以将其基本内容复制到 Go 应用程序中。以下是生成的代码的主要部分:

var _ = proto.Marshal

type DataItem struct {
   OddA  int64   `protobuf:"varint,1,opt,name=oddA" json:"oddA,omitempty"`
   EvenA int64   `protobuf:"varint,2,opt,name=evenA" json:"evenA,omitempty"`
   OddB  int32   `protobuf:"varint,3,opt,name=oddB" json:"oddB,omitempty"`
   EvenB int32   `protobuf:"varint,4,opt,name=evenB" json:"evenB,omitempty"`
   Small float32 `protobuf:"fixed32,5,opt,name=small" json:"small,omitempty"`
   Big   float32 `protobuf:"fixed32,6,opt,name=big" json:"big,omitempty"`
   Short string  `protobuf:"bytes,7,opt,name=short" json:"short,omitempty"`
   Long  string  `protobuf:"bytes,8,opt,name=long" json:"long,omitempty"`
}

func (m *DataItem) Reset()         { *m = DataItem{} }
func (m *DataItem) String() string { return proto.CompactTextString(m) }
func (*DataItem) ProtoMessage()    {}
func init() {}

编译器生成的代码具有 Go 结构 DataItem,该结构导出 Go 字段(名称现已大写开头),该字段与 Protobuf IDL 中声明的名称匹配。该结构字段具有标准的 Go 数据类型:int32int64float32string。在每个字段行的末尾,是描述 Protobuf 类型的字符串,提供 Protobuf IDL 文档中的数字标签及有关 JSON 信息的元数据,这将在后面讨论。

此外也有函数;最重要的是 Proto.Marshal,用于将 DataItem 结构的实例序列化为 Protobuf 格式。辅助函数包括:清除 DataItem 结构的 Reset,生成 DataItem 的单行字符串表示的 String

描述 Protobuf 编码的元数据应在更详细地分析 Go 程序之前进行仔细研究。

Protobuf 编码

Protobuf 消息的结构为键/值对的集合,其中数字标签为键,相应的字段为值。字段名称(例如,oddAsmall)是供人类阅读的,但是 protoc 编译器的确使用了字段名称来生成特定于语言的对应名称。例如,Protobuf IDL 中的 oddAsmall 名称在 Go 结构中分别成为字段 OddASmall

键和它们的值都被编码,但是有一个重要的区别:一些数字值具有固定大小的 32 或 64 位的编码,而其他数字(包括消息标签)则是 varint 编码的,位数取决于整数的绝对值。例如,整数值 1 到 15 需要 8 位 varint 编码,而值 16 到 2047 需要 16 位。varint 编码在本质上与 UTF-8 编码类似(但细节不同),它偏爱较小的整数值而不是较大的整数值。(有关详细分析,请参见 Protobuf 编码指南)结果是,Protobuf 消息应该在字段中具有较小的整数值(如果可能),并且键数应尽可能少,但每个字段至少得有一个键。

下表 1 列出了 Protobuf 编码的要点:

编码示例类型长度
varintint32uint32int64可变长度
fixedfixed32floatdouble固定的 32 位或 64 位长度
字节序列stringbytes序列长度

表 1. Protobuf 数据类型

未明确固定长度的整数类型是 varint 编码的;因此,在 varint 类型中,例如 uint32u 代表无符号),数字 32 描述了整数的范围(在这种情况下为 0 到 2 32 - 1),而不是其位的大小,该位大小取决于值。相比之下,对于固定长度类型(例如 fixed32double),Protobuf 编码分别需要 32 位和 64 位。Protobuf 中的字符串是字节序列;因此,字段编码的大小就是字节序列的长度。

另一个高效的方法值得一提。回想一下前面的示例,其中的 DataItems 消息由重复的 DataItem 实例组成:

message DataItems {
  repeated DataItem item = 1;
}

repeated 表示 DataItem 实例是打包的:集合具有单个标签,在这里是 1。因此,具有重复的 DataItem 实例的 DataItems 消息比具有多个但单独的 DataItem 字段、每个字段都需要自己的标签的消息的效率更高。

了解了这一背景,让我们回到 Go 程序。

dataItem 程序的细节

dataItem 程序创建一个 DataItem 实例,并使用适当类型的随机生成的值填充字段。Go 有一个 rand 包,带有用于生成伪随机整数和浮点值的函数,而我的 randString 函数可以从字符集中生成指定长度的伪随机字符串。设计目标是要有一个具有不同类型和位大小的字段值的 DataItem 实例。例如,OddAEvenA 值分别是 64 位非负整数值的奇数和偶数;但是 OddBEvenB 变体的大小为 32 位,并存放 0 到 2047 之间的小整数值。随机浮点值的大小为 32 位,字符串为 16(Short)和 32(Long)字符的长度。这是用随机值填充 DataItem 结构的代码段:

// 可变长度整数
n1 := rand.Int63()        // 大整数
if (n1 & 1) == 0 { n1++ } // 确保其是奇数
...
n3 := rand.Int31() % UpperBound // 小整数
if (n3 & 1) == 0 { n3++ }       // 确保其是奇数

// 固定长度浮点数
...
t1 := rand.Float32()
t2 := rand.Float32()
...
// 字符串
str1 := randString(StrShort)
str2 := randString(StrLong)

// 消息
dataItem := &DataItem {
   OddA:  n1,
   EvenA: n2,
   OddB:  n3,
   EvenB: n4,
   Big:   f1,
   Small: f2,
   Short: str1,
   Long:  str2,
}

创建并填充值后,DataItem 实例将以 XML、JSON 和 Protobuf 进行编码,每种编码均写入本地文件:

func encodeAndserialize(dataItem *DataItem) {
   bytes, _ := xml.MarshalIndent(dataItem, "", " ")  // Xml to dataitem.xml
   ioutil.WriteFile(XmlFile, bytes, 0644)            // 0644 is file access permissions

   bytes, _ = json.MarshalIndent(dataItem, "", " ")  // Json to dataitem.json
   ioutil.WriteFile(JsonFile, bytes, 0644)

   bytes, _ = proto.Marshal(dataItem)                // Protobuf to dataitem.pbuf
   ioutil.WriteFile(PbufFile, bytes, 0644)
}

这三个序列化函数使用术语 marshal,它与 serialize 意思大致相同。如代码所示,三个 Marshal 函数均返回一个字节数组,然后将其写入文件。(为简单起见,忽略可能的错误处理。)在示例运行中,文件大小为:

dataitem.xml:  262 bytes
dataitem.json: 212 bytes
dataitem.pbuf:  88 bytes

Protobuf 编码明显小于其他两个编码方案。通过消除缩进字符(在这种情况下为空白和换行符),可以稍微减小 XML 和 JSON 序列化的大小。

以下是 dataitem.json 文件,该文件最终是由 json.MarshalIndent 调用产生的,并添加了以 ## 开头的注释:

{
 "oddA":  4744002665212642479,                ## 64-bit >= 0
 "evenA": 2395006495604861128,                ## ditto
 "oddB":  57,                                 ## 32-bit >= 0 but < 2048
 "evenB": 468,                                ## ditto
 "small": 0.7562016,                          ## 32-bit floating-point
 "big":   0.85202795,                         ## ditto
 "short": "ClH1oDaTtoX$HBN5",                 ## 16 random chars
 "long":  "xId0rD3Cri%3Wt%^QjcFLJgyXBu9^DZI"  ## 32 random chars
}

尽管这些序列化的数据写入到本地文件中,但是也可以使用相同的方法将数据写入网络连接的输出流。

测试序列化和反序列化

Go 程序接下来通过将先前写入 dataitem.pbuf 文件的字节反序列化为 DataItem 实例来运行基本测试。这是代码段,其中去除了错误检查部分:

filebytes, err := ioutil.ReadFile(PbufFile) // get the bytes from the file
...
testItem.Reset()                            // clear the DataItem structure
err = proto.Unmarshal(filebytes, testItem)  // deserialize into a DataItem instance

用于 Protbuf 反序列化的 proto.Unmarshal 函数与 proto.Marshal 函数相反。原始的 DataItem 和反序列化的副本将被打印出来以确认完全匹配:

Original:
2041519981506242154 3041486079683013705 1192 1879
0.572123 0.326855
boPb#T0O8Xd&Ps5EnSZqDg4Qztvo7IIs 9vH66AiGSQgCDxk&

Deserialized:
2041519981506242154 3041486079683013705 1192 1879
0.572123 0.326855
boPb#T0O8Xd&Ps5EnSZqDg4Qztvo7IIs 9vH66AiGSQgCDxk&

一个 Java Protobuf 客户端

用 Java 写的示例是为了确认 Protobuf 的语言中立性。原始 IDL 文件可用于生成 Java 支持代码,其中涉及嵌套类。但是,为了抑制警告信息,可以进行一些补充。这是修订版,它指定了一个 DataMsg 作为外部类的名称,内部类在该 Protobuf 消息后面自动命名为 DataItem

syntax = "proto3";

package main;

option java_outer_classname = "DataMsg";

message DataItem {
...

进行此更改后,protoc 编译与以前相同,只是所期望的输出现在是 Java 而不是 Go:

% protoc --java_out=. dataitem.proto

生成的源文件(在名为 main 的子目录中)为 DataMsg.java,长度约为 1,120 行:Java 并不简洁。编译然后运行 Java 代码需要具有 Protobuf 库支持的 JAR 文件。该文件位于 Maven 存储库中。

放置好这些片段后,我的测试代码相对较短(并且在 ZIP 文件中以 Main.java 形式提供):

package main;
import java.io.FileInputStream;

public class Main {
   public static void main(String[] args) {
      String path = "dataitem.pbuf";  // from the Go program's serialization
      try {
         DataMsg.DataItem deserial =
           DataMsg.DataItem.newBuilder().mergeFrom(new FileInputStream(path)).build();

         System.out.println(deserial.getOddA()); // 64-bit odd
         System.out.println(deserial.getLong()); // 32-character string
      }
      catch(Exception e) { System.err.println(e); }
    }
}

当然,生产级的测试将更加彻底,但是即使是该初步测试也可以证明 Protobuf 的语言中立性:dataitem.pbuf 文件是 Go 程序对 Go 语言版的 DataItem 进行序列化的结果,并且该文件中的字节被反序列化以产生一个 Java 语言的 DataItem 实例。Java 测试的输出与 Go 测试的输出相同。

用 numPairs 程序来结束

让我们以一个示例作为结尾,来突出 Protobuf 效率,但又强调在任何编码技术中都会涉及到的成本。考虑以下 Protobuf IDL 文件:

syntax = "proto3";
package main;

message NumPairs {
  repeated NumPair pair = 1;
}

message NumPair {
  int32 odd = 1;
  int32 even = 2;
}

NumPair 消息由两个 int32 值以及每个字段的整数标签组成。NumPairs 消息是嵌入的 NumPair 消息的序列。

Go 语言的 numPairs 程序(如下)创建了 200 万个 NumPair 实例,每个实例都附加到 NumPairs 消息中。该消息可以按常规方式进行序列化和反序列化。

示例 2、numPairs 程序

package main

import (
   "math/rand"
   "time"
   "encoding/xml"
   "encoding/json"
   "io/ioutil"
   "github.com/golang/protobuf/proto"
)

// protoc-generated code: start
var _ = proto.Marshal
type NumPairs struct {
   Pair []*NumPair `protobuf:"bytes,1,rep,name=pair" json:"pair,omitempty"`
}

func (m *NumPairs) Reset()         { *m = NumPairs{} }
func (m *NumPairs) String() string { return proto.CompactTextString(m) }
func (*NumPairs) ProtoMessage()    {}
func (m *NumPairs) GetPair() []*NumPair {
   if m != nil { return m.Pair }
   return nil
}

type NumPair struct {
   Odd  int32 `protobuf:"varint,1,opt,name=odd" json:"odd,omitempty"`
   Even int32 `protobuf:"varint,2,opt,name=even" json:"even,omitempty"`
}

func (m *NumPair) Reset()         { *m = NumPair{} }
func (m *NumPair) String() string { return proto.CompactTextString(m) }
func (*NumPair) ProtoMessage()    {}
func init() {}
// protoc-generated code: finish

var numPairsStruct NumPairs
var numPairs = &numPairsStruct

func encodeAndserialize() {
   // XML encoding
   filename := "./pairs.xml"
   bytes, _ := xml.MarshalIndent(numPairs, "", " ")
   ioutil.WriteFile(filename, bytes, 0644)

   // JSON encoding
   filename = "./pairs.json"
   bytes, _ = json.MarshalIndent(numPairs, "", " ")
   ioutil.WriteFile(filename, bytes, 0644)

   // ProtoBuf encoding
   filename = "./pairs.pbuf"
   bytes, _ = proto.Marshal(numPairs)
   ioutil.WriteFile(filename, bytes, 0644)
}

const HowMany = 200 * 100  * 100 // two million

func main() {
   rand.Seed(time.Now().UnixNano())

   // uncomment the modulus operations to get the more efficient version
   for i := 0; i < HowMany; i++ {
      n1 := rand.Int31() // % 2047
      if (n1 & 1) == 0 { n1++ } // ensure it's odd
      n2 := rand.Int31() // % 2047
      if (n2 & 1) == 1 { n2++ } // ensure it's even

      next := &NumPair {
                 Odd:  n1,
                 Even: n2,
              }
      numPairs.Pair = append(numPairs.Pair, next)
   }
   encodeAndserialize()
}

每个 NumPair 中随机生成的奇数和偶数值的范围在 0 到 20 亿之间变化。就原始数据(而非编码数据)而言,Go 程序中生成的整数总共为 16MB:每个 NumPair 为两个整数,总计为 400 万个整数,每个值的大小为四个字节。

为了进行比较,下表列出了 XML、JSON 和 Protobuf 编码的示例 NumsPairs 消息的 200 万个 NumPair 实例。原始数据也包括在内。由于 numPairs 程序生成随机值,因此样本运行的输出有所不同,但接近表中显示的大小。

编码文件字节大小Pbuf/其它 比例
pairs.raw16MB169%
Protobufpairs.pbuf27MB
JSONpairs.json100MB27%
XMLpairs.xml126MB21%

表 2. 16MB 整数的编码开销

不出所料,Protobuf 和之后的 XML 和 JSON 差别明显。Protobuf 编码大约是 JSON 的四分之一,是 XML 的五分之一。但是原始数据清楚地表明 Protobuf 也会产生编码开销:序列化的 Protobuf 消息比原始数据大 11MB。包括 Protobuf 在内的任何编码都涉及结构化数据,这不可避免地会增加字节。

序列化的 200 万个 NumPair 实例中的每个实例都包含个整数值:Go 结构中的 EvenOdd 字段分别一个,而 Protobuf 编码中的每个字段、每个标签一个。对于原始数据(而不是编码数据),每个实例将达到 16 个字节,样本 NumPairs 消息中有 200 万个实例。但是 Protobuf 标记(如 NumPair 字段中的 int32 值)使用 varint 编码,因此字节长度有所不同。特别是,小的整数值(在这种情况下,包括标签在内)需要不到四个字节进行编码。

如果对 numPairs 程序进行了修改,以使两个 NumPair 字段的值小于 2048,且其编码为一或两个字节,则 Protobuf 编码将从 27MB 下降到 16MB,这正是原始数据的大小。下表总结了样本运行中的新编码大小。

编码文件字节大小Pbuf/其它 比例
Nonepairs.raw16MB100%
Protobufpairs.pbuf16MB
JSONpairs.json77MB21%
XMLpairs.xml103MB15%

表 3. 编码 16MB 的小于 2048 的整数

总之,修改后的 numPairs 程序的字段值小于 2048,可减少原始数据中每个四字节整数值的大小。但是 Protobuf 编码仍然需要标签,这些标签会在 Protobuf 消息中添加字节。Protobuf 编码确实会增加消息大小,但是如果要编码相对较小的整数值(无论是字段还是键),则可以通过 varint 因子来减少此开销。

对于包含混合类型的结构化数据(且整数值相对较小)的中等大小的消息,Protobuf 明显优于 XML 和 JSON 等选项。在其他情况下,数据可能不适合 Protobuf 编码。例如,如果两个应用程序需要共享大量文本记录或大整数值,则可以采用压缩而不是编码技术。


via: https://opensource.com/article/19/10/protobuf-data-interchange

作者:Marty Kalin 选题:lujun9972 译者:wxy 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

Ubuntu 19.10 的主要新特性之一就是 ZFS。现在你可以很容易的无需额外努力就可以在 Ubuntu 系统上安装 ZFS了。

一般情况下,安装 Linux 都会选择 Ext4 文件系统。但是如果是全新安装 Ubuntu 19.10,在安装的启动阶段可以看到 ZFS 选项。

你可以在安装 Ubuntu 19.10 的时候选择 ZFS

让我们看看 ZFS 为何重要,以及如何在已经安装了 ZFS 的 Ubuntu 上使用它。

ZFS 与其他文件系统有哪些区别?

ZFS 的设计初衷是:处理海量存储和避免数据损坏。ZFS 可以处理 256 千万亿的 ZB 数据。(这就是 ZFS 的 Z)且它可以处理最大 16 EB 的文件。

如果你仅有一个单磁盘的笔记本电脑,你可以体验 ZFS 的数据保护特性。写时复制(COW)特性确保正在使用的数据不会被覆盖,相反,新的数据会被写到一个新的块中,同时文件系统的元数据会被更新到新块中。ZFS 可容易的创建文件系统的快照。这个快照可追踪文件系统的更改,并共享数据块确保节省数据空间。

ZFS 为磁盘上的每个文件分配一个校验和。它会不断的校验文件的状态和校验和。如果发现文件被损坏了,它就会尝试修复文件。

我写过一个文章详细介绍 什么是 ZFS以及它有哪些特性。如果你感兴趣可以去阅读下。

注:请谨记 ZFS 的数据保护特性会导致性能下降。

Ubuntu 下使用 ZFS [适用于中高级用户]

一旦你在你的主磁盘上全新安装了带有 ZFS 的 Ubuntu,你就可以开始体验它的特性。

请注意所有的 ZFS 设置过程都需要命令行。我不知道它有任何 GUI 工具。

创建一个 ZFS 池

这段仅针对拥有多个磁盘的系统。如果你只有一个磁盘,Ubuntu 会在安装的时候自动创建池。

在创建池之前,你需要为池找到磁盘的 id。你可以用命令 lsblk 查询出这个信息。

为三个磁盘创建一个基础池,用以下命令:

sudo zpool create pool-test /dev/sdb /dev/sdc /dev/sdd

请记得替换 pool-test 为你选择的的命名。

这个命令将会设置“无冗余 RAID-0 池”。这意味着如果一个磁盘被破坏或有故障,你将会丢失数据。如果你执行以上命令,还是建议做一个常规备份。

你可以用下面命令将另一个磁盘增加到池中:

sudo zpool add pool-name /dev/sdx

查看 ZFS 池的状态

你可以用这个命令查询新建池的状态:

sudo zpool status pool-test

Zpool 状态

镜像一个 ZFS 池

为确保数据的安全性,你可以创建镜像。镜像意味着每个磁盘包含同样的数据。使用镜像设置,你可能会丢失三个磁盘中的两个,并且仍然拥有所有信息。

要创建镜像你可以用下面命令:

sudo zpool create pool-test mirror /dev/sdb /dev/sdc /dev/sdd

创建 ZFS 用于备份恢复的快照

快照允许你创建一个后备,以防某个文件被删除或被覆盖。比如,我们创建一个快照,当在用户主目录下删除一些目录后,然后把它恢复。

首先,你需要找到你想要的快照数据集。你可以这样做:

zfs list

Zfs List

你可以看到我的家目录位于 rpool/USERDATA/johnblood_uwcjk7

我们用下面命令创建一个名叫 1910 的快照:

sudo zfs snapshot rpool/USERDATA/johnblood_uwcjk7@1019

快照很快创建完成。现在你可以删除 DownloadsDocuments 目录。

现在你用以下命令恢复快照:

sudo zfs rollback rpool/USERDATA/johnblood_uwcjk7@1019

回滚的时间长短取决于有多少信息改变。现在你可以查看家目录,被删除的目录(和它的内容)将会被恢复过来。

要不要试试 ZFS ?

这篇文章仅简单介绍的 Ubuntu下 ZFS 的用法。更多的信息请参考 Ubuntu 的 ZFS Wiki页面。我也推荐阅读 ArsTechnica 的精彩文章

这个是试验性的功能。如果你还不了解 ZFS,你想用一个简单稳定的系统,请安装标准文件系统 EXT4。如果你想用闲置的机器体验,可以参照上面了解 ZFS。如果你是一个“专家”,并且知道自己在做什么,则可以随时随地随意尝试ZFS。

你之前用过 ZFS 吗?请在下面留言。


via: https://itsfoss.com/zfs-ubuntu/

作者:John Paul 选题:lujun9972 译者:guevaraya 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

know in the comments below. If you found this article interesting, please take a minute to share it on social media, Hacker News or Reddit.


via: https://itsfoss.com/zfs-ubuntu/

作者:John Paul 选题:lujun9972 译者:译者ID 校对:校对者ID

本文由 LCTT 原创编译,Linux中国 荣誉推出

Postfix 是一个自由开源的 MTA(邮件传输代理),用于在 Linux 系统上路由或传递电子邮件。在本指南中,你将学习如何在 CentOS 8 上安装和配置 Postfix。

Install-configure-Postfx-Server-CentOS8

实验室设置:

  • 系统:CentOS 8 服务器
  • IP 地址:192.168.1.13
  • 主机名:server1.crazytechgeek.info(确保域名指向服务器的 IP)

步骤 1)更新系统

第一步是确保系统软件包是最新的。为此,请按如下所示更新系统:

# dnf update

继续之前,还请确保不存在其他 MTA(如 Sendmail),因为这将导致与 Postfix 配置冲突。例如,要删除 Sendmail,请运行以下命令:

# dnf remove sendmail

步骤 2)设置主机名并更新 /etc/hosts

使用下面的 hostnamectl 命令在系统上设置主机名:

# hostnamectl set-hostname server1.crazytechgeek.info
# exec bash

此外,你需要在 /etc/hosts 中添加系统的主机名和 IP:

# vim /etc/hosts
192.168.1.13   server1.crazytechgeek.info

保存并退出文件。

步骤 3)安装 Postfix 邮件服务器

验证系统上没有其他 MTA 在运行后,运行以下命令安装 Postfix:

# dnf install postfix

Install-Postfix-Centos8

步骤 4)启动并启用 Postfix 服务

成功安装 Postfix 后,运行以下命令启动并启用 Postfix 服务:

# systemctl start postfix
# systemctl enable postfix

要检查 Postfix 状态,请运行以下 systemctl 命令:

# systemctl status postfix

Start-Postfix-check-status-centos8

太好了,我们已经验证了 Postfix 已启动并正在运行。接下来,我们将配置 Postfix 从本地发送邮件到我们的服务器。

步骤 5)安装 mailx 邮件客户端

在配置 Postfix 服务器之前,我们需要安装 mailx,要安装它,请运行以下命令:

# dnf install mailx

Install-Mailx-CentOS8

步骤 6)配置 Postfix 邮件服务器

Postfix 的配置文件位于 /etc/postfix/main.cf 中。我们需要对配置文件进行一些修改,因此请使用你喜欢的文本编辑器将其打开:

# vi /etc/postfix/main.cf

更改以下几行:

myhostname = server1.crazytechgeek.info
mydomain = crazytechgeek.info
myorigin = $mydomain
## 取消注释并将 inet_interfaces 设置为 all##
inet_interfaces = all
## 更改为 all ##
inet_protocols = all
## 注释 ##
#mydestination = $myhostname, localhost.$mydomain, localhost
## 取消注释 ##
mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain
## 取消注释并添加 IP 范围 ##
mynetworks = 192.168.1.0/24, 127.0.0.0/8
## 取消注释 ##
home_mailbox = Maildir/

完成后,保存并退出配置文件。重新启动 postfix 服务以使更改生效:

# systemctl restart postfix

步骤 7)测试 Postfix 邮件服务器

测试我们的配置是否有效,首先,创建一个测试用户。

# useradd postfixuser
# passwd postfixuser

接下来,运行以下命令,从本地用户 pkumar 发送邮件到另一个用户 postfixuser

# telnet localhost smtp
或者
# telnet localhost 25

如果未安装 telnet 服务,那么可以使用以下命令进行安装:

# dnf install telnet -y

如前所述运行命令时,应获得如下输出:

[root@linuxtechi ~]# telnet localhost 25
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
220 server1.crazytechgeek.info ESMTP Postfix

上面的结果确认与 postfix 邮件服务器的连接正常。接下来,输入命令:

# ehlo localhost

输出看上去像这样:

250-server1.crazytechgeek.info
250-PIPELINING
250-SIZE 10240000
250-VRFY
250-ETRN
250-STARTTLS
250-ENHANCEDSTATUSCODES
250-8BITMIME
250-DSN
250 SMTPUTF8

接下来,运行橙色高亮的命令,例如 mail fromrcpt todata,最后输入 quit

mail from:<pkumar>
250 2.1.0 Ok
rcpt to:<postfixuser>
250 2.1.5 Ok
data
354 End data with <CR><LF>.<CR><LF>
Hello, Welcome to my mailserver (Postfix)
.
250 2.0.0 Ok: queued as B56BF1189BEC
quit
221 2.0.0 Bye
Connection closed by foreign host

完成 telnet 命令可从本地用户 pkumar 发送邮件到另一个本地用户 postfixuser,如下所示:

Send-email-with-telnet-centos8

如果一切都按计划进行,那么你应该可以在新用户的家目录中查看发送的邮件:

# ls /home/postfixuser/Maildir/new
1573580091.Vfd02I20050b8M635437.server1.crazytechgeek.info
#

要阅读邮件,只需使用 cat 命令,如下所示:

# cat /home/postfixuser/Maildir/new/1573580091.Vfd02I20050b8M635437.server1.crazytechgeek.info

Read-postfix-email-linux

Postfix 邮件服务器日志

Postfix 邮件服务器邮件日志保存在文件 /var/log/maillog 中,使用以下命令查看实时日志,

# tail -f /var/log/maillog

postfix-maillogs-centos8

保护 Postfix 邮件服务器

建议始终确保客户端和 Postfix 服务器之间的通信安全,这可以使用 SSL 证书来实现,它们可以来自受信任的权威机构或自签名证书。在本教程中,我们将使用 openssl 命令生成用于 Postfix 的自签名证书,

我假设 openssl 已经安装在你的系统上,如果未安装,请使用以下 dnf 命令:

# dnf install openssl -y

使用下面的 openssl 命令生成私钥和 CSR(证书签名请求):

# openssl req -nodes -newkey rsa:2048 -keyout mail.key -out mail.csr

Postfix-Key-CSR-CentOS8

现在,使用以下 openssl 命令生成自签名证书:

# openssl x509 -req -days 365 -in mail.csr -signkey mail.key -out mail.crt
Signature ok
subject=C = IN, ST = New Delhi, L = New Delhi, O = IT, OU = IT, CN = server1.crazytechgeek.info, emailAddress = root@linuxtechi
Getting Private key
#

现在将私钥和证书文件复制到 /etc/postfix 目录下:

# cp mail.key mail.crt /etc/postfix

在 Postfix 配置文件中更新私钥和证书文件的路径:

# vi /etc/postfix/main.cf
………
smtpd_use_tls = yes
smtpd_tls_cert_file = /etc/postfix/mail.crt
smtpd_tls_key_file = /etc/postfix/mail.key
smtpd_tls_security_level = may
………

重启 Postfix 服务以使上述更改生效:

# systemctl restart postfix

让我们尝试使用 mailx 客户端将邮件发送到内部本地域和外部域。

pkumar 发送内部本地邮件到 postfixuser 中:

# echo "test email" | mailx -s "Test email from Postfix MailServer" -r root@linuxtechi root@linuxtechi

使用以下命令检查并阅读邮件:

# cd /home/postfixuser/Maildir/new/
# ll
total 8
-rw-------. 1 postfixuser postfixuser 476 Nov 12 17:34 1573580091.Vfd02I20050b8M635437.server1.crazytechgeek.info
-rw-------. 1 postfixuser postfixuser 612 Nov 13 02:40 1573612845.Vfd02I20050bbM466643.server1.crazytechgeek.info
# cat 1573612845.Vfd02I20050bbM466643.server1.crazytechgeek.info

Read-Postfixuser-Email-CentOS8

postfixuser 发送邮件到外部域([email protected]):

# echo "External Test email" | mailx -s "Postfix MailServer" -r root@linuxtechi root@linuxtechi

注意:如果你的 IP 没有被任何地方列入黑名单,那么你发送到外部域的邮件将被发送,否则它将被退回,并提示你的 IP 被 spamhaus 之类的数据库列入黑名单。

检查 Postfix 邮件队列

使用 mailq 命令列出队列中的邮件:

# mailq
Mail queue is empty
#

完成!我们的 Postfix 配置正常工作了!目前就这样了。我们希望你觉得本教程有见地,并且你可以轻松地设置本地 Postfix 服务器。


via: https://www.linuxtechi.com/install-configure-postfix-mailserver-centos-8/

作者:James Kiarie 选题:lujun9972 译者:geekpi 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

觉得你已经了解了 sudo 的所有知识了吗?再想想。

大家都知道 sudo,对吗?默认情况下,该工具已安装在大多数 Linux 系统上,并且可用于大多数 BSD 和商业 Unix 变体。不过,在与数百名 sudo 用户交谈之后,我得到的最常见的答案是 sudo 是一个使生活复杂化的工具。

有 root 用户和 su 命令,那么为什么还要使用另一个工具呢?对于许多人来说,sudo 只是管理命令的前缀。只有极少数人提到,当你在同一个系统上有多个管理员时,可以使用 sudo 日志查看谁做了什么。

那么,sudo 是什么? 根据 sudo 网站

“sudo 允许系统管理员通过授予某些用户以 root 用户或其他用户身份运行某些命令的能力,同时提供命令及其参数的审核记录,从而委派权限。”

默认情况下,sudo 只有简单的配置,一条规则允许一个用户或一组用户执行几乎所有操作(在本文后面的配置文件中有更多信息):

%wheel ALL=(ALL) ALL

在此示例中,参数表示以下含义:

  • 第一个参数(%wheel)定义组的成员。
  • 第二个参数(ALL)定义组成员可以在其上运行命令的主机。
  • 第三个参数((ALL))定义了可以执行命令的用户名。
  • 最后一个参数(ALL)定义可以运行的应用程序。

因此,在此示例中,wheel 组的成员可以以所有主机上的所有用户身份运行所有应用程序。但即使是这个一切允许的规则也很有用,因为它会记录谁在计算机上做了什么。

别名

当然,它不仅可以让你和你最好的朋友管理一个共享机器,你还可以微调权限。你可以将以上配置中的项目替换为列表:用户列表、命令列表等。多数情况下,你可能会复制并粘贴配置中的一些列表。

在这种情况下,别名可以派上用场。在多个位置维护相同的列表容易出错。你可以定义一次别名,然后可以多次使用。因此,当你对一位管理员不再信任时,将其从别名中删除就行了。使用多个列表而不是别名,很容易忘记从具有较高特权的列表之一中删除用户。

为特定组的用户启用功能

sudo 命令带有大量默认设置。不过,在某些情况下,你想覆盖其中的一些情况,这时你可以在配置中使用 Defaults 语句。通常,对每个用户都强制使用这些默认值,但是你可以根据主机、用户名等将设置缩小到一部分用户。这里有个我那一代的系统管理员都喜欢玩的一个示例:“羞辱”。这些只不过是一些有人输入错误密码时的有趣信息:

czanik@linux-mewy:~> sudo ls
[sudo] password for root:
Hold it up to the light --- not a brain in sight!  # 把灯举高点,脑仁太小看不到
[sudo] password for root:
My pet ferret can type better than you!     # 我的宠物貂也比你输入的好
[sudo] password for root:
sudo: 3 incorrect password attempts
czanik@linux-mewy:~>

由于并非所有人都喜欢系统管理员的这种幽默,因此默认情况下会禁用这些羞辱信息。以下示例说明了如何仅对经验丰富的系统管理员(即 wheel 组的成员)启用此设置:

Defaults !insults
Defaults:%wheel insults

我想,感谢我将这些消息带回来的人用两只手也数不过来吧。

摘要验证

当然,sudo 还有更严肃的功能。其中之一是摘要验证。你可以在配置中包括应用程序的摘要:

peter ALL = sha244:11925141bb22866afdf257ce7790bd6275feda80b3b241c108b79c88 /usr/bin/passwd

在这种情况下,sudo 在运行应用程序之前检查应用程序摘要,并将其与配置中存储的摘要进行比较。如果不匹配,sudo 拒绝运行该应用程序。尽管很难在配置中维护此信息(没有用于此目的的自动化工具),但是这些摘要可以为你提供额外的保护层。

会话记录

会话记录也是 sudo 鲜为人知的功能。在演示之后,许多人离开我的演讲后就计划在其基础设施上实施它。为什么?因为使用会话记录,你不仅可以看到命令名称,还可以看到终端中发生的所有事情。你可以看到你的管理员在做什么,要不他们用 shell 访问了机器而日志仅会显示启动了 bash

当前有一个限制。记录存储在本地,因此具有足够的权限的话,用户可以删除他们的痕迹。所以请继续关注即将推出的功能。

插件

从 1.8 版开始,sudo 更改为基于插件的模块化体系结构。通过将大多数功能实现为插件,你可以编写自己的功能轻松地替换或扩展 sudo 的功能。已经有了 sudo 上的开源和商业插件。

在我的演讲中,我演示了 sudo_pair 插件,该插件可在 GitHub 上获得。这个插件是用 Rust 开发的,这意味着它不是那么容易编译,甚至更难以分发其编译结果。另一方面,该插件提供了有趣的功能,需要第二个管理员通过 sudo 批准(或拒绝)运行命令。不仅如此,如果有可疑活动,可以在屏幕上跟踪会话并终止会话。

在最近的 All Things Open 会议上的一次演示中,我做了一个臭名昭著的演示:

czanik@linux-mewy:~> sudo  rm -fr /

看着屏幕上显示的命令。每个人都屏住呼吸,想看看我的笔记本电脑是否被毁了,然而它逃过一劫。

日志

正如我在开始时提到的,日志记录和警报是 sudo 的重要组成部分。如果你不会定期检查 sudo 日志,那么日志在使用 sudo 中并没有太多价值。该工具通过电子邮件提醒配置中指定的事件,并将所有事件记录到 syslog 中。可以打开调试日志用于调试规则或报告错误。

警报

电子邮件警报现在有点过时了,但是如果你使用 syslog-ng 来收集日志消息,则会自动解析 sudo 日志消息。你可以轻松创建自定义警报并将其发送到各种各样的目的地,包括 Slack、Telegram、Splunk 或 Elasticsearch。你可以从我在 syslong-ng.com 上的博客中了解有关此功能的更多信息。

配置

我们谈论了很多 sudo 功能,甚至还看到了几行配置。现在,让我们仔细看看 sudo 的配置方式。配置本身可以在 /etc/sudoers 中获得,这是一个简单的文本文件。不过,不建议直接编辑此文件。相反,请使用 visudo,因为此工具还会执行语法检查。如果你不喜欢 vi,则可以通过将 EDITOR 环境变量指向你的首选编辑器来更改要使用的编辑器。

在开始编辑 sudo 配置之前,请确保你知道 root 密码。(是的,即使在默认情况下 root 用户没有密码的 Ubuntu 上也是如此。)虽然 visudo 会检查语法,但创建语法正确而将你锁定在系统之外的配置也很容易。

如果在紧急情况下,而你手头有 root 密码,你也可以直接编辑配置。当涉及到 sudoers 文件时,有一件重要的事情要记住:从上到下读取该文件,以最后的设置为准。这个事实对你来说意味着你应该从通用设置开始,并在末尾放置例外情况,否则,通用设置会覆盖例外情况。

你可以在下面看到一个基于 CentOS 的简单 sudoers 文件,并添加我们之前讨论的几行:

Defaults !visiblepw
Defaults always_set_home
Defaults match_group_by_gid
Defaults always_query_group_plugin
Defaults env_reset
Defaults env_keep = "COLORS DISPLAY HOSTNAME HISTSIZE KDEDIR LS_COLORS"
Defaults env_keep += "MAIL PS1 PS2 QTDIR USERNAME LANG LC_ADDRESS LC_CTYPE"
Defaults secure_path = /sbin:/bin:/usr/sbin:/usr/bin
root ALL=(ALL) ALL
%wheel ALL=(ALL) ALL
Defaults:%wheel insults
Defaults !insults
Defaults log_output

该文件从更改多个默认值开始。然后是通常的默认规则:root 用户和 wheel 组的成员对计算机具有完全权限。接下来,我们对 wheel 组启用“羞辱”,但对其他所有人禁用它们。最后一行启用会话记录。

上面的配置在语法上是正确的,但是你可以发现逻辑错误吗?是的,有一个:后一个通用设置覆盖了先前的更具体设置,让所有人均禁用了“羞辱”。一旦交换了这两行的位置,设置就会按预期进行:wheel 组的成员会收到有趣的消息,但其他用户则不会收到。

配置管理

一旦必须在多台机器上维护 sudoers 文件,你很可能希望集中管理配置。这里主要有两种可能的开源方法。两者都有其优点和缺点。

你可以使用也用来配置其余基础设施的配置管理应用程序之一:Red Hat Ansible、Puppet 和 Chef 都具有用于配置 sudo 的模块。这种方法的问题在于更新配置远非实时。同样,用户仍然可以在本地编辑 sudoers 文件并更改设置。

sudo 工具也可以将其配置存储在 LDAP 中。在这种情况下,配置更改是实时的,用户不能弄乱sudoers 文件。另一方面,该方法也有局限性。例如,当 LDAP 服务器不可用时,你不能使用别名或使用 sudo

新功能

新版本的 sudo 即将推出。1.9 版将包含许多有趣的新功能。以下是最重要的计划功能:

  • 记录服务可集中收集会话记录,与本地存储相比,它具有许多优点:

    • 在一个地方搜索更方便。
    • 即使发送记录的机器关闭,也可以进行记录。
    • 记录不能被想要删除其痕迹的人删除。
  • audit 插件没有向 sudoers 添加新功能,而是为插件提供了 API,以方便地访问任何类型的 sudo 日志。这个插件允许使用插件从 sudo 事件创建自定义日志。
  • approval 插件无需使用第三方插件即可启用会话批准。
  • 以及我个人最喜欢的:插件的 Python 支持,这使你可以轻松地使用 Python 代码扩展 sudo,而不是使用 C 语言进行原生编码。 ### 总结

希望本文能向你证明 sudo 不仅仅是一个简单的命令前缀。有无数种可能性可以微调系统上的权限。你不仅可以微调权限,还可以通过检查摘要来提高安全性。会话记录使你能够检查系统上正在发生的事情。你也可以使用插件扩展 sudo 的功能,或者使用已有的插件或编写自己的插件。最后,从即将发布的功能列表中你可以看到,即使 sudo 已有数十年的历史,它也是一个不断发展的有生命力的项目。

如果你想了解有关 sudo 的更多信息,请参考以下资源:


via: https://opensource.com/article/19/10/know-about-sudo

作者:Peter Czanik 选题:lujun9972 译者:wxy 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

安装、设置、创建和开始使用 PostgreSQL 数据库。

每个人或许都有需要在数据库中保存的东西。即使你执着于使用纸质文件或电子文件,它们也会变得很麻烦。纸质文档可能会丢失或混乱,你需要访问的电子信息可能会隐藏在段落和页面的深处。

在我从事医学工作的时候,我使用 PostgreSQL 来跟踪我的住院患者名单并提交有关住院患者的信息。我将我的每日患者名单打印在口袋里,以便快速了解患者房间、诊断或其他细节的任何变化并做出快速记录。

我以为一切没问题,直到去年我妻子决定买一辆新车,我“接手”了她以前的那辆车。她保留了汽车维修和保养服务收据的文件夹,但随着时间的流逝,它变得杂乱。与其花时间筛选所有纸条以弄清楚什么时候做了什么,我认为 PostgreSQL 将是更好的跟踪此信息的方法。

安装 PostgreSQL

自上次使用 PostgreSQL 以来已经有一段时间了,我已经忘记了如何使用它。实际上,我甚至没有在计算机上安装它。安装它是第一步。我使用 Fedora,因此在控制台中运行:

dnf list postgresql*

请注意,你无需使用 sudo 即可使用 list 选项。该命令返回了很长的软件包列表。看了眼后,我决定只需要三个:postgresql、postgresql-server 和 postgresql-docs。

为了了解下一步需要做什么,我决定查看 PostgreSQL 文档。文档参考内容非常丰富,实际上,丰富到令人生畏。幸运的是,我发现我在升级 Fedora 时曾经做过的一些笔记,希望有效地导出数据库,在新版本上重新启动 PostgreSQL,以及导入旧数据库。

设置 PostgreSQL

与大多数其他软件不同,你不能只是安装好 PostgreSQL 就开始使用它。你必须预先执行两个基本步骤:首先,你需要设置 PostgreSQL,第二,你需要启动它。你必须以 root 用户身份执行这些操作(sudo 在这里不起作用)。

要设置它,请输入:

postgresql-setup –initdb

这将确定 PostgreSQL 数据库在计算机上的位置。然后(仍为 root)输入以下两个命令:

systemctl start postgresql.service
systemctl enable postgresql.service

第一个命令为当前会话启动 PostgreSQL(如果你关闭机器,那么 PostgreSQL 也将关闭)。第二个命令使 PostgreSQL 在随后的重启中自动启动。

创建用户

PostgreSQL 正在运行,但是你仍然不能使用它,因为你还没有用户。为此,你需要切换到特殊用户 postgres。当你仍以 root 身份运行时,输入:

su postgres

由于你是以 root 的身份执行此操作的,因此无需输入密码。root 用户可以在不知道密码的情况下以任何用户身份操作;这就是使其强大而危险的原因之一。

现在你就是 postgres 了,请运行下面两个命令,如下所示创建用户(创建用户 gregp):

createuser gregp
createdb gregp

你可能会看到错误消息,如:Could not switch to /home/gregp。这只是意味着用户 postgres不能访问该目录。尽管如此,你的用户和数据库已创建。接下来,输入 exit 并按回车两次,这样就回到了原来的用户下(root)。

设置数据库

要开始使用 PostgreSQL,请在命令行输入 psql。你应该在每行左侧看到类似 gregp=> 的内容,以显示你使用的是 PostgreSQL,并且只能使用它理解的命令。你自动获得一个数据库(我的名为 gregp),它里面完全没有内容。对 PostgreSQL 来说,数据库只是一个工作空间。在空间内,你可以创建。表包含变量列表,而表中的每个变量是构成数据库的数据。

以下是我设置汽车服务数据库的方式:

CREATE TABLE autorepairs (
        date            date,
        repairs         varchar(80),
        location        varchar(80),
        cost            numeric(6,2)
);

我本可以在一行内输入,但为了更好地说明结构,并表明 PostgreSQL 不会解释制表符和换行的空白,我分成了多行。字段包含在括号中,每个变量名和数据类型与下一个变量用逗号分隔(最后一个除外),命令以分号结尾。所有命令都必须以分号结尾!

第一个变量名是 date,它的数据类型也是 date,这在 PostgreSQL 中没关系。第二个和第三个变量 repairslocation 都是 varchar(80) 类型,这意味着它们可以是最多 80 个任意字符(字母、数字等)。最后一个变量 cost 使用 numeric 类型。括号中的数字表示最多有六位数字,其中两位是小数。最初,我尝试了 real 类型,这将是一个浮点数。real 类型的问题是作为数据类型在使用时,在遇到 WHERE 子句,类似 WHERE cost = 0 或其他任何特定数字。由于 real 值有些不精确,因此特定数字将永远不会匹配。

输入数据

接下来,你可以使用 INSERT INTO 命令添加一些数据(在 PostgreSQL 中称为):

INSERT INTO autorepairs VALUES ('2017-08-11', 'airbag recall', 'dealer', 0);

请注意,括号构成了一个值的容器,它必须以正确的顺序,用逗号分隔,并在命令末尾加上分号。datevarchar(80) 类型的值必须包含在单引号中,但数字值(如 numeric)不用。作为反馈,你应该会看到:

INSERT 0 1

与常规终端会话一样,你会有输入命令的历史记录,因此,在输入后续行时,通常可以按向上箭头键来显示最后一个命令并根据需要编辑数据,从而节省大量时间。

如果出了什么问题怎么办?使用 UPDATE 更改值:

UPDATE autorepairs SET date = '2017-11-08' WHERE repairs = 'airbag recall';

或者,也许你不再需要表中的行。使用 DELETE

DELETE FROM autorepairs WHERE repairs = 'airbag recall';

这将删除整行。

最后一件事:即使我在 PostgreSQL 命令中一直使用大写字母(在大多数文档中也这么做),你也可以用小写字母输入,我也经常如此。

输出数据

如果你想展示数据,使用 SELECT

SELECT * FROM autorepairs ORDER BY date;

没有 ORDER BY 的话,行将不管你输入的内容来显示。例如,以下就是我终端中输出的我的汽车服务数据:

SELECT date, repairs FROM autorepairs ORDER BY date;

    date   |                             repairs                             
-----------+-----------------------------------------------------------------
2008-08-08 | oil change, air filter, spark plugs
2011-09-30 | 35000 service, oil change, rotate tires/balance wheels
2012-03-07 | repl battery
2012-11-14 | 45000 maint, oil/filter
2014-04-09 | 55000 maint, oil/filter, spark plugs, air/dust filters
2014-04-21 | replace 4 tires
2014-04-21 | wheel alignment
2016-06-01 | 65000 mile service, oil change
2017-05-16 | oil change, replce oil filt housing
2017-05-26 | rotate tires
2017-06-05 | air filter, cabin filter,spark plugs
2017-06-05 | brake pads and rotors, flush brakes
2017-08-11 | airbag recall
2018-07-06 | oil/filter change, fuel filter, battery svc
2018-07-06 | transmission fl, p steering fl, rear diff fl
2019-07-22 | oil &amp; filter change, brake fluid flush, front differential flush
2019-08-20 | replace 4 tires
2019-10-09 | replace passenger taillight bulb
2019-10-25 | replace passenger taillight assembly
(19 rows)

要将此发送到文件,将输出更改为:

\o autorepairs.txt

然后再次运行 SELECT 命令。

退出 PostgreSQL

最后,在终端中退出 PostgreSQL,输入:

quit

或者它的缩写版:

\q

虽然这只是 PostgreSQL 的简要介绍,但我希望它展示了将数据库用于这样的简单任务既不困难也不费时。


via: https://opensource.com/article/19/11/getting-started-postgresql

作者:Greg Pittman 选题:lujun9972 译者:geekpi 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

开放剪贴画库兴衰的故事以及一个新的公共艺术品图书馆 FreeSVG.org 的诞生。

开放剪贴画库 Open Clip Art Library (OCAL)发布于 2004 年,成为了免费插图的来源,任何人都可以出于任何目的使用它们,而无需注明出处或提供任何回报。针对 1990 年代每个家庭办公室书架上的大量剪贴画 CD 以及由闭源公司和艺术品软件提供的艺术品转储,这个网站是开源世界的回应。

最初,这个剪贴画库主要由一些贡献者提供,但是在 2010 年,它重新打造成了一个全新的交互式网站,可以让任何人使用矢量插图应用程序创建和贡献剪贴画。该网站立即获得了来自全球的、各种形式的自由软件和自由文化项目的贡献。Inkscape 中甚至包含了该库的专用导入器。

但是,在 2019 年初,托管开放剪贴画库的网站离线了,没有任何警告或解释。它已经成长为有着成千上万的人的社区,起初以为这是暂时的故障。但是,这个网站一直离线已超过六个月,而没有任何清楚的解释。

谣言开始膨胀。该网站一直在更新中(“要偿还数年的技术债务”,网站开发者 Jon Philips 在一封电子邮件中说)。一个 Twitter 帐户声称,该网站遭受了猖狂的 DDoS 攻击。另一个 Twitter 帐户声称,该网站维护者已经成为身份盗用的牺牲品。今天,在撰写本文时,该网站的一个且唯一的页面声明它处于“维护和保护模式”,其含义不清楚,只是用户无法访问其内容。

恢复公地

网站会随着时间的流逝而消失,但是对其社区而言,开放剪贴画库的丢失尤其令人惊讶,因为它被视为一个社区项目。很少有社区成员知道托管该库的网站已经落入单个维护者手中,因此,由于 CC0 许可证,该库中的艺术品归所有人所有,但对它的访问是由单个维护者功能性拥有的。而且,由于该社区通过网站彼此保持联系,因此该维护者实际上拥有该社区。

当网站发生故障时,社区以及成员彼此之间都无法访问剪贴画。没有该网站,就没有社区。

最初,该网站离线后其上的所有东西都是被封锁的。不过,在几个月之后,用户开始意识到该网站的数据仍然在线,这意味着用户能够通过输入精确的 URL 访问单个剪贴画。换句话说,你不能通过在网站上到处点击来浏览剪贴画文件,但是如果你确切地知道该地址,你就可以在浏览器中访问它。类似的,技术型(或偷懒的)用户意识到能够通过类似 wget 的自动 Web 浏览器将网站“抓取”下来。

Linux 的 wget 命令从技术上来说是一个 Web 浏览器,虽然它不能让你像用 Firefox 一样交互式地浏览。相反,wget 可以连到互联网,获取文件或文件集,并下载到你的本次硬盘。然后,你可以在 Firefox、文本编辑器或最合适的应用程序中打开这些文件,查看内容。

通常,wget 需要知道要提取的特定文件。如果你使用的是安装了 wget 的 Linux 或 macOS,则可以通过下载 example.com 的索引页来尝试此过程:

$ wget example.org/index.html
[...]
$ tail index.html

<body><div>
    <h1>Example Domain</h1>
    <p>This domain is for illustrative examples in documents.
    You may use this domain in examples without permission.</p>
        <p><a href="http://www.iana.org/domains/example">More info</a></p>
</div></body></html>

为了抓取 OCAL,我使用了 --mirror 选项,以便可以只是将 wget 指向到包含剪贴画的目录,就可以下载该目录中的所有内容。此操作持续下载了连续四天(96 个小时),最终得到了超过 50,000 个社区成员贡献的 100,000 个 SVG 文件。不幸的是,任何没有适当元数据的文件的作者信息都是无法恢复的,因为此信息被锁定在该数据库中不可访问的文件中,但是 CC0 许可证意味着此问题在技术上无关紧要(因为 CC0 文件不需要归属)。

随意分析了一下下载的文件进行还显示,其中近 45,000 个文件是同一个文件(该网站的徽标)的副本。这是由于指向该网站徽标的重定向引起的(原因未知),仔细分析能够提取到原始的文件,又过了 96 个小时,并且恢复了直到最后一天发布在 OCAL 上的所有剪贴画:总共约有 156,000 张图像。

SVG 文件通常很小,但这仍然是大量工作,并且会带来一些非常实际的问题。首先,将需要数 GB 的在线存储空间,这样这些剪贴画才能供其先前的社区使用。其次,必须使用一种搜索剪贴画的方法,因为手动浏览 55,000 个文件是不现实的。

很明显,社区真正需要的是一个平台。

构建新的平台

一段时间以来,公共领域矢量图 网站一直在发布公共领域的矢量图。虽然它仍然是一个受欢迎的网站,但是开源用户通常只是将其用作辅助的图片资源,因为其中大多数文件都是 EPS 和 AI 格式的,这两者均与 Adobe 相关。这两种文件格式通常都可以转换为 SVG,但是特性会有所损失。

当公共领域矢量图网站的维护者(Vedran 和 Boris)得知 OCAL 丢失时,他们决定创建一个面向开源社区的网站。诚然,他们选择了开源 Laravel 框架作为后端,该框架为网站提供了管理控制台和用户访问权限。该框架功能强大且开发完善,还使他们能够快速响应错误报告和功能请求,并根据需要升级网站。他们正在建立的网站称为 FreeSVG.org,已经是一个强大而繁荣的公共艺术品图书馆。

从那时起,他们就一直从 OCAL 上载所有剪贴画,并且他们甚至在努力地对这些剪贴画进行标记和分类。作为公共领域矢量图网站的创建者,他们还以 SVG 格式贡献了自己的图像。他们的目标是成为互联网上具有 CC0 许可证的 SVG 图像的主要资源。

贡献

FreeSVG.org 的维护者意识到他们已经继承了重要的管理权。他们正在努力对网站上的所有图像加上标题和描述,以便用户可以轻松找到这些剪贴画,并在准备就绪后将其提供给社区,同时坚信同这些剪贴画一样,与这些剪贴画有关的元数据属于创建和使用它们的人。他们还意识到可能会发生无法预料的情况,因此他们会定期为其网站和内容创建备份,并打算在其网站出现故障时向公众提供最新备份。

如果要为 FreeSVG.org的知识共享内容添砖加瓦,请下载 Inkscape 并开始绘制。世界上有很多公共领域的艺术品,例如历史广告塔罗牌故事书,只是在等待转换为 SVG,因此即使你对自己的绘画技巧没有信心你也可以做出贡献。访问 FreeSVG 论坛与其他贡献者联系并支持他们。

公地的概念很重要。无论你是学生、老师、图书馆员、小企业主还是首席执行官,知识共享都会使所有人受益。如果你不直接捐款,那么你随时可以帮助推广。

这是自由文化的力量:它不仅可以扩展,而且随着更多人的参与,它会变得更好。

艰辛的教训

从 OCAL 的消亡到 FreeSVG.org 的兴起,开放文化社区已经吸取了一些艰辛的经验。对于以后,以下是我认为最重要的那些。

维护你的元数据

如果你是内容创建者,请帮助将来的档案管理员,将元数据添加到文件中。大多数图像、音乐、字体和视频文件格式都可以嵌入 EXIF 数据,其他格式在创建它们的应用程序中具有元数据输入界面。勤于用你的姓名、网站或公共电子邮件以及许可证来标记你的作品。

做个副本

不要以为别人在做备份。如果你关心公用数字内容,请自己备份,否则不要指望永远提供它。无论任何上传到互联网上的内容是永久的的说法是不是正确的,但这并不意味着你永远可以使用。如果 OCAL 文件不再暗地可用,那么任何人都不太可能成功地从网络上的某个位置或从全球范围内的人们的硬盘中成功地发现全部的 55,000 张图像。

创建外部渠道

如果一个社区是由单个网站或实际位置来定义的,那么该社区失去访问该空间的能力就如同解散了一样。如果你是由单个组织或网站驱动的社区的成员,则你应该自己与关心的人共享联系信息,并即使在该网站不可用时也可以建立沟通渠道。

例如,Opensource.com 本身维护其作者和通讯者的邮件列表和其他异地渠道,以便在有或没有网站干预或甚至没有网站的情况下相互交流。

自由文化值得为此努力

互联网有时被视为懒人社交俱乐部。你可以在需要时登录并在感到疲倦时将其关闭,也可以漫步到所需的任何社交圈。

但实际上,自由文化可能是项艰难的工作。但是这种艰难从某种意义上讲并不是说要成为其中的一分子很困难,而是你必须努力维护。如果你忽略你所在的社区,那么该社区可能会在你意识到之前就枯萎并褪色。

花点时间环顾四周,确定你属于哪个社区,如果不是,那么请告诉某人你对他们带给你生活的意义表示赞赏。同样重要的是,请记住,这样你也为社区的生活做出了贡献。

几周前,知识共享组织在华沙举行了它的全球峰会,令人惊叹的国际盛会…

老王文末感想:刚刚看到这篇文章的英文标题(How I used the wget Linux command to recover lost images)时,我以为这是一篇技术文章,然而翻译校对下来之后,却是一篇披着技术外衣的对自由文化社区的教训和反思。作为一个同样建设公地社区的负责人,我不禁深深地对 “Linux 中国”社区的将来有了忧虑。如何避免 “Linux 中国”也同样陷入这种困境,这是一个问题……

就目前来看,我们需要定期不定期的构建离线归档,也需要以更开放的方式留下现有文章的数据,也欢迎大家来支持和帮助我们。

又及,再给产生这一切的 Linux 中国翻译组织 LCTT 打个招新广告吧,欢迎大家加入: https://linux.cn/lctt/


via: https://opensource.com/article/19/10/how-community-saved-artwork-creative-commons

作者:Seth Kenlon 选题:lujun9972 译者:wxy 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出