设计模式之创建模式

本文主要归纳设计模式中的创建模式 (creational pattern),主要包括了工厂模式,单例多例,建造者模式,和原型模式等。

创建模式是指对象的实例化过程,这些模式都提供了一种将对象实例化从客户端分离的方法。

为什么要有创建模式,这也符合开闭原则,Java 自带的 new Object() 实例化对象没有任何问题,问题在于修改,一旦当具体实例涉及变化,那么就必须修改实例自身,不符合开闭原则,所以才有这么多的创建模式。将对外暴露的接口抽象起来,将对象创建的方式封装,对外接口尽量减少。

简单工厂

主要分为三个部分,工厂,抽象产品,具体产品。

工厂直接提供具体产品,也就是直接创建具体类。

工厂模式将产品的生产和其提供的功能隔离。

多态工厂方法

又称为工厂方法。

The Factory Method Pattern defines an interface for creating an object, but lets subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses.

分为四个部分,抽象工厂,具体工厂,抽象产品,具体产品。将产品实例化放到具体工厂子类中实现,抽象出工厂的一般方法。通常一个具体工厂产出一种具体产品。

因此在添加具体产品后不需要修改具体工厂类,而只需要相应的添加具体工厂来产出产品。

抽象工厂

和多态工厂相同,包含四个部分。

The Abstract Factory Pattern provides an interface for creating families of related or dependent objects without specifying their concrete classes.

抽象工厂工厂方法的区别在于,抽象工厂模式中,一个工厂可以提供多个抽象产品。在工厂方法模式中,由“具体工厂”决定提供哪一类具体产品;在抽象工厂中,由客户端决定返回哪一类产品。

单例

至始至终只有一个实例。

Eager initialization

The instance of Singleton is created at the time of class loading.

public class EagerSingletion {
	private static final EagerSingletion instance = new EagerSingletion();
	private EagerSingletion() {}
	public static EagerSingletion getInstance() {
		return instance;
	}
}

在 Singleton 不占用太多资源时可以使用 static 在类加载时使用这个方法。但是对于大对象这个方法就不是很好。

Static block initialization

Use static block to create instance

public class StaticBlockSingleton {
	private static StaticBlockSingleton instance = null;
	private StaticBlockSingleton() {}
	static {
		try {
			instance = new StaticBlockSingleton();
		} catch(Exception e) {
			throw new RunTimeException("Exception creating singleton");
		}
	}

	public static StaticBlockSingleton getInstance() {
		return instance;
	}
}

使用 static 字段和 static block 定义的实例都是在实例对象被使用之前就已经创建。

Lazy Initialization

在第一次使用时创建。

public class LazyInitialSingleton {
	public static LazyInitialSingleton instance = null;
	private LazyInitialSingleton() {}
	public static LazyInitialSingleton getInstance() {
		if (instance == null) {
			instance = new LazyInitialSingleton();
		}
		return instance;
	}
}

但是这个方法在单线程中是没有问题的,如果是多线程同时调用 getInstance 方法,就可能创建多个实例。

Thread Safe Singleton

public class ThreadSafeSingleton {
	public static ThreadSafeSingleton instance = null;
	private ThreadSafeSingleton() {}
	public static synchronized ThreadSafeSingleton getInstance() {
		if (instance == null) {
			instance = new ThreadSafeSingleton();
		}
		return instance;
	}
}

synchronized 作用于静态方法等同于对类加锁。

Double checked Singleton

public static ThreadSafeSingleton getInstance () {
	if (instance == null) {
		synchronized (ThreadSafeSingleton.class) {
			if (instance == null) {
				instance = new ThreadSafeSingleton();
			}
		}
	}
	return instance;
}

Bill Pugh Singleton

使用内部静态类

public class Singleton() {
	private Singleton() {}
	private static class Helper {
		private static final Singleton INSTANCE = new Singleton();
	}

	public static Singleton getInstance() {
		return Helper.INSTANCE;
	}
}

这里涉及到类加载机制,在类加载 Singleton 时,内部静态类是不会被加载的。只有当有调用 getInstance() 方法,实例才会初始化。

多例

一个类存在多个自身的实例,并且多例类需要自己创建,管理自己的实例,并向外提供自己的实例。

常见的线程池,数据库连接池就是多例的使用案例。

建造者模式

当需要创建的对象,创建步骤比较复杂时,可以将对象的”创建”和”表示”分离。Builder 模式允许用户将复杂对象的创建封装,允许使用者多个步骤来构造对象。

建造者模式包括:导演,抽象建造者,具体建造者,和产品

建造者模式的使用场景:

  • 对象内部结构复杂,比如包含不同零件的汽车类,比如一个复杂的邮件类
  • 对象内部属性有依赖,需要按照某一些顺序进行赋值
  • 对象创建过程依赖系统其他对象,而这些依赖的对象不易获取

优点:

  • 封装了内部构造细节
  • 隐藏了内部表示
  • 构造对象内部实现可以灵活改动,客户端只通过抽象接口来构造该对象

缺点:

  • 相较于工厂模式,客户端需要额外的知识来创建对象

原型模式

