王某某的笔记

记录我的编程之路

ElasticSearch集群的高可用和自平衡方案会在节点挂掉(重启)后自动在别的结点上复制该结点的分片,这将导致了大量的IO和网络开销。

如果离开的节点重新加入集群,elasticsearch为了对数据分片(shard)进行再平衡,会为重新加入的节点再次分配数据分片(Shard), 当一台es因为压力过大而挂掉以后,其他的es服务会备份本应那台es保存的数据,造成更大压力,于是整个集群会发生雪崩。

生产环境下建议关闭自动平衡。

数据分片与自平衡

一:关闭自动分片,即使新建index也无法分配数据分片

1
2
3
4
5
curl -XPUT http://192.168.1.213:9200/_cluster/settings -d '{
"transient" : {
"cluster.routing.allocation.enable" : "none"
}
}'

二:关闭自动平衡,只在增减ES节点时不自动平衡数据分片

1
2
3
4
5
curl -XPUT http://192.168.1.213:9200/_cluster/settings?pretty -d '{
"transient" : {
"cluster.routing.rebalance.enable" : "none"
}
}'

设置完以后查看设置是否添加成功:

1
curl http://192.168.1.213:9200/_cluster/settings?pretty

重新启用自动分片

1
2
3
4
5
curl -XPUT http://192.168.1.213:9200/_cluster/settings -d '{
"transient" : {
"cluster.routing.allocation.enable" : "all"
}
}

延迟副本的重新分配

1
2
3
4
5
6
PUT /_all/_settings
{
"settings": {
"index.unassigned.node_left.delayed_timeout": "5m"
}
}

未分配节点重新分配延迟到5分钟之后

下面是修改 elasticsearch.yml 文件

1
gateway.recover_after_nodes: 8

这将防止Elasticsearch立即开始数据恢复,直到集群中至少有八个(数据节点或主节点)节点存在。

1
2
3
gateway.expected_nodes: 10 

gateway.recover_after_time: 5m

集群开始数据恢复等到5分钟后或者10个节点加入,以先到者为准。

参考:
https://www.elastic.co/guide/en/elasticsearch/reference/2.4/modules-gateway.html

脑裂问题

对某一个实例进行重启后,很有可能会导致该实例无法找到master而将自己推举为master的情况出现,为防止这种情况,需要调整 elasticsearch.yml 中的内容

1
discovery.zen.minimum_master_nodes: 2

这个配置就是告诉Elasticsearch除非有足够可用的master候选节点,否则就不选举master,只有有足够可用的master候选节点才进行选举。
该设置应该始终被配置为有主节点资格的点数/2 + 1,例如:
有10个符合规则的节点数,则配置为6.
有3个则配置为2.


关于设置的有效性

persistent 重启后设置也会存在
transient 整个集群重启后会消失的设置

1
2
3
4
5
6
PUT /_cluster/settings
{
"persistent" : {
"discovery.zen.minimum_master_nodes" : 2
}
}



一般设置下面两个就可以了

1
2
3
4
5
6
7
# 通过配置大多数节点(节点总数/ 2 + 1)来防止脑裂
#
discovery.zen.minimum_master_nodes: 2

# 在一个完整的集群重新启动到N个节点开始之前,阻止初始恢复
#
gateway.recover_after_nodes: 3

mysqlbinlog

使用mysqlbinlog 命令查看

常用选项:

  • –start-position=953 起始pos点
  • –stop-position=1437 结束pos点
  • –start-datetime=”2013-11-29 13:18:54” 起始时间点
  • –stop-datetime=”2013-11-29 13:21:53” 结束时间点

如:

进入到binlog目录执行,指定具体的binlog文件名

1
2
3
4
5
6
# 从指定的时间开始查
$ mysqlbinlog mysql-bin.001575 --start-datetime="2018-09-25 11:25:54"

# 从指定位置开始查
$ mysqlbinlog mysql-bin.001575 --start-position=16839697

默认只能查看到base-64编码的信息
如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# at 22452867
#180925 11:44:02 server id 8 end_log_pos 22452944 CRC32 0xc23d3641 Table_map: `base`.`by_renew_history` mapped to number 182
# at 22452944
#180925 11:44:02 server id 8 end_log_pos 22453106 CRC32 0x7a809e4e Update_rows: table id 182 flags: STMT_END_F

