在这一行里,开发一个新的系统或者写一段新的代码,叫做挖坑

这个系统或代码,由后人来维护或修改,叫做填坑

如果说所有的系统和代码,都是坑,未免太过武断,这里面确有宝藏在其中,看过后如醍醐灌顶,大呼过瘾。

不过要说,绝大部分,或者说大部分,都是坑,大家可能都心里默默点头。

挖坑者可能并不是有心挖坑,但是填坑者却要小心填坑

所以写出这么一个填坑要诀,供大家参考。

(以上为本人不靠谱推测,不负任何法律责任)

我这篇博文,是不是也算是给自己挖坑

今天,发现hystrix监控的hystrix-dashboard里没有数据,这个hystrix-dashboard监控的是turbine聚合过的turbine.stream。

一开始怀疑是turbine使用的上游hystrix.stream没有数据,我手动获取hystrix.stream:

curl http://demo.com/hystrix.stream

发现是有数据的。
然后我看日志也没有发现什么异常。就把com.netflix的日志级别调整成debug看下:

java -jar -Dlogging.level.com.netflix=DEBUG hystrix-dashboard.jar

发现是获取到了上游的hystrix的host,全部状态为up,没有问题。

但是日志一直有这个提示:

Skipping event to catch up to end of feed and reduce latency

跳过了事件,以此来追上stream和减少延迟。

为啥要跳过事件,看下代码

com.netflix.turbine.monitor.instance

currentTime = System.currentTimeMillis();
if (skipLineLogic.get() && currentTime < skipProcessingUntil) {
    if (logger.isDebugEnabled()) {
        logger.debug("Skipping event to catch up to end of feed and reduce latency");
    }
} else {
    line = line.trim();
    if (line.length() != 0) {
        break;
    }
}

原来处理从上游获取的stream时,会比较stream里的时间戳和本地时间戳,发现延迟超过指定延迟时间就抛弃该事件。

延迟时间可配置:

turbine.InstanceMonitor.eventStream.skipLineLogic.latencyThreshold

默认为2500毫秒。

于是,我看了下这台服务器的时间:

date

发现比上游hystrix.stream的服务器快了10秒,于是就找运维同时更新了ntp,果然就没有问题了。

0x00

guava提供了Lists.transform这个方法来方便地对List进行转换,原型如下:

public static <F, T> List<T> transform(List<F> fromList, Function<? super F, ? extends T> function);

这个需求在实际开发中非常常见。
最常见的就是请求和响应的转换了。用户请求的Req和后台使用的Bean一定会有差异,同时给用户返回的Resp和后台使用的Bean也一定会有差异。造成这种差异的原因很多,比如请求和响应中有敏感数据,或者数据复杂等。

使用guava的Lists.transform可以方便的处理,比如:

    List<Integer> originList = Lists.newArrayList(1, 10);
    System.out.printf("originList is %s.\n", originList);

    List<Point> pointList = Lists.transform(originList, p -> {
        Point point = new Point(p, p);
        return point;
    });
    System.out.printf("pointList list is %s.\n", pointList);

输出:

originList is [1, 10].
pointList list is [java.awt.Point[x=1,y=1], java.awt.Point[x=10,y=10]].

上面这个代码将一个整数列表,转换成了一个X坐标和Y坐标相等的Point列表。

0x01

但是Lists.transform返回的这个List是有猫腻的!

如果你想对这个返回的List进行修改,如下:

for (Point point : pointList) {
    point.x = point.x + 1;
    point.y = point.y + 1;
}
System.out.printf("after change, pointList list is %s.\n", pointList);

输出:

after change, pointList list is [java.awt.Point[x=1,y=1], java.awt.Point[x=10,y=10]].

发现pointList里的值并没有发生改变!

如果你是用List.get()进行访问元素,将元素的hashcode打印出来。会发现,每次访问同一个元素获得的hashcode是不一样的,他们是不同的元素,所以修改的不是最初的对象!

Point point = pointList.get(0);
System.out.printf("pointList[0] is %s, hashcode is %d.\n", point, System.identityHashCode(point));
Point point2 = pointList.get(0);
System.out.printf("pointList[0] is %s, hashcode is %d.\n", point2, System.identityHashCode(point2));

输出:

pointList[0] is java.awt.Point[x=1,y=1], hashcode is 2101440631.
pointList[0] is java.awt.Point[x=1,y=1], hashcode is 2109957412.

0x10

[柯南BGM响起]
真相只有一个!
就是Lists.transform返回的List有问题!

仔细看下该方法的实现:

public static <F, T> List<T> transform(List<F> fromList, Function<? super F, ? extends T> function) {
    return (List)(fromList instanceof RandomAccess ? new Lists.TransformingRandomAccessList(fromList, function) : new Lists.TransformingSequentialList(fromList, function));
}

在这个例子中,传入的fromList出一个RandomAccess的List,所以返回一个Lists.TransformingRandomAccessList,这个类声明如下:

private static class TransformingRandomAccessList<F, T> extends AbstractList<T> implements RandomAccess, Serializable

这个类的override了多个方法,其中就有get方法,看下get方法的实现:

public T get(int index) {
    return this.function.apply(this.fromList.get(index));
}

即每次get的时候,都会调用一次传进来的Function。如果你的Function每次都new一个对象返回,那么每次get获取到的必然是不同的对象!

同理,迭代器的两个方法如下:

public Iterator<T> iterator() {
        return this.listIterator();
    }

    public ListIterator<T> listIterator(int index) {
        return new TransformedListIterator<F, T>(this.fromList.listIterator(index)) {
            T transform(F from) {
                return TransformingRandomAccessList.this.function.apply(from);
            }
        };
    }

每次迭代的时候也是调用了Function方法!

咿?如果每次都调用了Function方法,那么如果源List修改了,那么岂不是获取到数据也变了?试下:

Point point = pointList.get(0);
System.out.printf("pointList[0] is %s, hashcode is %d.\n", point, System.identityHashCode(point));
originList.set(0, 100);
Point point2 = pointList.get(0);
System.out.printf("pointList[0] is %s, hashcode is %d.\n", point2, System.identityHashCode(point2));

输出:

pointList[0] is java.awt.Point[x=1,y=1], hashcode is 2101440631.
pointList[0] is java.awt.Point[x=100,y=100], hashcode is 2109957412.

果然!
凶手原来是你!

不,凶手其实是我们!

0x11

说到这里,java8提供了类似方便的方法进行这样的处理:

List<Point> pointList = originList.stream().map(p -> {
    Point point = new Point(p, p);
    return point;
}).collect(Collectors.toList());

因为这个方法返回的是ArrayList的实例。

使用注解@Profile来标识加载的对象,配合@Component注解一起使用。
@Component类似的注解也可以一起使用,比如@Service@Controller@Repository@Configuration

如下,我们有两个HelloService的实现,一个是用于开发环境的,这个实现可以简单一点;另外一个是用于真正的生产环境的,要进行真正的处理,比如调用RPC,数据库处理等。

@Service
@Profile({"dev"})
public class HelloServiceDevImpl implements HelloService {
    // ...
}
@Service
@Profile({"pro"})
public class HelloServiceImpl implements HelloService {
    // ...
}

application.yml的配置,如果要使用HelloServiceDevImpl,要激活对应的profile,可以使用:

spring:
  profiles:
    active: dev

类似的正式环境的配置,可以使用:

spring:
  profiles:
    active: pro

如果对象依赖于多个profile,也可以传入多个:

@Service
@Profile({"pro", "A", "B"})
public class HelloServiceImpl implements HelloService {
    // ...
}

这样只有当pro、A、B者三个profile都激活的时候才会启用:

spring:
  profiles:
    active: pro,A,B

扩展开来,控制对象的加载的注解父类为@Conditional,使用该注解的话要自己编写一个类实现org.springframework.context.annotation.Condition来进行处理。
spring-boot提供了多个已经实现好的注解可以用,@Profile只是其中一个,其他类似的还有@ConditionalOnBean@ConditionalOnClass@ConditionalOnExpression@ConditionalOnJava@ConditionalOnProperty等。

ufw 全称 Uncomplicated Firewall ,从名称来看,不那么复杂的防火墙?。

提到linux的防火墙管理,就不得不提iptables,但是说实话,这个命令我实在记不住,用得少,同时参数多,比较复杂。
ufw就是用来解决这个问题的,他算是iptables的一个前端(frontend)。

## 安装ufw
在Debian系的电脑上直接执行以下命令:

   $ sudo apt-get update
   $ sudo apt-get install ufw

启用ufw

    $ sudo ufw enable

禁用ufw

    $ sudo ufw disable

查看默认规则

    $ grep 'DEFAULT_' /etc/default/ufw

Outputs:

DEFAULT_INPUT_POLICY="DROP"
DEFAULT_OUTPUT_POLICY="ACCEPT"
DEFAULT_FORWARD_POLICY="DROP"
DEFAULT_APPLICATION_POLICY="SKIP"

设置默认值

    ## 禁止所有传入,允许所有传出
    $ sudo ufw default allow outgoing
    $ sudo ufw default deny incoming

查看ufw的状态

    $ sudo ufw status
    $ sudo ufw status numbered

打开端口

    ## 打开12345端口,允许tcp协议和udp协议
    $ sudo ufw allow 12345

    ## 打开12345端口,允许tcp协议
    $ sudo ufw allow 12345/tcp

    ## 打开12345端口,允许udp协议
    $ sudo ufw allow 12345/udp

    ## 指定方向,in即传入,out即传出
    ## 如果不指定方向,默认是传入
    $ sudo ufw allow in 12345/tcp
    $ sudo ufw allow out 12345/tcp

关闭端口

    ## 禁止80端口
    $ sudo deny 80/tcp

打开或关闭连续端口

    ## 打开3000到5000之间的所有端口
    $ sudo ufw allow 3000:5000/tcp
    $ sudo ufw allow 3000:5000/udp

对ip做限制

    ## 允许指定ip访问指定端口
    $ sudo ufw allow from 1.2.3.4 to any port 443
    $ sudo ufw allow from 1.2.3.4 to any port 80
    ## 允许子网
    $ sudo ufw allow from 1.2.3.4/23 to any port 443
    $ sudo ufw allow from 1.2.3.4/23 to any port 80

    ## 禁止某ip的所有连接
    $ sudo ufw deny from 1.2.3.4
    ## 禁止子网
    $ sudo ufw deny from 1.2.3.4/24
    ## 禁止ip使用tcp协议访问某个端口
    $ sudo ufw deny from 1.2.3.4 to any port 22 proto tcp

删除操作

    $ sudo delete deny 80/tcp

重置

    $ sudo ufw reset   

重新加载

    $ sudo ufw reload

查看日志

    $ sudo tail -f /var/log/ufw.log