现象

某天,将某项目的snapshot版本的包deploy到nexus私服上,通过页面可以搜索到该包,但是在项目中引用时,始终无法下载,但是release版本的包可以。

原因

maven默认不下载snapshot版本的jar包,需要修改pom文件中的配置

解决

1
2
3
4
5
6
7
8
9
<repositories> 
<repository>
<id>nexus</id>
<url>http://......</url>
<snapshots>
<enable>true</enable>
<snapshots>
</repository>
</repositories>

pom.xml中如上配置后成功下载。仅做记录。

背景

系统中Redis使用三台服务器(slave01,slave02,slave03),交叉搭建了三主三从集群。一段时间内,Redis集群频繁出现CLUSTERDOWN异常,使用redis-cli客户端连上集群后,使用cluster info查看集群信息,发现 cluster_state状态为fail,排查发现slave02服务器负载超高,redis服务已经连不上了。但奇怪的是该服务器上只有一个master,理论上“宕机”后,该服务器上的master(slave02:7003)的从节点(slave01:7002)应该自动failover选举成master才对,但是集群的自动切换机制并没有生效,整个集群处于CLUSTERDOWN状态,不可对外提供服务,一直等到slave02这台服务器负载恢复正常后cluster_state才恢复为ok状态。

踩坑过程

  1. 没有开启日志的痛
    没有日志啊没有日志啊,忍不住嚎啕一嗓子,发生故障只能靠猜啊,搞半天摸不着头脑啊。后来逼于无奈,还是关闭了故障节点的Redis,添加了日志配置再重启,也帮助了后续问题的排查;
  2. redis-server启动路径之坑
    由于集群的配置文件***.conf中,没有指定aof文件名和路径,因此redis默认使用appendonly.aof作为AOF文件名,且,文件位于启动命令执行的目录,也就是说,你在哪里执行redis-server启动redis,这个AOF文件就从哪里加载,找不到的话就自动生成一个新的。而由于之前狂野粗放的操作,不同节点上的启动路径并不一致,而我们也没注意到这个问题,重启Redis的时候用了一个与之前不同的路径,导致Redis重启后加载不了持久化的数据….我们也是在链接redis后发现keys没了才注意到这个问题;
  3. cluster-config-file配置文件之坑
    跟上面的问题有点类似,在配置cluster-config-file的时候,没有指定路径,因此,redis默认在启动命令执行的目录去找集群节点信息的配置文件,如果找不到,它就自己创建一个新的。听起来好像没问题,但是这样创建的Redis节点,它虽然是集群模式,却因为配置文件丢失没有加入之前的集群,这时候它自己本身成了一个独立的集群。如果你企图用redis-trib将它加入原本的集群,那么它还是一个孤零零的master(而其实我们期待它恢复成某个master节点的slave),如果当做master用,还得给它分配槽;如果你企图将它手动指定为某个master的slave,它还可能傲娇地给你说:不行,i’m not empty,你只能逼于无奈将它的数据清空再来一次;
    所以,2跟3要合起来一起看,当你重启Redis时,一定要注意redis-server这个命令的执行位置,是否跟重启前一致,也千万别删掉原来的node***.conf文件,它保存了你原始的集群节点信息(登录后执行cluster nodes命令可以看到一样的信息),只有使用这个文件启动,重启的节点才能正确地加入之前的集群。
  4. masterauth之坑
    在解决问题3之后,我们发现一个奇葩的事,明明集群已经恢复了啊,为什么master节点的数据没有同步到slave节点!!!! 同时,我们尝试着手动kill掉master节点,为什么slave节点没有自动选举成master!!!!! 好在这一次我们开启了日志,直接查看slave节点的日志,发现一直在打印一个错误“Unexpected reply to PSYNC from master: -NOAUTH Authentication required.” 这时才恍然大悟,我特么配置了requirepass要求别的节点连我要密码,但是我没有配置masterauth啊, slave节点去同步master数据时,因为没有配置这个参数,导致一直同步失败,这也就是前面发生的几个现象-为什么明明集群状态是ok了但是数据没有从master同步过来,为什么master挂了slave不能自己选举成为master-的原因。配置了masterauth后,再重启slave节点,一切如丝般润滑:正确的同步数据,生成AOF文件,keys也有了,关闭master后它也能切换成新的master了….

    这里有个意外收获:原来,虽然配置的是AOF机制,但redis slave从master同步数据的时候,还是会先生成rdb文件,然后再生成AOF文件,然后再使用aof-rewrite机制,重写AOF文件。