通过原型指明要创建的对象类型,然后通过该原型创造更多的对象实例。Java 中可以通过 clone() ,或者反序列化来实现。原型模式允许客户端在不知道对象具体内部细节的情况下创建实例。

一个比较具体的使用场景,比如要对比两个对象修改前后的差异,可以在修改前复制一个对象实例,然后修改之后对比。再比如一次比较重的数据库查询,可以缓存该对象,再下一次请求到来时直接返回该对象的复制。

一些比较适合的场景:

  • 当创建一个实例比较昂贵或者复杂时

原型模式的优点:

  • 对客户端隐藏了构造对象的复杂细节
  • 对客户端提供了一种在不确定类型情况下创建对象实例的方法
  • 某一些场景下复制对象实例要比创建来的更有效率

缺点:

  • 对于一些只有少量对象的系统来说并不太需要
  • 每一个原型类的子类都需要实现 clone 方法,有些时候创建对象拷贝并不是非常轻松

reference


2019-02-13 design-pattern , factory , singleton , builder , creational-pattern

Kindle 篇一:如何把 Kindle Unlimited 的钱看回来

阅读专题书籍

Kindle Unlimited 特别适合专题阅读,比如想要了解日本文化,那么通过搜索日本,可以获得一系列的关于日本的书籍,借阅自己喜欢的书阅读即可。比如日本就可以搜索到,《静观日本》,《牛津通识读本现代日本》,《日本人的活法》,《走遍日本》,《图说日本》,《日本论》,《日本常识》等等的书籍,如果变换关键词也会搜索到像《菊与刀》这样的关于日本文化的书籍。那么通读这一系列的图书就能够快速建立起对于日本这个国家的认识。 同样的道理如果想要了解,政治学,心理学,可以直接以关键词去搜索,当然不一定这一个门类的图书都是在书名中包含关键字的,但是 Kindle Unlimited 的一个好处就是可以快速浏览目录,再去判断要不要去精读这本书。

善用豆瓣

豆瓣之于我,更像是一个工具,一个长周期的,管理信息的 Todo list,我一般会在豆瓣提前标注好想看的书,然后按照自己的喜欢程度来依次阅读。所以一般不会有书荒,也不会因为想看的书太多而造成压迫感。

制定读书计划

如果一个月看不到 3 本书,确实没必要买 Kindle Unlimited 了。所以这就体现了计划的重要性,除了上面提及的专题阅读,以及豆瓣的规划,最好在用豆瓣的豆列来指定当月或者当季度想看的书单。

看自己喜欢看的书籍

看自己喜欢的书,虽然听起来很简单,但很多时候其实很难实现,往往有些时候在书单中的书这个时候看非常晦涩,而在另外一个时候再回来读却能读出不一样的东西。很多书可能很长时间躺在书单中,但是却一直没有找到阅读的理由,但是往往可能因为别人的一句话或者看过的一部电影而重新从书单中被找出来,然后获得更多的体验。读某个阶段自己最关心的一个话题的书籍,或者某一个阶段最喜欢的一类图书即可。大可直接拿起一些图书,然后略读一个大概然后记住,等到真正关心起这段内容再仔细阅读。

不要一次性借阅太多的图书

把借书还书当成一次仪式,Kindle Unlimited 一次也只能够借阅 10 本,所以没必要同时阅读超过 2 两本书。一本本看,看完一本做些笔记,写些感想。


2019-02-12 kindle , amazon-kindle , kindle-unlimited , books

Google Forms 使用

Google Forms 是集成在 Google Docs 中可以用来快速创建调查问卷,建立报名系统,反馈调查等等的应用。Google Forms 原来属于 Google Docs,现在已经集成在了 Google Drive 中。

打开入口

Google Forms 的入口藏的有些深,如果不是之前就知道这个功能可能很多人用了多年 Google Docs 或者 Google Drive 都不曾知道有这个功能。在 Google Drive 中左上新建,然后在菜单中找到更多,然后其中有 Google Forms。

另外可以直接打开 Google Forms 的二级域名,也会跳转到对应的页面。

创建及修改

创建和修改部分其实非常简单,UI 也非常清晰。不过这里可以总结 Google Forms 支持的问题及答案类型:

  • 简短答案,可以用来回答比如姓名,电话,电子邮件等等简单的文本,甚至可以用正则来验证
  • 段落,比较长的文本
  • 选择题
  • 多选题
  • 下拉列表,可以根据不同的选项转到不同的小节
  • 上传文件
  • 线性量表,会使用单选形式出现多个选项,可以用来设计评分等等
  • 选择题网格,通常是每一行一问题,而每列中是不同的选择
  • 日期
  • 时间

通过组合这些功能能够设计出一个完整的调查问卷

收集结果

在 Forms 中能够直接查看回复,并且可以关联 Google Sheets,然后将结果在一张表中展示出来。


2019-02-01 google , google-docs , google-drive , quiz

expect 脚本使用

expect 是用来进行自动化控制和测试的工具。主要是和交互式软件 telnet ftp passwd fsck rlogin ssh tip 等进行自动化的交互。Linux 交互命令中经常需要输入 yes/no 或者 password 等操作,模拟这些输入,就可以使用 expect 脚本。expect 是由 tcl 语言演变而来的。简单地说,expect 是一个工具,可以根据用户设定的规则和系统进程进行自动化交互,例如远程登陆的密码输入、自动化的执行远程命令。