BINLOG '
Aq+pWxMIAAAATQAAANCaVgEAALYAAAAAAAEABGJhc2UAEGJ5X3JlbmV3X2hpc3RvcnkACwMPAw8S
ARIDEgMDB5YAWgAAAAD+B0E2PcI=
Aq+pWx8IAAAAogAAAHKbVgEAALYAAAAAAAEAAgAL/////wD+igAAAA4yMDE4MDcyNTE2MzcyNugD
AAAQMTExMTExMTExMDExMTExNJmgcwlbAZmgcwlbAwAAAJmgcwlaAP6KAAAADjIwMTgwNzI1MTYz
NzI26AMAABAxMTExMTExMTEwMTExMTEzmaBzCVsBmaBzCVsDAAAAmaBzCVpOnoB6
'/*!*/;
# at 22453106
#180925 11:44:02 server id 8 end_log_pos 22453137 CRC32 0x3787881e Xid = 8121164
COMMIT/*!*/;
SET @@SESSION.GTID_NEXT= 'AUTOMATIC' /* added by mysqlbinlog */ /*!*/;
DELIMITER ;
# End of log file
/*!50003 SET COMPLETION_TYPE=@OLD_COMPLETION_TYPE*/;
/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=0*/;

此时需要 –base64-output=value 选项

此选项确定何时应使用BINLOG语句将事件编码为base-64字符串。该选项具有以下允许值(不区分大小写):

  • AUTO (“自动”)或UNSPEC(“未指定”)在必要时自动显示BINLOG语句(即,用于格式描述事件和行事件)。如果没有给出–base64-output选项,则效果与–base64-output = AUTO相同。

  • NEVER 不显示BINLOG语句

  • DECODE-ROWS 通过配合–verbose选项将行事件解码并显示为注释的SQL语句。


使用参数–verbose(或-v),将生成带注释的语句,如果使用两次这个参数(如-v -v),会生成字段的类型、长度、是否为null等属性信息。

如:

1
$ mysqlbinlog -v --base64-output=DECODE-ROWS mysql-bin.001576 --start-datetime="2018-09-25 13:41:30"

输出:

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
# at 1664232
#180925 13:42:02 server id 8 end_log_pos 1664309 CRC32 0x28bbcc7e Table_map: `base`.`by_renew_history` mapped to number 182
# at 1664309
#180925 13:42:02 server id 8 end_log_pos 1664471 CRC32 0xca13320e Update_rows: table id 182 flags: STMT_END_F
### UPDATE `base`.`by_renew_history`
### WHERE
### @1=138
### @2='20180725163726'
### @3=1000
### @4='1111111110111113'
### @5='2018-07-25 16:37:27'
### @6=1
### @7='2018-07-25 16:37:27'
### @8=3
### @9='2018-07-25 16:37:26'
### @10=NULL
### @11=NULL
### SET
### @1=138
### @2='20180725163726'
### @3=1000
### @4='1111111110111114'
### @5='2018-07-25 16:37:27'
### @6=1
### @7='2018-07-25 16:37:27'
### @8=3
### @9='2018-07-25 16:37:26'
### @10=NULL
### @11=NULL
# at 1664471
#180925 13:42:02 server id 8 end_log_pos 1664502 CRC32 0xb64f6d4f Xid = 8266920
COMMIT/*!*/;
SET @@SESSION.GTID_NEXT= 'AUTOMATIC' /* added by mysqlbinlog */ /*!*/;
DELIMITER ;
# End of log file
/*!50003 SET COMPLETION_TYPE=@OLD_COMPLETION_TYPE*/;
/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=0*/;
1
mysqlbinlog -v -v --base64-output=DECODE-ROWS mysql-bin.001576 --start-datetime="2018-09-25 13:41:30"

