开发者博客 – IT技术 尽在开发者博客

开发者博客 – 科技是第一生产力


  • 首页

  • 归档

  • 搜索

javalangreflect包的浅析

发表于 2017-12-05

Object(源头)

Java的一切都是对象

Object是反射的源头

Class是反射的导演

Class(导演)

在Java程序运行时,JVM会对所有的对象维护一个独一无二的类型标识,这就是Class对象。

Java的基本类型和关键字void也对应一个Class对象。

相同元素类型和维数的数组也对应一个Class对象。

获取Class对象的几种方法:

  • 一个对象通过.class
  • 一个对象通过getClass方法
  • Class.forName(String)

Class对象有一些重要的方法:

  • getConstructor系列,用于获取构造方法
  • getField系列,用于获取属性
  • getMethod系列,用于获取方法

上述系列中包含Declared系列,可以获取当前对象的所有类型的反射(包含private),但是不能获取父类的反射

Constructor,Field,Method都是Class导演的三大利器,它们都位于java.lang.reflect包下,接下来将分别对这三大利器进行展示。

Constructor

通过Class对象获取到的Constructor对象之后,最常用的操作就是用来实例化对象,调用newInstance方法即可。

其实在Class对象中也有一个newInstance方法,也可以用来实例化对象,它们的区别是什么呢?

  • Class.newInstance() 只能够调用无参的构造函数,即默认的构造函数,要求被调用的构造函数是可见的,也即必须是public类型的;
  • Constructor.newInstance() 可以根据传入的参数,调用任意构造构造函数,在特定的情况下,可以调用私有的构造函数。

Demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
复制代码//Class.newInstance()
//只能调用public属性的无参构造函数
A a = (A)Class.forName(A.class.getName()).newInstance();

//Constructor.newInstance()
Class c= Class.forName(A.class.getName());

/*以下调用无参的、私有构造函数*/
//获得无参构造
Constructor c0=c.getDeclaredConstructor();
//设置无参构造是可访问的
c0.setAccessible(true);
A a0=(A)c0.newInstance();

//调用无参构造函数,生成对象实例
/*以下调用带参的、私有构造函数*/
Constructor c1=c.getDeclaredConstructor(new Class[]{int.class,int.class});
c1.setAccessible(true);
//调用有参构造函数,生成对象实例
A a1=(A)c1.newInstance(new Object[]{5,6});

使用场景:

如果使用接口模式,使用new创建一个门的对象,Door door = new WoodenDoor(),当以换成其他门,需要修改代码,Door door = new OtherDoor()。所以我们需要使用工厂模式,需要什么门就生产什么门,如果我们再使用newInstance()方法来生产,则只需要修改配置文件即可。

Field

通过Class对象获取到的Field对象之后,我们就可以自由的查看和设置对象的属性值。

关键方法:

  • get(Object object)查看特定对象的属性值
  • set(Object object, Object value)给特定对象设置属性
  • setAccessible(boolean flag)让private成员拥有public权限

Demo

1
2
3
4
5
6
7
8
9
10
11
12
13
复制代码//获取Class对象
Class aClass = MyObject.class
//通过Class对象获取Field对象
Field field = aClass.getDeclaredField("someField");
//设置可访问权限
field.setAccessible(true);

MyObject objectInstance = new MyObject();

//获取特定的对象的变量属性值
Object value = field.get(objectInstance);
//给特定对象的变量设置属性
field.set(objetInstance, value);

因为Constructor,Field,Method都继承自AccessibleObject类,所有都拥有setAccessible方法,个人感觉setAccessible有点窥探隐私的感觉,哈哈哈,不知道Class导演怎么看。

Method

通过Class对象获取到的Method对象之后,想都不用想啊,获取一个方法不调用它干嘛,所以我们最常用的操作应该就是invoke方法。

相信大家对invoke并不会陌生,因为很多的异常,最后都会定位到invoke方法。

1
2
3
4
5
6
复制代码java.lang.NullPointerException
at ......
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)

invoke(Object receiver, Object… args),可以分为两种,传递receiver参数表示调用特定对象的方法,传null表示调用静态方法。

1
2
3
4
5
复制代码//获取一个方法名为doSomesthing,参数类型为String的方法
Method method = MyObject.class.getMethod("doSomething", String.class);

//调用静态的doSomesthing方法,传递参数"parameter-value1"
Object returnValue = method.invoke(null, "parameter-value1");

最后说两句

本文只是对Clss对象以及reflect包下的对象进行简单的使用说明,关于反射的实现和原理,还有待于深入研究。例如AccessibleObject对象以及对应相关xxxAccessor的实现。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

堆排序与优先队列

发表于 2017-12-05

堆

说到堆就必须要说二叉树,二叉树指每个节点最多只能包含两个子节点的树。二叉树常用的实现为二叉搜索树(BinarySearchTree)和二叉堆(BinaryHeap)

这里不再对树的概念进行赘述,有需求的自行google,二叉堆其实对应着一棵完全二叉树,最后一层除外。因此使得一个堆可以利用数组来存储,二叉堆又分为大根堆和小根堆,下图展示了一个大根堆与数组的对应。

image

因此可以很简单的得到堆节点所对应的数组。 :banana:

  • 父节点parent = index / 2
  • 左节点left = index * 2
  • 右节点right = index * 2 + 1

堆排序

堆排序就是把最大堆堆顶的最大数取出,将剩余的堆继续调整为最大堆,再次将堆顶的最大数取出,这个过程持续到剩余数只有一个时结束。在堆中定义以下几种操作:

  • 最大堆调整(Max-Heapify):将堆的末端子节点作调整,使得子节点永远小于父节点。
  • 创建最大堆(Build-Max-Heap):将堆所有数据重新排序,使其成为最大堆。
  • 堆排序(Heap-Sort):移除位在第一个数据的根节点,并做最大堆调整的递归运算。

通俗的解释就是: 最大堆调整(Max-Heapify)其实就是一次 <下沉> 的过程,创建最大堆(Build-Max-Heap)就是 <循环> 每个节点进行 最大堆调整,堆排序(Heap-Sort)就是每次 创建完最大堆 之后需要将最大元素 <分离>。

一句话概括:循环树的每一个节点进行下沉,然后分离,继续循环。 :tomato:

下图展示了一次下沉的过程:

picture

优先队列

普通的队列满足先进先出,而优先队列满足每次出队列的都是优先级最高的元素。 :strawberry:

常见的优先队列有三种实现方式:

  • 有序数组(add时添加到排序的位置,poll时候移除队尾元素)
  • 无序数组(add时直接添加到队尾,poll时查找优先元素)
  • 二叉堆(add时上浮,poll时下沉)

image

优先队列与堆排序的关系

因为优先队列每次poll只需要最大优先级的元素,所以不需要维持整棵二叉堆的有序,只需要维持根节点满足最大优先级即可。所以只需要对根节点进行一次堆排序的最大堆调整(Max-Heapify)即可。

Java优先队列源码解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
复制代码    

//add方法也就是直接调用offer方法
public boolean offer(E e) {
//不允许插入null
if (e == null)
throw new NullPointerException();
modCount++;
int i = size;
//容量不够则进行扩容
if (i >= queue.length)
grow(i + 1);
siftUp(i, e);//这里调用了上浮
size = i + 1;
return true;
}

//上浮分为两种情况,判断是否设置了comparator
private void siftUp(int k, E x) {
if (comparator != null)
siftUpUsingComparator(k, x);
else
siftUpComparable(k, x);
}

//我们只分析这种没有设置comparator的方法,另一种类比
private void siftUpComparable(int k, E x) {
Comparable<? super E> key = (Comparable<? super E>) x;
while (k > 0) {
//找到父节点
int parent = (k - 1) >>> 1;
Object e = queue[parent];
//如果当前节点大于父节点则上浮结束
if (key.compareTo((E) e) >= 0)
break;
//否则,将父节点下沉
queue[k] = e;
k = parent;
}
//将节点赋值到正确的上浮位置
queue[k] = key;
}

//poll方法
public E poll() {
//如果没有元素,则返回null
if (size == 0)
return null;
int s = --size;
modCount++;
//返回根节点
E result = (E) queue[0];
E x = (E) queue[s];
//将队尾元素移到根节点,并进行下沉
queue[s] = null;
//队列中不止一个元素,则进行下沉
if (s != 0)
siftDown(0, x);
return result;
}

//下沉方法的实现与上浮类似,就不赘述了。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

