Leo的技术分享

记录开发的点滴

DES 为 Data Encryption Standard (数据加密标准)的缩写,是一种常见的对称加密算法。有关对称加密与非对称加密的特点及其应用场景,本文就不描述了,读者可以自行 google 。本文说明如何使用 Java 和 Python 两种语言来实现 DES 的加解密。

最近碰到的应用场景是这样的。我们需要对接一个系统 S,系统 S 已经对用户的身份进行了验证,新系统 N 也需要对用户的身份进行验证。采用的身份验证方法是由旧系统 S 对用户 ID 进行加密,然后新系统 N 对加密后的用户 ID 进行解密,从而获取用户 ID 并进行身份验证。
由于旧系统 S 是用 Java 实现的,新系统 N 使用 Python 实现。也就是说,需要使用 Python 语言来对 Java DES 加密的用户 ID 进行解密。

这里贴出 Java 实现的 DES 加密的代码。

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
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;


public class Main {

public static void main(String[] args) {
String content = "zx";
String key = "20171117";

System.out.println("加密前:" + content);
byte[] encrypted = DES_CBC_Encrypt(content.getBytes(), key.getBytes());
System.out.println("加密后:" + byteToHexString(encrypted));

byte[] decrypted = DES_CBC_Decrypt(encrypted, key.getBytes());
System.out.println("解密后:" + new String(decrypted));
}

public static byte[] DES_CBC_Encrypt(byte[] content, byte[] keyBytes) {
try {
DESKeySpec keySpec = new DESKeySpec(keyBytes);
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
SecretKey key = keyFactory.generateSecret(keySpec);

Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(keySpec.getKey()));
byte[] result = cipher.doFinal(content);
return result;
} catch (Exception e) {
System.out.println("exception:" + e.toString());
}
return null;
}

private static byte[] DES_CBC_Decrypt(byte[] content, byte[] keyBytes) {
try {
DESKeySpec keySpec = new DESKeySpec(keyBytes);
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
SecretKey key = keyFactory.generateSecret(keySpec);

Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(keyBytes));
byte[] result = cipher.doFinal(content);
return result;
} catch (Exception e) {
System.out.println("exception:" + e.toString());
}
return null;
}

private static String byteToHexString(byte[] bytes) {
StringBuffer sb = new StringBuffer(bytes.length);
String sTemp;
for (int i = 0; i < bytes.length; i++) {
sTemp = Integer.toHexString(0xFF & bytes[i]);
if (sTemp.length() < 2)
sb.append(0);
sb.append(sTemp.toUpperCase());
}
return sb.toString();
}
}

Java 代码采用的 DES 加密采用 CBC 模式,采用 PKCS5Padding 的填充模式,使用的初始化向量是加密的密钥。

执行以上 Java 代码,输出:

阅读全文 »

Elastic Stack 是原 ELK Stack 在 5.0 版本加入 Beats 套件后的新称呼。Elastic Stack 在实时日志处理领域已开成开源界的第一选择。
本文讲述如何搭建 Elastic Stack 日志系统,使用的套件包括 Kibana,Elasticsearch,以及 Filebeat。搭建的环境选择阿里云 ECS 服务器,系统为 CentOS 7.4 64 位。

搭建 Kibana

Kibana 用来实现数据的可视化。Kibana 能够以图表的形式呈现数据,并且具有可扩展的用户界面,可以供我们全方位配置和管理 Elatic Stack。

搭建 Kibana 的步骤如下。

(1)添加 yum 源
在 /etc/yum.repos.d 目录创建 kibana.repo 文件,内容如下:

1
2
3
4
5
6
7
8
[kibana-5.x]
name=Kibana repository for 5.x packages
baseurl=https://artifacts.elastic.co/packages/5.x/yum
gpgcheck=1
gpgkey=https://artifacts.elastic.co/GPG-KEY-elasticsearch
enabled=1
autorefresh=1
type=rpm-md

(2)安装 Kibana
运行安装命令:

yum install kibana

Kibana 安装后的主目录为 /usr/share/kibana,日志目录为 /var/log/kibana/ 。