输出:

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
# at 1899587
#180925 13:44:02 server id 8 end_log_pos 1899664 CRC32 0x7fff0312 Table_map: `base`.`by_renew_history` mapped to number 182
# at 1899664
#180925 13:44:02 server id 8 end_log_pos 1899826 CRC32 0x5cdb92c1 Update_rows: table id 182 flags: STMT_END_F
### UPDATE `base`.`by_renew_history`
### WHERE
### @1=138 /* INT meta=0 nullable=0 is_null=0 */
### @2='20180725163726' /* VARSTRING(150) meta=150 nullable=1 is_null=0 */
### @3=1000 /* INT meta=0 nullable=1 is_null=0 */
### @4='1111111110111114' /* VARSTRING(90) meta=90 nullable=1 is_null=0 */
### @5='2018-07-25 16:37:27' /* DATETIME(0) meta=0 nullable=1 is_null=0 */
### @6=1 /* TINYINT meta=0 nullable=1 is_null=0 */
### @7='2018-07-25 16:37:27' /* DATETIME(0) meta=0 nullable=1 is_null=0 */
### @8=3 /* INT meta=0 nullable=1 is_null=0 */
### @9='2018-07-25 16:37:26' /* DATETIME(0) meta=0 nullable=1 is_null=0 */
### @10=NULL /* INT meta=0 nullable=1 is_null=1 */
### @11=NULL /* INT meta=0 nullable=1 is_null=1 */
### SET
### @1=138 /* INT meta=0 nullable=0 is_null=0 */
### @2='20180725163726' /* VARSTRING(150) meta=150 nullable=1 is_null=0 */
### @3=1000 /* INT meta=0 nullable=1 is_null=0 */
### @4='1111111110111113' /* VARSTRING(90) meta=90 nullable=1 is_null=0 */
### @5='2018-07-25 16:37:27' /* DATETIME(0) meta=0 nullable=1 is_null=0 */
### @6=1 /* TINYINT meta=0 nullable=1 is_null=0 */
### @7='2018-07-25 16:37:27' /* DATETIME(0) meta=0 nullable=1 is_null=0 */
### @8=3 /* INT meta=0 nullable=1 is_null=0 */
### @9='2018-07-25 16:37:26' /* DATETIME(0) meta=0 nullable=1 is_null=0 */
### @10=NULL /* INT meta=0 nullable=1 is_null=1 */
### @11=NULL /* INT meta=0 nullable=1 is_null=1 */
# at 1899826
#180925 13:44:02 server id 8 end_log_pos 1899857 CRC32 0xc1b4c0c7 Xid = 8297691
COMMIT/*!*/;
SET @@SESSION.GTID_NEXT= 'AUTOMATIC' /* added by mysqlbinlog */ /*!*/;
DELIMITER ;
# End of log file
/*!50003 SET COMPLETION_TYPE=@OLD_COMPLETION_TYPE*/;
/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=0*/;

用背景透明的png图片

1722585581717.png

不规则窗体

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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
package com.wwh.excel.tools.swing;

import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.datatransfer.DataFlavor;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetAdapter;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.io.File;
import java.util.List;

import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;

import com.wwh.excel.tools.worker.Csv2Xls;

public class ImageFrame extends JFrame {

private static final long serialVersionUID = 1L;
private ImageIcon icon;
private Point origin = new Point();; // 用于移动窗体

// 小弹出框
private PopupDialog popupDialog;

public ImageFrame(ImageIcon icon2) {
this.icon = icon2;
JLabel imageLabel = new JLabel() {
private static final long serialVersionUID = 1L;

@Override
public void paint(Graphics g) {
super.paint(g);
icon.paintIcon(this, g, 0, 0);
}
};
this.add(imageLabel);

this.setUndecorated(true); // 关键语句1 不启用窗体装饰

this.setSize(icon.getIconWidth(), icon.getIconHeight()); // 设置窗口大小

// 这个需要包的支持
// AWTUtilities.setWindowOpaque(this, false);

// JDK 1.7 版本以上,使用 this.setBackground(new Color(0, 0, 0, 0));
this.setBackground(new Color(0, 0, 0, 0)); // 关键句2

// 设置透明度
this.setOpacity(0.9f);

this.setLocationRelativeTo(null); // 设置窗口居中
// setVisible(true);

this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

// 鼠标事件监听
// 由于取消了默认的窗体结构,所以我们要手动设置一下移动窗体的方法
this.addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent e) {
origin.x = e.getX();
origin.y = e.getY();
}

// 窗体上单击鼠标右键关闭程序
public void mouseClicked(MouseEvent e) {

if (e.getButton() == MouseEvent.BUTTON3) {
System.exit(0);
} else {
if (popupDialog != null) {
popupDialog.showDelayHide();
}
}
}

});

this.addMouseMotionListener(new MouseMotionAdapter() {
public void mouseDragged(MouseEvent e) {
Point p = getLocation();
setLocation(p.x + e.getX() - origin.x, p.y + e.getY() - origin.y);
}
});
drag(imageLabel);

// 设置窗口图标
Image img2 = Toolkit.getDefaultToolkit().getImage(ImageFrame.class.getResource("/icon.png"));
setIconImage(img2);
}