动态规划的理解与案例分析

发表于 2017-12-05

动态规划的本质

常用的五大算法,包含 动态规划、分治法、贪心求解法、回朔法、分支限界法。

动态规划(Dynamic Programming),与其说是一种算法,不如说是一种解决问题的思路。 :peach:

Dynamic Programming is a methed for solving a complex problem by breaking it down into a collection of simpler subproblems.

上述引自维基百科,也就是说动态规划就是将一个复杂的问题分解成若干简单的问题集的一种方法。

那么怎么分解问题就成了动态规划的本质。

而分解问题,依靠的就是 问题的状态 和 状态之间的转移。

  1. 如何定义一个状态

我们需要找到一个问题在某一个状态的 最优解。 :strawberry:

举个例子:

最长递增子序列(LIS)
给定一个长度为N的数组,找出一个最长的单调自增子序列(不一定连续,但是顺序不能乱)
例如:给定一个长度为6的数组A{5, 6, 7, 1, 2, 8}
则其最长的单调递增子序列为{5,6,7,8},长度为4

由此我们定义一个状态,当以第i个数组元素结尾的最长递增子序列dp(i)。
整个数组的LIS就是dp(1)…dp(n)的最大值。

由此我们就定义好了一个问题的状态,下面我们就看看不同状态之间的转移。

  1. 如何找到状态的转移

首先,我们需要找到状态的 边界值。 :watermelon:

根据上述LIS的问题,边界值为当i=1时,最长递增子序列为1。

然后,我们需要找到状态之间的 关系。 :banana:

dp(i) = max(1,dp(j)+1...) (0<=j<i) 当array[j]<array[i]

解释一下,在保证第i项比第j项大的情况下,要取之前所有项的最长递增子序列加1的最大值。

这里可以看出,这里的状态转移方程,就是定义了问题和子问题之间的关系。
可以看出,状态转移方程就是带有条件的递推式。

动态规划经典练习

这里罗列了6道比较经典的动态规划练习。 :corn:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
复制代码/**
* No1.塔树选择和最大问题
*
* 一个高度为N的由正整数组成的三角形,从上走到下,求经过的数字和的最大值。
* 每次只能走到下一层相邻的数上,例如从第3层的6向下走,只能走到第4层的2或9上。
* 5
* 8 4
* 3 6 9
* 7 2 9 5
* 例子中的最优方案是:5 + 8 + 6 + 9 = 28
*
* 输入:符合塔树的二维数组。
* 输出:经过的最大值。
*/

/**
* No2.∑乘法表问题
*
* ∑ | a b c
* ——————————————
* a | b b a
* b | c b a
* c | a c c
*
* 依此乘法表,对任一定义于∑上的字符串,适当加括号表达式后得到一个表达式。
* 例如,对于字符串x=bbbba,它的其中一个加括号表达式为i(b(bb))(ba)。
* 依乘法表,该表达式的值为a。试设计一个动态规划算法,对任一定义于∑上的字符串x=x1x2…xn。
* 计算有多少种不同的加括号方式,使由x导出的加括号表达式的值为a。
*
* 输入:输入一个以a,b,c组成的任意一个字符串。
* 输出:计算出的加括号方式数。
*/

/**
* No.3跳台阶
*
* 一只青蛙一次可以跳上1级台阶,也可以跳上2级。
* 求该青蛙跳上一个n级的台阶总共有多少种跳法。
*
* 输入:台阶数n。
* 输出:跳法总数。
*/

/**
* No.4最长递增子序列(LIS)
*
* 给定一个长度为N的数组,找出一个最长的单调自增子序列(不一定连续,但是顺序不能乱)。
* 例如:给定一个长度为6的数组A{5, 6, 7, 1, 2, 8}。
* 则其最长的单调递增子序列为{5,6,7,8},长度为4。
*
* 输入:一个数组。
* 输出:最长递增子序列的长度。
*/

/**
* No.5背包问题
*
* 有N件物品和一个容量为V的背包。
* 第i件物品的大小是c[i],价值是w[i]。
* 求解将哪些物品装入背包可使价值总和最大。
*
* 输入:物品大小数组c,物品价值数组w,背包容量。
* 输出:最大的价值。
*/

/**
* No.6最长公共子序列(LCS)
*
* 给出两个字符串a, b,求它们的最长的公共子序列。
*
* 输入:字符串a和字符串b。
* 输出:最长的公共子序列的长度。
*/

请先根据动态规划的本质,定义出 状态 和 状态之间的关系,然后再进行代码编写。

如果你已经完成了练习,这里有上述问题的答案,戳这里。

分享一个对于动态规划比较不错的理解。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

MySQL57 安全操作

发表于 2017-12-05

一、前言

笔者之前写过《MySQL 性能优化技巧》文章,但没有涉及到 MySQL 安全方面的知识。虽说这是 DBA 需要学习的内容与后端开发人员关系不大,但俗话说技多不压身,即便不深入学习,也需要对其相关内容有所了解。

测试环境 MySQL 5.7.20

以下便是笔者浅学后的内容总结。

二、用户相关

创建新用户并合理地设置权限是安全的保障。

# 2.1 新建用户

1
复制代码mysql> create user 用户名 identified by "密码"

使用新用户名登录后,由于没有权限只能查看一个数据库:

1
2
3
4
5
6
7
8
9
10
11
12
13
复制代码mysql> show databases;

+--------------------+

| Database |

+--------------------+

| information_schema |

+--------------------+

1 row in set (0.00 sec)

# 2.2 修改用户名

1
复制代码mysql> rename user 旧用户名 to 新用户名

# 2.3 修改密码

不需要登录 MySQL 的情况:

1
2
3
4
5
复制代码shell> mysqladmin -u 用户名 -p password "新密码"



提示需要输入旧密码

登陆 MySQL 后,修改新密码的情况,有如下几种方式:

1
复制代码mysql> alter user 'root'@'localhost' IDENTIFIED BY '新密码'
1
复制代码mysql> alter user user() IDENTIFIED BY '新密码'
1
复制代码mysql> SET PASSWORD FOR 'root'@'localhost' = '新密码'
1
复制代码mysql> SET PASSWORD = '新密码';
1
复制代码mysql> grant usage on *.* to "用户名"@"%" identified by "新密码"

针对 MySQL 5.7.6 以上版本,以上命令在设置密码时,不需要使用 password() 给密码加密。

忘记密码,需要重置密码的情况:

  1. 关闭 MySQL 服务
1
复制代码shell> service mysqld stop
  1. 创建临时启动文件(/root/mysql-init),内容如下:
1
复制代码ALTER USER 'root'@'localhost' IDENTIFIED BY '新密码';
  1. 启动 MySQL 服务
1
复制代码shell> mysqld --user=root --init-file=/root/mysql-init &
  1. MySQL 服务启动后,删除 /root/mysql-init 文件。

踩坑提醒:

将 /root/mysql-init 文件删除后,笔者执行 service mysqld stop 和 service mysqld start 启动就报错:

1
复制代码[ERROR] Could not open unix socket lock file /var/lib/mysql/mysql.sock.lock

通过 ll /var/lib/mysql/ 查看 mysql.sock.lock 所属用户和用户组,发现是 root 权限。

而 mysqld 命令启动使用的是普通用户的权限,因此无法打开文件。

于是笔者将其修改成 mysql 用户和 mysql 组启动成功:

1
2
3
4
5
复制代码chown -R mysql:mysql /var/lib/mysql/mysql.sock



chown -R mysql:mysql /var/lib/mysql/mysql.sock.lock

# 2.4 删除用户

1
复制代码mysql> drop user 用户名

drop 命令还可以删除多个用户,多个用户名通过 “,” 隔开。

三、授权相关

MySQL 提供了许多权限类型,读者可以参考文章末尾的资料了解更多知识。

为了测试方便,笔者使用 all 权限进行测试。

# 3.1 全局级别

授权:

1
复制代码mysql> grant all on *.* to 用户名

撤销权限:

1
复制代码mysql> revoke all on *.* from 用户名

其中, * 分别表示库名和表名。

# 3.2 数据库级别

授权:

1
复制代码mysql> grant all on 数据库名.* to 用户名

撤销权限::

1
复制代码mysql> revoke all on 数据库名.* from 用户名

# 3.3 表级别

授权:

1
复制代码mysql> grant select,insert,update on 数据库名.表名 to 用户名

四、备份数据

