消息系统顺序问题小计

今天就系统中的问题简单调研了一下消息系统顺序执行的解决方案。

环境

使用的消息系统是RabbitMQ。

问题

有两个操作操作1和操作2要放入消息系统,操作2要在操作1成功之后执行,否则操作2会异常。

这两个操作是前端一次用户操作里的两次请求,网上有很多说法如 将两条消息放入同一个队列、使用消息序列号SN等,但在不修改前端、两次请求到达后端的顺序不确定的情况下,不好实现。

解决

1、消费者收到操作2消息后先确认操作1的执行结果,符合条件再执行,否则将消息退回消息队列,可以延迟退回以防下次派发执行时操作1还未执行完成,同时未避免操作2可能的死循环,还可以设置重试次数,超过次数则放入死信队列或存储下来人工处理。

2、另外一种解决是先将操作2的内容存储起来,定时去尝试执行(可以临时解决问题但感觉不够通用,还需要专门去存储临时数据)。

Java List 排序(附中文排序)

1、Collections.sort的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 相关源码
package java.util;

...

public class Collections {
// 方法1
public static <T extends Comparable<? super T>> void sort(List<T> list) {
list.sort(null);
}
// 方法2
public static <T> void sort(List<T> list, Comparator<? super T> c) {
list.sort(c);
}
}

Collections.sort()方法可以对List的具体实现进行排序。

1
2
3
4
5
6
// 一个简单的例子

List<String> list = new ArrayList<>();
// list add data
Collections.sort(list); // 正序
Collections.sort(list, Collections.reverseOrder()); // 倒序

String实现了Comparable接口,因此我们可以调用 方法1 对由String组成的ArrayList进行排序。当然Java中还有很多实现Comparable接口的类,像Integer、Boolean、BigDecimal、File和Date等。

2、使用Comparable排序

1
2
3
4
5
6
7
// 相关源码
package java.lang;
import java.util.*;

public interface Comparable<T> {
public int compareTo(T o);
}

实现 Comparable接口的类需要重写compareTo()方法,该方法接收一个同类型的对象,要实现当前对象和接收到的对象的比较。compareTo()方法返回int类型的比较结果:

  • 正值表示当前对象比传递给compareTo()的对象大
  • 负值表示当前对象比传递给compareTo()的对象小
  • 零表示两个对象相等

待排序的对象X实现了Comparable接口,实现了compareTo方法后就可以使用 方法1 Collections.sort(list)进行排序了。

3、使用Comparator排序

1
2
3
4
5
6
7
8
9
10
11
// 相关源码
package java.util;

...

public interface Comparator<T> {
int compare(T o1, T o2);
}
...


Comparator接口提供了一个叫compare()的方法,要注意的是compare()接受两个相同类型的对象进行比较。
实现该接口实现该方法后,即可使用 方法2 Collections.sort(list, comparator)进行排序了。

最后附上一个字符串简体中文的排序。

1
2
3
List<String> names = new ArrayList<>();
// add data
Collections.sort(names, Collator.getInstance(Locale.SIMPLIFIED_CHINESE));

Java多线程-synchronized的使用

Java的同步主要是通过synchronized和Lock来实现。
本文主要说的是Java关键字synchronized的使用。

相关概念

  • 监视器
    在Java中每个对象都有一个锁标记(monitor),也称监视器,多线程同时访问某个对象时,线程只有获取了该对象的锁才能访问。

在Java中,可以使用关键字synchronized来标记一个方法或者代码块。

注意

synchronized可修饰方法、代码块,但不可去修饰属性、构造方法。

使用(同步方法与同步代码块)

方法同步

1
2
3
4
// 方法同步
public synchronized void test() {
// do something ...
}

synchronized用在方法上,则线程调用此方法时会获取该实例的对象锁,方法未结束之前其他线程只能等待。

同时其他线程也无法访问该实例的其他synchronized方法,因为一个对象只有一把锁,当一个线程获取了该对象的锁之后,其他线程就无法获取该对象锁。

同理,当一个线程访问该实例的synchronized方法时,其他线程能访问该对象的非synchronized方法,因为没使用到对象锁。

上面的前提是各线程调用的是同一对象实例,否则synchronized无效。

但若该synchronized方法为类方法,即方法修饰符为static,那么使用synchronized调用此方法的线程会获取该类的锁,只要该线程没有退出当前方法,其他线程也无法访问该方法。又若一个线程访问的是该对象的普通(非static)synchronized方法,另一个线程访问的是该对象(该类)的static synchronized方法,也不会发生互斥现象。因为它们一个获取的是对象锁,另一个是类锁,不冲突。

代码块同步

1
2
3
4
5
6
7
8
public void test2() {
...
// 代码块同步
synchronized(object) {
// do something ...
}
...
}

而用在代码块上,当线程运行到该代码块时,就会获得object对象的对象锁,如果多线程使用同一个object对象,那么此时就会同步,使得其他线程无法同时访问该代码块。使用synchronized代码块只对需要的部分进行同步,效率也更高。
当object为this时,代表获取的是当前实例的锁。即