// 定义的拖拽方法
public void drag(Component c) {
// c 表示要接受拖拽的控件
new DropTarget(c, DnDConstants.ACTION_COPY_OR_MOVE, new DropTargetAdapter() {
@Override
public void drop(DropTargetDropEvent dtde)// 重写适配器的drop方法
{
try {
if (dtde.isDataFlavorSupported(DataFlavor.javaFileListFlavor))// 如果拖入的文件格式受支持
{
dtde.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);// 接收拖拽来的数据

@SuppressWarnings("unchecked")
List<File> list = (List<File>) (dtde.getTransferable()
.getTransferData(DataFlavor.javaFileListFlavor));

if (list.size() > 1) {
JOptionPane.showMessageDialog(null, "一次拖一个文件,靓仔");
return;
}

Csv2Xls.convert(list.get(0));

popupDialog.showDelayHide();

dtde.dropComplete(true);// 指示拖拽操作已完成

} else {
dtde.rejectDrop();// 否则拒绝拖拽来的数据
}
} catch (Exception e) {
e.printStackTrace();
JOptionPane.showMessageDialog(null, "出错了,靓仔\n" + e.getMessage());
}
}
});
}

public PopupDialog getPopupDialog() {
return popupDialog;
}

public void setPopupDialog(PopupDialog popupDialog) {
this.popupDialog = popupDialog;
}

}

不规则弹出框

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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
package com.wwh.excel.tools.swing;

import java.awt.Color;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

import javax.swing.ImageIcon;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.Timer;

public class PopupDialog extends JDialog {

private static final long serialVersionUID = 1L;

private ImageIcon icon;

private Frame owner;

private Timer timer;

private void initTimer(int delayTime) {
// 定时消失
ActionListener taskPerformer = new ActionListener() {
public void actionPerformed(ActionEvent evt) {
setVisible(false);
}
};
timer = new Timer(delayTime, taskPerformer);
timer.setRepeats(false);
}

/**
* Create the dialog.
*/
public PopupDialog(Frame owner, ImageIcon icon2, int delayTime) {
super(owner);
this.icon = icon2;
this.owner = owner;

initTimer(delayTime);

JLabel imageLabel = new JLabel() {
private static final long serialVersionUID = 1L;

@Override
public void paint(Graphics g) {
super.paint(g);
icon.paintIcon(this, g, 0, 0);
}
};

this.add(imageLabel);

this.setUndecorated(true); // 关键语句1 不启用窗体装饰

this.setSize(icon.getIconWidth(), icon.getIconHeight()); // 设置窗口大小

// 这个需要包的支持
// AWTUtilities.setWindowOpaque(this, false);

// JDK 1.7 版本以上,使用 this.setBackground(new Color(0, 0, 0, 0));
this.setBackground(new Color(0, 0, 0, 0)); // 关键句2

this.setLocationRelativeTo(owner);

this.setDefaultCloseOperation(HIDE_ON_CLOSE);

this.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {

PopupDialog.this.setVisible(false);

}
});
}

/**
* 先显示再延期隐藏
*/
public void showDelayHide() {
this.setLocationRelativeTo(owner);
setVisible(true);
timer.start();
}

@Override
public void setVisible(boolean b) {
super.setVisible(b);
if (!b) {
timer.stop();
}
}

