- 本书的阅读又搁置了许久,虽然感觉 Manning 出版社的这一
100 Mistakes系列从书的质量不是那么的高,但开了头还是继续从本书 40% 的位置往下。
开始要讲述到异常了,异常还是有必要认真对待的,比如- Java 中很容易被 CheckedException 弄得代码不整洁
- 缺少必要的参数检查,不舍得抛出异常,视异常为 Bug
- 不明确出现异常时后续如何处理,
- 或者是捕获而隐藏了异常致使定位错误变得更难。
Java 的主要异常大分类是Throwable
NullPointerException, 这恐怕是一个最常见的异常,Java 对一个对象是否能为 null 值没什么约束,甚至用 null 来表示业务上的空。比如说方法的参数与返回值,Java 都可以是 null 值,而在 Kotlin 中非明确可为 null 的时不能为 null Read More
├── Error
└── Exception
└── RuntimeException
继续阅读本书,编程语言处理数值都有可能出现问题,如溢出,整数的最大最小值不对称,Double.NaN 等。
由于 Java 学了 C,也用 0 开始的数字来表示 8 进制数,如 037, 010 分别是十进制的 31 和 8,这与现实不相符。因为如果你在纸上写下 037, 010, 几乎所有人(除了某些程序员)都会认为它们就是十进制的 37 和 10。但是 Java 表示 2 进制, 16 进制的方式没有问题的,如 0b10, 0x37。IntelliJ IDEA 看到使用 0 开头的 8 进制数会不建议那么使用. 8 进制数字的范围是 0~8, 所以 09 是错误的, 但是 Java 编译器似乎对此很陌生.
int a = 09;
IntelliJ IDEA 会提示Integer number too large, 编译器提示说java: ';' expected, 有点驴唇不对马嘴.
现在几乎没有必要使用 0 开始的 8 进制数的方式, 或许还有用的就是表示 Unix 下文件权限, 如
int fileMode = 0644
所以任何时候看到 0 开头的数字都必须仔细检视, 基本可以禁止使用这种方式 Read More
这几日在阅读 Manning 出版社的 《100 Java Mistakes and How to Avoid Them》, 其中列举的确实是一些容易带入到代码中的错误,不少还是通过代码 Review 或单元测试很难发现的问题。也有些看似很弱智,却可能是隐匿许久的定时炸弹,只等某一特定条件出现时即爆。
阅读的同时简单的作了笔记及少许联想,所以内容有些杂乱无条理。最前面介绍了一些静态代码分析工具,也有两个动态分析工具。本书目前还是 Manning 的 MEAP 体验版,未正式发售。一共讲了 100 个常见错误如何避免(例如,怎么用最新 Java(Java 9 -- Java 21) 语法, API 来改进),以及用静态分析工具,单元测试及早发现。
这是读完了 1/4 数量的记录,笔记开始 Read More
无论在何处,有多重任务要处理时,并发编程总是要得到考虑的。比如有 IO 等待时的并发或 CPU 密集型时的并行计算,并发通常是指在同一个 CPU 上按时间片轮换执行,并行是任务在不同的 CPU 上执行。能有效使用 CPU 多核的语言可以让线程运行在不同的核上实现并行,如果是启动的子进程能由操作系统运行在其他 CPU 核上。
回到 AWS Lambda 中的 Python 代码,如果是处理 IO 等待,使用多线程并发就行,大致的代码如下:with ThreadPoolExecutor(10) as executor:
以上代码在 AWS Lambda 中是可以运行的。
result = executor.map(task_function, task_inputs)
如果是 CPU 密集型的任务,用 Python 的多线程就要歇菜了,因为存在著名的 Python's GIL 的约束。 这时候就必须要考虑多进程并行的方式,同时应知晓当前选择的 Lambda 运行环境有多少个 CPU 内核,因为如果是单核的话再多进程也无济于事,没必要启动多于核心数的进程。 底下是本人上篇博客测试收集的不同 AWS Lambda 内存选择对应的 CPU 核心数,以及实际可用内存大小的关系表 Read More
AWS Lambda 允许设置
Debugging and error handling, 在 Lambda 出现异常,达到最大的重试次数后,把以下信息放到选择的 SNS 或 SQS 主题作为死信队列(DLQ - Dead Letter Queue),包括- 原始 Lambda 接收到的消息(基于 SNS 和 SQS 消息的总大小,可能会被截取,本人猜测,尤其是 Kinesis 的消息会比较大)
- 原始 Lambda 的 RequestId
- ErrorCode(三位数字的 HTTP 错误码)
- ErrorMessage, 即原 Lambda 抛出 Exception 的 getMessage() 信息,截取 1 KB 字符串
并且 Lambda 要使用 DLQ 的话还必须设置当前 Lambda 的 IAM role 有对于 SNS/SQS 主题相应的
sns:Publish和sqs:SendMessage权限。AWS Lambda 基本重试规则:对于 Kinesis 消息会无限重试直至消息过期,对于 SNS 或 SQS 的消息出现异常后会再重试两次。参考:AWS Lambda Retry Behavior。
而在重试次数用完后仍然失败,并且设置了 DLQ 的话就会发送消息到 DLQ 中去。 Read More
当 AWS Lambda 由 Kinesis 消息来触发时,一个 Kinesis Shard 会相应启动一个 Lambda 实例,比如说 Kinesis Stream 有 5 个 Shards, 那同时只会启动 5 个 Lambda 实例。那么把多条消息发送到同一个 Kinesis Shard 中去,这些消息会被如何消费呢?答案是按顺消息,不管这些消息是否被不同的 Lambda 实例处理。本文就是关于怎么去理解 https://aws.amazon.com/lambda/faqs/ 的下面那段话的内容:Q: How does AWS Lambda process data from Amazon Kinesis streams and Amazon DynamoDB Streams?
可以做几个试验,下面的代码可以保证消息总是被发送到同一个 Kinesis Shard,因为 PartitionKey 参数是一个常量 Read More
AWS Lambda 如何处理来自于 Amazon Kinesis 和 DynamoDB 的数据 The Amazon Kinesis and DynamoDB Streams records sent to your AWS Lambda function are strictly serialized, per shard. This means that if you put two records in the same shard, Lambda guarantees that your Lambda function will be successfully invoked with the first record before it is invoked with the second record. If the invocation for one record times out, is throttled, or encounters any other error, Lambda will retry until it succeeds (or the record reaches its 24-hour expiration) before moving on to the next record. The ordering of records across different shards is not guaranteed, and processing of each shard happens in parallel. 从 Kinesis 和 DynamoDB 单个 Shard 上的记录会被 Lambda 严格的按序处理。这意味着如果你送两条记录到相同的 Shard, Lambda 将会保证第一条记录成功处理后才会处理第二条记录。假如处理第一条记录时超时,或超过资源使用上限,或碰到任何错误, Lambda 将会不断重试直到成功(或记录在 24 小时后过期), 而后才会去处理下一条记录。跨 Shard 的记录不保证到达顺序,且是并行处理多个 Shard 来的记录。
一句话概要:对 Lambda 环境变量的任何改动都会引起一次 Lambda 的冷启动,大可放心在 handleRequest(...) 方法外使用环境变量。
从 AWS 上 Java Lambda 应用记要 中,我学到了 Lambda 的实例是跨请求共享的,所以为使用 Lambda 配置的环境变量时曾写出了下面复杂而多余的 AWS Lambda 代码:这段代码看起来很在理,既然 Lambda 实例是共享的,那么在必变环境变量之后就可能不会重新初始始化实例,所以在每次的请求方法中对比如果环境变量值改动了就重新用最新的配置值来初始化线程池。然而上面的代码结结实实是多余的,真是把 Lambda 想得太简单了,如果是很多环境变量岂不是逐一判断。 Read More1public class Handler implements RequestHandler<SNSEvent, String> { 2 3 private int threadPoolSize = getThreadPoolSizeFromEnv(); 4 private ExecutorService threadPool = Executors.newFixedThreadPool(threadPoolSize); 5 6 @Override 7 public String handleRequest(SNSEvent snsEvent, Context context) { 8 int configuredThreadPoolSize = getThreadPoolSizeFromEnv(); 9 if(configuredThreadPoolSize != threadPoolSize) { 10 threadPoolSize = configuredThreadPoolSize; 11 threadPool = Executors.newFixedThreadPool(threadPoolSize); 12 } 13 14 return "Hello Lambda"; 15 } 16 17 private int getThreadPoolSizeFromEnv() { 18 return Integer.parseInt(System.getenv().getOrDefault("threadpool_size", "50")); 19 } 20}
直接一句话:去掉 Log4J 的依赖,把 Slf4J, Logback, 和 log4j-over-slf4j 依赖加进来就行了,配置文件换成 logback.xml,这就完了,不要往下看了,都是些废话。当我们用 Serverless 命令sls create -t aws-java-maven -p hello-lambda创建的示例项目中直接用的是 Log4J 日志组件,而且也没用像 Slf4j, 或 Apache Common Logging 更上一层的通用日志框架。查看了几个 AWS 本身的组件 S3, SNS, 和 Kinesis 的 SDK, 它们内部是用的 Apache Common Logging 声明的日志变量import org.apache.commons.logging.LogFactory;
而我们自己的组件中通用日志组件是 Slf4j, 底层实现为 Logback, 所以我们希望在 Lambda 中使用 Logback 来写日志。
import org.apache.commons.logging.Logger;
private static final Log log = LogFactory.getLog(AmazonKinesis.class)
选用一个通用日志框架总是明智之举,因为一个项目经常杂糅了多种日志实现,使用 Slf4J 或 Apache Common Logging 可以把它们(Log4J, Logback, 或更多)输出到共同的目的地,并且有统一的日志输出接口。而我们认为通用日志框架还是 Slf4J 要先进些,所以我们在 Java Lambda 中的日志方案是 Slf4J + Logback,还需要把 Log4J 的日志桥接到 Slf4J 上来,再经由 Logback 输出。
回到前面创建的hello-lambda项目,看其中怎么用的 Log4J,先瞧瞧pom.xml文件怎么引入的 Log4J, 它是间接通过一个 AWS 定义的 Log4J Appender 引入的 Read More
AWS 的 Lambda 给了那些不想自己管理 EC2 服务器和配置负载人员很大的便利,所以 Lambda 被描述为 Serverless。真正的只关注业务就行,怎么调度,同时有多少个实例运行交给亚马逊去处理就是了。运行 Lambda 的环境也是亚马逊内部的 EC2 服务器,镜像是 Amazon Linux, 所以如果想运行系统命令,那是 Linux 的。Lambda 支持多种语言 Node.js, Python, C#(.net core), 还有 Java 8,我们就选择了 Java 8, 一开始还担心它与别的语言比起来会多大劣势,其实不然。而且所谓的 Java 8, 并非单指 Java 语言,而是指 JVM 平台,所以也可以用 Scala, Clojure, Groovy, Kotlin 来写。
Java 与脚本语言如 Node.js, Python 相比给人一个明显的感觉是启动慢,还有人用统计数据来比划 AWS Lambda cold start(pseudeo-)benchmark. 不过真不用担心,人家说的是冷启动,也就发生在部署后第一次执行启动会比较慢。要是我们的 Lambda 经常被调用,或每天触发比较集中,Lambda 在任务到来之前处理待续状态,就不会有冷启动的耗时过程。或者是每次任务要执行 3 分钟左右,又何必在乎毫秒级的冷启动时间。
说到底就是别理会下面的数据20ms startup time for Python ~ $0.04167540ms startup time for Node.js ~ $0.0833580ms startup time for Java ~ $0.1667
Lambda 实例重用
Java 的 Lambda 就是一个微服务,在首次触发时微服务冷启动有些慢,但一旦启动之后就可以用这个微服务实例接受后续的请求,只有在比较长的一段时间内未被触发 AWS 才会把这个微服务杀掉。 Read More- 学习 Clojure 一般是用
lein repl启动控制台, 每次启动lein repl都会发现它打开了一个端口, 例如➜ ~ lein repl
一直不清楚上面显示的
nREPL server started on port 57212 on host 127.0.0.1 - nrepl://127.0.0.1:57212
REPL-y 0.3.7, nREPL 0.2.12
Clojure 1.8.0
...........nrepl://127.0.0.1:57212该作何用, 而且端口号是随机的, 这其中定有文章.
幸好有 Google 帮忙, 查一查, 然后才意识到何不运行lein help repl来看看repl中到底有何玄机, 原来是:
<none> -> :startlein repl默认的行为, 见下. 未指定 :host 或 :port , :host 默认为 127.0.0.1, :port 为随机
:start [:host host] [:port port] 原来lein repl设计为为 CS 结构. :start 会启动一个 nREPL server, 并且立即启动一个 client 连接上它. :host 默认为 127.0.0.1, :port 默认为随机的
:headless [:host host] [:port port] 只启动 nREPL server, 等待别人来连接它, 相同的默认 :host, :port 规则. 也就是它不会进到 Clojure 控制台
:connect [dest] 连接一个 nREPL server. 目标服务器的指定有三种方式: HTTPS(S) URL, host:port, 或 port Read More