Mysql从库恢复失败

推断是因为一句修改表结构的语句在从库执行失败(或者未同步?)导致mysql从库同步异常,使用命令show slave status;可以看到

1
2
Slave_IO_Running: Yes
Slave_SQL_Running: No

然后通过Relay_Master_Log_FileExec_Master_Log_Pos定位到具体的sql语句,发现是条更新语句,更新某个表的19个字段,然而从库该表只有18个字段。

  1. 找到历史更新记录,然后手动在从库添加该字段
  2. 暂停同步stop slave;
  3. 跳过当前执行的同步sql set global sql_slave_skip_counter =1;
  4. 继续同步 start slave;

查看同步状态,发现开始正常地执行历史sql了。然而,过了大约半个小时,我再次查看状态时,发现另一个sql又执行报错,重复几次后发现又有不同的sql执行报错,而且由于前面跳过了一些重要数据没有同步,导致后面大片失败,这时候只好选择重建从库。

重建过程

  1. 1
    mysqldump -u*** -p*** -h db-master --default-character-set=utf8 --master-data=2 --single-transaction --databases DB1 DB2 DB3  > product_data_backup_20170515.sql

    这里将需要同步的数据库DB1/DB2/DB3导出到文件。注意–master-data=2 –single-transaction的配合使用,前一个参数会在开始导出时锁全表,记录当前的binlog文件和位置,然后释放锁,然后在同一个事务中导出数据,以保证一致性。

  2. 从库stop slave;

  3. 从库先drop需要同步的数据库,然后source product_data_backup_20170515.sql导入数据;
  4. grep 'CHANGE MASTER TO MASTER_LOG_FILE' product_data_backup_20170515.sql拿到导出时的MASTER_LOG_FILE和MASTER_LOG_POS,例:-- CHANGE MASTER TO MASTER_LOG_FILE='mysql-bin.000068', MASTER_LOG_POS=70371793;
  5. 1
    CHANGE MASTER TO MASTER_HOST='10.*.*.*',MASTER_USER='slaveUser',MASTER_PASSWORD='***',MASTER_LOG_FILE='mysql-bin.000068', MASTER_LOG_POS=70371793;
  6. start slave;

  7. show slave status;发现从库正在同步最新的数据,一会儿之后就已经同步到最新的POS,恢复正常

其他问题

  1. 不要修改从库的表结构以及数据,避免同步冲突失败;
  2. 不要滥用set global sql_slave_skip_counter,这会跳过某些同步sql,可能导致数据不一致;
  3. 附件类数据尽量不要直接存储在数据库中,备份和恢复时会特别慢

dapeng容器目前的模式,每个请求都需要以调用远程服务的方式请求服务,即使它请求的服务运行在本地同一个容器内。打个比方,客户端调用A服务,A服务内需要调用B服务,这时候,对于A服务来说,它是B服务的客户端,它请求B服务的方式与标准的前端没有任何区别,都需要经过各种过滤链,走网络传输,然后调用到本地(或其他节点)的服务。

这样做的优点是,对于所有请求,模式是统一的,每一个请求都是完整的服务调用。
缺点在于:

  1. 如果一个节点有很多服务,一个请求链的互相调用过程中,即使每次都是调用本节点,也依然有大量的网络传输过程开销;
  2. 对一个完整的服务调用来说,每个请求处理需要占用一个新的线程,如果一个请求中调用了十次本节点的服务,那么就至少占用了本节点10个线程,并且大多数都处于等待状态;
  3. 在分布式服务的调用过程中,如果后面某个服务调用异常,前面成功的服务调用无法自动回滚,但如果都是在同一个线程同一个事务,后面的异常可以使前面的数据库操作回滚。

因此,提出了这么一种需求:“如果本节点存在即将调用的服务,那么,不经过网络传输,直接用当前线程调用本地服务”,以此来解决上述的3个问题。

阅读全文

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. 拦截器纪录追踪数据

阅读全文