(3)查看系统的初始化进程

查找进程 ID 为 1 的进程:

ps -p 1

输出:

1
2
PID TTY          TIME CMD
1 ? 00:00:00 systemd

可以看到我们的系统使用 systemd 作为初始化进程。

阅读全文 »

GDB 是一个功能强大的调试器,也是 Linux 系统中默认的调试器。GDB 主要提供以下四种功能,这些功能可以方便我们定位程序的 BUG。

  • 启动程序
  • 设置断点
  • 检查程序运行状态,例如查看变量的值
  • 修改程序运行状态,例如修改变量的值

本文简单讲述 GDB 的调试方法。

我们需要调试的程序如下所示,该程序只是为了演示 GDB 的用法,并没有实际的意义。

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
#include <iostream>

using namespace std;

double f1(int a, double d)
{
int b = (a + 5) * 2;
double ret = d * b;

return ret;
}


double f2(float f)
{
int a = 10;
double d = 3.14;
double ret = f * f1(a, d);

return ret;
}


int main() {
cout << "begin to gdb test" << endl;
float f = 1.1;
double ret = f2(f);
cout << ret << endl;

return 0;
}
阅读全文 »

忙了几个月的项目终于在生产环境上上线,虽然接下来还有不少工作需要跟进,但终究不像上段时间那么忙了,因此也就能利用周末的时间,对之前优化程序性能的过程作下记录,也当是个小小的经验总结。

程序的应用场景是往 Redis 写入具有大量记录的二维表,或者从 Redis 读取具有大量记录的二维表数据。由于二维表的记录数量巨大,比如有些二维表的记录数达几十万条,因此对于程序的性能有较高的要求。

程序使用 hiredis 来写入 redis 数据,由于二维表的数据量巨大,且没有采用 pipeline 的方式每次写入一行记录而是采用一次性写入所有记录的方式,因此,需要构造一个具有较大容量的字符串缓冲区来发送二维表数据。在初始化缓冲区时,使用了memset函数来将字符串缓冲区所有位置为0:

1
memset(buff, '\0', 10240000);

上面语句的作用是将buff所指向的缓冲区初始化为0,10240000表示缓冲区的大小约为10MB。

程序运行后,发现性能很低,通过定位,发现性能的瓶颈正好是这个memset函数的使用,下面的代码可以测试memset的执行时间:

1
2
3
4
5
6
7
clock_t start, finish;
double duration;
start = clock();
memset(buff, '\0', 10240000);
finish = clock();
duration = (double)(finish - start) / CLOCKS_PER_SEC;
printf("memset run time: %f\n", duration);

发现程序执行memset时,不仅耗时而且还耗CPU,故需要对代码进行优化。通过分析,实际上自己并不需要将缓存区buff每个字节都置为0,在上面的例子中buff会被当成C语言的字符串来使用,因此,每次使用前,只需要将buff的末尾置为0就行了,例如:

1
2
3
4
// 初始化
buff[0] = '\0';
// 对buff进行赋值后,再将其末尾置为0
buff[n] = '\0';

去除了memset的使用,发现程序的性能有了大大的提高。

阅读全文 »

Supervisor 是一个用 Python 实现的进程管理工具,可以很方便地启动,关闭,重启,查看,以及监控进程,当进程由于某种原因崩溃或者被误杀后,可以自动重启并发送事件通知。Supervisor 可谓运维利器,使用 Supervisor 来管理进程,可以提高系统的高可用特性。
随着 Redis 越来越流行,越来越多的公司都使用上了 Redis,因此 Redis 的进程管理就成了很多公司都需要面临的问题,本文介绍如何使用 Supervisor 来管理 Redis 进程。

Supervisor 简介

Supervisor 包括以下四个组件。

  • supervisord
    服务端程序,主要功能是启动 supervisord 服务及其管理的子进程,记录日志,重启崩溃的子进程,等。

  • supervisorctl
    命令行客户端程序,它提供一个类似 shell 的接口,通过 UNIX 域套接字或者 TCP 套接字并使用 XML_RPC 协议与 supervisord 进程进行数据通信。它的主要功能是管理(启动,关闭,重启,查看状态)子进程。

  • Web Server
    实现在界面上管理进程,还能查看进程日志和清除日志。

  • XML-RPC 接口
    可以通过 XML_RPC 协议对 supervisord 进行远程管理,达到和 supervisorctl 以及 Web Server 一样的管理功能。