/**
* Launch the application.
*/
public static void main(String[] args) {
try {
ImageIcon icon = new ImageIcon(PopupDialog.class.getResource("/images/success.png"));
PopupDialog dialog = new PopupDialog(null, icon, 10000);
dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
// dialog.setVisible(true);
dialog.showDelayHide();

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

}

先找到占用端口的进程号

1
2
3
C:\Users\Administrator>netstat -ano  | grep 8080
TCP 0.0.0.0:8080 0.0.0.0:0 LISTENING 10288
TCP [::]:8080 [::]:0 LISTENING 10288

grep 命令原生是没有的,通过写批处理命令的方式支持的,可以用findstr命令

1
>netstat -ano  | findstr 8080

杀进程

1
2
3
C:\Users\Administrator>taskkill -PID 10288
错误: 无法终止 PID 为 10288 的进程。
原因: 只能强行终止这个进程(带 /F 选项)。

强制杀进程

1
2
C:\Users\Administrator>taskkill /F -PID 10288
成功: 已终止 PID 为 10288 的进程。

依赖本地jar包

有的jar包,在maven中心库里面没有,我们一般是放到项目的lib目录下
如:这里的xxxx.jar

1
2
3
4
5
6
7
8
9
10
11
12
13
├─lib
│ └─xxxx.jar
├─src
│ ├─main
│ │ ├─java
│ │ │ └─com
│ │ │ └─xxx
│ │ └─resources
│ │ └─xxx
│ └─test
│ ├─java
│ └─resources
└─target

此时在pom.xml里面增加依赖引入该jar
例如:

1
2
3
4
5
6
7
8
9
<dependencies>
<dependency>
<groupId>org.xxx</groupId>
<artifactId>xxx</artifactId>
<version>1.0</version>
<scope>system</scope>
<systemPath>${basedir}/lib/xxxxx.jar</systemPath>
</dependency>
</dependencies>

要使用maven插件提供的goal,首先得知道这些插件提供了哪些goal以及他们的用法。

maven的核心插件之一 — help插件(Maven Help Plugin)可以用于查看插件提供了哪些goal。

1
2
mvn help:describe -Dplugin=groupId:artifactId:version

可以省略版本号

还可以使用插件目标前缀替换坐标:

1
$ mvn help:describe -Dplugin=compiler

例子:

查看 maven-source-plugin 的信息

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
mvn help:describe -Dplugin=org.apache.maven.plugins:maven-source-plugin


......
Name: Apache Maven Source Plugin
Description: The Maven Source Plugin creates a JAR archive of the source
files of the current project.
Group Id: org.apache.maven.plugins
Artifact Id: maven-source-plugin
Version: 3.0.1
Goal Prefix: source

This plugin has 7 goals:

source:aggregate
Description: Aggregate sources for all modules in an aggregator project.

source:generated-test-jar
Description: This plugin bundles all the test sources into a jar archive.

source:help
Description: Display help information on maven-source-plugin.
Call mvn source:help -Ddetail=true -Dgoal=<goal-name> to display parameter
details.

source:jar
Description: This plugin bundles all the sources into a jar archive.

source:jar-no-fork
Description: This goal bundles all the sources into a jar archive. This
goal functions the same as the jar goal but does not fork the build and is
suitable for attaching to the build lifecycle.

source:test-jar
Description: This plugin bundles all the test sources into a jar archive.

source:test-jar-no-fork
Description: This goal bundles all the test sources into a jar archive.
This goal functions the same as the test-jar goal but does not fork the
build, and is suitable for attaching to the build lifecycle.

For more information, run 'mvn help:describe [...] -Ddetail'
......

Redis 安装

1. 安装 gcc-c++

1
yum install gcc-c++

一般都装好了

2. 下载

下载地址:http://download.redis.io/releases/
这里下载最新的稳定版 (当前的稳定版本是:4.0.2)

1
2
3
4
5
6
7
cd /var/tmp

wget http://download.redis.io/releases/redis-stable.tar.gz

#or

curl -O http://download.redis.io/releases/redis-stable.tar.gz

3. 解压编译

1
2
3
4
5
tar -zxvf redis-stable.tar.gz

cd redis-stable

make

4. 安装

1
make PREFIX=/usr/local/redis install

5. 复制配置文件

1
2
cp redis.conf /usr/local/redis/

6. 启动

1
2
3
4
5
6
7
8
9
cd /usr/local/redis/

ls

cd bin

./redis-server


7. 配置文件

修改配置文件

1
2
3
cd ..
vi redis.conf

后台启动

1
2
3
4
5
# 修改
# daemonize no
# 为 yes

daemonize yes

指定服务器绑定的IP地址

1
bind 0.0.0.0

监听端口号,默认为 6379

1
port 6379

定本地数据文件名,默认值为dump.rdb

1
dbfilename dump.rdb

数据文件存放目录,可以改成绝对路径

1
2
#dir ./
dir /data/redis/data/

8. 通过指定配置文件进行启动

实现后台运行等

1
bin/redis-server redis.conf 

9. 关闭Redis

1
bin/redis-cli shutdown

不要随便杀进程

9. 服务方式运行

进入到程序包的 redis-4.0.2/utils 目录中
执行./install_server.sh 文件,按照提示进行服务安装:

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
 ./install_server.sh 
Welcome to the redis service installer
This script will help you easily set up a running redis server

Please select the redis port for this instance: [6379]
Selecting default: 6379
Please select the redis config file name [/etc/redis/6379.conf] /usr/local/redis/redis.conf
Please select the redis log file name [/var/log/redis_6379.log]
Selected default - /var/log/redis_6379.log
Please select the data directory for this instance [/var/lib/redis/6379] /data/redis
Please select the redis executable path [] /usr/local/redis/bin/redis-server
Selected config:
Port : 6379
Config file : /usr/local/redis/redis.conf
Log file : /var/log/redis_6379.log
Data dir : /data/redis
Executable : /usr/local/redis/bin/redis-server
Cli Executable : /usr/local/redis/bin/redis-cli
Is this ok? Then press ENTER to go on or Ctrl-C to abort.
Copied /tmp/6379.conf => /etc/init.d/redis_6379
Installing service...
Successfully added to chkconfig!
Successfully added to runlevels 345!
Starting Redis server...
Installation successful!

完成后将在/etc/rc.d/init.d目录生成一个redis_6379 文件,安装的服务也叫这个名字,服务默认开机启动。

启动redis服务:

1
service redis_6379 start

停止redis服务:

1
service redis_6379 stop

设为开机启动:

1
chkconfig redis_6379 on

关闭开机启动:

1
chkconfig redis_6379 off

先在applicationContext.xml 文件中集成Curator

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- 重连策略 -->
<bean id="retryPolicy" class="org.apache.curator.retry.ExponentialBackoffRetry">
<constructor-arg index="0" value="${curator.retry.baseSleepTimeMs}" /> <!-- 间隔时间基数 -->
<constructor-arg index="1" value="${curator.retry.maxRetries}" /><!-- 最多重试几次 -->
</bean>

<bean id="curatorClient" class="org.apache.curator.framework.CuratorFrameworkFactory" factory-method="newClient" init-method="start" destroy-method="close">
<constructor-arg index="0" value="${curator.server.list}" />
<constructor-arg index="1" value="${curator.session.timeout}" /><!-- sessionTimeoutMs会话超时时间,单位为毫秒。默认是60000ms -->
<constructor-arg index="2" value="${curator.connection.timeout}" /><!-- connectionTimeoutMs连接创建超时时间,单位毫秒,默认15000ms -->
<constructor-arg index="3" ref="retryPolicy" />
</bean>

config.properties

1
2
3
4
5
6
7
8
9
## curator 的相关配置
#间隔时间基数
curator.retry.baseSleepTimeMs=1000
#最多重试几次
curator.retry.maxRetries=3

curator.server.list=192.168.1.213:2181
curator.session.timeout=15000
curator.connection.timeout=10000

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
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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146

import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**
* <pre>
* 任务线程
* </pre>
*
* @author wwh
*/
@Component
public class TaskThread2 implements Runnable {

private final static Logger logger = LoggerFactory.getLogger(TaskThread2.class);
/**
* 获取锁的超时时间
*/
private static final int LOCK_ACQUIRE_TIMEOUT = 1000;
/**
* 锁路径<br>
* 全路径为:zkBasePath + LOCK_PATH + task.getId();
*/
private static final String LOCK_PATH = "/lock/";
@Autowired
private CuratorFramework curatorClient;
/**
* 基础路径
*/
@Value("${zookeeper.basePath}")
private String zkBasePath;

private Thread thread;

/**
* 运行标记
*/
private boolean runFlag = true;

private final ReentrantLock lock = new ReentrantLock();
private final Condition sysClose = lock.newCondition();

private String task;

@Override
public void run() {
InterProcessMutex lock = new InterProcessMutex(curatorClient, getLockPath());

while (runFlag) {
try {
if (lock.acquire(LOCK_ACQUIRE_TIMEOUT, TimeUnit.MILLISECONDS)) {
try {
logger.debug("当前线程获取到锁,开始处理数据");
doWithLock();
} catch (Exception e) {
logger.error("执行任务:{} 处理逻辑时异常,", task, e);
} finally {
try {
lock.release();
} catch (Exception e) {
logger.error("任务:{} , 释放锁时:ZK错误或链接中断", task, e);
}
}
}
} catch (Exception e) {
logger.error("任务:{} 获取锁时:ZK错误或链接中断", task, e);
}
}
logger.info("任务:{} , 处理线程退出", task);
// 移除zookeeper上锁节点
destroyZKLockPath();
}

/**
* 具体执行代码<br>
* 获取到锁的时候才会运行
*/
private void doWithLock() {
}

/**
* 获取锁的路径,以任务ID为锁
*
* @return
*/
private String getLockPath() {
// 以任务id作为锁的路径
return zkBasePath + LOCK_PATH + task;
}

private void destroyZKLockPath() {
// 删除锁的条件是没有其他的节点了
try {
String path = getLockPath();
List<String> list = curatorClient.getChildren().forPath(path);
if (list == null || list.isEmpty()) {
// 如果没有其他节点还在获取锁就删除
curatorClient.delete().forPath(path);
}
} catch (Exception e) {
e.printStackTrace();
}
}

/**
* 启动任务
*
* @param task
*/
public void start(String task) {
// 只能启动一次
if (thread != null) {
throw new IllegalStateException("任务线程只能启动一次");
}
logger.info("开始处理任务:{}", task);
this.task = task;
// 启动线程
thread = new Thread(this, "T-" + task);
thread.start();
}

/**
* 停止任务
*/
public void stop() {
runFlag = false;
// 如果是获取锁时的等待无法唤醒
lock.lock();
try {
sysClose.signalAll();// 唤醒空数据时的等待
} finally {
lock.unlock();
}
}
}

http://curator.apache.org/getting-started.html

学习ZooKeeper
http: //zookeeper.apache.org/doc/trunk/zookeeperStarted.html

使用curator

curator的JAR包可从Maven Central获得。Maven,Gradle,Ant等的用户可以轻松地将curator包含在他们的构建脚本中。

Maven

1
2
3
4
5
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>4.0.0</version>
</dependency>

获得连接

curator使用 Fluent Style 风格的API。

curator连接实例(CuratorFramework)由CuratorFrameworkFactory分配。要连接到的ZooKeeper群集只需要 一个 CuratorFramework对象:

1
2
CuratorFrameworkFactory.newClient(zookeeperConnectionString, retryPolicy)

使用默认值创建与ZooKeeper群集的连接,唯一需要指定的是重试策略。在大多数情况下,应该使用:

1
2
3
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3)
CuratorFramework client = CuratorFrameworkFactory.newClient(zookeeperConnectionString, retryPolicy);
client.start();