一个非常典型的使用场景就是一般在公司中都会使用 relay 来连接管理服务器的远程连接和使用,通常会需要在 SSH 登录的时候使用用户名和密码,甚至需要二步验证来增强安全性,但是如果不想每一次都重复输入用户名和密码就可以使用 expect 命令来快速实现登录。

安装

Debian/Ubuntu/Linux Mint 系安装非常简单

apt install expect

关键命令

expect 下几个非常重要的指令:

  • spawn: 启动进程(由 spawn 启动的进程的输出可以被 expect 所捕获)
  • expect: 从进程接收字符串,期望获得字符串
  • send: 向进程发送字符串,用于模拟用户的输入,注意一定要加 \r 回车
  • interact: 用户交互
  • sleep n: 使脚本暂停给定的秒数

spawn 指令用来开启比如 Shell, FTP, SSH ,SCP 等等的交互指令。

命令行参数

$argc,$argv 0,$argv 1 ... $argv n

argc 表示命令行参数个数,后面分别表示各个参数项,0 表示第一个参数,1 表示第二个参数,以此类推,可以通过 lindex 获取对应参数值 (lindex $argv 0)。

if {$argc < 2} {
    puts stdout "$argv0 err params\n"
    exit 1
}

if {[llength $argv] == 0} {
    puts stdout "need server name as param"
    exit 1
}

输入输出

puts stderr "Usage: $argv0 login passwaord.n "
puts "hello world"
puts stdout "1234"

变量赋值

set argv [lindex $argv 0]
set user "einverne"
set count 3
set ip "192.168.2.1"

复合指令

比如要将脚本的参数赋值给变量

set my_var [lindex $argv 0]

命令调用

spawn ssh $user@$ip

spawn 启动一个进程,进程执行 ssh 命令,程序后面可以通过 expect/send 和新起的进程进行交互

分支语句

单一分支语法:

expect "hello" {send "you said hello"}

多分支模式语法:

expect {
    "lilei" {send "hello lilei"; exp_continue}
    "hanmeimei" {send "hello hanmeimei"; exp_continue}
    "how do you do ?" {send "how do you do ?"}
}

switch 分支

switch -- $var {
{
  }
{
  }
{
  }
}

if else 分支

set NUM 1
if { $NUM < 5 } {
    puts "\Smaller than 5\n"
} elseif { $NUM > 5 } {
    puts "\Bigger than 5\n"
} else {
    puts "\Equals 5\n"
}

循环

while 循环语法

#!/usr/bin/expect -f
set NUM 0
while { $NUM <= 5 } {
    puts "Number is $NUM"
    set NUM [ expr $NUM + 1 ]
}

for 循环

for {set NUM 0} {$NUM <= 5} {incr NUM} {
    puts "\nNUM = $NUM"
}
puts ""

自定义方法

定义

proc myfunc { TOTAL } {
    set TOTAL [expr $TOTAL + 1]
    return "$TOTAL"
}

使用

set NUM [myfunc $NUM]

使用正则表达式判断

if {[regexp {^[0-9]+$} $NUM]} {
  Do something
} else {
  Exit
}

其他表示

if {![regexp {\D+} $NUM]}
if {![string match {[^0-9]+} $NUM]}

实例

登录远程服务器并创建文件夹

#!/usr/bin/expect               // 告诉系统脚本的执行方式
set timeout -1                  // 等待超时时间, -1 为无限制等待
spawn ssh root@192.168.2.1      // spawn 执行 expect 内部命令,给 ssh 运行进程加壳,传递交互指令
expect {                        // expect 是 expect 内部命令,判断上次输出结果是否包含,如果有则立即执行操作
    "password" {send "123456\r";}           // send 执行交互动作,与手工输入密码等效
    "yes/no" {send "yes\r";exp_continue}
}
expect "root" {send "mkdir testFolder\r"}
expect eof
exit

expect eof 等待结束标志,spawn 启动的命令在结束时会产生一个 eof 标志。

带参数的脚本

如果脚本依赖外部输入,比如有输入参数,那么可以在后面添加参数:

./expect.ex 192.168.2.1 123456

脚本可以首先用 set 给变量赋值

#!/usr/bin/expect

set ip [lindex $argv 0]
set password [lindex $argv 1]
set timeout -1
spawn ssh root@ip
expect {
    "password" {send "$password\r";}
    "yes/no" {send "yes\r";exp_continue}
}
expect "root" {send "mkdir test1\r"}
send "exit\r"
expect eof
exit

等待手动操作

登录远程服务器之后使用 interact 来等待手动操作,比如:

./expect.ex 192.168.2.1 123456

脚本:

#!/usr/bin/expect
set ip [lindex $argv 0]
set password [lindex $argv 1]
set timeout -1
spawn ssh root@$ip
expect {
    "password" {send "$password\r";}
    "yes/no" {send "yes\r";exp_continue}
}
interact                // 完成后保持交互状态,把控制权交给控制台

