
Kafka 消息的 Producer 在调用
producer.send()方法发送消息时会先把消息放到本地缓冲中,然后由 Kafka 网络线程从缓冲中提取消息再送到 Kafka 代理上去。本地缓冲区大小由buffer.memory来配置,默认为 32M(32 * 1024 * 1024L)。如果发消息到网络慢于提交消息到缓冲区的话,缓冲区就可能会满就无法接受新的消息,这时候就要依照block.on.buffer.full设置是否暂停还是抛出异常,默认为暂停producer.send();暂停时间由max.block.ms决定,默认为 60 秒。producer.send()返回一个Future<RecordMetadata>, 也就是每次调用send()方法在缓冲区满后要等待 60 秒才能获得结果(异常)。这里的关系是
send()--a-->缓冲区--b-->发送到 Kafka 代理,自然要在a与b之间进行流量控制,如果b太慢,缓冲区满的话必须把a放慢下来。如果能基于缓冲区已使用大小来放缓a也是也行的,留待以后进行研究。本文提供另一种实现参考,为 Producer 配置一个Interceptor能够大致统计多少消息提交到缓冲区,多少消息从缓冲区取出。Kafka 的所有配置项常量可以在这个页面 https://kafka.apache.org/0100/javadoc/constant-values.html 找到。对 `interceptor.classes` 的解释是:可以为 Producer 配置一个或多个 Interceptor(需要实现 ProducerInterceptor)。另外 Consumer 也有自己的 Interceptor(实现 ConsumerInterceptor)。 Read More
上一篇 Linux 下配置滚动日志之 logrotate, 介绍了定时服务 logrotate 的方式对日志进行滚动,删除旧归档。logrotate 是目前流行 Linux 发行版内置的定时服务,默认每日根据配置文件来滚动日志文件。那么它可能有一个弊端就是,如果每日增长的日志超大,同时会暴盘,那么就必须调整 logrotate 这个 cron 定时服务为每小时,或自定义的 cron 表达式来控制。
而本文所介绍的 rotatelogs 工具(与 logrotate 名称太过相似) 是采用管道操作的方式来控制日志的滚动,可以基于实时监控的日志文件大小来滚动日志,也可以配置像 logrotate 来定时滚动日志。rotatelogs 是出自于 Apache HTTP Server 家族的,它被用于 Apache HTTP Server 的就错误日志,访问日志的滚动控制。
类似于 rotatelogs 的工具不有一个老旧的 cronolog,也是应用管道操作控制日志,它早已无人问津,最近更新在五年前,且功能很弱,只带滚动,不能清理旧归档,磁盘空间占用仍然是无上限。 Read More

