安装shadowsocks
  1. 使用root登陆,下载和运行一键安装脚本

    1
    2
    3
    wget --no-check-certificate https://raw.githubusercontent.com/teddysun/shadowsocks_install/master/shadowsocks.sh
    chmod +x shadowsocks.sh
    ./shadowsocks.sh 2>&1 | tee shadowsocks.log
  2. 按提示修改端口和密码

    • 配置地址:/etc/shadowsocks.json
    • 启动:/etc/init.d/shadowsocks start
    • 停止:/etc/init.d/shadowsocks stop
    • 重启:/etc/init.d/shadowsocks restart
    • 状态:/etc/init.d/shadowsocks status
    • 卸载:./shadowsocks.sh uninstall
  3. 默认开机启动
修改ssh端口
  1. 修改/etc/ssh/sshd_config文件,将Port修改为自己想要的端口
  2. systemctl restart sshd.service 重启服务
  3. netstat -tulnp|grep sshd 查看端口
安装git

yum install git

安装nginx
1
2
yum -y install epel-release
yum install nginx

背景描述

自定义了一个注解:

1
2
3
4
5
6
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface ScheduledTask {
}

在一个实现类上添加此注解:

1
2
@ScheduledTask
public class TimerTaskServiceImpl implements TimeTaskService {

在代码中期望通过判断是否包含注解来执行操作:

1
2
if(bean.getClass().isAnnotationPresent(ScheduledTask.class)){
...

本来一切运行正常。但是,当我试图在实现类上添加事务注解@Transactional后,这里的判断就一直是false。调试发现,当跑到这一行代码时,bean不是实现类,而是一个Proxy代理类(JdkDynamicAopProxy),所以自定义的注解一直获取不到。搜索之后,才知道这是spring的事务管理对添加了@Transactional注解的类做了其他操作,我们获取到的是一个处理后的代理类。

阅读全文

背景

为了提高系统的健壮性,我们常常做出多节点负载均衡的设计,通过zookeeper注册和发现可用服务,调用端通过一定的负载均衡策略决定请求哪一个可用服务节点。

然后,在某些情况下,服务的调用并非由客户端发起,而是由这个服务自身发起。比如,一个服务可能存在一些定时任务,每分钟去操作一下数据库之类的。当系统只有一个容器时,不用考虑主从的问题,只管到时间了就执行。但如果系统是分布式的,一个服务可能同时运行在多个容器中,查询类的定时任务没有影响,但是某些定时任务每次只需要执行一次,没有区分主从的情况下,每个容器下的服务都会企图去执行,很可能会造成不可预料的结果。

所以,我们需要达到的目标是,服务能够判断自己是否是Master,如果是,则执行,如果不是,则不执行。同时,如果Master服务掉线(比如宕机了),那么某个容器里的slave服务能够自动升级为Master,并执行Master执行的任务。

基础

  • Zookeeper客户端可以创建临时节点并保持长连接,当客户端断开连接时,临时节点会被删除
  • Zookeeper客户端可以监听节点变化

实现

  1. 定义一个持久化节点/gzcb/master/services,此节点下的子节点为临时节点,分别代表不同的Master服务
  2. Container_1中的服务AccountService,在启动时,在zookeeper中创建临时节点/gzcb/master/services/AccountService:1.0.0,节点的数据为192.168.99.100:9090。这代表,192.168.99.100:9090这个容器中的AccountService(版本为1.0.0)成功竞选为Master服务。Container_1中维护一个缓存,如果竞选成功,对应service:version置为true,否则置为false;
  3. Container_2中的服务AccountService,在启动时,也试图创建临时节点/gzcb/master/services/AccountService:1.0.0,但是会创建失败,返回结果码显示该节点已经存在。所以服务就知道已经有一个Master的AccountService(1.0.0)存在,它竞选失败。
  4. Container_2会保持对该临时节点的监听,如果监听到该零时节点被删除,则试图再次创建(创建临时节点的过程就是竞选master的过程),创建成功,则更新缓存对应service:version为true,否则继续保持监听。

优化

不管竞选成功还是失败,可以维护一份Master缓存信息,并保持监听,实时更新。这样,不仅能够自动竞选master,还能够通过修改临时节点数据的方式,手动指定Master。

关键代码

阅读全文

最近在做一个工具包的时候,有这么一种需求:在运行main函数时,需要将resources资源文件夹下的xml子文件夹中的文件加载。原本可以使用Main.class.getClassLoader().getResoruceAsStream()将文件一一加载,但是xml子文件夹中的文件非常多,不可能一个个列文件名。所以最初我的写法是:

1
2
3
4
5
6
File parent = new File(ClassLoader.getSystemClassLoader().getResource("").getPath() + "/xml");
File[] files = parent.listFiles();
for (int i = 0; i < xmlFiles.length; i++) {
File xmlFile = xmlFiles[i];
//...
}

在IDE中运行并没有问题,能够正常得读取到资源文件夹和文件。但是当我使用maven将工程打成jar包,试图使用命令行启动时,跑到这里就会抛空指针异常了。不断的测试和查资料,终于明白,当打成一个jar包后,整个jar包是一个文件,只能使用流的方式读取资源,这时候就不能通过File来操作资源了,得通过getResourceAsStream来读取文件内容并操作。在IDE中之所以能正常运行,是因为IDE中的资源文件在target/classes目录下,是正常的文件系统结构。

然而问题来了,我不是读一个文件,而是试图读取一个文件夹,依次读取文件夹下所有文件资源。我试图按照网上的说法,读取资源,然后创建一个临时文件,在操作该临时文件,例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
File file = null;
URL res = PostHelper.class.getClassLoader().getResource("xml/");
if (res.toString().startsWith("jar:")) {
try {
InputStream input = PostHelper.class.getClassLoader().getResourceAsStream("xml/");
file = File.createTempFile("tempfile", ".tmp");
OutputStream out = new FileOutputStream(file);
int read;
byte[] bytes = new byte[1024];
while ((read = input.read(bytes)) != -1) {
out.write(bytes, 0, read);
}
file.deleteOnExit();
} catch (IOException ex) {
ex.printStackTrace();
}
} else {
file = new File(res.getFile());
}

阅读全文

思路

使用Java1.8的CompletableFuture特性,给soa框架的前后端添加异步特性。

  • 对于后端服务开发者(Porvider),要做的是构造一个包装了预期返回结构体的Future,并返回,在其他线程中完成数据的准备,并调用CompletableFuture.complete方法。
  • 对于前端的方法调用者(Consumer),发起了一个请求,并返回了一个CompletableFuture的结果,在需要使用该结果时调用Future.get()方法。
  • 对于框架的层面,针对后端的异步,需要做的是维持数据通道,在Provider确定数据准备好之后,将数据取出,并返回给客户端(Consumer);对于前端的异步,要做的是,接收到对应请求的返回结果后,将结果封装到该请求对应的Future中。
代码生成
Service
1
2
3
public interface HelloService {
Future<String> sayHello(String name) throws com.isuwang.soa.core.SoaException;
}
Client
1
2
3
4
5
6
7
8
9
public Future<String> sayHello(String name) throws SoaException {
initContext("sayHello", true); //Is Async call
try {
sayHello_args sayHello_args = new sayHello_args();
sayHello_args.setName(name);
Future<String> response = new CompletableFuture<>();
sendBase(sayHello_args, new sayHello_result(), new SayHello_argsSerializer(), new SayHello_resultSerializer(), response);
return response;
...
Codec

打解包生成与同步的代码生成结果一致,在发送和接收到数据前后进行Future的封装和获取。

使用
服务端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 @Override
public Future<String> sayHello(String name) throws SoaException {

CompletableFuture<String> result = new CompletableFuture<>();

new Thread(() -> {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
result.complete("result");
}).start();

return result;
}

简单来说,即构造一个封装了返回结果结构体的Future,并返回这个Future,此时工作线程已经返回,而组装数据、通知完成的过程,则交给另一个线程处理,工作线程不需要在此阻塞,得到结果才返回。

客户端

客户端如常请求服务,并立即得到一个Future封装的结果,该结果可能还没有被写入值,但此时线程可以执行其他后续任务,直到需要使用结果时调用Future.get()方法,此时如果结果已经从服务器返回,则立即获得,否则阻塞至结果返回或超时。

1
2
3
4
5
HelloServiceClient client = new HelloServiceClient();
Future<String> result = client.sayHello("LiLei");

//do other job
System.out.println(result.get());

或者给Future添加一个回调方法,complete该Future的线程(或线程池)会执行该回调方法,客户端不需要在此阻塞。

1
2
3
4
5
6
7
8
CompletableFuture<String> normalRequest = (CompletableFuture<String>) client.sayHello("Tomy", 15000l);
normalRequest.whenComplete((str, ex) -> {
if (str != null) {
System.out.println(str);
} else {
ex.printStackTrace();
}
});

优化以及关注点

  • 客户端可以自定义异步请求超时时间,时间到了如果服务端还没返回会自动抛超时异常(已完成)
  • 服务端异常可以传递到客户端(已完成)
  • 服务端completeExceptionally可以正确传递给客户端(已完成)
  • 各种资源的正确释放以及其他问题

相关链接

JAVA1.5开始有个Future类,允许异步返回结果,但是对于Future的Consumer来说,他在获取结果(Future.get())时,还是需要阻塞等待的。JAVA8提供了一个
新的类CompletableFuture,更好的支持了异步特性,使用类似于回调方法的方式,使得consumer不需要再阻塞等待。

提取/修改包装的值

创建一个CompletableFuture对象,返回给你的客户端,只要当你的结果可用时,调用complete()方法,就会给所有等待这个future值的客户端解锁。

1
2
3
4
public CompletableFuture<String> ask() {
final CompletableFuture<String> future = new CompletableFuture<>();
return future;
}

所有调用ask().get()的线程将一直阻塞,直到future.complete("result")发生。

future.complete()只能调用一次,后续的重复调用会失效。

CompletableFuture.obtrudeValue(…)可以覆盖Future之前的值,小心使用

1
2
3
4
5
6
7
8
9
 /**
* If not already completed, causes invocations of {@link #get()}
* and related methods to throw the given exception.
*
* @param ex the exception
* @return {@code true} if this invocation caused this CompletableFuture
* to transition to a completed state, else {@code false}
*/
public boolean completeExceptionally(Throwable ex)

如果future还没有complete,那么调用此方法可以使get()方法抛出指定异常。

创建和获取CompletableFuture

除了手动创建,还可以通过下面的静态方法创建CompletableFuture:

1
2
3
4
static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier);
static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor);
static CompletableFuture<Void> runAsync(Runnable runnable);
static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor);

注意runAsync中的runnable不会返回任何值,所以得到的是Completable对象,如果想要异步处理并且得到返回结果,应该使用supplyAsync(…):

1
2
3
4
5
6
7
final CompletableFuture<String> future = CompletableFuture.supplyAsync(new Supplier<String>() {
@Override
public String get() {
//...long running...
return "42";
}
}, executor);

阅读全文