注意最后的 interact

总结

expect 在 Linux 运维中非常有用,可以用来多机器重启,远程 copy 等等场景,shell 中受限于密码输入的操作,可以通过 expect 来节省很多工作。

涉及到脚本地址

reference


2019-01-29 expect , linux , automate , shell , script , 运维 , 自动化

记录一下 Linux Mint 19 升级过程

记录一下从 mint 18.3 升级到 19, 然后小版本升级的过程。虽然总结这篇文章的时候比较早,但是一直没有发布出去,现在想想 Mint 20 都快出了,放在这里做个记录吧。

都知道 Ubuntu 大版本更新极有可能会让一些熟悉的应用没有及时维护而无法使用。Mint 也是一样从 18.3 升级到 19 的过程就比较痛苦,需要升级各种依赖版本。

备份

使用 Mint 自带的 timeshift 应用进行备份:

sudo apt install timeshift

查看当前的 dm, 并变更为 lightdm:

cat/etc/X11/default-display-manager
/usr/sbin/mdm
sudo apt install lightdm lightdm-settings slick-greeter

sudo dpkg-reconfigure lightdm

安装 mintupgrade:

sudo apt install mintupgrade
# 检查目前的依赖及安装包
sudo mintupgrade check
# 下载最新的安装包
sudo mintupgrade download
# 执行升级的过程
sudo mintupgrade upgrade

Update 工具中,选择 Edit, 然后选择升级到某某某版本。

在升级 Ubuntu 或者 Mint 的时候,千万小心,有很大的程度可能导致无法进入系统,最好做到资料的备份,如果进不去系统也不要担心,Linux 下提供了很多 Debug 工具,可以通过各种方法做到不丢数据。现在想一想我 MSI 笔记本上的系统升级过两次大版本,都或多或少的遇到过各种问题,但从来没有丢过数据,并通过一些配置都可以很快的进入桌面。


2019-01-23 mint , linux , ubuntu , cinnamon

alibaba arthas 使用

Arthas is a Java diagnostic tool that promises to help developers troubleshoot production issues for Java applications without modifying the code or restarting the server.

Main features:

  • Useful for trouble-shooting jar file conflicts
  • Decompile a class to ensure the code is running as expected
  • View classloader statistics
  • View the method invocation details
  • Check the stack trace of specified method invocation
  • Trace the method invocation to find slow sub-invocations
  • Monitor method invocation statistics
  • Monitor system metrics, thread states and CPU usage, gc statistics, and etc
  • Support command line interactive mode, with auto-completed feature enabled.
  • Support telnet and WebSocket, enabling both local and remote diagnostics with command line and browsers

Install

安装非常简单

curl -L https://alibaba.github.io/arthas/install.sh | sh

然后使用 ./as.sh 启动即可。

其他安装方式

实例

全局监控

进入 arthas 之后使用 dashboard 可以快速查看全局信息,包括系统基本信息,CPU 使用率,堆内存,gc 次数,gc 耗时等等

查看 JVM 线程堆栈信息

使用 thread 可以查看线程堆栈信息。当 thread 没有参数时会打印所有的线程信息。

thread 命令也支持打印当前最忙的前 N 个线程

thread -n 3

后面增加 id 可以打印指定线程堆栈

thread id

使用 -b 参数一键找出当前阻塞其他线程的线程

thread -b

目前只支持找出 synchronized 关键字阻塞住的线程, 如果是 java.util.concurrent.Lock, 目前还不支持。

查看类从哪一个 jar 加载

当知道类的路径时可以直接使用 sc -d package.Class 这样的方式来查看当前类的详细信息

$ sc -d demo.MathGame
 class-info        demo.MathGame
 code-source       /home/einverne/arthas/arthas-demo.jar
 name              demo.MathGame
 isInterface       false
 isAnnotation      false
 isEnum            false
 isAnonymousClass  false
 isArray           false
 isLocalClass      false
 isMemberClass     false
 isPrimitive       false
 isSynthetic       false
 simple-name       MathGame
 modifier          public
 annotation
 interfaces
 super-class       +-java.lang.Object
 class-loader      +-sun.misc.Launcher$AppClassLoader@5c647e05
                     +-sun.misc.Launcher$ExtClassLoader@197c17c6
 classLoaderHash   5c647e05

信息中可以清晰的看到加载的 jar 的路径等等一些信息。

代码为什么没有执行

要回答这个问题,最好的方法就是立马检查下当前正在跑的代码是否符合预期。使用如下命令:

jad demo.package.Class

直接查看当前运行的代码

查看函数调用栈耗时

如果特别关心某一函数的耗时情况,可以使用 trace 来查看

trace package.Class method

监控有异常的接口

使用 tt 命令可以记录下指定方法被调用时的入参和返回值。tt 是 TimeTunnel 的缩写,为了解决 watch 命令的复杂。

tt -t package.Class methodName -n 100
tt -t package.Class methodName -n 100 > temp.log

-n 表示会统计之后的多少请求,> temp.log 表示输出到 arthas 的 cache 中,地址在 ~/logs/arthas-cache/

回放请求

