無標題文檔

rx-filewatcher.java,基于 NIO WatchService 的 Reactive 封装

嫌我啰嗦的话,直接看代码: https://github.com/mingcheng/rx-filewatcher.java

在写个小东西需要监控文件改变状态的时候,用到了 NIO 东西很方便也好用。但是,在使用 WatchService 的时候碰到了些问题。

首先,使用原生的接口必须开启个线程,然后使用个持续循环去读取 WatchEvent (这也是大部分教程描述这样子做的):

WatchKey key;
while ((key = watchService.take()) != null) {
    for (WatchEvent<?> event : key.pollEvents()) {
        //process
    }
    key.reset();
}

这样子做有几个不好的地方:一是开了个匿名线程,如果后期代码也有类似的情况,线程就不好处理,而且很容易无法监控到这个线程的状态;第二就是破坏了代码原有的业务逻辑,读到这里如果阅读代码的小伙伴不熟悉 WatchService 就很难理解。

其次,WatchService 的注册是一次性的,也就是说无法监控到子目录以下的情况,必须随时监控是否创建了子目录然后再对应 register 子目录 watch(这样子一个简单的功能就会搞得非常的复杂)。

我考虑到使用 Rx 重写这块的代码,然后翻阅 Github 的时候发现已经有人造了这个轮子。代码量不大,但核心功能是我想要的,只是最新更新已经在三年前了。

集成了 Helmbold 这个哥们的库以后,又发现了几个问题:

  1. 这个库是写在三年前的,还在用 Rx1 的库,和我目前的 Rx2 有部分的不兼容;
  2. dispose 部分有问题,dispose 以后线程没有终止还在运行;
  3. 获取到 WatchEvent 的 Context 只有文件名没有路径。

因为代码量本身就不是很多,同时考虑到这块还是需要以后自己维护,因此 fork 了他的代码自己做了些改造

对比以上的原生代码,使用 Rx 封装的代码就会干净和直观很多:

PathObservables
  .subscribeOn(Schedulers.io())
  .watchRecursive(Paths.get("some/directory/"))
  .subscribe(event -> System.out.println(event));

然后,基本上已经满足了我的需求。

顺便说下,WatchService 里面还有个坑,就是使用网络磁盘映射的时候,可能会造成 WatchEvent 会有丢失,在这里有详细的讨论,这是目前 NIO 已知的 Bug。如果你对文件状态监控要求实时性比较高,建议不要使用 WatchService 使用其他的方案。

相关的资源:

  1. https://github.com/mingcheng/rx-filewatcher.java
  2. https://docs.oracle.com/javase/tutorial/essential/io/notification.html

后记,原作者已经 merge 了我的 pull requests

Arch Linux 下安装 EM7345 4G 网卡小记

💊 后记(10/18):人生苦短,后来发现安装完 Manjaro Linux 以后,这个 4G 网卡已经都配置好可以直接使用。

-

一直使用台 ThinkPad X1 Carbon 用来当做开发用机,使用方面都很稳定系统方面选择安装了 Arch Linux 。

由于硬件方面并不是算很新,所以系统支持的硬件都很顺利。还注意到 X1 Carbon 支持 4G 网卡,手头上刚好有一张联通的流量卡,考虑能否用起来。但是打开了盖板以后发现只是预留了插槽和天线,并没有 4G 网卡模块。

https://friable.rocks/_/2018_09_20/1537424625.jpg

根据 ArchLiux 的文档,淘宝上找了家靠谱的卖家,名为  Sierra EM7345 的模块很便宜,原装拆机的只要一百多,而且对 Linux 的支持很好,于是下单。

硬件安装好了以后,启动使用 # lsusb 就能看到硬件了。

然后安装对应的软件包:

# pacman -S usbutils usb_modeswitch modemmanager mobile-broadband-provider-info

但是启动 ModemManage 的时候发现了问题,有报错信息:

ModemManager[13625]: <info>  Couldn't check support for device '/sys/devices/pci0000:00/0000:00:14.0/usb2/2-1': not supported by any plugin
ModemManager[13625]: <info>  Couldn't check support for device '/sys/devices/pci0000:00/0000:00:19.0': not supported by any plugin
ModemManager[13625]: <info>  Couldn't check support for device '/sys/devices/pci0000:00/0000:00:1c.1/0000:04:00.0': not supported by any plugin

好在 ArchLinux 的 Wiki 上有对应的信息说明,对应的应该是 udev 的权限问题。于是增加相应的配置文件,发现并不成功。然后搜索了一圈,并没有对应的解决方案。

后来,在 rules.d 目录中发现了另外个文件 /lib/udev/rules.d/77-mm-sierra.rules 里面虽然没有对应的 Vendor, 但是从注释可以看出来这是 Sierra 相对应的 udev 权限配置文件,于是将配置加入到 /etc/udev/rules.d 其中。

