java IO

java1.4之前,java只支持一种阻塞式IO,可以称之为BIO。对应于网络编程,服务端提供了位置信息(ip和端口),客户端向服务端监听的端口发起连接请求,通过三次握手建立连接,然后通过网络套接字(socket)进行通信。

这是一种同步阻塞式的通信模式,通常由一个独立的Acceptor线程监听客户端连接,收到连接请求后,它为每一个客户端连接创建一个线程进行处理,阻塞式等待输入完成,从输入流获取数据后,处理请求,再通过输出流返回数据,最后关闭连接,销毁线程。

IO实例

服务端

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
public class HelloServer {


static int port = 9090;

public static void main(String[] args) {

final ServerSocket server;
try {
server = new ServerSocket(port);
System.out.println("Hello Server started at " + port);
Socket socket = null;

do {
socket = server.accept();
new Thread(new HelloServerHandler(socket)).start();

} while (true);

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


public static class HelloServerHandler implements Runnable {

private Socket socket;

public HelloServerHandler(Socket socket) {
this.socket = socket;
}

@Override
public void run() {


BufferedReader in = null;
PrintWriter out = null;

try {
in = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
out = new PrintWriter(this.socket.getOutputStream(), true);
while (true) {
String message = in.readLine();
if (message == null) break;

System.out.println("message from client: " + message);
out.println("Hello, " + message);
}

} catch (Exception e) {
e.printStackTrace();
if (in != null)
try {
in.close();
} catch (IOException e1) {
e1.printStackTrace();
}

if (out != null) {
out.close();
}

if (this.socket != null) {
try {
socket.close();
} catch (IOException e1) {
e1.printStackTrace();
}
socket = null;
}
}

}
}

}

阅读全文

经过

  1. 今天早上生产出现事故,所有到网站的请求nginx报500错误,查看后台日志,发现连不上redis了;
  2. 进入服务器,可以连上redis, 但是所有操作提示需要登陆,当时没有重视,只是纪录并重启redis;
  3. 下午再次出现相同情况。查看连接到redis端口的日志,发现了不属于公司服务器的IP,怀疑是被阿里云内网机器脚本扫描并攻击端口。
  4. 修改了阿里云的安全策略,由允许内网访问(当时没考虑到阿里云的其它内网机器也可能进行攻击)修改为只允许安全组内机器访问,再次重启,之后一直正常。
  5. 然后重启后发现,生产上的一些缓存组件查不到数据。折腾了很久,查看启动日志也正常,后来才想到,可能是redis-slave没有重启,不会自动重连(写操作在redis-master,读操作在redis-slave),重启redis-slave,解决问题。

问题

  1. 不知道攻击者是如何通过连接redis,修改配置,使得正常的请求无法连接,需要了解redis配置;
  2. 不知道redis-slave不会自动重连,需要了解redis集群配置以及注意事项。

补充

//todo

资源文件

pinpoint agent在启动的时候,会加载plugin文件夹下所有的插件。它会扫描插件jar包中META-INF/services目录下的两个配置文件来确认ProfilerPluginTraceMetadataProvider的实现类。
META-INF/services/com.naercorp.pinpoint.bootstrap.plugin.ProfilerPlugin:

1
com.isuwang.dapeng.pinpoint.plugin.DapengPinpintPlugin

META-INF/services/com.navercorp.pinpoint.common.trace.TraceMetadataProvider:

1
com.isuwang.dapeng.pinpoint.plugin.DapengTraceMetadataProvider

TraceMetadataProvider

只需要实现setup方法,添加ServiceTypeAnnotationKey, 主要用于服务类型和记录的数据的标识,agent上送给collector, collectorweb通过它们区分不同的服务节点类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class DapengTraceMetadataProvider implements TraceMetadataProvider {

public static ServiceType DAPENG_PROVIDER_SERVICE_TYPE = ServiceTypeFactory.of(1999, "DAPENG_PROVIDER", RECORD_STATISTICS);
public static ServiceType DAPENG_CONSUMER_SERVICE_TYPE = ServiceTypeFactory.of(9999, "DAPENG_CONSUMER", RECORD_STATISTICS);
public static AnnotationKey DAPENG_ARGS_ANNOTATION_KEY = AnnotationKeyFactory.of(900, "dapeng.args", VIEW_IN_RECORD_SET);
public static AnnotationKey DAPENG_RESULT_ANNOTATION_KEY = AnnotationKeyFactory.of(999, "dapeng.result", VIEW_IN_RECORD_SET);

@Override
public void setup(TraceMetadataSetupContext context) {


context.addServiceType(DAPENG_PROVIDER_SERVICE_TYPE);
context.addServiceType(DAPENG_CONSUMER_SERVICE_TYPE);
context.addAnnotationKey(DAPENG_ARGS_ANNOTATION_KEY);
context.addAnnotationKey(DAPENG_RESULT_ANNOTATION_KEY);
}
}

注意这里的ServiceType和AnnotationKey的code是有范围的.
RECORD_STATICSTICS类型的ServiceType, agent会统计它的耗时。
VIEW_IN_RECORD_SET属性的AnnotationKey将会在调用树状图中行显示。

阅读全文

主要节选翻译自官方文档 Pinpoint Plugin Developer Guide

在pinpoint里面,一个“事务”(或者说请求?)是由一组spans构成的。每个span代表了请求经历的一个单独的逻辑节点。

一个span记录了重要方法的信息和它们的相关数据,请求,返回值等,概括它们为spanEvents. 一个span和它包含的所有spanEvents代表了一个方法调用。

PinPoint插件结构

pinpoint插件是由TraceMetadataProviderProfilerPlugin的实现组成。TraceMetadataProvider的实现提供了ServiceType和AnnotationKey给PinPoint agent/web/collector. ProfilerPlugin的实现被agent使用,用来修改目标类,记录跟踪数据。

插件放在agent的plugin目录,web和collector的WEB-INF/lib目录。插件需要在资源文件夹的META-INF/services目录中,声明自己实现的TraceMetadataProvider和ProfilerPlugin。

TraceMetadataProvider

ServiceType

每个SpanSpanEvent都包含了ServiceType, 代表了正在追踪的方法属于谁。web也需要通过serviceType来展示。

需要注意的是,每个ServiceType由name,code,description,properties组成。其中,code是不能乱用的,每个serviceType的code唯一。因此,pinpoint预留了一部分code范围供我们自己开发使用。

AnnotationKey

同样的,AnnotationKey也有自己的唯一code, 900-999是pinpoint团队为我们预留的。

ProfilerPlugin

profilerPlugin修改目标类以收集跟踪数据。

它工作的步骤是:

  1. jvm 启动时启动pinpoint agent
  2. agent加载插件目录下所有的插件
  3. agent调用每个插件的ProfilerPlugin.setup(ProfilerPluginsetupContext)方法
  4. 在setup方法里面,插件决定是否要转换类,并且注册一个transformerCallerBack
  5. 应用启动
  6. 每次类加载的时候,agent会寻找注册在该类上的TransformerCallback
  7. 如果找到了,agent会调用callback里面的doInTransform方法
  8. TransformerCallerBack会修改目标类的字节码,添加拦截,添加字段等等
  9. 修改后的字节码被返回给jvm,然后类被加载
  10. 应用继续
  11. 当一个修改后的方法被调用时,注入的拦截器beforeafter方法会被调用
  12. 拦截器纪录追踪数据

阅读全文

hbase配置

  1. 下载并解压hbase(我使用的是hbase-1.2.4)
  2. 编辑hbase-1.2.4/conf/hbase-env.sh,添加JAVA_HOME配置:

    1
    export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_31.jdk/Contents/Home
  3. 编辑hbase-site.xml:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <configuration>
    <property>
    <name>hbase.rootdir</name>
    <value>file:///Users/tangliu/Tmp/hbase</value>
    </property>
    <property>
    <name>hbase.zookeeper.property.dataDir</name>
    <value>/Users/tangliu/Tmp/zookeeper</value>
    </property>
    </configuration>

    分别表示数据和zookeeper数据存放地点。这样配置是本地单实例模式启动,具体和集群配置可以参考hbase官网。

  4. ./hbase-1.2.4/bin/start-hbase.sh启动
  5. ./hbase-1.2.4/bin/hbase shell hbase-create.hbase这是初始化pinpoint需要的表
  6. 验证页面:http://localhost:16010/master-status

配置pinpoint-collector

  1. 解压pinpoint-collector-1.6.1-SNAPSHOT.war包到tomcat-collector/webapps/ROOT/

    1
    unzip pinpoint-collector-1.6.1-SNAPSHOT.war -d ……./tomcat-collector/webapps/ROOT/
  2. 配置ROOT/WEB-INF/classes/hbase.properties:

    1
    2
    hbase.client.host=localhost
    hbase.client.port=2181

    指向zookeeper即可

  3. 配置tomcat-collector/conf/server.xml:

    1
    2
    3
    <Server port="8005" shutdown="SHUTDOWN">
    <Connector port="8088" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />
    <!--<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" /> -->
  4. 启动tomcat

阅读全文

之前做文档站点的时候有一个需求,做一个在线部署页面,能够通过页面上点点点就自动部署远程服务器上的服务,并且看到部署日志。简单的思路就是,页面通过websocket连接到java后台,java代码调用shell脚本执行发布操作,获取输出,并通过websocket将输出内容返回页面。

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
Runtime runtime = Runtime.getRuntime();
Process process;
BufferedReader br = null;
BufferedWriter wr = null;
try {
process = runtime.exec("//要执行的命令");

br = new BufferedReader(new InputStreamReader(process.getInputStream()));
wr = new BufferedWriter(new OutputStreamWriter(process.getOutputStream()));

String inline;
while ((inline = br.readLine()) != null) {
if (!inline.equals("")) {
inline = inline.replaceAll("<", "&lt;").replaceAll(">", "&gt;");
session.getBasicRemote().sendText(inline); //返回给页面
if (inline.endsWith("发布到服务器?[Y/N]:")) {
wr.write("y"); //自动输入y
wr.newLine();
wr.flush();
session.getBasicRemote().sendText("y");
}
} else {
session.getBasicRemote().sendText("\n"); //换行
}
}
br.close();
br = new BufferedReader(new InputStreamReader(process.getErrorStream())); //错误信息
while ((inline = br.readLine()) != null) {
if (!inline.equals(""))
session.getBasicRemote().sendText("<font color='red'>" + inline + "</font>");
else
session.getBasicRemote().sendText("\n");
}
} catch (Exception e) {
LOGGER.error(e.getMessage(), e);
session.getBasicRemote().sendText("<font color='red'>" + e.getMessage() + "</font>");
} finally {
if (br != null)
br.close();
if (wr != null)
wr.close();
session.getBasicRemote().sendText("End.");
}