必须启动客户端(不再需要时关闭)。

直接使用ZooKeeper

一旦有一个CuratorFramework实例,就可以直接调用ZooKeeper,方法与使用ZooKeeper发行版中提供的原始ZooKeeper对象类似。例如:

1
2
client.create().forPath("/my/path", myData)

这里的好处是curator管理ZooKeeper连接,如果有连接问题,将重试操作。

食谱 Recipes

分布式锁

1
2
3
4
5
6
7
8
9
10
11
12
InterProcessMutex lock = new InterProcessMutex(client, lockPath);
if ( lock.acquire(maxWait, waitUnit) )
{
try
{
// do some work inside of the critical section here
}
finally
{
lock.release();
}
}

领导选举

1
2
3
4
5
6
7
8
9
10
11
12
13
LeaderSelectorListener listener = new LeaderSelectorListenerAdapter()
{
public void takeLeadership(CuratorFramework client) throws Exception
{
// this callback will get called when you are the leader
// do whatever leader work you need to and only exit
// this method when you want to relinquish leadership
}
}

LeaderSelector selector = new LeaderSelector(client, path, listener);
selector.autoRequeue(); // not required, but this is behavior that you will probably expect
selector.start();

pom.xml

1
2
3
4
5
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>

简单测试

1
2
3
4
5
6
public static void main(String[] args) {
Jedis jedis = new Jedis("192.168.1.213");
jedis.set("foo", "bar");
String value = jedis.get("foo");
System.out.println(value);
}