上面使用 tt 命令记录的内容可以用来回放请求,在 temp.log 日志中找到 index 表示的即为该请求的入参,使用 tt 命令可以用来回放请求,下面命令中的 index 就是文件中的 index

tt --play -i index

在使用回放的时候需要注意:1. ThreadLocal 信息丢失 2. 引用对象, tt 命令将当前环境的对象引用保存,如果方法对入参进行了修改,那么 tt 命令无法查看到准确的值。

watch 命令查看有异常的入参

tt 命令可以一直监控方法的入参,但是有的时候并不关心正常运行的参数,而只关心有异常的方法的入参,这个时候就可以使用 watch 命令

watch package.Class method -e -x 2 '{params[0], params[1].toString, throwExp}'

监控 JVM 运行状态

使用 jvm 命令即可查看 JVM 的运行状态

jvm

sysprop sysenv 查看系统变量

sysprop 可以查看系统变量,sysenv 可以查看系统的环境变量。

reference


2019-01-23 arthas , java , diagnostic-tool

开源软负载均衡 HAProxy 使用及配置

HAProxy is free, open source software that provides a high availability load balancer and proxy server for TCP and HTTP-based applications that spreads requests across multiple servers. It is written in C and has a reputation for being fast and efficient.

Features:

  • Layer 4 (TCP) and Layer 7 (HTTP) load balancing
  • URL rewriting
  • Rate limiting
  • SSL/TLS termination
  • Gzip compression
  • Proxy Protocol support
  • Health checking
  • Connection and HTTP message logging
  • HTTP/2
  • Multithreading
  • Hitless Reloads

Performance:

Similiar:

  • LVS
  • Nginx

HAProxy 的负载均衡算法:

  • roundrobin,表示简单的轮询
  • static-rr,表示根据权重
  • leastconn,表示最少连接者先处理
  • source,表示根据请求源 IP,和 Nginx IP_hash 类似
  • uri,请求 URI
  • url_param,请求的 URI 参数
  • hdr(name), name 指定的 HTTP 首部
  • rdp-cookie(cookie), 根据 cookie(name) 哈希请求

HAProxy 关键配置

HAProxy 的配置文件共有 5 个域

  • global:用于配置全局参数
  • default:用于配置所有 frontend 和 backend 的默认属性
  • frontend:用于配置前端服务(即 HAProxy 自身提供的服务)实例
  • backend:用于配置后端服务(即 HAProxy 后面接的服务)实例组
  • listen:frontend+backend 的组合配置,可以理解成更简洁的配置方法

配置举例

HTTP

global
    daemon
    maxconn 256

defaults
    mode http
    timeout connect 5000ms
    timeout client 50000ms
    timeout server 50000ms

frontend http-in
    bind *:80
    default_backend servers

backend servers
    server server1 127.0.0.1:8000 maxconn 32

定义监控

在配置文件中增加

listen stats    #定义监控页面
    bind *:1080                     # 绑定端口 1080
    mode http                       # http mode
    stats hide-version              # Hide HAProxy version
    stats refresh 30s               # 每 30 秒更新监控数据
    stats uri /stats                # 访问监控页面的 uri
    stats realm HAProxy\ Stats      # 监控页面的认证提示
    stats auth admin:admin          # 监控页面的用户名和密码

然后通过 http://ip:1080/stats 即可访问

HAProxy 主备

现在个人代理需求不强烈,等折腾到了再搞。

reference


2019-01-20 linux , load-balance , haproxy , proxy , tcp , http

oneplus 3 recovery ROM kernel 选择

所有下载链接建议有国外 VPS 的直接从国外的 VPS 下载,scp 拉回本地,浏览器下载太慢。

Recovery

ROM

AOSP Extended

LineageOS

Kernel

GApps

Magisk

Decryption Unsuccessful

在安装完新的 AOSP Extended ROM 之后启动突然出现了 “Decryption Unsuccessful” 的问题,一查可能是之前的数据分区加密过,新的系统无法解密读取这些文件了。

There’s some weird issues with the latest AOSP based ROM that makes encrypted ext4 based data partition unable to be decrypted in both system and older version of TWRP if you’re encrypted before (intentionally or unintentionally). This however doesn’t apply to F2FS and unencrypted ext4 data partition

xda 提供了两种解决方案:

推荐的方式是重新分配分区 F2FS,这需要提前备份所有 internal storage 数据,然后抹去所有数据

  • 通过 TWRP 挂载 SD 卡,备份全部数据
  • 格式化所有数据,在 TWRP 中选择 “Format”, 然后选择 “Wipe data”
  • 然后 Flash ROM 和 GApps

第二种方式是安装一个第三方修改过的 Kernel,这种方式不需要抹除数据

  • Xenial b25x or newer
  • Revolt EAS-R13

这里补充下 F2FS,很多人可能好奇 F2FS 是什么,具体做什么用,F2FS 是 “Flash-Friendly File system”,是一种新的文件系统,用于 NAND 闪存一类的储存设备,F2FS 和主流的 ext3/4 相比,有更好的加密特性,更快的读取速度,对碎片小文件读写优化,增加了固态存储的寿命等等优点。但 F2FS 也有其缺点,就是在大文件读写上性能可能会波动。