注意,直接修改 /lib/udev/rules.d/ 目录的 rule 文件可能在下次更新的时候被覆盖掉。

ACTION!="add|change|move|bind", GOTO="mm_sierra_end"
SUBSYSTEMS=="usb", ATTRS{idVendor}=="1199", GOTO="mm_sierra"
GOTO="mm_sierra_end"

LABEL="mm_sierra"

# @see https://wiki.archlinux.org/index.php/USB_3G_Modem
# @see https://support.lenovo.com/us/en/solutions/pd031021
# @see https://www.freedesktop.org/software/ModemManager/api/1.8.0/ref-overview-modem-filter.html

ATTRS{idVendor}=="1199", ATTRS{idProduct}=="a001", ENV{FILTER_RULE_TTY_ACM_INTERFACE}="1"
ATTRS{idVendor}=="1199", ATTRS{idProduct}=="a001", ENV{MM_FILTER_RULE_CDC_WDM}="1"
ATTRS{idVendor}=="1199", ATTRS{idProduct}=="a001", ENV{MM_FILTER_RULE_EXPLICIT_WHITELIST}="1"
ATTRS{idVendor}=="1199", ATTRS{idProduct}=="a001", ENV{MM_FILTER_RULE_NET}="1"
ATTRS{idVendor}=="1199", ATTRS{idProduct}=="a001", ENV{MM_FILTER_RULE_TTY_BLACKLIST}="1"
ATTRS{idVendor}=="1199", ATTRS{idProduct}=="a001", ENV{MM_FILTER_RULE_TTY_DEFAULT_ALLOWED}="1"
ATTRS{idVendor}=="1199", ATTRS{idProduct}=="a001", ENV{MM_FILTER_RULE_TTY_DRIVER}="1"
ATTRS{idVendor}=="1199", ATTRS{idProduct}=="a001", ENV{MM_FILTER_RULE_TTY_MANUAL_SCAN_ONLY}="1"
ATTRS{idVendor}=="1199", ATTRS{idProduct}=="a001", ENV{MM_FILTER_RULE_TTY_PLATFORM_DRIVER}="1"
ATTRS{idVendor}=="1199", ATTRS{idProduct}=="a001", ENV{MM_FILTER_RULE_TTY_WITH_NET}="1"
ATTRS{idVendor}=="1199", ATTRS{idProduct}=="a001", ENV{MM_FILTER_RULE_TTY}="1"
ATTRS{idVendor}=="1199", ATTRS{idProduct}=="a001", ENV{MM_FILTER_RULE_VIRTUAL}="1"

LABEL="mm_sierra_end"

然后,刷新 udev 的配置,并通知 kernel 启用新的权限:

# udevadm control -R && udevadm trigger

再看下对应设备文件文件的 rule 是否已经启用成功:

# udevadm info /dev/cdc-wdm0

对应的输出

P: /devices/pci0000:00/0000:00:14.0/usb2/2-4/2-4:1.0/usbmisc/cdc-wdm0
N: cdc-wdm0
E: DEVNAME=/dev/cdc-wdm0
E: DEVPATH=/devices/pci0000:00/0000:00:14.0/usb2/2-4/2-4:1.0/usbmisc/cdc-wdm0
E: FILTER_RULE_TTY_ACM_INTERFACE=1
E: ID_MM_CANDIDATE=1
E: ID_MM_DEVICE_PROCESS=1
E: MAJOR=180
E: MINOR=0
E: MM_FILTER_RULE_CDC_WDM=1
E: MM_FILTER_RULE_EXPLICIT_WHITELIST=1
E: MM_FILTER_RULE_NET=1
E: MM_FILTER_RULE_TTY=1
E: MM_FILTER_RULE_TTY_BLACKLIST=1
E: MM_FILTER_RULE_TTY_DEFAULT_ALLOWED=1
E: MM_FILTER_RULE_TTY_DRIVER=1
E: MM_FILTER_RULE_TTY_MANUAL_SCAN_ONLY=1
E: MM_FILTER_RULE_TTY_PLATFORM_DRIVER=1
E: MM_FILTER_RULE_TTY_WITH_NET=1
E: MM_FILTER_RULE_VIRTUAL=1
E: SUBSYSTEM=usbmisc
E: USEC_INITIALIZED=10423134

配置生效,kernel 的配置看起来一切正常。

这时候启动 ModemManage 还是发现有错误信息,但是过了一段时间以后对应的 Modem 已经是 enable 可用状态(这点百思不得解,可能是 4G 网络连接需要初始化比较慢)。