如果用这种方法连集群,会看到如下的错误

1
Exception in thread "main" redis.clients.jedis.exceptions.JedisMovedDataException: MOVED 12182 192.168.1.214:6382

Redis集群测试

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
public static void main(String[] args) {
Set<HostAndPort> jedisClusterNodes = new HashSet<HostAndPort>();
// Jedis集群将尝试自动发现集群节点
jedisClusterNodes.add(new HostAndPort("192.168.1.214", 6379));
// jedisClusterNodes.add(new HostAndPort("192.168.1.214", 6380));
// jedisClusterNodes.add(new HostAndPort("192.168.1.214", 6381));
JedisCluster jc = new JedisCluster(jedisClusterNodes);
jc.set("foo", "bar");
jc.set("foo1", "bar1");
jc.set("foo2", "bar2");
jc.set("foo3", "bar3");
jc.set("foo4", "bar4");
jc.set("foo5", "bar5");
jc.set("foo6", "bar6");

System.out.println(jc.get("foo"));
System.out.println(jc.get("foo1"));
System.out.println(jc.get("foo2"));
System.out.println(jc.get("foo3"));
System.out.println(jc.get("foo4"));
System.out.println(jc.get("foo5"));
System.out.println(jc.get("foo6"));

System.out.println("===================");

Map<String, JedisPool> map = jc.getClusterNodes();
for (Map.Entry<String, JedisPool> e : map.entrySet()) {
System.out.println(e.getKey());
System.out.println("===================");
}

}