2019-01-19 oneplus , rom , kernel

Hive SQL 介绍及使用

Hive 是 Hadoop 生态中必不可少的工具,提供了 SQL 方言用于查询存储在 HDFS 中的数据和其他与 Hadoop 集成的文件系统(Amazon S3 ,HBase 数据库,Cassandra 的数据)

Hive 最适合数据仓库应用,使用应用进行相关静态数据分析,不需要快速响应得到结果,数据本身不会频繁发生变化。Hive 不是完整的数据库,Hadoop 和 HDFS 的设计约束和局限限制了 Hive 所能胜任的工作。最大的限制就是 Hive 不支持记录级别的更新、插入或者删除。

Hive 不支持 OLTP(联机事务处理)而更接近 OLAP(联机分析技术),如果要对大规模数据使用 OLTP,应该使用 NoSQL 数据库,例如 HBase 等。和大多数 SQL 方言一样,HiveQL 不符合 ANSI SQL 标准,和 常规的 MySQL,SQL Server 支持的 SQL 有很多方面差异,HiveQL 和 MySQL 提供的 SQL 方言最接近。

安装 Hive 运行环境

Hive 的运行依赖很多环境,需要一一配置

JDK

首先保证 Java 运行环境,jdk 1.7 及以上

java -version

验证

Hadoop 环境搭建

下载 tar,解压,配置环境变量

export HADOOP_HOME=/usr/local/hadoop
export HADDOP_CONF_DIR=$HADOOP_HOME/etc/hadoop

export HADOOP_MAPRED_HOME=$HADOOP_HOME
export HADOOP_COMMON_HOME=$HADOOP_HOME

export HADOOP_HDFS_HOME=$HADOOP_HOME
export YARN_HOME=$HADOOP_HOME
export HADOOP_COMMON_LIB_NATIVE_DIR=$HADOOP_HOME/lib/native
export HADOOP_OPTS="-Djava.library.path=$HADOOP_HOME/lib/native"

export PATH=$PATH:$HADOOP_HOME/sbin:$HADOOP_HOME/bin
export HADOOP_INSTALL=$HADOOP_HOME

配置完可以查看下版本:

hadoop version

配置文件

配置 core-site.xml

<property>
  <name>fs.default.name</name>
  <value>hdfs://localhost:9000</value>
</property>

配置 hdfs-site.xml

<property>
   <name>dfs.replication</name>
   <value>1</value>
</property>
<property>
   <name>dfs.name.dir</name>
   <value>file:///home/hadoop/hadoopinfra/hdfs/namenode </value>
</property>
<property>
   <name>dfs.data.dir</name>
   <value>file:///home/hadoop/hadoopinfra/hdfs/datanode </value >
</property>

配置 yarn-site.xml

<property>
   <name>yarn.nodemanager.aux-services</name>
   <value>mapreduce_shuffle</value>
</property>

配置 mapred-site.xml

cp mapred-site.xml.template mapred-site.xml

添加如下

<property>
  <name>mapreduce.framework.name</name>
  <value>yarn</value>
</property

验证,首先格式化 namenode

hdfs namenode -format

启动 Hadoop dfs

start-dfs.sh

启动 Yarn Script

start-yarn.sh

安装成功后访问

http://localhost:50070/
http://localhost:8088/

Hive 的大多数工作是使用 Hadoop 的 job,Hive 的行为可以反映出用户所使用的 Hadoop 运行模式。

Hive 环境搭建

下载 tar,解压,配置环境变量(根据不同的路径配置)

wget http://archive.apache.org/dist/hive/hive-2.1.0/apache-hive-2.1.0-bin.tar.gz
tar -xzf apache-hive-2.1.0-bin.tar.gz
export HIVE_HOME=/home/einverne/apache-hive-2.1.0-bin
export PATH=$PATH:$HIVE_HOME/bin

配置

cd $HIVE_HOME/conf
cp hive-env.sh.template hive-env.sh

编辑 hive-env.sh

export HADOOP_HOME=/usr/local/hadoop

配置 hive-site.xml

cp hive-default.xml.template hive-site.xml

使用 hive 内置的 derby (Apache Derby 非常小巧,核心部分 derby.jar 只有 2M,所以既可以做为单独的数据库服务器使用,也可以内嵌在应用程序中使用),初始化 Derby 数据库:

schematool -initSchema -dbType derby

验证

hive --version

进入 hive 之后

hive> show databases;

查看结果。默认会有一个 default 数据库,至此所有的安装都完成了。

Hive 目录的一些说明:

  • $HIVE_HOME/lib 下有很多 jar 包,每一个 jar 包提供 Hive 功能中的特定部分。
  • $HIVE_HOME/bin 目录下包含各种可执行文件,包括 hive 的命令行 CLI。
  • Hive 客户端都需要一个 metastoreservice 元数据服务,Hive 使用该服务来存储表模式信息和其他元数据信息。通常会使用一个关系型数据库的表来提供存储。默认情况下,Hive 会使用内置的 Derby SQL 服务,提供有限的单进程服务。
  • conf 目录下存放 Hive 配置文件。