1
2
3
4
5
6
7
8
9
10
11
12
public void test3() {
...
synchronized(this) {
// do something ...
}
...
}

// 此时,其效果等同于
public synchronized void test4() {
// do something ...
}

最后,使用synchronized出现异常时,JVM会自动释放当前线程占用的锁,因此不会由于异常导致出现死锁现象。

替换源解决Python pip安装慢的问题

问题

python官方源pip安装速度慢

解决

使用阿里云pip源替换官方源

新建编辑配置文件

1
vi ~/.config/pip/pip.conf

进行如下配置

1
2
3
4
5
[global]
timeout = 60
trusted-host = mirrors.aliyun.com
index-url = http://mirrors.aliyun.com/pypi/simple

Python项目依赖管理

使用requirements.txt管理Python项目依赖

1、生成requirements.txt文件

1
pip freeze > requirements.txt

执行后requirements.txt文件会有类似下面的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
adium-theme-ubuntu==0.3.4
click==6.6
dominate==2.2.1
Flask==0.11.1
Flask-Bootstrap==3.3.7.0
Flask-SQLAlchemy==2.1
itsdangerous==0.24
Jinja2==2.8
MarkupSafe==0.23
MySQL-python==1.2.5
pygobject==3.20.0
SQLAlchemy==1.1.2
unity-lens-photos==1.0
visitor==0.1.3
Werkzeug==0.11.11

2、安装requirements.txt依赖

1
pip install -r requirements.txt

执行后会安装指定依赖

Ubuntu终端快捷键

系统

Ubuntu 16.04
终端使用Terminator代替默认终端。因为它可在同一个窗口启动多个终端,方便使用。

常用快捷键

=======
常用快捷键

快捷键 描述
Ctrl+Alt+T 启动终端
F11 全屏切换
Ctrl+Shift+C 复制
Ctrl+Shift+V 粘贴
Ctrl+Shift+E 新建标签页
Ctrl+Shift+W 关闭标签页
Ctrl+Shift+I 新建终端窗口
Ctrl+Shift+Q 关闭终端窗口
Alt+方向键 切换标签,Ctrl+Shift+P、Ctrl+Shift+N 也可进行切换
Ctrl+Shift++ 放大字体
Ctrl+- 缩小字体
上、下方向键 显示上一条、下一条命令,Ctrl+P、Ctrl+N也可切换
Ctrl+R 搜索历史命令
Ctrl+O、Ctrl+J、Ctrl+M 回车 同enter键
Ctrl+A 光标移至行头
Ctrl+E 光标移至行尾
Ctrl+B 光标向前移动一位,同左方向键
Ctrl+F 光标向后移动一位,同右方向键
Ctrl+左方向键 光标移至单词词头
Ctrl+右方向键 光标移至单词词尾
Ctrl+T 将光标位置的字符和前一个字符进行位置交换
Ctrl+U 剪切光标前一个位置到行头的所有内容
Ctrl+K 剪切光标位置到行尾的所有内容
Ctrl+Y 粘贴Ctrl+U或Ctrl+K剪切的内容
Ctrl+H、Ctrl+* 删除光标前一个字符,同Backspace
Ctrl+D 删除光标位置一个字符,同Delete
Ctrl+W 删除光标位置的前一个单词,同Alt+Backspace)
Ctrl+& 恢复操作
Ctrl+L 清除当前屏幕内容,同clear命令,不过Ctrl+L当前输入的命令会保留

vim中文乱码

问题

vim打开文件出现中文乱码。

解决

在当前用户根目录下找vim配置文件。开始找到了.viminfo。

1
$ vi ~/.viminfo

发现如下配置

1
*encoding=latin1

尝试直接编辑及使用管理员权限编辑.viminfo文件,想把latin1修改为utf-8,发现保存退出后修改并没有生效。

后来,在当前用户根目录下创建或更新.vimrc文件,进行如下配置

1
2
3
set fileencodings=utf-8,ucs-bom,gb18030,gbk,gb2312,cp936
set termencoding=utf-8
set encoding=utf-8

中文乱码问题解决。同时.viminfo文件中latin1也自动变为utf-8了。

java日志小记

常用日志框架

slf4j,logback,commons-logging,log4j,jdk自带logging等。

一般要记录的日志

重要的业务逻辑、程序异常、记录类(如方法执行时间)及安全审计日志等。

注意

  • 不要使用System.out或System.err记录日志,不方便管理
  • 程序异常若为用户异常(如用户密码错误)没记录必要,多了影响真正异常的阅读;
  • 日志记录信息要完整,必要的业务数据要记录在日志中;
  • 日志级别要合理,要进行合理分类输出;
  • 新上线服务器做好日志分析,以确认新功能是否正常;
  • 异常不要记录后又重新向外抛出;
  • 对于分布式服务,日志要能定位到具体服务器,同时可开启其中一台服务器日志DEBUG级别好进行观察;
  • 做好日志监控报警,比用户先发现问题;
  • 规划好日志切分、删除工作。

