skaka的博客

every flight begins with a fall

微服务框架Finagle介绍 Part2: 在Finagle中开发基于Http协议的应用

| Comments

上篇文章中我介绍了Finagle中的Future/Service/Filter. 这篇文章里, 我们将构建一个基于Http协议的echo服务端和客户端, 下篇文章将构建一个基于thrift协议的客户端和服务端. 这两篇文章对应的源代码地址在Github. 代码中有Java和Scala版本两套版本的实现, 但是这里我只会介绍Java版本.

首先来看echo应用的Server端代码, 打开java-finagle-example/src/main/java/com/akkafun/finagle/Server.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Server extends Service<Request, Response> {                             //1

    @Override
    public Future<Response> apply(Request request) {                                 //2
        System.out.println("request: " + request.getContentString());
        Response response = Response.apply(Version.Http11$.MODULE$, Status.Ok());
        response.setContentString(request.getContentString());
        return Future.value(response);
    }

    public static void main(String[] args) throws Exception {
        Server service = new Server();

        ListeningServer server = Http.server().                                      //3
                withLabel("echo-server").
                withTracer(ZipkinTracer.mk("192.168.99.100",
                    9410, DefaultStatsReceiver$.MODULE$, 1.0f)).
                serve(new InetSocketAddress(8081), service);

        Await.result(server);
    }
}

微服务框架Spring Cloud介绍 Part1: 使用事件和消息队列实现分布式事务

| Comments

不同于单一架构应用(Monolith), 分布式环境下, 进行事务操作将变得困难, 因为分布式环境通常会有多个数据源, 只用本地数据库事务难以保证多个数据源数据的一致性. 这种情况下, 可以使用两阶段或者三阶段提交协议来完成分布式事务.但是使用这种方式一般来说性能较差, 因为事务管理器需要在多个数据源之间进行多次等待. 有一种方法同样可以解决分布式事务问题, 并且性能较好, 这就是我这篇文章要介绍的使用事件,本地事务以及消息队列来实现分布式事务.

我们从一个简单的实例入手. 基本所有互联网应用都会有用户注册的功能. 在这个例子中, 我们对于用户注册有两步操作:
1. 注册成功, 保存用户信息.
2. 需要给用户发放一张代金券, 目的是鼓励用户进行消费.
如果是一个单一架构应用, 实现这个功能非常简单: 在一个本地事务里, 往用户表插一条记录, 并且在代金券表里插一条记录, 提交事务就完成了. 但是如果我们的应用是用微服务实现的, 可能用户和代金券是两个独立的服务, 他们有各自的应用和数据库, 那么就没有办法简单的使用本地事务来保证操作的原子性了. 现在来看看如何使用事件机制和消息队列来实现这个需求.(我在这里使用的消息队列是kafka, 原理同样适用于ActiveMQ/RabbitMQ等其他队列)

我们会为用户注册这个操作创建一个事件, 该事件就叫做用户创建事件(USER_CREATED). 用户服务成功保存用户记录后, 会发送用户创建事件到消息队列, 代金券服务会监听用户创建事件, 一旦接收到该事件, 代金券服务就会在自己的数据库中为该用户创建一张代金券. 好了, 这些步骤看起来都相当的简单直观, 但是怎么保证事务的原子性呢? 考虑下面这两个场景:
1. 用户服务在保存用户记录, 还没来得及向消息队列发送消息之前就宕机了. 怎么保证用户创建事件一定发送到消息队列了?
2. 代金券服务接收到用户创建事件, 还没来得及处理事件就宕机了. 重新启动之后如何消费之前的用户创建事件?
这两个问题的本质是: 如何让操作数据库和操作消息队列这两个操作成为一个原子操作. 不考虑2PC, 这里我们可以通过事件表来解决这个问题. 下面是类图.

EventPublish是记录待发布事件的表. 其中:
id: 每个事件在创建的时候都会生成一个全局唯一ID, 例如UUID.
status: 事件状态, 枚举类型. 现在只有两个状态: 待发布(NEW), 已发布(PUBLISHED).
payload: 事件内容. 这里我们会将事件内容转成json存到这个字段里.
eventType: 事件类型, 枚举类型. 每个事件都会有一个类型, 比如我们之前提到的创建用户USER_CREATED就是一个事件类型.
EventProcess是用来记录待处理的事件. 字段与EventPublish基本相同.

微服务框架Finagle介绍 Part1: Future, Service, Filter

| Comments

微服务架构可能是时下最热的一种架构模式了. 这篇系列里, 我想介绍一些常用的微服务框架. 通过学习这些框架, 我们将会了解实现微服务的过程中会遇到哪些问题, 以及这些微服务框架是如何帮助我们解决这些问题的. 所以这是一篇关于微服务实践的系列, 我不会讨论太多概念性的东西. 系列末尾我会给出一些微服务架构相关的链接, 感兴趣的可以参考.

微服务不同于单一架构应用, 是典型的分布式场景, 各服务之间通过IPC进行通信. 实现微服务的过程中, 我们需要解决以下问题:
1. 服务注册和服务发现.
2. 根据应用选择合适的通信协议和数据协议. 例如可以选用thrift, protocol buffer或REST.
3. 服务负载均衡. 一个服务一般会部署多个实例. 如果使压力均匀分布是需要考虑的问题.
4. 服务路由与限流.
5. 容错处理. 相对于单机应用, 分布式环境下错误发生的概率会大大提高, 服务宕机, 网络不可用的情况时常发生.
6. 服务监控. 各服务实例的性能指标, 例如请求响应时间, 请求并发数量, 以及服务实例的部署数量等.
7. 事务一致性. 一般来说这个问题需要我们结合业务自己处理, 框架不会给我们太多帮助.