日志是个好东西,便于定位历史问题,但记录太多,不滚动,不除旧总暴盘的时候。如果是用日志框架输出的日志,像 Log4j 或 Logback 通过选择具有滚动特性的 Appender 就能实现日志的滚动,并删除旧的归档日志文件。但也有在程序当中难以控制的日志输出文件,这用的话必须采取事后补救措施,程序尽管往一个日志文件里写,由另一个程序来对该日志文件进行归档,清理操作。
与此相关的工具,我们可以找到以下几个
- logrotate, 如今的多数 Linux 发布版都自带了,感觉有一种主场优势。github 上 logrotate/logrotate 仍活跃着
- newsyslog, FreeBSD 和 Mac 系统自带,应该不常用。Mac OS 下可以看下配置文件
/etc/newsyslog.conf - cronolog, 原本的官网 www.cronolog.org 全是日文了,找到它的快照 fleible web log rotation, github 上 fordmason/cronolog 最近更新是五年前
- rotatelogs, 出自于 Apache HTTP 项目, Apache HTTP server 用它滚动访问和错误日志
本人最为推崇使用第一个工具
logrotate, 因为多数 Linux 系统自带,不像 cronolog 和 rotatelogs 需要额外安装。它也有着更完备的功能,下面慢慢领略logrotate 的工作机制
Linux 下默认有一个每日执行的 Cron Job,配置在
/etc/cron.daily/logrotate,文件内容为(以 centos7 为例) Read More
什么是线程栈
继续纠缠 Java 9 的新特性,仍然是一个边角料,即 Java 9 增加了对线程栈遍历的 API。那么什么是线程栈,JVM 在创建每一个线程的同时都会创建一个私有的虚拟机栈,每一桢代表着一个方法调用,每次方法的调用与退出意味着压栈与出栈。每一桢上有局部变量,操作数常量引用等信息,这也是为什么局部变量是能最快被销毁的对象。过深的栈(比如过多的递归调用) 会出现我们程序员赖以生存的 StackOverflow。
浅显些说,线程栈就是通常我们捕获到异常后,用e.printStackTrace()看到自 main 方法追溯到当前方法的调用。例如:java.lang.RuntimeException: stack
调用层次是 main() 调用 m1(), m1() 调用 m2(), m2() 中的代码如下
at cc.unmi.TestStackWalking.m2(TestStackWalking.java:15)
at cc.unmi.TestStackWalking.m1(TestStackWalking.java:10)
at cc.unmi.TestStackWalking.main(TestStackWalking.java:6)1try { 2 throw new RuntimeException("stack"); 3} catch (Exception ex) { 4 ex.printStackTrace(); 5}
上面输出的每一行就是一个栈桢,输出了当前类名,方法名,代码行号。 Read More
JUnit 5 刚出来那时,也就是第一个版本 5.0.0 时,还不能很好的支持 Mockito 的测试,因为 Mockito 没能跟得那么紧密。那时候 JUnit 5 只能试验性的提供了一个极不正式的 com.example.mockito.MockitoExtension, 看那包名就知道不是来真的,所以决定再等。JUnit 5 不再原生支持 JUnit 4 的 Rule,一切都将是 Extension,那也是要求 Mockito 能够与之俱进。现在等来了,JUnit 5 进化到了 5.2.0, Mockito 也早已有了一个单独的模块mockito-junit-jupiter来迎接它。
在 Mockito 2.1.0 的 What's new in Mockito 2 中记述了 JUnit 5 为 Mockito 2 开发了一个 MockitoExtension。追溯到 Mockito 2 的 Release Notes, 我们发现 Mockito 2 官方最早引入 MockitoExtension 的版本是 2.16.3(2018-03023)。我对 Mockito 对 JUnit 5 支持的最新更新是从这个 Pull Request MockitoExtension for JUnit5 得知的。
一句话讲就是现在的 Mockito 2 有原生态的 MockitoExtension 来支援 JUnit 5, 可以非常放心可靠的让 JUnit 5 和 Mockito 2 一起稳定工作。因此前面那个包名带example字样的 MockitoExtension 链接也就无效了。 Read More
还是从 OSChina 网站上得知 Clojure 1.9 在 2017 年 12 月发布的,时值这么久才开始真正去了解一下这个新版本。距离上一个版本 1.8 的发布时间(2016 年 1 月), 大概两年才出一个版本,而且总的说来 Clojure 1.9 并没有带来多大惊喜。
唯一能带来点喜气的也就是 Clolure 有了自己的命令行工具了,再也无需寄身于 Leiningen(一个 Clojure 构建工具,相当于 sbt 之于 Scala) 的篱笆之下了。以 Mac OS 平台为例,以前试图用 brew 来直接安装clojure的时候会提示找不到clojure, 建议安装leiningen.$ brew install clojure
在 Clojure 1.9 出来之前,上面的命令会得到如下提示信息Error: No available formula with the name "clojure"
所以那时候不得不用 Read More
Clojure isn't really a program but a library managed as part of a
project and Leiningen is the user interface to that library. To install Clojure you should install Leiningen:
brew install leiningen
and then follow the tutorial:
https://github.com/technomancy/leiningen/blob/stable/doc/TUTORIAL.md
前脚研究完 Mockito 中被 Mocked 的对象属性及方法的默认值, 虽然目今更多的是拥抱着 Mockito, 但总有时对 JMockit 也会挤眉弄眼,谁叫 JMockit 无所不能呢!被 Mockito 的 Mock 对象方法的默认返回值洗脑之后,进而觉察出 JMockit 应该有同样的实现方式。
经过类似的测试,这里不详细列出测试过程,只是在基于前篇的测试中加入 JMockit 的依赖,最新版是 1.36。测试类 MyClassTest 中使用1@Mocked 2private MyClass myClass;
来构造 MyClass 的 mock 对象 myClass, 其余代码是一样的。相关代码请前往上篇 Mockito 中被 Mocked 的对象属性及方法的默认值 中找。 使用 JMockit 后跑出来的效果如下: Read More- 昨天刚刚侍弄完 Spring 下基于自定义注解拦截方法调用, 现在试下纯 AspectJ 的方式来打造,因为不是每一个项目都是 Spring。这次要推到 5 年前试验过用 javac 命令行编译的方式织入方面, 见 AspectJ 基于自定义的方法注解来拦截方法, 这次着重在用 aspectj-maven-plugin 插件的方法来织入 AspectJ 方面。
基本上代码还是昨天的,需求还是一样的:
被 @LogStartTime 注解的方法在进入该方法时记录当前时间在 ThreadLocal 中,并能根据 @LogStartTime 的属性值决定处理逻辑
因为 Java5+ 之后 AspectJ 可以写成 Java 类加注解的方式,*.aj 文件一般都没太大必要了,所以可以和 Spring AOP 共用一个 @Aspect 注解的方面代码MethodStartAspect。
我们将采用编译器织入,因此项目依赖只需要一个org.aspectj:aspectjrt:1.8.0, 它也不会引入别的组件。同样我们从 Main 方法和测试用例两方面来验证实现的效果,下面是整个测试项目的布局,以及依赖,除掉单元测试的其时就只需要一个 jar 包。 Read More
上一篇 JUnit 5 快速上手(从 JUnit 4 到 JUnit 5) 介绍了如何在一个项目中同时使用 JUnit 4 和 JUnit 5。现在来开始了解 JUnit 5 的新特性. 我们现在的项目基本是用 Maven 来管理依赖,在 Maven 项目中如何引入 JUnit 5 可以参考官方例子 junit5-maven-consumer. 我们知道 JUnit 5 包括三个模块,不用 JUnit 4 的话只要 Platform 和 Jupiter, 而 Jupiter Maven 模块本身依赖于 JUnit Platform, 因此应用 JUnit 5 的项目 Maven 配置就是1<dependency> 2 <groupId>org.junit.jupiter</groupId> 3 <artifactId>junit-jupiter-engine</artifactId> 4 <version>5.0.0</version> 5 <scope>test</scope> 6</dependency>
这样在当前的 IntelliJ IDEA(2017.2.4) 可以执行 JUnit 5 的测试用例。但要让 Maven 找到 JUnit 5 的测试用例,还得在pom.xml中加上 Read More
在研究 JUnit 5 新特性的时候,学习到其中有一节 Test Instance Lifecycle, 才意识到对 JUnit 的理解一直存在一个误区,以为 JUnit 是以测试类为一个生命周期的,其实不然。不管是 JUnit 5 还是 JUnit 4 或更早的版本,JUnit 都是以测试方法为一个独立的生命周期。
只是到了 JUnit 5 提供了方法来把生命周期由方法改为测试类,对于单个测试类可以使用注解
@TestInstance(Lifecycle.PER_CLASS)来指定用一个测试实例来跑所有的测试方法,这就意味着测试类中的成员变量只被初始化一次。@TestInstance的 Lifecycle 默认是 PER_METHOD, JUnit 4 就是 PER_METHOD, 而且是不能改的。如果在 JUnit 5 中改变为 PER_CLASS, 恐怕反而会出许多乱子,每个测试方法本就该是完全独立的。比如在同一个类中多个测试方法使用了同一个实例变量的情况下,总会用一个
@After方法来复位该实例变量,现在才知道那是多余的。像下面的代码1public class CalculatorTest { 2 3 private int number = 100; 4 5 @Test 6 public void test1() { 7 System.out.println(number); 8 number = 200; 9 } 10 11 @Test 12 public void test2() { 13 System.out.println(number); 14 number = 300; 15 } 16}