Sep 20 14:25:44 x1-carbon ModemManager[2047]: opening device...
Sep 20 14:25:44 x1-carbon ModemManager[2047]: [/dev/cdc-wdm0] Read max control message size from descriptors file: 512
Sep 20 14:25:44 x1-carbon ModemManager[2047]: <info>  Couldn't check support for device '/sys/devices/pci0000:00/0000:00:14.0/usb2/2-1': not supported by any plugin
Sep 20 14:25:50 x1-carbon ModemManager[2047]: <info>  Modem: state changed (unknown -> disabled)
Sep 20 14:25:50 x1-carbon ModemManager[2047]: <info>  Modem /org/freedesktop/ModemManager1/Modem/0: state changed (disabled -> enabling)
Sep 20 14:25:54 x1-carbon ModemManager[2047]: <info>  Modem /org/freedesktop/ModemManager1/Modem/0: state changed (enabling -> enabled)
Sep 20 14:25:54 x1-carbon ModemManager[2047]: <info>  Modem /org/freedesktop/ModemManager1/Modem/0: 3GPP Registration state changed (unknown -> registering)
Sep 20 14:25:54 x1-carbon ModemManager[2047]: <info>  Modem /org/freedesktop/ModemManager1/Modem/0: 3GPP Registration state changed (registering -> home)
Sep 20 14:25:54 x1-carbon ModemManager[2047]: <info>  Modem /org/freedesktop/ModemManager1/Modem/0: state changed (enabled -> registered)

然后在控制台下使用 mmcli 获取相关的信息,具体的 mmcli 可以参考这里的文档。发现了一个不大不小的问题,就是 sim 卡的日期还是 20040101 ,很明显没有和网络同步(后来发现这块并不重要)。

$ mmcli -m 0 --simple-status

/org/freedesktop/ModemManager1/Modem/0
  -------------------------
  Status |          state: 'connected'
         | signal quality: '67' (recent)
         |          bands: 'unknown'
         |    access tech: 'lte'
  -------------------------
  3GPP   |   registration: 'home'
         |  operator code: '46001'
         |  operator name: 'CHN-UNICOM'
         |   subscription: 'unknown'

再尝试启动 modem-manage-gui 看其信息,过一会发现已经注册了网络并能获取对应的信息。

https://friable.rocks/_/2018_09_20/1537427550.png

硬件方面的配置没有问题,那么软件方面的设置就简单多了。然后使用 Gnome 的网络配置,配置 APN 已经相关的网络参数,过一会就能看到久违的信号了。

https://friable.rocks/_/2018_09_20/1537427570.png

总结下,这个问题困扰了我一个晚上的时间,然后也走了很多的弯路。

主要还是在硬件资源的权限方面,这块还是要多看 Arch 的 Wiki 以及对应软件的 Manual(开源社区的锅都扔给用户了)。

相关的参考链接:

PS,有什么靠谱的联通流量卡推荐下?

- eof -

集成 Dubbo Spring Boot 时的 ZooKeeper 版本问题

微服务节点使用 Spring Boot 会方便很多,在搭建 Spring Boot 的时候碰到了个不大不小的问题,在这里记录下。

主要情况是配置好了 Dubbo Spring Boot 启动 Provider 节点的时候发现异常,抛出了两个错误:

Caused by: org.apache.zookeeper.KeeperException$UnimplementedException: KeeperErrorCode = Unimplemented for ...
Caused by: java.lang.IllegalStateException: KeeperErrorCode = Unimplemented for ...

检查堆栈发现是 Dubbo 建立 ZooKeeper 链接的时候,就直接抛出了异常:

// from com.alibaba.dubbo.registry.zookeeper.ZookeeperRegistry.doRegister(ZookeeperRegistry.java:116) ~[dubbo-2.6.2.jar:2.6.2]

protected void doRegister(URL url) {
    try {
        this.zkClient.create(this.toUrlPath(url), url.getParameter("dynamic", true));
    } catch (Throwable var3) {
        throw new RpcException("Failed to register " + url + " to zookeeper " + this.getUrl() + ", cause: " + var3.getMessage(), var3);
    }
}

但是 ZooKeeper 的服务器配置是正常的,百思不得:

$ echo stat | nc localhost 2181
Zookeeper version: 3.4.13, built on 06/29/2018 04:05 GMT

然后继续查看 KeeperException$UnimplementedException 异常的定义,

    public static class UnimplementedException extends KeeperException {
        public UnimplementedException() {
            super(Code.UNIMPLEMENTED);
        }
    }

对应的调用:

// @from org.apache.zookeeper.ZooKeeper.create
public String create(final String path, byte data[], List<ACL> acl,
        CreateMode createMode, Stat stat, long ttl)
        throws KeeperException, InterruptedException {
    final String clientPath = path;
    PathUtils.validatePath(clientPath, createMode.isSequential());
    EphemeralType.validateTTL(createMode, ttl);

    final String serverPath = prependChroot(clientPath);

    RequestHeader h = new RequestHeader();
    setCreateHeader(createMode, h);
    Create2Response response = new Create2Response();
    if (acl != null && acl.size() == 0) {
        throw new KeeperException.InvalidACLException();
    }
    Record record = makeCreateRecord(createMode, serverPath, data, acl, ttl);
    ReplyHeader r = cnxn.submitRequest(h, record, response, null);
    if (r.getErr() != 0) { // 这里抛出的异常
        throw KeeperException.create(KeeperException.Code.get(r.getErr()),
                clientPath);
    }
    if (stat != null) {
        DataTree.copyStat(response.getStat(), stat);
    }
    if (cnxn.chrootPath == null) {
        return response.getPath();
    } else {
        return response.getPath().substring(cnxn.chrootPath.length());
    }
}

那么基本上可以判定 1、是 ZooKeeper 本身链接的问题,和 Dubbo 没有关系;2、实际上 ZooKeeper 本身的服务已经连接上,但 makeCreateRecord 方法调用出现了异常。

那么,可以得出结论是 Java 端和 ZooKeeper 服务端出现了通讯问题。然后发现日志上 jar 包的版本是 zookeeper-3.5.3-beta.jar ,同时查看了下 Manifest 内容如下:

Implementation-Title: org.apache.zookeeper
Implementation-Version: 3.5.3-beta
Implementation-Vendor: The Apache Software Foundation

然后再运行客户端查看了下服务器的版本,两者版本不一致,突然觉得应该是版本的问题。

$ echo stat | nc localhost 2181
Zookeeper version: 3.4.13, built on 06/29/2018 04:05 GMT

很明显 Java 端的版本比服务端的版本要新,那么考虑使用和服务端同个版本的 jar 包试试。

修改对应 build.gradle 的 dependencies 如下,不要纳入 dubbo-spring-boot-starter 提供的 ZooKeeper 的 jar 包

dependencies {
    // ...
    compile('com.alibaba.boot:dubbo-spring-boot-starter:0.2.0') {
        exclude(module: 'org.apache.zookeeper')
    }
    compile 'org.apache.zookeeper:zookeeper:3.4.13'
}

然后再运行 gradle clean bootRun -x test 发现 Dubbo 正常启动,问题解决。

这个问题有点坑,同时很难发现,我又查了下对应的资料。官方其实已经有对应说明,简单的说就是 ZooKeeper 3.5.x 和 ZooKeeper 3.4.x 有不兼容的情况。

而回过头来看 dubbo-spring-boot-starter 包的 pom.xml 定义,对应的 ZooKeeper 这块的引用是这样子的:

<dependency>
    <groupId>org.apache.zookeeper</groupId>
    <artifactId>zookeeper</artifactId>
</dependency>

没有指定版本,也就是默认是取 Maven 库的最新版本,目前是 3.5.4-beta 自然对应本地版本 3.4.13 就有冲突了(竟然不向下兼容,坑)。

相关讨论,看来被坑的不止我: https://stackoverflow.com/questions/35734590/apache-curator-unimplemented-errors-when-trying-to-create-znodes

最后顺便说一句,如果有用到 Spring Cloud 相关的 Zookeeper 组件,也要留个心眼:

dependencies {
    // ...
    compile('org.springframework.cloud:spring-cloud-starter-zookeeper-config') {
        exclude group: 'org.apache.zookeeper', module: 'zookeeper'
    }
    compile('org.springframework.cloud:spring-cloud-starter-zookeeper-discovery') {
        exclude group: 'org.apache.zookeeper', module: 'zookeeper'
    }
    compile 'org.apache.zookeeper:zookeeper:3.4.13'
}

这样子就能保证统一引用的是指定版本的 ZooKeeper 的 jar 包了。

总结下,dubbo-spring-boot-starter 项目目前相对来说还是比较新,相关的文档还是没跟上,但是已经能够日常和生产环境使用了,还是推荐使用简化配置提高些开发效率。

- eof -

我的照片

嗨!我叫「明城」,八零后、码农、宁波佬,现居杭州。除了这里,同时也欢迎您关注我的 GitHubTwitterInstagram 等。

这个 Blog 原先的名字叫 Gracecode.com 、现在叫 「無標題文檔」 。 要知道作为码农取名是件很难的事情,所以不想在取名这事情上太费心思。

作为八零后,自认为还仅存点点可能不怎么被理解的幽默感,以及对平淡生活的追求和向往。 为了避免不必要的麻烦,声明本站所输出的内容以及观点仅代表个人,不代表自己所服务公司或组织的任何立场。

如果您想联系我,可以发我邮件 `echo bWluZ2NoZW5nQG91dGxvb2suY29tCg== | base64 -d`

分类

搜索

文章