运行时报错:

1
2
3
4
5
6
Exception in thread "main" redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool
at redis.clients.util.Pool.getResource(Pool.java:53)
at redis.clients.jedis.JedisPool.getResource(JedisPool.java:226)
at redis.clients.jedis.JedisSlotBasedConnectionHandler.getConnectionFromSlot(JedisSlotBasedConnectionHandler.java:66)
...
...

因为在之前创建创建集群时使用的IP是127.0.0.1,当返回(error) MOVED 10439 127.0.0.1:6381时,系统尝试连接本地的端口,然后连不上失败了

修改Redis配置文件,绑定到正确的IP上,然后重新创建集群指定具体的IP地址:

1
./redis-trib.rb create --replicas 1 192.168.1.214:6379 192.168.1.214:6381 192.168.1.214:6382 192.168.1.214:6383 192.168.1.214:6384 192.168.1.214:6385

1
2
3
4
5
6
7
8
9
10
#杀掉redis进程
ps -ef | grep redis | awk '{print $2}' | xargs kill

#删除
rm -f /data/redis/d63*/dump.rdb
rm -f /data/redis/d63*/nodes.conf

#批量替换
sed -i 's/192.168.1.214/0.0.0.0/' */redis.conf

Jedis客户端分片测试

分片使用一种称为“一致哈希”的技术,并根据一些散列算法(md5和murmur,后者不太标准,但速度更快)在一组redis服务器上同等地分配key值。这样的节点被称为“分片”。优点是每个分片只会占总内存的 1/n(对于n是分片数量)。

先配置三个独立的redis服务

直连接方式

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
public static void main(String[] args) {
List<JedisShardInfo> shards = new ArrayList<JedisShardInfo>();
JedisShardInfo si = new JedisShardInfo("192.168.1.213", 6380);
shards.add(si);
si = new JedisShardInfo("192.168.1.213", 6381);
shards.add(si);
si = new JedisShardInfo("192.168.1.213", 6382);
shards.add(si);

// 直接连接
ShardedJedis jedis = new ShardedJedis(shards);
jedis.set("a", "foo");
jedis.set("a1", "foo1");
jedis.set("a2", "foo2");
jedis.set("a3", "foo3");
jedis.set("a4", "foo4");

System.out.println(jedis.get("a"));
System.out.println(jedis.get("a1"));
System.out.println(jedis.get("a2"));
System.out.println(jedis.get("a3"));
System.out.println(jedis.get("a4"));

ShardInfo<?> sinfo = jedis.getShardInfo("a");
System.out.println(sinfo);

jedis.disconnect();
jedis.close();
}

连接池方式

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
public static void main(String[] args) {
List<JedisShardInfo> shards = new ArrayList<JedisShardInfo>();
JedisShardInfo si = new JedisShardInfo("192.168.1.213", 6380);
shards.add(si);
si = new JedisShardInfo("192.168.1.213", 6381);
shards.add(si);
si = new JedisShardInfo("192.168.1.213", 6382);
shards.add(si);

// 池连接
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
ShardedJedisPool pool = new ShardedJedisPool(jedisPoolConfig, shards);

try (ShardedJedis jedis = pool.getResource()) {
jedis.set("x", "foo");
jedis.set("x1", "foo1");
jedis.set("x2", "foo2");
}

try (ShardedJedis jedis2 = pool.getResource()) {
jedis2.set("y", "bar");
jedis2.set("y1", "bar1");
jedis2.set("y2", "bar2");
}

try (ShardedJedis jedis = pool.getResource()) {
System.out.println(jedis.get("x"));
System.out.println(jedis.get("x1"));
System.out.println(jedis.get("x2"));
}

try (ShardedJedis jedis2 = pool.getResource()) {
System.out.println(jedis2.get("y"));
System.out.println(jedis2.get("y1"));
System.out.println(jedis2.get("y2"));
}

pool.close();
}
0%