个人常用日志框架

slf4j+logback
logback日志等级:TRACE<DEBUG<INFO<WARN<ERROR
配置文件一般classpath下建logback.xml即可,详见官方文档。
logback中3个重要的概念(其他框架也差不多):Logger,Appender,Layout

  • Logger:日志记录器,把它关联到应用对应的context上后,主要用于存放日志对象,定义日志类型、级别。
  • Appender: 指定日志输出的目的地,目的地可以是控制台,文件,或者数据库等。
  • Layout:负责把事件转换成字符串,格式化日志信息的输出

基础配置文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<configuration>

<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- encoders are assigned the type
ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>

<root level="debug">
<appender-ref ref="STDOUT" />
</root>
</configuration>

Logback官方文档

JPA与Hibernate

什么是JPA

说JPA先得说下对象关系映射ORM(Object-Relation Mapping),ORM是随面向对象软件开发方法的发展而产生的。面向对象开发方法是当今企业级开发的主流方法,关系型数据库是企业级应用主流存储数据的系统。对象和关系数据是业务实体的两种表现形式,业务实体在内存中表现为对象,在数据库中表现为关系数据。

在没有ORM框架之前,开发者往往要编写大量的、重复的操作数据库的代码。ORM框架则将Java对象映射到关系数据库,能直接持久化Java对象,使开发者能把更多的精力放在了业务上。目前流行的ORM框架有Hibernate、iBatis等。

JPA则是官方提出的持久化标准规范,使用JPA规范可避免使用某些ORM框架而受制于该ORM标准。

JPA(JAVA EE7的描述)包括了:

  • The Java Persistence API
  • The query language
  • The Java Persistence Criteria API
  • Object/relational mapping metadata

JPA与Hibernate

JPA只是ORM的规范,没有具体实现,若要使用需配合JPA实现框架。Hibernate封装了JDBC API, 可实现Java对象持久化。

同时Hibernate自3.2版本后开始支持JPA。因此它也是一种JPA实现。

以上参考了相关文档及百科。

Ubuntu用户管理

创建用户 - adduser

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
root@iZ28rwmhep0Z:~# adduser xyz
Adding user `xyz' ...
Adding new group `xyz' (1000) ...
Adding new user `xyz' (1000) with group `xyz' ...
Creating home directory `/home/xyz' ...
Copying files from `/etc/skel' ...
Enter new UNIX password:
Retype new UNIX password:
passwd: password updated successfully
Changing the user information for xyz
Enter the new value, or press ENTER for the default
Full Name []:
Room Number []:
Work Phone []:
Home Phone []:
Other []:
Is the information correct? [Y/n] y

查看帮助

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
xyz@iZ28rwmhep0Z:~$ adduser -help
adduser [--home DIR] [--shell SHELL] [--no-create-home] [--uid ID]
[--firstuid ID] [--lastuid ID] [--gecos GECOS] [--ingroup GROUP | --gid ID]
[--disabled-password] [--disabled-login] [--encrypt-home] USER
Add a normal user
adduser --system [--home DIR] [--shell SHELL] [--no-create-home] [--uid ID]
[--gecos GECOS] [--group | --ingroup GROUP | --gid ID] [--disabled-password]
[--disabled-login] USER
Add a system user
adduser --group [--gid ID] GROUP
addgroup [--gid ID] GROUP
Add a user group
addgroup --system [--gid ID] GROUP
Add a system group
adduser USER GROUP
Add an existing user to an existing group
general options:
--quiet | -q don't give process information to stdout
--force-badname allow usernames which do not match the
NAME_REGEX[_SYSTEM] configuration variable
--help | -h usage message
--version | -v version number and copyright
--conf | -c FILE use FILE as configuration file

删除用户 - deluser

deluser -r xx 加-r可删除该用户home目录,直接删除不删目录

新增用户组 - addgroup

-help查看详细参数

删除用户组 - delgroup

-help查看详细参数
su命令可切换到root命令, su otherUser 则切换至其他用户,对于有sudo权限的用户可直接使用sudo执行root身份的命令

其他

Linux用户及用户组都有对应的id,UID为用户id,GID为用户组id。

使用id可查看uid,gid,groups信息

1
2
xyz@iZ28rwmhep0Z:~$ id
uid=1000(xyz) gid=1000(xyz) groups=1000(xyz)

使用groups可查看当前用户所在用户组
group信息在/etc/group下

/etc/passwd 可查看账户信息

1
2
3
4
xyz@iZ28rwmhep0Z:~$ cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
列数 含义 说明
1 用户名
2 密码 原密码位置,现密码在/etc/shadow,这里为字母x
3 UID 用户ID
4 GID 用户组ID
5 现不用
6 用户home目录 登陆后的位置
7 登录Shell 用户登录所用的Shell