Hive 的安装总体过程比较复杂可以具体参考这里

问题

启动 start-dfs.sh 时遇到

19/01/29 18:36:55 WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable

需要配置如下环境变量:

export HADOOP_OPTS="$HADOOP_OPTS -Djava.library.path=$HADOOP_HOME/lib/native"

关于使用 derby 的问题,hive 内嵌一个 derby ,如果要使用内嵌的 derby 那么在 hive-site.xml 中需要配置连接字串为:

<property>
   <name>javax.jdo.option.ConnectionURL</name>
   <value>jdbc:derby:metastore_db;create=true</value>
   <description>JDBC connect string for a JDBC metastore </description>
</property>

而如果选择单独启动 derby 那么需要配置:

<property>
   <name>javax.jdo.option.ConnectionURL</name>
   <value>jdbc:derby://localhost:1527/metastore_db;create=true </value>
   <description>JDBC connect string for a JDBC metastore </description>
</property>

而这个时候则需要单独的配置启动 derby.

使用

Metastore

Hive 的元数据都存放在 metastore 中,包括:服务和后台数据。metastore 的配置有三种方式:

  • 内置 metastore
  • 本地 metastore
  • 远程 metastore

在上面的安装步骤中,使用了内置的 Derby 数据库,但这个只适用于本地试验,如果要部署到生产环境还是需要单独配置 Metastore 数据库,使用外部的 Derby 或者 MySQL 等等。

当使用内置的 Derby 并且在本地一个目录中启动终端 hive,那么进入 hive shell 时,hive 默认会在当前目录下生成一个 metastore_db 目录,用来保存在 Shell 中的 sql 结果,新建的表,添加的分区等等。

这种存储方式导致同一个目录只能同时有一个 hive 客户端访问,切换目录使用 shell 时,无法查看之前创建的表。

如果要使用 MySQL 那么需要配置几点。首先将 mysql-jdbc-driver.jar 拷贝到 $HIVE_HOME/lib 目录下,然后配置 hive-site.xml 文件

<property>  
  <name>hive.metastore.local</name>  
  <value>true</value>  
</property>  
<property>  
  <name>javax.jdo.option.ConnectionURL</name>  
  <value>jdbc:mysql://localhost/hive?createDatabaseIfNotExist=true</value>  
</property>  
<property>  
  <name>javax.jdo.option.ConnectionDriverName</name>  
  <value>com.mysql.jdbc.Driver</value>  
</property>  
<property>  
  <name>javax.jdo.option.ConnectionUserName</name>  
  <value>hive</value>  
</property>  
<property>  
  <name>javax.jdo.option.ConnectionPassword</name>  
  <value>password</value>  
</property>

数据库相关

CREATE DATABASE demo;
CREATE DATABASE IF NOT EXISTS demo;
SHOW DATABASES;
SHOW DATABASE LIKE 'd.*';
DESCRIBE DATABASE demo;

CREATE TABLE IF NOT EXISTS mydb.employees (
    name    STRING COMMENT 'name',
    age     INT COMMENT 'age',
    salary  FLOAT COMMENT 'salary',
    subordinates    ARRAY<STRING> COMMENT 'Names of subordinates',
    deductions      MAP<STRING, FLOAT> COMMENT 'Keys are deductions name, values are percentages',
    address         STRUCT<street:STRING, city:STRING, state:STRING, zip:INT> COMMENT 'Home address'
)
COMMENT 'Description of the table'
TBLPROPERTIES ('creator'='me', 'create_at'='2019-01-01 00:00:00')
LOCATION '/user/hive/warehouse/mydb.db/employees';

DROP TABLE IF EXISTS employees;

reference


2019-01-17 hive , sql , hadoop , hbase

kudu 笔记

Kudu 是一个分布式结构化存储系统,由 Cloudera 开源,和 Apache Hadoop 生态紧密结合,共享了很多相同的工具集,可以跑在相同的硬件上,都支持水平扩展。

Kudu 要解决的问题

在了解 Kudu 之前先要了解两个概念 OLTP 和 OLAP:

  • OLTP,online transaction processing ,database modifying system 数据库
  • OLAP,online analytical procession, online database query answering system 线上分析处理

上面两种系统可以简单的理解为一类是需要频繁修改低延迟写入的数据库系统,一种是需要大量连续读分析的高性能分析系统

结构化数据在 Hadoop 生态系统中存储,通常分为两类:

  • 静态数据,二进制存储 HDFS (Avro ),HDFS 为高吞吐连续访问数据场景设计,单独行更新或者随机访问性能差
  • 动态数据,半结构化方式存储(HBase,Cassandra),低延迟读写单独行,对 SQL 分析连续大量读取性能差

两类系统各有自己的优缺点,为了解决各自的问题,一种常用的做法就是 pipeline,比如 Kafka,在数据快速写入到 HBase 的同时通过 pipeline 将其导出给其他分析系统,这时又会遇到一致性等问题。

Kudu 本意上为了解决上面的问题,寻求一个结合点,在一个系统的一份数据中,Kudu 结合了 HDFS 和 HBase 的有点,既能够支持 OLTP 实时读写能力,又能支持 OLAP 的分析能力。Kudu 提供简单的插入,更新和删除,同时通过 table scan 来处理数据分析。