# 4.1 物理备份

时机:数据库服务关闭。如果需要运行数据库备份,需要锁定数据库避免在备份期间数据产生变化。

方式:直接拷贝数据库目录和文件(/var/lib/mysql)。

优点:备份速度比逻辑备份快,且包含日志文件和配置文件等信息。

# 4.2 逻辑备份

时机:数据库服务开启。

# 4.2.1备份所有数据库

1
2
3
4
5
复制代码shell>mysqldump -h 主机名 -u 用户名 -p --all-databases > dump.sql



提示输入密码

如果不是远程备份,主机名参数可以省略。

# 4.2.2 备份指定数据库

1
2
3
4
5
复制代码shell>mysqldump -h 主机名 -u 用户名 -p --databases 库名1 [库名2 ...] > dump.sql



提示输入密码

备份多个数据库,库名之间使用空格隔开。

# 4.2.3 备份指定数据库表

1
2
3
4
5
复制代码shell>mysqldump -h 主机名 -u 用户名 -p 库名 表名1 [表名2 ...] > dump.sql



提示输入密码

备份多张表,表名之间使用空格隔开。

五、还原数据

# 5.1 物理备份还原

直接将备份目录放在数据库数据目录下(/var/lib/mysql)。

# 5.2 逻辑备份还原

针对所有数据库:

1
2
3
4
5
复制代码shell> mysql -uroot -p < dump.sql



提示输入密码

或

1
复制代码mysql> source  dump.sql

针对某个库还原:

1
2
3
4
5
复制代码shell> mysql -uroot -p 库名 < dump.sql



提示输入密码

六、参考资料

  • dev.mysql.com/doc/refman/… MySQL 安全建议
  • dev.mysql.com/doc/refman/… 权限类型
  • dev.mysql.com/doc/refman/… 备份相关

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

获取多台主机命令执行结果

发表于 2017-12-05

在多台主机之间批量执行shell语句的效果

之前在主产品用过一个运维同事写的工具,devpssh。可以通过指定主机列表来执行一条shell命令,然后获取到所有的返回结果,输出到屏幕上。

我个人觉得这个工具很实用,尤其是在有多台Nginx服务器的时候,由于负载均衡策略下,不同的请求可能会被下放到不同的get机,因此产生的日志文件就可能分布在多台机器上。如果我们一个个地到每台get机上去执行shell语句。首先工作量会很大,另外获取到的结果也不容易整理。而此时用一下devpssh,就没有这些负担了。

在正式介绍如何写一个这样的工具之前,先来看看需要哪些基础的知识。

  • 主机间信任
  • shell脚本

主机间信任

说到主机之间的信任,还是要将历史往前追溯一下。谈谈SSH。简单来说SSH是一种网络协议,用于计算机之间的加密登录。之所以是加密登录就是应为原始的用户远程登录是明文的,一旦被截获,信息就泄露了。

SSH是协议,具体有很多实现。有商业实现的,也有开源实现。不过大致来看,用法是一致的。

先来看看安装了ssh的机器有什么不同吧。

执行过ssh-keygen后

id_rsa是使用RSA算法得到的私钥
id_rsa.pub是对称的RSA算法得到的公钥。
了解过对称加密算法的应该都知道,妥善保存好私钥是一件很重要的事情。

一般来说,第一次使用ssh登录到远程主机的时候,会有如下提示信息:

1
2
3
复制代码The authenticity of host 'host (12.18.429.21)' can't be established.
  RSA key fingerprint is 98:2e:d7:e0:de:9f:ac:67:28:c2:42:2d:37:16:58:4d.
  Are you sure you want to continue connecting (yes/no)?

这段话的大致意思是说,无法确认你即将登录的远程主机的真实性,但是可以了解的就只有它的公钥指纹,如果确定要进行连接,选择yes即可。
然后会出现如下字样:

1
复制代码  Warning: Permanently added 'host,12.18.429.21' (RSA) to the list of known hosts.

然后ssh会提示你使用密码进行登录了。正确输入密码后,就可以正常的登录了。这个时候,其实远程主机的公钥就被保存到了~/.ssh/known_hosts文件中了。内容如下:

known_hosts文件内容

其中每一行都代表了一个曾经成功连接过的远程主机的公钥信息。

但是每次远程登录都需要输入一遍密码,感觉次数多了,总是感觉有点麻烦。而ssh也支持使用公钥进行登录,这样就省去了每次登陆都要输入一遍密码的步骤了。

具体的做法如下:
将自己的电脑的公钥发送到目标主机的.ssh/authorized_keys中,这样登录的时候ssh协议通过对称加密,解密的验证过程,就可以实现公钥登录了。

这个对称加密,解密大致有这么个流程。

  • 本地主机使用ssh进行远程登录
  • 远程主机借助authorized_keys里面本地主机的信息生成一个随机字符串发给本地主机
  • 本地主机用自己的私钥将这个字符串进行加密,发给远程主机。
  • 远程主机使用本地主机的公钥进行 解密,如果成功,身份验证也就通过了。

最后来一个小总结:

  • known_hosts里面是已经成功远程登录过的主机的公钥信息。
  • authenrized_keys是已经授权的,可以免密码登录到本机的“主机”的公钥信息。

公钥免密登录也会是待会主机间的信任的基础。再来回顾下需求,我要在某一台主机上执行一条命令,然后获取全部的get机上相对应的内容。那么这台主机就可以作为master。

在master上,将通过ssh-keygen命令生成的公钥发送到要进行远程登录的get机的.ssh/authenrized_keys中。
比如:
master机器为192.168.30.100
get机列表是:
192.168.32.102
192.168.32.105
192.168.32.109
192.168.32.110
我们就可以依次将100的公钥使用SCP命令或者其他的上传工具,上传到对应的get机的authenrized_keys文件中。

1
2
3
4
5
复制代码ssh-keygen -t rsa //此处一路回车,生成秘钥

scp .ssh/id_rsa.pub 192.168.32.102:~/ //把秘钥拷贝到其他远程机器

ssh 192.168.30.102 ‘cat id_rsa.pub >> .ssh/authorized_keys’ //(远程执行命令)在远程机器上生成认证文件

这样,将master的公钥就成功的添加到102这台get机上了。其他的get机就可以按照同样的方法做下处理。处理完之后,就可以使用公钥进行免密码登录到远程主机了。

至此,主机间的信任就算结束了。


shell脚本编写