进程被 Supervisor 管理后,其运行状态的转化图如下图 1 所示:


图 1 :子进程状态转移图

阅读全文 »

文章 Redis 快照持久化学习笔记 介绍 Redis 快照持久化的功能,除了快照持久化外,Redis 还提供了 AOF(Append Only File)持久化功能。与快照持久化通过直接保存 Redis 的键值对数据不同,AOF 持久化是通过保存 Redis 执行的写命令来记录 Redis 的内存数据。

AOF 持久化的原理

理论上说,只要我们保存了所有可能修改 Redis 内存数据的命令(也就是写命令),那么根据这些保存的写命令,我们可以重新恢复 Redis 的内存状态。AOF 持久化正是利用这个原理来实现数据的持久化与数据的恢复的。
Redis AOF 持久化原理如图 1 与图 2 所示。

图1:Redis 将写命令保存到 AOF 文件中


图2:Redis 利用 AOF 文件还原内存数据

阅读全文 »

Redis 是一种内存数据库,它将数据存储在内存中,所以如果不将数据保存到硬盘中,那么一旦 Redis 进程退出,保存在内存中的数据将会丢失。为此,Redis 提供了两种不同的持久化方法来将数据存储到硬盘里面。一种方法叫做快照(snapshotting),它可以将存在于某一时刻的所有数据写入硬盘里面。另一种方法叫 AOF(append-only file),它会在执行写命令时,将被执行的写命令复制到硬盘里面。这种两持久化方法既可以同时使用,也可以单独使用,当然如果 Redis 单纯只作为缓存系统使用的话,也可以两种持久化方法都不使用。具体选择哪种持久化方法取决于用户的应用场景。
快照持久化方法是 Redis 默认开启的持久化方法,本文介绍快照持久化方法,AOF 方法将在另一篇文章中介绍。

RDB 文件

Redis 将某一时刻的快照保存成一种称为 RDB 格式的文件中。RDB 文件是一个经过压缩的二进制文件,通过该文件,Redis 可以将内存中的数据恢复成某一时刻的状态。Redis 与 RDB 文件的关系可以通过下图 1 和图 2 来表示。


图 1:Redis 将内存的数据保存为 RDB 文件


图2:Redis 用 RDB 文件还原内存数据

阅读全文 »

使用 Redis 这么久,发现自己还没写过一篇有关 Redis 数据结构的文章,从构造 Redis 整个知识体系来说,显示是不完整的。故这篇文章再次让自己回归到 Redis 的五种基本数据结构,除了描述这些数据结构的特点,也介绍如何使用 Redis 命令来操作这些数据结构。
Redis 支持的数据结构包括:

  • 字符串
  • 列表
  • 集合
  • 有序集合
  • 哈希表

需要指出的是,这些数据结构不是指 Redis 内部实现所采用的数据结构,而是指 Redis 提供给用户使用的数据结构。例如,对于集合这种数据结构,其内部可以使用整数集合(intset)来实现,也可以使用哈希表(hashtable)来实现。一般情况下,对于用户来说,我们并不需要关心集合内部的实现,只需要知道集合这种数据结构的应用场景以及集合为我们提供的操作命令就可以了。
接下来,本文依次对 Redis 这些数据结构进行介绍。

字符串

字符串是 Redis 最简单的数据结构。熟悉 Memcached 的朋友对于字符串这种数据结构一定感到很亲切,因为 Memcached 也提供了这种数据结构。跟 Memcached 不同的是,Redis 除了提供字符串这种数据结构外,还提供了其他的数据结构,而 Memcached 则只提供字符串数据结构而已。
Redis 字符串使用 SDS 来实现。SDS 是 Redis 内部使用的一种数据结构,用作 Redis 字符串的表示。由于 Redis 的键都是字符串,这里我们说字符串是指 Redis 值的数据结构类型,例如客户端执行下面命令:

1
SET website leehao.me

那么,Redis 将创建一个新的键值对,其中,

  • 键是一个字符串,字符串内部使用 SDS 来实现,用来保存“website”
  • 值也是一个字符串,字符串内部也是使用 SDS 来实现,用来保存“leehao.me”

下文我们所说的数据结构也是针对键值对的值的数据结构类型来说的,且值的数据结构是什么,我们就称之什么类型的键,例如字符串键,即键值对的值的数据结构为字符串。
使用命令 SET 可以设置字符串的值,如果需要取出字符串,可以使用命令 GET。

阅读全文 »

Pipelining(流水线)允许 Redis 客户端一次向 Redis 发送多个命令,Redis 在接收到这些命令后,按顺序进行处理,然后将请求的处理结果一次性返回给客户端。流水线可以减少客户端与 Redis 之间的网络通信次数来提升 Redis 客户端在发送多个命令时的性能,可谓提升客户端性能的一个利器。
我们熟悉的 Python 版本的 Redis 客户端 redis-py 提供了 StrictPipeline 对象来实现流水线,使用起来很是方便,具体用法可以参考文章《Redis 事务学习笔记》。作为 C/C++ 版本的 Redis 客户端,hiredis 实现流水线稍显有点复杂,不过通过使用 hiredis 来实现流水线却可以更深刻了解流水线的内部实现原理。

Hiredis 提供redisCommand()函数来向 Redis 服务端发送命令,redisCommand()函数的原型如下:

1
void *redisCommand(redisContext *c, const char *format, ...);

redisCommand()执行后,返回一个redisReply *指针,指向redisReply结构体,该结构体包含了返回的结果信息。
redisCommand()函数是阻塞的(是指使用阻塞版的redisContext对象,下文我们同样有这个假定),每调用一次,都会等待 Redis 服务端的返回,然后再继续执行程序下面的逻辑。
redisCommand()函数的使用示例如下所示,完整的代码和编译可以参考文章《Redis C 语言客户端 hiredis 的使用》

1
2
3
4
5
6
7
redisReply *reply;
reply = redisCommand(conn, "SET %s %s", "foo", "bar");
freeReplyObject(reply);

reply = redisCommand(conn, "GET %s", "foo");
printf("%s\n", reply->str);
freeReplyObject(reply);
阅读全文 »

最近使用 Python 来发送 SOAP 请求以测试 Web Service 的性能,由于 SOAP 是基于 XML 的,故免不了需要使用 Python 来处理 XML 数据。在对比了几种方案后,最后选定使用 xml.etree.ElementTree 模块来实现。
这篇文章记录了使用 xml.etree.ElementTree 模块常用的几个操作,也算是总结一下,免得以后忘记了。

概述

对比其他 Python 处理 XML 的方案,xml.etree.ElementTree 模块(下文我们以 ET 来表示)相对来说比较简单,接口也较友好。
官方文档 里面对 ET 模块进行了较为详细的描述,总的来说,ET 模块可以归纳为三个部分:ElementTree类,Element类以及一些操作 XML 的函数。
XML 可以看成是一种树状结构,ET 使用ElementTree类来表示整个 XML 文档,使用Element类来表示 XML 的一个结点。对整 XML 文档的操作一般是对ElementTree对象进行,而对 XML 结点的操作一般是对Element对象进行。

解析 XML 文件

ET 模块支持从一个 XML 文件构造ElementTree对象,例如我们的 XML 文件example.xml内容如下(下文会继续使用这个 XML 文档):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0" encoding="utf-8"?>
<data>
<country name="Liechtenstein">
<rank>1</rank>
<year>2008</year>
<gdppc>141100</gdppc>
<neighbor name="Austria" direction="E"/>
<neighbor name="Switzerland" direction="W"/>
</country>
<country name="Singapore">
<rank>4</rank>
<year>2011</year>
<gdppc>59900</gdppc>
<neighbor name="Malaysia" direction="N"/>
</country>
</data>
阅读全文 »
0%