Kudu 解决方案

Kudu 提供 table 概念,每个 table 都有预先定义好的 schema,Schema 定义多个 column,每一个 column 有名字,类型,是否允许 NULL 等。column 组成 primary key。在使用 Kudu 之前用户必须先建立 table,可以通过 DDL 语句添加或者删除 column,但不能删除包含 primary key 的 column。Kudu 的数据模型和关系型数据库非常相似。

Kudu 提供两种一致性模型:snapshot consistency 和 external consistency. snapshot consistency 为默认方式,更好的读性能。external consistency 能够保证写入数据后,任何读取都能读到最新数据。

Kudu 使用 Raft 算法来保证分布式环境下面数据一致性。

Kudu 应用场景

  • 实时分析,数据更新实时体现在分析结果上,比如实时报表
  • 迭代计算,每次计算的结果需要修改数据集
  • 时间序存储,通过 hash partition 可以防止出现热点

架构

Kudu 采用 Master-Slave 形式的中心节点架构,管理节点叫做 Kudu Master,数据节点叫做 Tablet Server。表数据被分割为一个或者多个 Tablet,Tablet 部署在 Tablet Server 提供数据读写服务。

Master 作用

  • 作为 catalog manager,存放 Schema 信息,负责处理建表请求
  • 作为 cluster coordinator,管理 Tablet Server,在 Tablet Server 异常后协调数据重新部署
  • 作为 tablet directory,存放 Tablet 到 Tablet Server 的信息

master 存放集群所有的信息,当 client 需要读写时先请求 master,然后路由到对应 client,但这样 master 一定会成为瓶颈,所以 client 会缓存最近路由,当失效时才会从 master 获取

Kudu 的数据模型类似于关系型数据库,数据存储在结构化的表中。同时 Kudu 是一个分布式的存储引擎,数据最终会分成很多的 tablets 存储在不同的机器上,而表的 partition 则定义了数据会被放到哪些 tablet 中。在同一个 tablet 中,数据根据主键有序排列。

column schema

Kudu column schema :

  • 表包含多个 column,至少需要包含一个 key column(主键)
  • column 必须指定 name 和 type
  • column 还可以指定 encoding 方式和压缩方式

partition

Kudu 支持两种类型的 partition:rang 和 hash

  • range partition 需要指定 N 个 splits key,这些 key 将数据的 key 空间分成 N+1 块,每块都对应与一个 tablet。一个表只可以指定包含一个 range partition
  • hash partition 需要指定根据哪些 column 来进行 hash 分桶,以及分桶的个数 M。一个 table 可以指定多个 hash partition,但 paritions 的 key column 之间不能有重叠

最终 table 的 tablets 数目为 N*M

Kudu 的分区可以按照 Range 和 Hash 方式划分到不同的 Tablet。Hash Partition 数据较均匀的分布在 Tablet 中,原来的数据排序特点被打乱。Range Partition 数据按照用户指定的有序 Primary Key Column 的组合 String 顺序分区。

Tablet 在 Kudu 中被切分更小的单位,RowSets,只存在内存的叫做 MemRowSets,而另一些使用 disk 和 memory 共享存放,叫做 DiskRowSets,任何一行数据只存在一个 RowSets 中。

任何时候,一个 Tablet 仅有一个单独的 MemRowSet 用来保存最近插入的数据,后台线程定期将 MemRowSet 刷入 disk。之前的 MemRowSet 被刷入 disk 后变成 DiskRowSet,空出新的 MemRowSet。

reference


2019-01-14 kudu , apache , sql , hbase , oltp

电子书

最近文章

  • 给网站加上实时聊天对话框 tawk.to 使用记录 tawk.to 是一个可以在网页上添加客户聊天对话框的应用。用户可以通过 tawk.to 泡泡快速地得到支持。
  • 下载 YouTube 视频方法总结 之前就简单地介绍过使用yt-dlp 来下载 YouTube 视频,yt-dlp 是自从 youtube-dl 不再更新之后有人接手开发的新工具。但这篇文章重点是在于下载 YouTube 视频,我会整理一下我目前了解的所有可视化,命令行,Telegram bot 等等工具。
  • Tailscale 出口节点功能配置流量出口 之前的文章中介绍过 Tailscale ,是一个功能非常强大的虚拟组网的工具,底层使用更高级的 [[WireGuard]] 协议进行通信。之前的文章中只简单的介绍了一下 Tailscale 的使用,但是过去的时间里面 Tailscale 又更新了很多的新特性,这篇文章就介绍其中的一个特性 Exit Nodes。
  • Porkbun 免费领取一年 app wiki 等域名 [[Porkbun]] 通常被人戏称为「猪肉包」,是一家新成立于美国俄勒冈州波特兰市的域名注册商,母公司是 Top Level Design,后者是 design, ink 和 wiki 三个顶级域名后缀的管理局。这家域名注册商虽然成立时间比较短,但是胜在价格实惠。短短几年时间就打开了知名度。
  • 《负动产时代》读书笔记 怎么知道的这一本书