目标需求是获取所有get机上执行的shell命令,并进行整合输出。我在网上找了一个shell脚本,大致的内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
复制代码#!/usr/bin bash
docommand()
{
hosts=`sed -n '/^[^#]/p' hostlist`
for host in $hosts
do
echo "" # 换个行
ssh $host "$@"
done
return 0
}
if [ $# -lt 1 ]
then
echo "$0 cmd"
exit
fi
docommand "$@"

然后需要在同级目录下创建一个get主机列表。

1
2
3
4
复制代码192.168.32.102
192.168.32.105
192.168.32.109
192.168.32.110

然后懒得输入bash前缀来执行命令的话,就可以写一个alias了在~/.bashrc 文件末尾添加如下内容:

1
复制代码alias devpssh='bash /home/developer/runcommand.sh'

然后**source ~/.bashrc*

这样就可以通过如下格式,来批量在主机之间执行shell命令了,具体的格式如下:

1
复制代码devpssh 'cat /var/log/nginx/api_acces.log | grep curuserid=2614677 | tail -1'

在多台主机之间批量执行shell语句的效果

至此,在多台主机之间执行shell命令也就得以实现了。


总结

本次内容比较少,单纯的了解了下ssh的一些相关知识点。然后是利用公钥免密登录并执行相关的shell命令。

麻雀虽小,但是却很实用。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

分布式领域架构师要掌握的技术

发表于 2017-12-05

摘要:分布式系统无疑是持久的热门话题,但其实如果不是一定有必要,强烈建议不要进入分布式领域,在集中式的情况下很多问题都会简单不少,技术人员千万不要因为外界火热的例如微服务,就把自己的产品的也去做改造,一定要仔细判断是否有必要,不要为了技术而技术,那么在必须分布式的情况下(访问量、存储量或开发人数),一个分布式领域的合格的架构师要掌握哪些技术呢,这篇文章就聊聊这个话题。

分布式系统无疑是持久的热门话题,但其实如果不是一定有必要,强烈建议不要进入分布式领域,在集中式的情况下很多问题都会简单不少,技术人员千万不要因为外界火热的例如微服务,就把自己的产品的也去做改造,一定要仔细判断是否有必要,不要为了技术而技术,那么在必须分布式的情况下(访问量、存储量或开发人数),一个分布式领域的合格的架构师要掌握哪些技术呢,这篇文章就聊聊这个话题。

简单重复下我对架构师的标准,一个架构师最重要的不是画几个框,连几条线(这是基本要求),而是控制技术风险,要控制技术风险显然不是看几个结构性的ppt就能学会的。

通信

既然是分布式系统,系统间通信的技术就不可避免的要掌握。

首先要掌握一些基础知识,例如网络通信协议(诸如TCP/UDP等等)、网络IO(Blocking-IO,NonBlocking-IO、Asyn-IO)、网卡(多队列等);更偏应用的层面,需要了解例如连接复用、序列化/反序列化、RPC、负载均衡等。

学了这些基本知识后,基本上可以写一个简单的分布式系统里的通信模块,但这其实远远不够,既然进入了分布式领域,对规模其实就已经有了不低的要求,通常也就意味着需要的是能支持大量连接、高并发、低资源消耗的通信程序。

大量的连接通常会有两种方式:

1. 大量client连一个server

在现如今NonBlocking-IO这么成熟的情况下,一个支持大量client的server已经不那么难写了,但在大规模,并且通常长连接的情况下,有一个点要特别注意,就是当server挂掉的时候,不能出现所有client都在一个时间点发起重连,那样基本就是灾难,在没有经验的情况下我看过好几起类似的case,到client规模上去后,server一重启基本就直接被冲进来的大量建连冲垮了(当然,server的backlog队列首先应该稍微设置大一些),通常可以采用的方法是client重连前都做随机时间的sleep,另外就是重连的间隔采取避让算法。

2. 一个client连大量的server

有些场景也会出现需要连大量server的现象,在这种情况下,同样要注意的也是不要并发同时去建所有的连接,而是在能力范围内分批去建。

除了建连接外,另外还要注意的地方是并发发送请求也同样,一定要做好限流,否则很容易会因为一些点慢导致内存爆掉。

这些问题在技术风险上得考虑进去,并在设计和代码实现上体现,否则一旦随着规模上去了,问题一时半会还真不太好解。

高并发这个点需要掌握CAS、常见的lock-free算法、读写锁、线程相关知识(例如线程交互、线程池)等,通信层面的高并发在NonBlocking-IO的情况下,最重要的是要注意在整体设计和代码实现上尽量减少对io线程池的时间占用。

低资源消耗这点的话NonBlocking-IO本身基本已经做到。

伸缩性

分布式系统基本就意味着规模不小了,对于这类系统在设计的时候必须考虑伸缩性问题,架构图上画的任何一个点,如果请求量或者是数据量不断增大,怎么做到可以通过加机器的方式来解决,当然,这个过程也不用考虑无限大的场景,如果经历过从比较小到非常大规模的架构师,显然优势是不小的,同样也会是越来越稀缺的。

伸缩性的问题围绕着以下两种场景在解决:

1. 无状态场景

对于无状态场景,要实现随量增长而加机器支撑会比较简单,这种情况下只用解决节点发现的问题,通常只要基于负载均衡就可以搞定,硬件或软件方式都有;

无状态场景通常会把很多状态放在db,当量到一定阶段后会需要引入服务化,去缓解对db连接数太多的情况。

2. 有状态场景

所谓状态其实就是数据,通常采用Sharding来实现伸缩性,Sharding有多种的实现方式,常见的有这么一些:

2.1 规则Sharding

基于一定规则把状态数据进行Sharding,例如分库分表很多时候采用的就是这样的,这种方式支持了伸缩性,但通常也带来了很复杂的管理、状态数据搬迁,甚至业务功能很难实现的问题,例如全局join,跨表事务等。

2.2 一致性Hash

一致性Hash方案会使得加机器代价更低一些,另外就是压力可以更为均衡,例如分布式cache经常采用,和规则Sharding带来的问题基本一样。

2.3 Auto Sharding

Auto Sharding的好处是基本上不用管数据搬迁,而且随着量上涨加机器就OK,但通常Auto Sharding的情况下对如何使用会有比较高的要求,而这个通常也就会造成一些限制,这种方案例如HBase。

2.4 Copy

Copy这种常见于读远多于写的情况,实现起来又会有最终一致的方案和全局一致的方案,最终一致的多数可通过消息机制等,全局一致的例如zookeeper/etcd之类的,既要全局一致又要做到很高的写支撑能力就很难实现了。

即使发展到今天,Sharding方式下的伸缩性问题仍然是很大的挑战,非常不好做。

上面所写的基本都还只是解决的方向,到细节点基本就很容易判断是一个解决过多大规模场景问题的架构师,:)

稳定性

作为分布式系统,必须要考虑清楚整个系统中任何一个点挂掉应该怎么处理(到了一定机器规模,每天挂掉一些机器很正常),同样主要还是分成了无状态和有状态:

1. 无状态场景

对于无状态场景,通常好办,只用节点发现的机制上具备心跳等检测机制就OK,经验上来说无非就是纯粹靠4层的检测对业务不太够,通常得做成7层的,当然,做成7层的就得处理好规模大了后的问题。

2. 有状态场景

对于有状态场景,就比较麻烦了,对数据一致性要求不高的还OK,主备类型的方案基本也可以用,当然,主备方案要做的很好也非常不容易,有各种各样的方案,对于主备方案又觉得不太爽的情况下,例如HBase这样的,就意味着挂掉一台,另外一台接管的话是需要一定时间的,这个对可用性还是有一定影响的;

全局一致类型的场景中,如果一台挂了,就通常意味着得有选举机制来决定其他机器哪台成为主,常见的例如基于paxos的实现。

可维护性

维护性是很容易被遗漏的部分,但对分布式系统来说其实是很重要的部分,例如整个系统环境应该怎么搭建,部署,配套的维护工具、监控点、报警点、问题定位、问题处理策略等等。

从上面要掌握的这些技术,就可以知道为什么要找到一个合格的分布式领域的架构师那么的难,何况上面这些提到的还只是通用的分布式领域的技术点,但通常其实需要的都是特定分布式领域的架构师,例如分布式文件系统、分布式cache等,特定领域的架构师需要在具备上面的这些技术点的基础上还具备特定领域的知识技能,这就更不容易了。

随着互联网的发展,分布式领域的很多技术都在成熟化,想想在8年或9年前,一个大规模的网站的伸缩性是怎么设计的还是很热门的探讨话题,但是到了今天基本的结构大家其实都清楚,并且还有很多不错的系统开源出来,使得很多需要经验的东西也被沉淀下去了,在有了各种不错的开源产品的支撑下以后要做一个分布式系统的难度一定会越来越大幅降低,云更是会加速这个过程。

ps: 在写这篇文章的过程中,发现要判断一个技术人的功底有多厚,其实还真不难,就是请TA写或者讲自己觉得懂的所有技术,看看能写多厚或讲多久…要写厚或讲很久其实都不容易,尽管我也不否认要很简洁的写明白或讲清楚也不容易,但一定是先厚然后薄。

作者:阿里毕玄

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

iText7使用IExternalSignatureCont

发表于 2017-12-05

写在前面

一般情况下我们都是使用iText7自带的

1
复制代码pdfsigner.detach()

方法对pdf文件进行签名,iText7已经自己封装好了PKC7,所以这里还是挺方便的。但如果因为某种需求需要我们自己来进行P7签名,那么我们就可以使用

1
复制代码pdfsigner.signExternalContainer()

来自己实现对pdf的签名,即itext7只要提供要签名的数据给我们就行了。

签名和验签大致流程

我们可以看下这幅图,来自《Acrobat_DigitalSignatures_in_PDF》:

Acrobat_DigitalSignatures_in_PDF

大致的意思就是说

  1. 要签名的时候会把文档转换成字节流叫ByteRange
  2. ByteRange有四个数字,分成三部分(以图为例),我们要用来签名的数据就在0- 840和960-1200这部分,然后签名就存放在840~960里面。
  3. 因此我们验签的时候获取签名值就来自于840~960也就是Contents里。
  4. 要验签的原文就是ByteRange里除去签名值的部分。

IExternalSignatureContainer介绍

