2014 年终总结

今年过完春节回来就提离职流程走人,2月底到深圳后大概找了两周多,确定了现在的公司后是等入职流程两周,趁这个时间去了一趟帝都,也算出过省了哈,回来后就是往常的上班日子。

一、工作

在现在这家公司做的项目是面向互联网用户的一个网站,普通用户的访问量不大,数据量一般,千万级到亿级。系统的主要压力是在跟公司核心系统对接的 MQ 消息处理上,还有就是其他关联系统对我们系统的 API 调用。

这个项目是 13 年拼命赶出来的,今年是进入运维期,代码质量真不咋地;每天看着那样的代码,也经常在想,如果自己负责一个项目的研发,该怎么保证代码质量,在整理一份自己的项目规范,希望以后有机会实践下。

技术上没有太难的东西,能让我记起来的也就是定位解决了几个生产问题。

继续阅读

Spring Hessian 集成

一、介绍

Hessian 是工作在 HTTP 协议上的远程调用框架:请求信息被序列化为二进制数据通过 HTTP 请求传输到服务器端,服务端查找目标方法,用请求参数进行调用,然后把响应结果序列化为二进制数据,作为 HTTP 响应返回给客户端,客户端再解析数据组成应用所使用的 Java 对象。

二、服务接口

首先要定义一个 Java 接口来表示远程服务:

public interface IHessianService {
     User getUser();

     User createUser(String name, int age, char sex);

     Map<String, Object> getMap();

     String getString(String value);

     int getInt(int i);
}

定义多个方法是为了测试下不同的数据类型的支持度。User 是个自定义的类型,必须实现 java.io.Serializable

服务端暴露这个接口来表示可以被调用的服务,客户端使用这个接口来表示可以调用的远程服务。

继续阅读

数据不可变 与 方法副作用

一、问题

之前的文章提到公司要求项目组做所谓的网络标准化,在 Spring MVC 的 Controller 层与 Service 层之间拆分成两个项目来部署,中间通过 Hessian 远程调用来通信。

这样可就有大问题了:

Controller 调用 Service 的方法时,传递一个 POJO 作为参数,Service 里修改了这个参数的某个属性,最终返还 Controller,Controller 再根据这个被修改的属性继续处理;

Service 调用的深层方法通过 Spring MVC 通过的 org.springframework.web.context.request.RequestContextHolder 获取了 HttpSession 对象,然后设置了一些状态值。

在同一 JVM 里面,上面的都没有问题;但按标准化的要求,部署在两个 JVM 上时,Service 层对 Controller 层传入的 POJO 所做的修改在 Controller 层就不可见了。

这些问题是实现这个标准化的最大困难,几乎所有代码都要检查是否存在上面的情况。

二、如果上天再给一次机会。。。

上面的问题可以说是“参数的可变性”和“方法副作用”导致的,“参数” 是个特殊的东西,它穿过两个方法之间调用时形成的边界。如果“参数”的属性在被调用的方法里被修改了,可以说这个方法是有副作用的。

如果上天再给一次机会:把所有方法都实现为无副作用,参数封装为不可变的对象,方法的所有效果都通过返回值来体现。这样每一个方法都可以变成一个远程服务。

之前在微博上看到一个说他的架构宣言是:Passing Message Everywhere。有点感触。


欢迎关注我的微信公众号: coderbee笔记,可以更及时回复你的讨论。

Java 任务处理

最近梳理其他同事以前写的 job 后有点想法,记录下。

一、业务场景

在大多数的系统都有类似这样的逻辑,比如下单了给用户赠送积分,用户在论坛上发表了帖子,给用户增加积分等等。

下单赠送积分,那么一个订单肯定不能重复赠送积分,所以需要一些状态来比较来哪些是已赠送的,哪些是没有赠送的。或许可以在订单表里加个字段来标记是否赠送了积分。

有时候,业务人员出于营销的需要,可能要搞个某某时间段内下单返券的活动。难道又在订单表里加个字段?肯定不能,谁知道还要搞多少活动呢。

二、实现

为了使核心的业务流程尽可能简单高效,赠送积分、返券(后面简称为task)之类的逻辑应该通过异步的job来处理。

因为 task 的处理状态不能放在核心的业务表里,所以,可以另外建一个表示异步任务的 async_task 表,结构如下:

-- 业务job处理 任务
create table async_task (
  id number(11) primary key,
  key_work  varchar2(32),  --  不同业务逻辑的task用不同的keyword
  biz_id char(32),         --  业务数据 ID,比如订单号
  biz_data varchar2(256),  --  核心的业务数据,用于避免关联业务表;具体结构取决于keyword
  status number,           --  任务的处理状态; -2:未处理, -1:处理中, 0:已处理, 大于 0 的数字表示失败次数
  create_tm date,          --  任务的创建时间
  modify_tm date           --  任务的修改时间
);

处于性能考虑,可以在 key_work 字段上建立分区,在 biz_id 上建立索引。

当业务表有需要处理的数据时,就往 async_task 插入一条相应的记录(可以异步插入),异步 job 再从 async_task 表里取数据来处理。

注意:处理 task 时,要保证数据的一致性。所在的项目组曾出现过,下单返券的活动里,送券与更新状态的操作没有放在同一个事务里,出现券送了,状态没更新,重复送券的问题。一定要注意事务的正确处理。

继续阅读

踩坑之 Java 可变长参数列表

Java 可变长参数列表

这是 Java 5 引入的一个特性,如果一个方法要接收的参数数量是不确定的,那么这个特性就可以派上用场了。

比如,在涉及IO操作的地方,基本上至少需要关闭两个流:输入、输出,我喜欢把流关闭的操作封装成下面的方法,这样只需一次调用就可以关闭多个流。

public static void closeSilent(Closeable... closeables) {
     for (Closeable closeable : closeables) {
          if (closeable != null) {
               try {
                    closeable.close();
               } catch (IOException ignored) {
               }
          }
     }
}

这是我觉得这个特性唯一适合使用的地方,具备下面的特点:

  • 这些参数具有相同的类型;
  • 参数数量不确定,每一个都是可选的;
  • 这些参数的用途都是一样的,比如上面都是执行关闭。

Java 可变长参数列表只能放在方法参数列表的最后。

继续阅读