分类目录归档:框架/架构

Spring 事务管理的一个 trick

问题

最近有同事碰到这个异常信息: Transaction rolled back because it has been marked as rollback-only ,异常栈被吃了,没打印出来。

调用代码大概如下:

@Component
public class InnerService {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Transactional(rollbackFor = Throwable.class)
    public void innerTx(boolean ex) {
        jdbcTemplate.execute("insert into t_user(uname, age) values('liuwhb', 31)");
        if (ex) {
            throw new NullPointerException();
        }
    }

}

@Component
public class OutterService {
    @Autowired
    private InnerService innerService;

    @Transactional(rollbackFor = Throwable.class)
    public void outTx(boolean ex) {
        try {
            innerService.innerTx(ex);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

outterService.outTx(true);

他期望的是 innerService.innerTx(ex); 调用即使失败了也不会影响 OutterService.outTx 方法上的事务,只回滚了 innerTx 的操作。

结果没有得到他想要的,调用 OutterService.outTx 的外围方法捕获到了异常,异常信息是 Transaction rolled back because it has been marked as rollback-onlyoutTx 的其他操作也没有提交事务。

分析

上述方法的事务传播机制的默认的,也就是 Propagation.REQUIRED,如果当前已有事务就加入当前事务,没有就新建一个事务。

事务性的方法 outTx 调用了另一个事务性的方法 innerTx 。调用方对被调用的事务方法进行异常捕获,目的是希望被调用方的异常不会影响调用方的事务。

但还是会影响调用方的行为的。Spring 捕获到被调用事务方法的异常后,会把事务标记为 read-only,然后调用方提交事务的时候发现事务是只读的,就会抛出上面的异常。

继续阅读

Feign

简介

Feign 是一个 Java 到 HTTP 客户端的粘合剂。Feign 的目标是以最少的开销和代码把 你的代码连接到 http api 上。通过定制的编解码器和错误处理,你可以请求任何基于文本的 http api 。

原理

通过处理注解信息来生成模板化请求。在发出请求前,参数直接应用到这些模板上。这限制了 Feign 只支持基于文本的 api,这显著简化了系统的一些方面如重放请求。

为什么选择 Feign

  • 依赖问题。目前项目组用的是 Hessian 做远程调用,由于 Hessian 存在对 jar 包的依赖,特别是一些项目升级到 JDK 1.8,采用 Maven 构建;而老的一些任然采用 1.6 ,是个简单的 Eclipse 工程,导致打包、部署非常麻烦。

  • 依赖于接口,使用简洁。对于客户端,只依赖于接口类,通过 Spring 注入实现,迁移基本不需要很大的改动。

  • 与 Spring Cloud 集成。Feign 本身是 Spring Cloud 的一个组件,可以通过 Ribbon 做路由,可以进一步提升服务化。

  • 系统对性能的要求并不是那种很严苛的,基于文本的调用也方便调试。

继续阅读

分布式系统间请求跟踪

一、请求跟踪基本原理

现在的很多应用都是由很多系统系统在支持,这些系统还会部署很多个实例,用户的一个请求可能在多个系统的部署实例之间流转。为了跟踪一个请求的完整处理过程,我们可以给请求分配一个唯一的 ID traceID,当请求调用到另一个系统时,我们传递这个 traceID。在输出日志时,把这个 traceID 也输出到日志里,这样,根据日志文件,提取出现这个 traceID 的日志就可以分析这个请求的完整调用过程,甚至进行性能分析。

当然,在一个系统内部,我们不希望每次调用一个方法时都要传递这个 traceID,因此在 Java 里,一般把这个 traceID 放到某种形式的 ThreadLocal 变量里。

日志类库在输出日志时,就从这个 ThreadLocal 变量里取出 traceID,跟要输出的日志信息一起写入日志文件。

这样对于应用的开发者来说,基本不需要关注这个 traceID

二、远程调用间传递跟踪信息

如何使用的是自定义的 RPC 实现,这些 RPC 一般都预留了扩展机制来传递一些自定义的信息,我们可以把 traceID 作为自定义信息进行传递。

对于 Hessian 这种基于 HTTP 协议的 RPC 方法,它把序列化后的调用信息作为 POST 的请求体。如果我们不想去修改 Hessian 的调用机制,可以把 traceID 放到 HTTP 的请求头里。

在客户端只需要提供封装好的 RPC 调用代理。

在服务端通过 Filter 得到 traceID 放入 ThreadLocal 变量。

继续阅读

《Netty in action》 第三章 Netty 组件和设计

第三章 Netty 组件和设计

从高层视角,Netty address 两个等价的关注域:技术和架构
* 首先,它是构建于 Java NIO 上的异步、事件驱动的实现,保证了在高负载下最大的应用性能和伸缩性;
* Netty 体现了一组设计模式,从网络层解耦应用逻辑,简化开发,最大化可测试性、模块化、代码重用。

当我们更细微地学习 Netty 的独立组件时,我们将聚焦于它们是如何协作来支持这些架构最佳实践。通过遵循同样的原则,我们将获得 Netty 能提供的所有好处。

3.1 Channel, EventLoop, ChannelFuture

Channel, EventLoop, ChannelFuture 放到一起,可以代表了 Netty 的网络层抽象:

  • Channel: Sockets
  • EventLoop: 控制流,多线程,并发
  • ChannelFuture: 异步通知

3.1.1 Channel 接口

基本的 I/O 操作(bind(), connect(), read(), write()) 依赖于底层网络传输的提供的原子操作。在基于 Java 的网络里,基础结构是 Socket 类。Netty 的 Channel 接口提供的 API 极大简化了直接操作 Sockets 的复杂工作。

3.1.2 EventLoop 接口

EventLoop 定义了 Netty 处理一个连接生命周期里发生的事件的核心抽象。Channel、EventLoop、EventLoopGroup 之间的关系如下:

  • 一个 EventLoopGroup 包含一个或多个 EventLoop;
  • 一个 EventLoop 在它的生命周期里是绑定到单一线程的;
  • 一个 EventLoop 处理的所有 I/O 事件都是在它的专用线程上进行的;
  • 一个 Channel 在它的生命周期里是注册到到单一的 EventLoop;
  • 单一的 EventLoop 可能被赋给一个或多个 Channel。

注意,在这种设计里,给定 Channel 的 I/O 事件都由同一个线程来执行,事实上消除了对同步的需要。

继续阅读

《程序员必读之软件架构》–笔记

架构是什么

架构作为名词解释时,概括起来都与结构有关:将产品分解为一序列组件、模块和交互。

架构作为动词来解释时,包括了理解你需要构建什么、设定愿景以便进行构建和做出恰当的设计决策。所有这些都以需求为基础,因为需求驱动架构。关键在于,架构是关于交流愿景以及引入技术领导力的,这样参与构建产品的每个人都能理解这个愿景,并为产品的成功做出积极贡献。

不论何种领域的架构,其实主要就是结构和愿景。

架构分类

应用程序架构的关注点是应用程序,通常包括将应用程序结构为类和组件,确保设计模式正确应用,构建或使用框架,等等。本质上,应用程序架构谈论的是软件设计的低级别切面,通常只考虑单一的技术栈。应用程序架构着重考虑软件和代码组织。

系统架构是更大规模的应用程序架构。大多数软件系统实际上是由横跨不同层次和技术的多个应用程序组成。系统架构还关注互操作性和与环境中其他系统的集成。系统架构描述为从组件和服务到子系统等高层次的抽象。系统架构的定义大多数还包括了软件和硬件。

软件架构就是应用程序和系统架构的结合。从代码结构和基础到将代码成功部署到生产环境,与一个软件系统重要元素相关的所有东西就是软件架构。

企业架构一般是指整个组织的中心工作,着眼于如何组织和利用人员、流程和技术来使企业有效和高效地工作。它是关于企业如何分成组或部门,业务流程如何在这上层运作,以及技术如何支撑这一切。

好的架构带来敏捷。

软件架构角色

架构 VS. 设计

作为名词,设计是指一个系统内命名的结构或行为,解决或有助于月解决该系统的一个或多个问题。因而,设计代表了潜在的决策空间中的一个点。

所有架构都是设计,但并非所有设计都是架构。

架构反映了使一个系统成型的重要设计决策,而重要性则通过改变的成本来衡量。

尽管“重要决策”没法彻底消失,但能通过架构分层等多种策略来改变。软件系统架构流程的一部分就是搞清楚哪些是重要的及为什么。

思考软件架构的好处

  • 让团队跟随一个清晰的愿景和路线图,无论这个愿景是一个人所有还是整个团队共有;
  • 技术领导力和更好的协调;
  • 与人交流的刺激因素,以便回答与重要决策、非功能需求、限制和其他横切关注点相关的问题;
  • 识别或减轻风险的框架;
  • 方法和标准的一致性,随之而来的结构良好的代码库;
  • 正在构建的产品的坚实基础;
  • 对不同的听众,以不同层次的抽象来交流解决方案的结构。

C4:语境、容器、组件和类

c4: 语境 容器 组件 类
软件的静态视图:

  • 语境:设定场景的高层次图,包括关键的系统依赖和参与者。

    语境图帮助回答下面的问题:

    1. 我们构建的(或已经构建的)软件系统是什么?
    2. 谁会用它?
    3. 如何融入已有的 IT 系统?
  • 容器:荣企图显示了高层次的技术选择,容器如何分担职责、如何通信。

    1. 软件系统的整体形态是什么样的?
    2. 高层次技术决策有哪些?
    3. 职责在系统中如何分布?
    4. 容器之间如何相互交流?
    5. 为了实现特性,作为一个开发者,我需要在哪里写代码?
  • 组件:组件图可以让你看到每个容器的关键逻辑组件及之间的关系。

    1. 系统由哪些组件/服务组成?
    2. 在高层次上,系统如何工作是否清晰?
    3. 所有组件/服务都有一个家吗(即驻留在一个容器中)?
  • 类:可选的细节层次。

原则

约束通常是强加于你的,而原则是你为了将一致性和清晰度引入最终代码库而想采用的原则(如编码规范、自动化测试等)或架构的原则(如分层策略,架构模式等)。

%e5%85%b3%e6%b3%a8%e7%82%b9

《 Reactive Microservices Architecture 》 响应式微服务架构 摘记

第三部分未完结,挖坑先。。。

第一章 介绍

我们只在我们没有其他选择时改变单体 (monolithic) 系统。与其说是敏捷地抓住机会,我们考虑它是否确实值得倾覆我们称为纸牌屋的企业系统的微妙平衡。通常,机会很快就消失了,被快速的公司抓住。

Flaus Schwab: 在新世界,不是大鱼吃小鱼,是快鱼吃慢鱼。

基于微服务的架构是个简单的概念:它提倡用一组小的、隔离的服务来创建系统。这些服务拥有它自己的数据,各自是独立的、可扩展且对失败有弹性。服务集成其他服务来构建一个内聚的系统,它比我们当前构建的典型企业系统更灵活。

传统企业系统设计为单体应用,难以扩展,难以理解且难以维护。单体应用可能很快就变成噩梦,扼杀创新、进度和乐趣。单体应用导致的负面作用对一个公司可能是灾难性的,从底层的士气到高层雇员的周转,从阻止公司雇佣顶尖工程天才到失去市场机会,在极端情况下,甚至是公司的失败。

战争故事通常听起来是这样的:“我们终于做了决定来改变我们的 Java EE 应用,在得到管理层批准后。然后我们经过长达数月的大设计,在我们最终开始构建点东西之前。但我们构建期间的大多数时间都花在尝试弄清楚单体应用真正做了什么。我们因害怕而瘫痪,担心一个小的错误可能导致非预期的、未知的副作用。最终,在数月的担忧、害怕和艰难工作后,改变实现了,但地狱也打开了。”

这样的经历会加强害怕,使我们瘫痪更严重。这就是系统、公司如何停滞的。如果有更好的方式呢?

Steve Jobs:You’ve got to start with the customer experience and work back towards the technology 。

微服务的客户是投资于系统的组织,所以让我们从客户开始:开发人员、架构师和关键利益相关者。

需要拯救的服务

Helen Keller:Although the world is full of suffering, it is also full of the overcomming of it .

因为技术原因,微服务是软件里不纯粹的下一代设计演进。微服务这个词汇呈现的思想在我们进入面向服务的架构(SOA, Service Oriented Architecture)之前已经遍布各处。阻止我们采用微服务内嵌的概念的特定技术约束已经进入下一水平:单台机器运行着单个内核处理器,慢速的网络,昂贵的磁盘,昂贵的 RAM,组织结构是个单体。像把系统组织成一个定义良好的、有单一职责的系统的想法不是新的。

快进到 2016 年,阻止我们采用微服务的技术限制已经消失了。网络是快速的,磁盘是便宜的(且更快速),RAM 是便宜的,多核处理器是便宜的,云架构正在彻底改革我们如何设计、部署系统。现在,我们终于可以考虑着客户来构建我们的系统。

设计和编写软件是有趣的,这是大多数的我们进入软件行业的初衷。微服务不仅仅是一序列的原则和技术。它们以一种更专业的方式来达成复杂问题的系统设计。

微服务允许我们以组织我们团队的方式来构建系统,在团队成员之间划分职责,保证他们自由处理自己的工作。当我们理顺了我们的系统,我们把权利从中央治理主体移交到小团队里,小团队可以快速抓住机会并保持敏捷,因为他们明白 他们控制的、有定义良好的边界的软件。

继续阅读

Spring AOP 与 事务实现

1. AOP 与方法调用

对于 接口 interface,可以通过 java.lang.reflect.Proxy 来生成动态代理对象;
对于 类 class,可以通过字节码技术,如 CBLIB 来生成一个子类作为代理对象,此时类不能声明为 final 。

当 Spring 把 Bean 注入到另一个 Bean 时,其实注入的是它生成的代理对象,进行方法调用时,首先调用的是代理对象上的方法,代理对象的方法最终再调目标对象的方法,也就是开发人员编写的方法。Spring 通过在调用目标方法前后做处理来实现一些特性,例如事务管理、缓存等。

Spring 的 AOP 是基于对代理对象的方法调用的拦截的,只能拦截外部对象 对 某个对象的方法的调用,对象的方法调用同一个类的方法是不会被拦截的。

@Service
public class SpringTxService {
    private static final Logger LOGGER = LoggerFactory
            .getLogger(SpringTxService.class);

    @Transactional(propagation = Propagation.REQUIRED)
    public void add() {
        LOGGER.info("do add");
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void requireNew() {
        LOGGER.info("do requireNew");
    }

    public void composite() {
        add();
        requireNew();
    }
}

上面的代码是基于注解进行事务控制的,composite 方法没有声明事务属性,它会调用 add、 requireNew
方法。当把 SpringTxService 注入到另一个 BeanB:
1. 在 BeanB 的方法里调用代理对象 composite 时,最终执行 add、 requireNew 是没有在事务里执行的,只是普通的方法调用。
2. 在 BeanB 的方法里调用代理对象的 add、 requireNew 方法时,这两个方法都会分别在相应的事务里执行。

当发现基于 AOP 实现的特性没有预期的效果时,一定要看看是不是在代理对象上调用还是目标类内部的方法调用。

继续阅读