回顾整个踩坑过程:

  1. 第一是没有开启redis日志,导致集群其实没有配置成功但是没人知道,后续发生故障后也只能靠猜测排查问题;
  2. 第二是集群异常时,没人想到是不是集群没有配置正确,都默认以集群正确配置为前提来排查;
  3. 第三是没想到启动位置会影响redis aof文件以及集群配置文件生成的路径;
  4. 第四是没注意到只配置了自己的服务端密码(requirepass)没有配置对应另一个节点的客户端密码(masterauth)….

这几个小问题凑一起之后,就把人弄得晕头转向一头雾水,折腾了很久才搞清楚“莫名其妙的现象”背后的原因。
以此为戒,吸取教训,第一是,以后使用应用时,一定要开启日志,方便排查问题,第二个教训就是还是得了解你使用的应用的原理和主要配置项,尽量避免发生故障后临时抱佛脚。

背景

最近接到一个需求,业务数据表中有三张表,只想保留最近365天的数据,过期的就删除,避免这些没有太大意义的数据造成数据表越来越大。

当时想了两种解决方案:

方案一

利用mysqlevent事件机制,通过写sql脚本,可以很快的完成这个需求,优点很明显:

  1. 快速实现,时间成本低
  2. 不需要再引入什么乱七八糟的东西或者代码

但是,也有很明显的缺点:

  1. 没有日志啊,怎么知道有没有跑,成没成功呢
  2. 只能定时跑,不能人工触发,也没法做管理

方案二

当时出现在脑海里的第二个思路就是直接利用现有代码里面的ScheduledTask,写点java代码就把这个需求给搞了,这样的话日志也有了,加个controller连手动触发也有了,再积极点,搞个异常告警什么的也不是事。

然而,这样真的好吗?这次是要清三个表,就写三个定时任务,三段sql,下次再加个表,再加代码上线?好累啊,有没有更好点的方式呢?

方案三

所以就干脆自己写个工具算了,期望是以后有类似的需求就别再开发了。
总的来说,设计思路是这样的:

  1. 使用SpringBoot快速开发一个Web应用
  2. 启动的时候读配置文件,使用quartz加载定时任务
  3. 配置文件里面配置了清理的时间(cron表达式)、数据库url用户名密码要清理的库名表名清理依赖的字段保存时间
  4. 定时任务执行的时候,根据配置文件的配置,创建链接,拼装清理的sql语句,然后执行,执行完关闭链接

通过上面的设计,一是能够满足这次的需求,二是以后有类似的数据清理需求的时候,可以不用再做代码开发了,改改配置就好了,三是以后可以方便的做拓展,比如,加入zookeeper,就可以做分布式任务协调,加个页面,就可以页面操作和管理所有的清理任务,想简单点的话,有类似需求的项目可以自己一行命令运行这个工具类,想得复杂一点,可以加入不同的清理对象,使得不仅仅支持Mysql,然后可以搭建一个平台类项目,不同项目的数据清理需求都可以在这里维护管理….

当然,我想多了,其实就是完成这么一个简单的需求而已。

那么有没有其他方案呢?我想也有的,引入个分布式调度框架,然后写个清理脚本,用人家成熟的产品来做这个活儿嘛,但是想想好像更重了。

附上github地址:https://github.com/DevotedTangLiu/data-clean-util

背景

之前公司某个需求,需要使用FTP传输文件,由于服务器间网络不通,需要使用一台跳板机才能访问,所以我们需要一个FTP代理软件,让我们客户端可以通过它访问外网的FTP服务器。

辛酸泪

最先想到的是nginx,但是作为一个SFTP的代理也许可行,毕竟只需要一个SSH端口,作为一个静态文件服务器也行,但是作为一个FTP代理,需要考虑到控制端口和数据端口,nginx并不能满足FTP代理的需求。

然后调用squid, 看文档表示它是支持FTP协议的。下载了各种版本,参考网上各种配置进行尝试,结果都是error error error ! 谷歌了各种资料,一个是当时的squid版本有bug,一个是squid并不支持主动模式的FTP代理,而公司的防火墙策略要求所有FTP必须是主动模式,GG。

然后只能各种搜索,google啊翻了个遍,看看别人怎么做FTP代理的,结果都是说建议抛弃主动模式,采用被动模式,没办法了,就跑去github搜。

曙光

在github上搜到了某位外国友人的项目ftpproxy,看了他的说明和代码,第一感觉是这个还比较靠谱,代码量也少。

于是拉下来试用,结果发现,这东东虽然支持主动模式(可以在配置文件中配置),但是只支持代理到FTP服务端的主动模式,客户端到代理端还是使用的被动模式。没法子了,自己改造吧。

终章

为了满足公司内网络情况要求,对ftpproxy进行了改造。

  1. 改造了socket创建方式,使之支持客户端与代理之间的主动模式
  2. 增加了socket5代理的代码,使之支持SFTP代理