我们先看下IExternalSignatureContainer这个接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
复制代码/**
* Interface to sign a document. The signing is fully done externally, including the container composition.
* 这是一个用来签署文件的接口,它让所有的签名都完全来自于外部扩展实现。
* @author Paulo Soares
*/
public interface IExternalSignatureContainer {

/**
* Produces the container with the signature.
* @param data the data to sign
* @return a container with the signature and other objects, like CRL and OCSP. The container will generally be a PKCS7 one.
* @throws GeneralSecurityException
*/
byte[] sign(InputStream data) throws GeneralSecurityException;

/**
* Modifies the signature dictionary to suit the container. At least the keys {@link PdfName#Filter} and
* {@link PdfName#SubFilter} will have to be set.
* @param signDic the signature dictionary
*/
void modifySigningDictionary(PdfDictionary signDic);
}

signExternalContainer()方法介绍

接下来我们看下需要用到IExternalSignatureContainer 的方法 signExternalContainer() 的介绍:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
复制代码    /**
* Sign the document using an external container, usually a PKCS7. The signature is fully composed
* externally, iText will just put the container inside the document.
* <br><br>
* NOTE: This method closes the underlying pdf document. This means, that current instance
* of PdfSigner cannot be used after this method call.
*
* @param externalSignatureContainer the interface providing the actual signing
* @param estimatedSize the reserved size for the signature
* @throws GeneralSecurityException
* @throws IOException
*/
public void signExternalContainer(IExternalSignatureContainer externalSignatureContainer, int estimatedSize) throws GeneralSecurityException, IOException {
//省略部分源码

//关注这里,调用getRangeStream()方法获取到要签名的数据
//传入到externalSignatureContainer.sign()方法里给我们签
InputStream data = getRangeStream();
byte[] encodedSig = externalSignatureContainer.sign(data);

//省略部分源码


/**
* Gets the document bytes that are hashable when using external signatures. 在使用外部签名的时候会返回可用于哈希的文件字节。
* The general sequence is:
* {@link #preClose(Map)}, {@link #getRangeStream()} and {@link #close(PdfDictionary)}.
*
* @return The {@link InputStream} of bytes to be signed.
* 返回用于签名的字节
*/
protected InputStream getRangeStream() throws IOException {
RandomAccessSourceFactory fac = new RandomAccessSourceFactory();
return new RASInputStream(fac.createRanged(getUnderlyingSource(), range));
}

可以看到这个方法需要两个参数IExternalSignatureContainer(扩展签名容器) 和 estimatedSize(预估值)。

开始重写IExternalSignatureContainer

那么我们先重写IExternalSignatureContainer:

注:以下使用到的哈希方法,签名方法是做一个说明,毕竟要用到IExternalSignatureContainer表示你已经是有了自己的一套哈希和签名工具了

在进行签名的时候有两个subFilter可以然后我们进行使用,分别是adbe.pkcs7.detached和adbe.pkcs7.sha1,在pdf1.7文档里的解释是

adbe.pkcs7.detached: No data is encapsulated in the PKCS#7 signed-data field.

adbe.pkcs7.sha1: The SHA1 digest of the byte range is encapsulated in the PKCS#7 signed-data field with ContentInfo of type Data.

adbe.pkcs7.detached是目前用得最多的,在这里我们直接将数据进行p7不带原文签名即可;

adbe.pkcs7.sha1则是先对数据进行哈希,然后再调用p7带原文签名。不过这种应该是后来的标准里被废除了。

Adbe.pkcs7.detached

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
复制代码IExternalSignatureContainer externalP7DetachSignatureContainer = new IExternalSignatureContainer() {
@Override
public byte[] sign(InputStream data) throws GeneralSecurityException {

//将要签名的数据进行 P7不带原文 签名
byte[] result = SignUtil.P7DetachSigned(data);

return result;
}

//必须设置 PdfName.Filter 和 PdfName.SubFilter
@Override
public void modifySigningDictionary(PdfDictionary signDic) {
signDic.put(PdfName.Filter, PdfName.Adobe_PPKLite);
//注意这里
signDic.put(PdfName.SubFilter, PdfName.Adbe_pkcs7_detached);
}
};

Adbe.pkcs7.sha1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
复制代码IExternalSignatureContainer externalP7Sha1SignatureContainer = new IExternalSignatureContainer() {
@Override
public byte[] sign(InputStream data) throws GeneralSecurityException {

//对要签名的数据先进行哈希
byte[]hashData = HashUtil.hash(data , "SHA-1");
//将哈希后的数据进行 P7带原文 签名
byte[] result = SignUtil.P7AttachSigned(hashData);

return result;
}

//必须设置 PdfName.Filter 和 PdfName.SubFilter
@Override
public void modifySigningDictionary(PdfDictionary signDic) {
signDic.put(PdfName.Filter, PdfName.Adobe_PPKLite);
//注意这里
signDic.put(PdfName.SubFilter, PdfName.Adbe_pkcs7_sha1);
}
};

调用signExternalContainer()方法

1
2
3
4
5
6
复制代码PdfReader pdfReader = new PdfReader(new ByteArrayInputStream(pdfFile));
PdfSigner pdfSigner = new PdfSigner(pdfReader, new FileOutputStream(signedPath), false);

//estimatedSize可以自己设置预估大小
//但建议开启一个循环来判断,如果太小就增大值,直到签名成功
pdfSigner.signExternalContainer(externalP7DetachSignatureContainer, estimatedSize);

如改成这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
复制代码        //是否签名成功标志
boolean success = false;
//预估大小
int estimatedSize = 3000;

//通过调整预估大小直到签名成功
while (!success) {
try {
PdfReader pdfReader = new PdfReader(new ByteArrayInputStream(pdfFile));
PdfSigner pdfSigner = new PdfSigner(pdfReader, new FileOutputStream(signedPath), false);
pdfSigner.signExternalContainer(externalP7DetachSignatureContainer, estimatedSize);

success = true;

} catch (IOException e) {
e.printStackTrace();
estimatedSize += 1000;
} catch (GeneralSecurityException e) {
e.printStackTrace();
}
}

盖章

假设我们现在需要为文件进行盖章,我们可以准备一张图章图片,将它添加到签名域里。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
复制代码    /**
* 对pdf进行签名图片操作(添加签章)
*
* @param pdfSigner
* @param imgBytes 图片文件
* @param leftBottomX 图片的左下方x坐标
* @param leftBottomY 图片的左下方y坐标
* @param imgWidth 图片的宽度
* @param imgHeight 图片的高度
* @param pageNum 页码
*/
private void doImageStamp(PdfSigner pdfSigner, byte[] imgBytes, int leftBottomX, int leftBottomY, int imgWidth, int imgHeight, int pageNum) {

ImageData imageData = ImageDataFactory.create(imgBytes);

PdfSignatureAppearance appearance = pdfSigner.getSignatureAppearance();
Rectangle rectangle = new Rectangle(leftBottomX , leftBottomY , imgWidth , imgHeight);

appearance.setPageRect(rectangle)
.setPageNumber(pageNum)
.setSignatureGraphic(imageData)
.setRenderingMode(PdfSignatureAppearance.RenderingMode.GRAPHIC);

}

验签

用我们的自己的签名工具进行签名后,我们可以更进一步的做验签。

Adbe.pkcs7.detached验签

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
复制代码    /**
* 验签pdf
*
* @param pdf 签名好的pdf
* @return 验签结果 true/false
*/
public boolean verifyPdf(byte[] pdf) {

boolean result = false;

try {
PdfReader pdfReader = new PdfReader(new ByteArrayInputStream(pdf));
PdfDocument pdfDocument = new PdfDocument(pdfReader);
SignatureUtil signatureUtil = new SignatureUtil(pdfDocument);
List<String> signedNames = signatureUtil.getSignatureNames();

//遍历签名的内容并做验签
for (String signedName : signedNames) {

//获取源数据
byte[] originData = getOriginData(pdfReader, signatureUtil, signedName);

//获取签名值
byte[] signedData = getSignData(signatureUtil , signedName);

//校验签名
result = SignUtil.verifyP7DetachData(originData , signedData);

}
} catch (IOException e) {
e.printStackTrace();
}

return result;
}

Adbe.pkcs7.sha1验签

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
复制代码    /**
* 验签pdf
*
* @param pdf 签名好的pdf
* @return 验签结果 true/false
*/
public boolean verifyPdf(byte[] pdf) {

boolean result = false;

try {
PdfReader pdfReader = new PdfReader(new ByteArrayInputStream(pdf));
PdfDocument pdfDocument = new PdfDocument(pdfReader);
SignatureUtil signatureUtil = new SignatureUtil(pdfDocument);
List<String> signedNames = signatureUtil.getSignatureNames();

//遍历签名的内容并做验签
for (String signedName : signedNames) {

//获取签名值
byte[] signedData = getSignData(signatureUtil , signedName);

//校验签名
result = SignUtil.verifyP7AttachData(signedData);

}
} catch (IOException e) {
e.printStackTrace();
}

return result;
}

获取源数据和签名数据方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
复制代码    /**
* 获取签名数据
* @param signatureUtil
* @param signedName
* @return
*/
private byte[] getSignData(SignatureUtil signatureUtil, String signedName) {
PdfDictionary pdfDictionary = signatureUtil.getSignatureDictionary(signedName);
PdfString contents = pdfDictionary.getAsString(PdfName.Contents);
return contents.getValueBytes();
}

/**
* 获取源数据(如果subFilter使用的是Adbe.pkcs7.detached就需要在验签的时候获取 源数据 并与 签名数据 进行 p7detach 校验)
* @param pdfReader
* @param signatureUtil
* @param signedName
* @return
*/
private byte[] getOriginData(PdfReader pdfReader, SignatureUtil signatureUtil, String signedName) {

byte[] originData = null;

try {
PdfSignature pdfSignature = signatureUtil.getSignature(signedName);
PdfArray pdfArray = pdfSignature.getByteRange();
RandomAccessFileOrArray randomAccessFileOrArray = pdfReader.getSafeFile();
InputStream rg = new RASInputStream(new RandomAccessSourceFactory().createRanged(randomAccessFileOrArray.createSourceView(), SignatureUtil.asLongArray(pdfArray)));
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
byte[] buf = new byte[8192];
int n = 0;
while (-1 != (n = rg.read(buf))) {
outputStream.write(buf, 0, n);
}

originData = outputStream.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}

return originData;

}

获取签名信息

当我们对pdf进行签名后,可以获取到这份pdf里的签名信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
复制代码/**
* 获取签名信息实体类
*/
public class PdfSignInfo {

private Date signDate;
private String digestAlgorithm;
private String reason;
private String location;
private String signatureName;
private String encryptionAlgorithm;
private String signerName;
private String contactInfo;
private int revisionNumber;

public int getRevisionNumber() {
return revisionNumber;
}

public String getContactInfo() {
return contactInfo;
}

public String getSignerName() {
return signerName;
}

public void setSignerName(String signerName) {
this.signerName = signerName;
}

public String getEncryptionAlgorithm() {
return encryptionAlgorithm;
}

public void setEncryptionAlgorithm(String encryptionAlgorithm) {
this.encryptionAlgorithm = encryptionAlgorithm;
}

public String getSignatureName() {
return signatureName;
}

public void setSignatureName(String signatureName) {
this.signatureName = signatureName;
}

public void setSignDate(Date signDate) {
this.signDate = signDate;
}

public Date getSignDate() {
return signDate;
}

public String getReason() {
return reason;
}

public void setReason(String reason) {
this.reason = reason;
}

public String getLocation() {
return location;
}

public void setLocation(String location) {
this.location = location;
}

public String getDigestAlgorithm() {
return digestAlgorithm;
}

public void setDigestAlgorithm(String digestAlgorithm) {
this.digestAlgorithm = digestAlgorithm;
}

public void setContactInfo(String contactInfo) {
this.contactInfo = contactInfo;
}

public void setRevisionNumber(int revisionNumber) {
this.revisionNumber = revisionNumber;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
复制代码    /**
* 解析返回签名信息
* @param pdf
* @return
*/
public List<PdfSignInfo> getPdfSignInfo(byte[] pdf){

//添加BC库支持
BouncyCastleProvider provider = new BouncyCastleProvider();
Security.addProvider(provider);

List<PdfSignInfo> signInfoList = new ArrayList<>();

try {
PdfReader pdfReader = new PdfReader(new ByteArrayInputStream(pdf));
PdfDocument pdfDocument = new PdfDocument(pdfReader);

SignatureUtil signatureUtil = new SignatureUtil(pdfDocument);

List<String> signedNames = signatureUtil.getSignatureNames();

//遍历签名信息
for (String signedName : signedNames) {

PdfSignInfo pdfSignInfo = new PdfSignInfo();
pdfSignInfo.setSignatureName(signedName);
pdfSignInfo.setRevisionNumber(signatureUtil.getRevision(signedName));

PdfPKCS7 pdfPKCS7 = signatureUtil.verifySignature(signedName , "BC");

pdfSignInfo.setSignDate(pdfPKCS7.getSignDate().getTime());
pdfSignInfo.setDigestAlgorithm(pdfPKCS7.getDigestAlgorithm());
pdfSignInfo.setLocation(pdfPKCS7.getLocation());
pdfSignInfo.setReason(pdfPKCS7.getReason());
pdfSignInfo.setEncryptionAlgorithm(pdfPKCS7.getEncryptionAlgorithm());

X509Certificate signCert = pdfPKCS7.getSigningCertificate();

pdfSignInfo.setSignerName(CertificateInfo.getSubjectFields(signCert).getField("CN"));

PdfDictionary sigDict = signatureUtil.getSignatureDictionary(signedName);
PdfString contactInfo = sigDict.getAsString(PdfName.ContactInfo);
if (contactInfo != null) {
pdfSignInfo.setContactInfo(contactInfo.toString());
}

signInfoList.add(pdfSignInfo);

}
} catch (IOException e) {
e.printStackTrace();
}

return signInfoList;

}

参考

How can I get ByteRange with iText7?

SignatureTest.java

C2_07_SignatureAppearances.java

pdf_reference_1-7.pdf

Why I can’t use SHA1 before PKCS7.detached in iText7?

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

ActiveMQ的初步了解

发表于 2017-12-05

什么是ActiveMQ:

  1. 首先你得了解什么是MOM:
    MOM(Message Oriented Middleware),分布式系统的集成,指的是利用高效可靠的消息传递机制进行平台无关的数据交流,并基于数据通信来进行分布式系统的集成。
  2. 然后你得知道什么是JMS:
    JMS(Java Message Service)Java消息服务,应用程序接口,是一个Java平台中关于面向消息中间件(MOM)的API,用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。

摘要来自百度百科,这里就不对前两个进行赘述,有兴趣的自行Google。 :cherries:

  1. 最后才是ActiveMQ:
    Apache下的一个非常流行的消息中间件,使用JAVA支持的JMS Provider实现,所以和JAVA程序完全兼容,开发java项目中间件首选。当然ActiveMQ不仅仅支持JAVA,在C++、Dotnet、Python、Php、Ruby、Websocket等多种客户端都可以提供良好的服务。

ActiveMQ的使用场景:

其实也就是为什么要使用MQ。 :watermelon:

  1. 异步通信
    不需要即时处理的业务,将其放去消息队列中,在需要处理的时候直接去队列中取出来,达到了生产者和消费者不用互相了解对方,生产者只需要专注于生产,消费者专注于消费。
  2. 解耦
    降低工程之间的耦合程度,从设计角度来讲,达到低耦合高内聚的目的。当应用需要维护的时候,不同应用可以独立的扩展或修改,只需要遵循同样的接口约束即可。
  3. 冗余
    消息队列可以对队列中的消息进行持久化处理,防止数据丢失。很多消息队列都采用“插入-获取-删除”的模式,只有当处理数据的过程成功并且返回提示,才会进行消息的删除,否则消息将一直保存在队列之中。
  4. 过载保护
    在请求量突发的高峰期间,为了让系统保持正常工作,又不想每时每刻都按最大峰值投入资源。使用消息队列就可以让关键组件顶住突发压力,不至于让整个系统崩溃。
  5. 保证有序
    消息队列可以对消息进行优先级设定,然后根据优先级来对消息进行排序,达到重要数据优先处理。
  6. 缓冲
    消息队列有助于控制和优化数据流经过系统的速度。以调节系统响应时间。
  7. 数据流处理
    大数据业务需要对数据流进行分析,在消息队列中进行处理是最好不过的。

ActiveMQ原理剖析:

  • 两种运行模型
+ PTP点对点通信:
使用queue作为信息载体,满足生产者与消费者模式,一个消息只能被一个消费者使用,没有被消费的消息可以持久保持在queue 中等待被消费。
+ Pub/Sub发布订阅模式:
使用Topic主题作为通信载体,类似于广播模式,在消息广播期间,所有的订阅者都可以接受到广播消息,在一条消息广播之后才订阅的用户是收不到该条消息的。
  • ActiveMQ的组成模块
+ Broker:消息服务器,作为server提供消息核心服务。
+ Producer:消息生产者,业务的发起方,负责生产消息传输给broker。
+ Consumer:消息消费者,业务的处理方,负责从broker获取消息并进行业务逻辑处理。
+ Topic:主题,发布订阅模式下的消息统一汇集地,不同生产者向topic发送消息,由MQ服务器分发到不同的订阅者,实现消息的广播。
+ Queue:队列,PTP模式下,特定生产者向特定queue发送消息,消费者订阅特定的 queue完成指定消息的接收。
+ Message:消息体,根据不同通信协议定义的固定格式进行编码的数据包,来封装业务 数据,实现消息的传输。
  • ActiveMQ的常用协议
+ AMQP协议
`AMQP即Advanced Message Queuing Protocol,一个提供统一消息服务的应用层标准高级消 息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品,不同开发语言等条件的限制。`
+ MQTT协议
`MQTT(Message Queuing Telemetry Transport,消息队列遥测传输)是IBM开发的一个即时 通讯协议,有可能成为物联网的重要组成部分。该协议支持所有平台,几乎可以把所有联网 物品和外部连接起来,被用来当做传感器和致动器(比如通过Twitter让房屋联网)的通信协 议。`
+ STOMP协议
`STOMP(Streaming Text Orientated Message Protocol)是流文本定向消息协议,是一种为 MOM(Message Oriented Middleware,面向消息的中间件)设计的简单文本协议。STOMP提 供一个可互操作的连接格式,允许客户端与任意STOMP消息代理(Broker)进行交互。`
+ OPENWIRE协议
ActiveMQ特有的协议,官方描述如下
`OpenWire is our cross language Wire Protocol to allow native access to ActiveMQ from a number of different languages and platforms. The Java OpenWire transport is the default transport in ActiveMQ 4.x or later. For other languages see the following...`

对于ActiveMQ的上述协议,每种协议端口都不一样,可以自行修改。:tangerine:

编辑activemq.xml,在transportConnectors标签中注销、修改或删除不使用的协议。

1
2
3
4
5
复制代码<transportConnector name="openwire" uri="tcp://0.0.0.0:61616?maximumConnections=1000&amp;wireFormat.maxFrameSize=104857600"/>
<transportConnector name="amqp" uri="amqp://0.0.0.0:5672?maximumConnections=1000&amp;wireFormat.maxFrameSize=104857600"/>
<transportConnector name="stomp" uri="stomp://0.0.0.0:61613?maximumConnections=1000&amp;wireFormat.maxFrameSize=104857600"/>
<transportConnector name="mqtt" uri="mqtt://0.0.0.0:1883?maximumConnections=1000&amp;wireFormat.maxFrameSize=104857600"/>
<transportConnector name="ws" uri="ws://0.0.0.0:61614?maximumConnections=1000&amp;wireFormat.maxFrameSize=104857600"/>

ActiveMQ运用实践:

csharp作为生产者实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
复制代码//根据URI创建NMS连接工厂
NMSConnectionFactory factory = new NMSConnectionFactory(brokerUri);
//根据用户名密码创建连接
IConnection connection = factory.CreateConnection(user, password);
//打开连接
connection.Start();
//创建Session
ISession session = connection.CreateSession(AcknowledgementMode.AutoAcknowledge);
//根据destination创建主题
IDestination dest = session.GetQueue(destination);
//创建生产者
IMessageProducer producer = session.CreateProducer(dest);
producer.DeliveryMode = MsgDeliveryMode.NonPersistent;
//发送消息
String body = "hello,javaJMS!! I'm C#";
producer.Send(session.CreateTextMessage(body));
//关闭连接
connection.Close();

创建Session时可以为会话设定消息确认模式:

  • AUTO_ACKNOWLEDGE
    当客户端成功从receive或onMessage方法返回之后,会话自动确认客户端的消息。
  • CLIENT_ACKNOWLEDGE
    客户通过消息的acknowledge 方法确认消息。需要注意的是,在这种模式中,确认是在会话层上进行:确认一个被消费的消息将自动确认所有已被会话消费的消息。
  • DUPS_ACKNOWLEDGE
    该选择只是会话迟钝的确认消息的提交。如果JMS provider 失败,那么可能会导致一些重复的消息。如果是重复的消息,那么JMS provider 必须把消息头的JMSRedelivered 字段设置为true。
  • SESSION_TRANSACTED
    即消息发送者发送消息后,需要提交事务,否则消息不进入broker待发送队列中。

相关实践在这里不进行详细描述,详情可以参考我的Demo项目。:lemon:
以ActiveMQ为消息中间件,关联dotnet,java,ws三种平台

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

如何定位 golang 进程 hang 死的 bug

发表于 2017-12-05

之前在 golang 群里有人问过为什么程序会莫名其妙的 hang 死然后不再响应任何请求。单核 cpu 打满。

这个特征和我们公司的某个系统曾经遇到的情况很相似,内部经过了很长时间的定位分析总结,期间还各种阅读 golang 的 runtime 和 gc 代码,最终才定位到是业务里出现了类型下面这样的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
复制代码package main

import "time"

func main() {
var ch = make(chan int, 100)
go func() {
for i := 0; i < 100; i++ {
ch <- 1
}
}()

for {
// the wrong part
if len(ch) == 100 {
sum := 0
itemNum := len(ch)
for i := 0; i < itemNum; i++ {
sum += <-ch
}
if sum == itemNum {
return
}
}
}

}

在 main goroutine 里循环判断 ch 里是否数据被填满,在另一个 goroutine 里把 100 条数据塞到 ch 里。看起来程序应该是可以正常结束的对不对?

感兴趣的话你可以自己尝试运行一下。实际上这个程序在稍微老一些版本的 golang 上是会卡死在后面这个 for 循环上的。原因呢?

使用:

1
复制代码GODEBUG="schedtrace=300,scheddetail=1" ./test1

应该可以看到这时候 gcwaiting 标记为 1。所以当初都怀疑是 golang gc 的 bug。。但最终折腾了半天才发现还是自己的代码的问题。

因为在 for 循环中没有函数调用的话,编译器不会插入调度代码,所以这个执行 for 循环的 goroutine 没有办法被调出,而在循环期间碰到 gc,那么就会卡在 gcwaiting 阶段,并且整个进程永远 hang 死在这个循环上。并不再对外响应。

当然,上面这段程序在最新版本的 golang 1.8/1.9 中已经不会 hang 住了(实验结果,没有深究原因)。某次更新说明中官方声称在密集循环中理论上也会让其它的 goroutine 有被调度的机会,那么我们选择相信官方,试一下下面这个程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
复制代码package main

import (
"fmt"
"io"
"log"
"net/http"
"runtime"
"time"
)

func main() {
runtime.GOMAXPROCS(runtime.NumCPU())
go server()
go printNum()
var i = 1
for {
// will block here, and never go out
i++
}
fmt.Println("for loop end")
time.Sleep(time.Second * 3600)
}

func printNum() {
i := 0
for {
fmt.Println(i)
i++
}
}

func HelloServer(w http.ResponseWriter, req *http.Request) {
io.WriteString(w, "hello, world!\n")
}

func server() {
http.HandleFunc("/", HelloServer)
err := http.ListenAndServe(":12345", nil)

if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}

运行几秒之后 curl 一发:

1
复制代码curl localhost:12345

感觉还是不要再相信官方了。研究研究之后不小心写出了这样的 bug 怎么定位比较好。首先分析一下这种类型 bug 发生时的程序特征:

1
2
3
复制代码1. 卡死在 for 循环上
2. gcwaiting=1
3. 没有系统调用

由于没有系统调用,不是系统调用导致的锅,所以我们没有办法借助 strace 之类的工具看程序是不是 hang 在系统调用上。而 gcwaiting=1 实际上并不能帮我们定位到问题到底出现在哪里。

然后就剩卡死在 for 循环上了,密集的 for 循环一般会导致一个 cpu 核心被打满。如果之前做过系统编程的同学应该对 perf 这个工具很了解,可以使用:

1
复制代码perf top

对 cpu 的使用情况进行采样,这样我们就可以对 cpu 使用排名前列的程序函数进行定位。实际上 perf top 的执行结果也非常直观:

1
2
3
4
5
6
7
8
9
复制代码  99.52%  ffff                     [.] main.main
0.06% [kernel] [k] __do_softirq
0.05% [kernel] [k] 0x00007fff81843a35
0.03% [kernel] [k] mpt_put_msg_frame
0.03% [kernel] [k] finish_task_switch
0.03% [kernel] [k] tick_nohz_idle_enter
0.02% perf [.] 0x00000000000824d7
0.02% [kernel] [k] e1000_xmit_frame
0.02% [kernel] [k] VbglGRPerform

你看,我们的程序实际上是卡在了 main.main 函数上。一发命令秒级定位。

妈妈再也不用担心我的程序不小心写出死循环了。实际上有时候我的一个普通循环为什么变成了死循环并不是像上面这样简单的 demo 那样好查,这时候你还可以用上 delve,最近就帮 jsoniter 定位了一个类似上面这样的 bug:

github.com/gin-gonic/g…

从 perf 定位到函数,再用 pid attach 到进程,找到正在执行循环的 goroutine,然后结合 locals 的打印一路 next。

问题定位 over。

最后一行小字:感谢之前毛总在 golang 群的指导。

本文行文仓储,应该还是难免疏漏,如果你觉得哪里写的有问题,或者对文末提到的毛总顶礼膜拜,再或者对即将进入 k8s 的 json 解析库 jsoniter 非常感性趣,想要深入了解 jsoniter 作者大牛的心路历程,那么你有下面几个选择:

1.加入b站和毛总学习先进姿势水平

2.加入滴滴和 jsoniter 作者大牛 @taowen 做同事

3.加入滴滴来喷本文作者

简历可以发送到: caochunhui@didichuxing.com

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

go语言死循环分析

发表于 2017-12-05

最近看了一篇文章,如何定位 golang 进程 hang 死的 bug,里面有这张一段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
复制代码package main
import (
"fmt"
"io"
"log"
"net/http"
"runtime"
"time"
)
func main() {
runtime.GOMAXPROCS(runtime.NumCPU())
go server()
go printNum()
var i = 1
for {
// will block here, and never go out
i++
}
fmt.Println("for loop end")
time.Sleep(time.Second * 3600)
}
func printNum() {
i := 0
for {
fmt.Println(i)
i++
}
}
func HelloServer(w http.ResponseWriter, req *http.Request) {
fmt.Println("hello world")
io.WriteString(w, "hello, world!\n")
}
func server() {
http.HandleFunc("/", HelloServer)
err := http.ListenAndServe(":12345", nil)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}

运行,会发现打印一会儿数字后停了,我们执行

1
复制代码curl localhost:12345

程序卡死。关于程序挂在哪里借助dlv是很好定位的:

1
复制代码dlv debug hang.go

进去之后运行程序,打印停止进入卡死状态,我们执行ctrl C,dlv会显示断开的地方:

1
2
3
4
5
6
7
8
9
10
11
12
13
复制代码received SIGINT, stopping process (will not forward signal)> main.main() ./hang.go:17 (PC: 0x12dd7c8)
12: func main() {
13: runtime.GOMAXPROCS(runtime.NumCPU())
14: go server()
15: go printNum()
16: var i = 1
=> 17: for {
18: // will block here, and never go out
19: i++
20: }
21: fmt.Println("for loop end")
22: time.Sleep(time.Second * 3600)
(dlv)

但是我还是不明白,不明白的地方主要是因为:

  • 我又看了两篇文章Goroutine调度实例简要分析和也谈goroutine调度器,是同一位作者Tony Bai写的,写得非常好。第二篇文章解释了goroutine的调度和cpu数量的关系(不多加解释,建议大家看看),我的mac是双核四线程(这里不明白的同学自行google
    cpu 超线程),go版本是1.9,理论上讲可以跑4个goroutine而不用考虑死循环,一个死循环最多把一个cpu打死,上面的代码中只有3个goroutine,而且他们看上去都挂住了。
  • 上面说的理论上讲,不是我主观臆测的,我跑了1中第一篇文章中的一个例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
复制代码package main
import (
"fmt"
"time"
)
func deadloop() {
for {
}
}
func main() {
go deadloop()
for {
time.Sleep(time.Second * 1)
fmt.Println("I got scheduled!")
}
}

上面代码有两个goroutine,一个是main goroutine,一个是deadloop goroutine,跑得时候deadloop gouroutine不会对main goroutine造成影响,打印一直在持续,作者的文章解释了原因。

  • 如何定位 golang 进程 hang 死的 bug这篇文章提到了gcwaiting,然而没有解释。

在如何定位 golang 进程 hang 死的 bug有这样一段话:

因为在 for 循环中没有函数调用的话,编译器不会插入调度代码,所以这个执行 for 循环的 goroutine 没有办法被调出,而在循环期间碰到 gc,那么就会卡在 gcwaiting 阶段,并且整个进程永远 hang 死在这个循环上。并不再对外响应。

这个其实就是我们的第一段代码卡死的原因,也是我们第二段代码没有卡死的原因,就是在gc上!

我们再看一篇文章,golang的垃圾回收(GC)机制,这篇文章很短,但每句话都很重要:

  1. 设置gcwaiting=1,这个在每一个G任务之前会检查一次这个状态,如是,则会将当前M 休眠;
  2. 如果这个M里面正在运行一个长时间的G任务,咋办呢,难道会等待这个G任务自己切换吗?这样的话可要等10ms啊,不能等!坚决不能等!
    所以会主动发出抢占标记(类似于上一篇),让当前G任务中断,再运行下一个G任务的时候,就会走到第1步

那么如果这时候运行的是没有函数调用的死循环呢,gc也发出了抢占标记,但是如果死循环没有函数调用,就没有地方被标记,无法被抢占,那就只能设置gcwaiting=1,而M没有休眠,stop the world卡住了(死锁),gcwaiting一直是1,整个程序都卡住了!

这里其实已经解释了第一份代码的现象,第二份代码为什么没有hang住相信大家也能猜到了:代码里没有触发gc!我们来手动触发一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
复制代码package main
import (
"fmt"
"log"
"net/http"
_ "net/http/pprof"
// "runtime"
"time"
)
func deadloop() {
for {
}
}
func main() {
go func() {
log.Println(http.ListenAndServe("localhost:6060", nil))
}()
go deadloop()
i := 3
for {
time.Sleep(time.Second * 1)
i--
fmt.Println("I got scheduled!")
if i == 0 {
runtime.GC()
}
}
}

会发现打印了3行之后,程序也卡死了,bingo🎉

我们来看看gcwaiting是不是等于1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
复制代码$ go build hang2.go
$ GODEBUG="schedtrace=300,scheddetail=1" ./hang2
SCHED 2443ms: gomaxprocs=4 idleprocs=3 threads=7 spinningthreads=0 idlethreads=2 runqueue=0 gcwaiting=0 nmidlelocked=0 stopwait=0 sysmonwait=0
P0: status=1 schedtick=4 syscalltick=5 m=5 runqsize=0 gfreecnt=1
P1: status=0 schedtick=14 syscalltick=0 m=-1 runqsize=0 gfreecnt=0
P2: status=0 schedtick=3 syscalltick=4 m=-1 runqsize=0 gfreecnt=0
......
SCHED 2751ms: gomaxprocs=4 idleprocs=0 threads=7 spinningthreads=0 idlethreads=2 runqueue=0 gcwaiting=1 nmidlelocked=0 stopwait=1 sysmonwait=0
P0: status=1 schedtick=4 syscalltick=5 m=5 runqsize=0 gfreecnt=1
P1: status=3 schedtick=14 syscalltick=0 m=-1 runqsize=0 gfreecnt=0
P2: status=3 schedtick=3 syscalltick=10 m=-1 runqsize=0 gfreecnt=0
P3: status=3 schedtick=1 syscalltick=26 m=0 runqsize=0 gfreecnt=0
M6: p=-1 curg=-1 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 helpgc=0 spinning=false blocked=false lockedg=-1
M5: p=0 curg=19 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 helpg

代码诚不欺我也!

参考资料

  • 如何定位 golang 进程 hang 死的 bug
  • Goroutine调度实例简要分析
  • 也谈goroutine调度器
  • golang的垃圾回收(GC)机制

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

1…917918919…956

开发者博客

9558 日志
1953 标签
RSS
© 2025 开发者博客
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%