Lenshood

Software Developer @ThoughtWorks

0%

先跑起来

不说什么具体的知识,我们先一步步的,来写个最简单的测试,并且让他啊跑起来,看看 rust 下的测试是什么样子的: 1. 创建个 lib 工程:cargo new simplest-test --lib 2. 在 src/lib.rs 里面,rust 已经自动帮我们写下了如下代码:

1
2
3
4
5
6
7
#[cfg(test)]
mod test {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}
3. 运行一下:cargo test
1
2
3
4
5
6
7
8
9
10
11
12
13
14
   Compiling simplest-test v0.1.0 (/User/xxx/simplest-test)
Finished dev [unoptimized + debuginfo] target(s) in 5.91s
Running target/debug/deps/simplest_test-c430fbaec5f55b85

running 1 test
test tests::it_works ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

Doc-tests simplest-test

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

经过上述三步,我们已经创建了一个最简单的测试工程,并且运行了自带的测试。

在代码层面,测试本身无需多说,结构上我们看到,与业务代码不同,测试在 module 上增加了 attribute: #[cfg(test)],在测试方法上增加了 attribute: #[test]。 - #[cfg(test)]:配置编译条件,只有在 test 模式下,被标记的代码块才会被编译(换句话说,它确保 release 中不包含测试代码) - #[test]:被标记的方法将被视为测试来执行

在 output 中,还包含了两部分“running x test”,第一部分是我们已有的测试,第二部分为文档测试,本文暂不涉及。

Read more »

在如今这样一个张口分布式,闭口微服务的软件开发趋势下,多实例似乎已经不是某种选择而是一个无需多说的基本技术要求了。

多实例为我们带来稳定性提升的同时,也伴随着更复杂的技术要求,原先在本地即可处理的问题,全部扩展为分布式问题,其中就包含我们今天会聊到的多实例同步即分布式锁问题。JDK 提供的锁实现已经能够非常好的解决本地同步问题,而扩展到多实例环境下,Redis、ZooKeeper 等优秀的实现也使得我们使用分布式锁变得更加简单。

其实对于分布式锁的原理、分布式锁的 Redis 实现、ZK 实现等等各类文章不计其数,然而只要简单一搜就会发现,大多数文章都在教大家 Redis 分布式锁的原理和实现方法,但却没有几篇会写什么实现是好的实现,是适合用于生产环境,高效而考虑全面的实现。这将是本文讨论的内容。

Read more »

依赖倒置就是每一个实现都要抽一个接口出来吗?

本文的标题实际上来自于一次与项目上同事中午吃饭时的讨论:

A: 我觉得我们现在的抽象有点多,infra 层里面每一个类都抽取了接口,这些被调用的类多半只有一个实现, 我们是不是做的太细了?

B: 从依赖倒置的角度讲,domain 层和 service 层并不应该直接调用 infra 层的实现,因此我们确实是需要每一个实现都抽一个接口出来。

A: 那依赖倒置就是每一个实现都要抽一个接口出来吗?

B: 这个...

看来小伙伴 A 不经意间触碰到了 S.O.L.I.D. 的深水区...

相比于单一职责、开闭、接口隔离等原则,依赖倒置与里氏替换类似,属于更偏向操作指导的一类原则,比如从依赖倒置的定义来看:

依赖倒置:高层模块不应直接依赖低层模块,他们都应该依赖于彼此间的抽象。

以开发的角度理解:高层不要直接调用低层,而是调用抽取出来的接口。

那这么说,依赖倒置就是每一个实现都要抽一个接口出来吗?

为了解释这个问题,我们尝试来提出一个新的问题:为啥要依赖倒置?

Read more »

Caches

举例

1
2
3
4
5
6
7
8
9
10
11
LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
.maximumSize(1000)
.expireAfterWrite(10, TimeUnit.MINUTES)
.removalListener(MY_LISTENER)
.build(
new CacheLoader<Key, Graph>() {
@Override
public Graph load(Key key) throws AnyException {
return createExpensiveGraph(key);
}
});
Read more »

集合工具类

任何体验过 JDK 集合框架的人都了解且喜爱java.util.Collections中的工具。 Guava 在这方面提供了更多工具:应用于所有集合的静态方法。 而且这些是 Guava 最流行也最成熟的部分。

与特定 interface 相关联的方法以相对直观的规则来分组:

Interface JDK or Guava? Corresponding Guava utility class
Collection JDK Collections2
List JDK Lists
Set JDK Sets
SortedSet JDK Sets
Map JDK Maps
SortedMap JDK Maps
Queue JDK Queues
Multiset Guava Multisets
Multimap Guava Multimaps
BiMap Guava Maps
Table Guava Tables

在寻找变换、过滤等工具吗? 它们在我们的功能性语法下的 functional 文章里.

Read more »

新集合类型

Guava 引入了许多 JDK 未包含但我们却广泛使用的的新集合类型。他们全都设计为可以和 JDK 的集合框架友好的共存, 而不是硬塞入 JDK 的集合类抽象中。

一般来说,Guava 的集合会精确地按照 JDK interface 所定义的契约来实现。

Read more »

分布式锁

在传统单体应用中,我们用锁来保证非幂等操作在并发调用时不会产生意外的结果。最常见的应用场景应该就是 MySQL 用锁保证写表时不会重复写入或读表时读到脏数据。

进入微服务时代,整个业务系统可能是由数十个不同的服务节点组成,每个服务节点还包括多个实例确保高可用。在这样的环境下,一个写请求可能会由于负载均衡通过不同的服务实例操作数据,大多 NoSQL 实现为了并发性而牺牲了事务,则可能导致数据的正确性被破坏。这时如果有一个全局锁来对不同服务的操作进行限制,那么会一定程度解决上述问题。(对于复杂场景还需要采用分布式事务来处理回滚等等。)

与本地锁类似,分布式锁也是独立的对象,只不过存储在独立的节点上。最朴素的方法是在数据库中存储一段数据,以此为锁对象,存在则表示锁已被其他服务获取,不存在则表示可获取。当然此方案完全没考虑过死锁、可重入性等问题,而且如果是用关系型数据库来实现,则无法支撑高并发的场景。因此通常我们会采用 Redis、ZooKeeper 等方案来实现,并对锁代码进行一定设计,增加超时、重试等等功能。

Read more »

不可变集合

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static final ImmutableSet<String> COLOR_NAMES = ImmutableSet.of(
"red",
"orange",
"yellow",
"green",
"blue",
"purple");

class Foo {
final ImmutableSet<Bar> bars;
Foo(Set<Bar> bars) {
this.bars = ImmutableSet.copyOf(bars); // defensive copy!
}
}
Read more »

排序

示例

1
assertTrue(byLengthOrdering.reverse().isOrdered(list));

简述

Ordering是 Guava 的“流式”Comparator类。它能够用于构建复杂的比较器,并且能应用于集合对象上。

一个Ordering实例的核心仅仅是一个特殊的Comparator实例。Ordering简单的取用一些依赖Comparator的静态方法(例如 Collections.max)将之改造为实例方法。此外,Ordering类还提供了链式方法来改进、增强现有的比较器。

Read more »