改造之后,经过调试测试,也应用在生产环境差不多两年了,目前来看运行良好,没有发生过故障。

今天突然想起这事,觉得也应该记录和分享一下。

项目代码在这里https://github.com/DevotedTangLiu/ftpproxy,请有需要的同志自行取阅吧。

最近在使用grpc做项目,信息安全的同事提出要求,需要将来往报文加密,避免抓包。阅读grpc的文档,发现它已经支持ssl(tls),因此直接选这种认证和加密方式。

服务端和客户端代码参考grpc-java项目中的demo,摘取关键代码如下:

服务端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private SslContextBuilder getSslContextBuilder() {
InputStream cacert = ZooPorterRpcServer.class.getClassLoader().getResourceAsStream("ssl/server.crt");
InputStream privkey = ZooPorterRpcServer.class.getClassLoader().getResourceAsStream("ssl/server.pem");
SslContextBuilder sslContextBuilder = SslContextBuilder.forServer(cacert, privkey);
return GrpcSslContexts.configure(sslContextBuilder, SslProvider.OPENSSL);
}

private void start() throws IOException {
port = settings.getInt("server.port");
server = NettyServerBuilder.forPort(port).addService(BeanUtil.me().getBean(ElasticsearchServiceGrpcImpl.class))
.sslContext(getSslContextBuilder().build())
.build()
.start();

logger.info("Server started, listening on " + port);
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
ZooPorterRpcServer.this.stop();
logger.error("*** server shut down");
}
});
}

客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
protected final ManagedChannel channel;

private static SslContext buildSslContext() throws SSLException {
SslContextBuilder builder = GrpcSslContexts.forClient();
builder.trustManager(ZooPorterClient.class.getClassLoader().getResourceAsStream("ssl/ca.crt"));
return builder.build();
}

public ZooPorterClient() throws SSLException {

channel = NettyChannelBuilder.forAddress(Settings.HOST, Settings.PORT)
.negotiationType(NegotiationType.TLS)
.sslContext(buildSslContext())
.build();
}

密钥和证书

上述代码中,ca.cert和server.pem/server.cert是通过openssl生成的密钥和证书,生成的脚本可以直接参考项目中的readme文档。

值得注意的是,按照readme中的脚本,只能生成一个指定了域名的证书(localhost),假使我们想通过多个域名甚至多个IP来访问服务(后端做负载均衡和服务发现),这样生成的证书就不合用了,访问时会抛异常。因此修改了脚本,下面脚本仅做记录使用。

1
2
3
4
5
6
7
8
9
openssl genrsa -passout pass:1111 -des3 -out ca.key 1024
openssl req -passin pass:1111 -new -x509 -days 7300 -key ca.key -out ca.crt -subj "/C=CN/ST=GuangDong/CN=www.ido.com" -extensions SAN -config <(cat /etc/pki/tls/openssl.cnf <(printf "[SAN]\nsubjectAltName=DNS:www.ido.com,IP:127.0.0.1,IP:180.137.128.151"))
openssl genrsa -passout pass:1111 -des3 -out server.key 1024
openssl req -passin pass:1111 -new -key server.key -out server.csr -subj "/C=CN/ST=GuangDong/CN=www.ido.com" -reqexts SAN -config <(cat /etc/pki/tls/openssl.cnf <(printf "[SAN]\nsubjectAltName=DNS:www.ido.com,IP:127.0.0.1,IP:180.137.128.151"))
# 以下这句是grpc官方例子中生成crt文件的命令,但发现这种方式无法指定ip和多域名,因此弃用
# openssl x509 -req -passin pass:1111 -days 7300 -in server.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out server.crt
openssl ca -passin pass:1111 -days 7300 -in server.csr -keyfile ca.key -cert ca.crt -extensions SAN -config <(cat /etc/pki/tls/openssl.cnf <(printf "[SAN]\nsubjectAltName=DNS:www.ido.com,IP:127.0.0.1,IP:180.137.128.151"))
openssl rsa -passin pass:1111 -in server.key -out server.key
openssl pkcs8 -topk8 -nocrypt -in server.key -out server.pem

这样生成的证书,经过测试,可以部署在多个IP(这里是127.0.0.1和180.137.128.151)服务器上,正常访问。

其他

然而,这需要先知道有哪些IP即将部署服务啊!!假如以后部署服务的服务器越来越多,难道要重新生成证书并且更新服务和客户端吗?

得考虑下,这个ip能否是*号,或者其他任何方法,使得服务端增加节点,不影响 客户端正常调用。

原文地址https://github.com/CyC2018/CS-Notes/blob/master/notes/Java%20%E5%9F%BA%E7%A1%80.md

阅读全文