好的微服务框架应该能帮助我们解决上面的全部或者大部分问题. 这里我选择JVM上比较热门的三个微服务框架: Finagle, Spring Cloud(NetflixOSS), Dubbox. 我会从实例入手, 介绍这些框架的使用方式, 特点和适用场景.

首先来看Finagle. Finagle是Twitter在2011年开源的一款RPC框架, 在国外使用较多, 例如Pinterest, Nest, Tumblr, 感兴趣的可以Google. Finagle有着较为丰富的生态圈, 例如可以使用Finch很方便的实现REST, 使用Finagle OAuth2实现OAuth认证, 使用zipkin实现服务监控. Finagle使用Scala开发, 官方宣称同时支持Scala和Java语言.

学习Finagle的使用之前, 首先要了解Finagle中的三个核心概念: Future, Service, Filter.

简单介绍函数式编程中的Functor(函子),Applicative(加强版函子),Monad(单子)

| Comments

如果你是刚接触函数式编程,可能很容易被下面这些术语弄迷惑:Functor(函子),Applicative(加强版函子),Monad(单子)。 这些概念不是空穴来风,它们出自范畴论,如果你上网去搜范畴论,可能会找到大篇的术语定义,学术资料,这些资料大多都不是入门友好的。 这里我不会探讨定义,只会介绍这些概念在代码中到底起了什么样的作用,以及怎么样运用它们。

下面的示例代码大部分是Haskell,有一小部分是Java8,不会Haskell完全没关系,你可以把它们看作伪代码,我会对每一段代码进行解释。 这篇文章适合刚刚接触函数式编程的同学。我在刚接触这些概念的时候一头雾水,网上找的资料要么level太高看不懂, 要么直接就blabla给你介绍一大片背景知识了。后来经过长时间的摸爬滚打加实践,我发现这些概念理解起来也不是很困难,所以就想写一篇入门级的介绍。 如果你想要对函数式编程有一定的了解,这些概念你是绕不过去的,特别是Monad,当你发现你理解了Monad的机制,很多看起来不可思议的代码就能理解了。

开始之前,我简单介绍一下类型类(typeclass)和类型构造器的概念。函数式编程中的类型类是定义行为的接口。 如果一个类型是某个类型类的实例,那么这个类型必须实现所有该类型类所定义的行为。不要因为有“类”这个词就把类型类与面向对象中的类混淆, 他们是完全不同的概念。类型构造器能够接收其他类型为参数,创建出新的类型。举个例子,Scala的List即为接收一个类型参数的类型构造器, 当类型参数为Int时,List类型构造器的返回类型为List[Int],当类型参数为String时,返回类型为List[String]。 与类型构造器相对的概念是值构造器,比如Int(2)。

1. Functor

首先看看函子的类型类用代码怎么表示:

1
2
3
-- Haskell中一个函数如果有两个参数一个返回值,写法是这样: a(第一个参数) -> b(第二个参数) -> c(返回值)
class Functor f where
  fmap :: (a -> b) -> f a -> f b

函子的类型类只定义了一个fmap函数: fmap函数接收两个参数,第一个参数是以a为参数,b为返回值的函数;第二个参数类型为f a,fmap的返回值类型为f b. 注意这里的a, b可以为任意类型, f为接收一个类型参数的类型构造器。这样说可能有点抽象,来看一个具体的例子。 已知[](列表)是一个functor实例,他的fmap函数声明为:

1
fmap :: (a -> b) -> [a] -> [b]

接收一个以a为参数,b为返回值的函数以及元素类型为a的列表,返回元素类型为b的列表。 至此,你能看出functor所抽象的行为吗?你可以从下面两个角度思考fmap: 1. 接受函数和函子值,返回在函子值上映射函数的结果(返回也是函子值)。 2. 接受函数,把该函数从操作普通类型的函数提升(lift)为操作函子值的函数。 这就是函子,不难吧?

给Java开发者的Play Framework(2.4)介绍 Part3:搭建Play的开发环境

| Comments

1. 开始前的准备

这篇文章会介绍Play项目开发环境的搭建,以及日常开发的常用命令。进行之前,首先得准备下面的工具:

1.JDK8+
2.Scala 2.11.6+
3.sbt 0.13.7+
4.IntelliJ IDEA 14+
5.MySQL 5.5+MariaDB 10+
6.Redis 2.7+
7.科学上网工具

这些软件安装都可以参照官方的文档,这里就不介绍了。

推荐使用的IDE是IntelliJ IDEA,如果对Eclipse有执念的也可以考虑用Typesafe维护的ScalaIDE
但是在目前看来,IDEA对Scala的支持最好,另外IDEA相比Eclipse也有很多优点,没有尝试过的朋友可以试一下,保证你不会想切回Eclipse了:)

MySQL和Redis是这篇文章的demo项目启动需要的,如果不打算运行项目可以忽略。

另外科学上网工具也是需要的, demo项目的一些包需要连接Google Code仓库获取,而且没有科学上网的话下载速度会奇慢无比,相信我,你没有那么多时间用来浪费的。

2. 搭建开发环境

我的操作系统是Ubuntu 14.04,所以下面涉及到命令或文件路径地方,可能都是Linux风格的。如果你的操作系统是Windows或OS X,需要做相应替换。