摘记–《富爸爸穷爸爸》

国庆的后面几天看完了《富爸爸穷爸爸》,做了些摘记:

一个人的观念对他的一生影响巨大。

贫穷和破产的区别是:破产是暂时的,而贫穷是永久的。

如果你认为是我的问题,你就会想改变我;如果你认为问题在那儿,你就会改变自己,学习一些东西让自己变得更聪明。大多数人认为世界上除了自己外,其他人都应该改变。改变自己比改变他人更容易。

真正的学习需要精力、激情和热切的愿望。愤怒是其中一个重要的组成部分,因为激情正是愤怒和热爱的结合体。

你现在才 9 岁,已经有了为钱工作的体验了。你只需要把上个月的生活重复 50 年,就会知道大多数人是如何度过一生的了。(类似“一年的工作经验重复用了 N年”)

老鼠赛跑:起床,上班,付账,再起床,再上班,再付账单—-他们的生活从此被这两种感觉所控制:恐惧和贪婪。给他们更多的钱,他们就会以更高的开支重复这种循环。

正是因为有感情,我们才成为人。感情使我们更加真实,它是我们行动的动力。忠实于你的感情,以你喜欢的方式运用你的头脑和感情,不要让它们(恐惧和贪婪)控制你。

不幸的是,对许多人来说,离开学校是学习的终点而不是起点。

继续阅读

《深入理解 Scala》第五章 — 利用隐式转换编写更有表达力

Scala 的隐式转换系统定义了一套定义良好的查找机制,让编译器能够调整代码。Scala 编译器可以推导下面两种情况:

  • 缺少参数的方法调用或构造器调用;
  • 缺少了的从一种类型到另一种类型的转换。(是指调用某个对象上不存在的方法时,隐式转换自动把对象转换成有该方法的对象)

介绍隐式转换系统

implicit 关键字可通过两种方式使用:1、方法或变量定义;2、方法参数列表。如果关键字用在变量或方法上,等于告诉编译器在隐式解析(implicit resolution)的过程中可以使用这些方法和变量。

隐式解析是指编译器发现代码里的缺少部分信息,而去查找缺少信息的过程。

implicit 关键字用在方法参数列表的开头,是告诉编译器应当通过隐式解析来确定所有的参数值。

scala> def findAnInt(implicit x: Int) = x
findAnInt: (implicit x: Int)Int

scala> findAnInt
<console>:9: error: could not find implicit value for parameter x: Int
              findAnInt
              ^

scala> implicit val test = 5
test: Int = 5

scala> findAnInt
res1: Int = 5

scala> def findTwoInt(implicit a: Int, b: Int) = a + b
findTwoInt: (implicit a: Int, implicit b: Int)Int

scala> findTwoInt  // implicit 作用于整个参数列表
res2: Int = 10

scala> implicit val test2 = 5
test2: Int = 5

scala> findTwoInt
<console>:11: error: ambiguous implicit values:
 both value test of type => Int
 and value test2 of type => Int
 match expected type Int
              findTwoInt
              ^

scala> findTwoInt(2, 5)  // 隐式的方法参数仍然能够显式给出
res4: Int = 7

隐式的方法参数仍然能够显式给出。

继续阅读

隐式类型转换导致全表扫描

最近系统改版了一部分(其实就是重做。。。),上线之后又开始出现数据库 IO 告警了。

DBA 抓出一个全表扫描的 SQL,是把范围在两个钟内的数据找出来做更新的,这个查询的时间字段是有索引的,结果却没有用上,
跟另一个表关联的时候,关联字段也是有索引的,但还是没有用上。

在两个钟内的数据量应该是很小的,相对于总量来说,时间字段的索引没理由不用啊;从主表找出来的数据量不大,再去关联表查询也应该走索引的。
感觉就是执行计划不对了,去找 DBA 做固化。

找 DBA 先用 hint 生成一个走索引的执行计划,然后做了固化。

这个 SQL 是两个钟才执行一次的,结果固化之后值班 DBA 说主表还是全表扫描。找 DBA 再做一次固化,还是主表全表扫描。最后开了 tunning ,提示说有隐式类型转换,主表没法走索引。

大致扫了一下代码,Java 里用的数据类型是 Date 的,而表的列的类型也是 Date,应该不需要做隐式类型转换啊。

问题出在 MyBatis 的参数类型指定为 timestamp 而不是 Date;由于查询需要时分秒,而 Date 会抹去这些值,所以被指定为 timestamp,
而这个类型刚好对应到 Oracle 的 timestamp 类型,所以就出现隐式类型转换。

细节吶。。。


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

Spring MVC 与线程

最近又被一个连环坑坑惨了,我踩的坑是与 Spring MVC 的线程模型有关的。

我们的系统现在有两个 war 包,分别部署在不同的 JBoss 上,第一个是互联网可访问的,叫 front 吧,第二个是在防火墙之后的,只能通过 front 来访问,叫 backend,是可以访问数据库的。

front 通过 Hessian 调用 backend 的服务。

现在有个需求要求 backend 记录互联网用户的 IP 和其他一些参数,接口太多,不可能每个方法再添加参数,由于 Hessian 也运行是在 HTTP 上的,所以我想在 Hessian 的 HTTP 请求头里把这些需要的参数传过去。

根据 debug,Hessian 采用的是 JDK 的 HessianURLConnectionFactory 来创建连接,处理 HTTP 请求,所以我就扩展了它的方法 public HessianConnection open(URL url) throws IOException ,在里面把参数放到请求头里。由于要取的是客户端实际 IP 等信息,所以还需要获取客户端的请求 HttpServletRequest ,刚好 Spring MVC 里面有个 RequestContextHolder 工具,持有了 HttpServletRequest 。然后就有了下面这行代码:HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); 。坑就挖成了。

点开 RequestContextHolder.getRequestAttributes() 方法来看看:

public static RequestAttributes getRequestAttributes() {
     RequestAttributes attributes = requestAttributesHolder.get();
     if (attributes == null) {
          attributes = inheritableRequestAttributesHolder.get();
     }
     return attributes;
}

requestAttributesHolder 是这样的:

private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
          new NamedThreadLocal<RequestAttributes>("Request attributes");

NamedThreadLocal 是这样定义的: public class NamedThreadLocal<T> extends ThreadLocal<T> {
也就是说这个请求是跟线程绑定的,在别的线程是获取不到的。

其他的坑是这样的:
在 front 有个地方用了异步去调用 backend,其实是根本没必要用异步的(因为当前线程发出请求后是在等待响应的);而这个异步调用在测试环境时没有启用,被开关切去走另一个同步的分支,而生产环境确是必须走这个异步分支的。


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

最近处理的两个坑:Spring 启动问题与 log4j 配置

Spring 依赖注入问题

近半年,在生产环境老是出现应用在 JBoss 里启动不来,在下面的方法输出日志、进入循环后就走不出那个循环了:

Spring 3.0.5: org.springframework.beans.factory.support.DefaultListableBeanFactory

public void preInstantiateSingletons() throws BeansException {
     if (this.logger.isInfoEnabled()) {
          this.logger.info("Pre-instantiating singletons in " + this);
     }

     synchronized (this.beanDefinitionMap) {
          for (String beanName : this.beanDefinitionNames) {
               RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
               if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
                    if (isFactoryBean(beanName)) {
                         final FactoryBean factory = (FactoryBean) getBean(FACTORY_BEAN_PREFIX + beanName);
                         boolean isEagerInit;
                         if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
                              isEagerInit = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
                                   public Boolean run() {
                                        return ((SmartFactoryBean) factory).isEagerInit();
                                   }
                              }, getAccessControlContext());
                         }
                         else {
                              isEagerInit = factory instanceof SmartFactoryBean && ((SmartFactoryBean) factory).isEagerInit(); 
                         }
                         if (isEagerInit) {
                              getBean(beanName);
                         }
                    }
                    else {
                         getBean(beanName);
                    }
               }
          }
     }
}

因为是生产环境,没法直接远程 debug,只能 dump 出栈和堆来分析,从栈来看,Spring 一直在做 bean 实例化;从堆来看,是这样的:

Top elements include:

•13,701 × Error creating bean with name ‘sqlSessionFactory’ … > (496 bytes)
•13,732 × Could not autowire method: public final void org.m… (336 bytes)
…….//还有很多其他的类创建失败

这个问题有一定的随机性,因为不是总是起不来,重启多次之后就可能顺利启动了,搞得每次发版本都胆战心惊。

继续阅读

mybatis 批量插入 插件

2017-01-07 更新:这个插件做了重命名、梳理,新的 github 地址为 MyBatis-Batch

可直接从 Maven 中央仓库引用:
<dependency>
<groupId>net.coderbee</groupId>
<artifactId>mybatis-batch</artifactId>
<version>1.1.0</version>
</dependency>

背景

项目中有个设计不合理的表,总共 8 个字段,有 5 个索引,有几个索引字段还是 32 位的字符串。该表数据量已达 1 亿,最近每天新增 100 万。根据日志看,有次用户上传一个有 200 行记录的 excel,需要往这个表插入 3940 条记录,耗时 72 秒。这么大延迟是没法接受的。

要分析数据库方面的问题,首先是找 DBA 分析下表的情况,说跟以往没多大区别,只是跟这个表有关的插入的执行计划很多。因为这个表的批量插入是这样的:

<select id="batchSave" parameterType="java.util.List">
    INSERT INTO TABLE_NAME(ID,NAME) 
<foreach collection="list"item="itm" separator="union all">
    (SELECT #{itm.id}, #{itm.name} FROM DUAL)
</foreach></select>

这是用 MyBatis 对 Oracle 做批量插入的唯一方法。副作用是:假定 List 的最大长度是 N,那么 Oracle 服务器端就可能有 N 个插入的执行计划。这么多执行计划,DBA 也不乐意去分析呀,而且确实生成的每个执行计划都是很简单的。

优化

如果放弃 union all 的方式,则每条记录都需要各提交一次到数据库,显然也不好。

为了解决 N 个执行计划的问题,做到真正的批量插入,只能修改 MyBatis 的执行逻辑,因此就有了这个项目:mybatis-batchinsert-plugin

目前可以批量插入,但不支持返回主键等其他的功能,有空再完善。

我做这个插件主要是希望达到:

  • 1、解决 Oracle 服务器端对一个表做插入的执行计划过多的问题;用 union all 的方式还可能导致硬解析增多,比如需要插入的总记录有 123 条,以 100 条为一批拼接成一条 sql A,那么剩下的 23 条也会拼接成一条 B,而 B 的频率肯定比 A 的频率低很多,因为要做批量插入的记录总数不是固定的,所以余数也不是固定的,容易导致 B 的解析过期,被清除出去,然后又来了条余数 23 的,那么就需要硬解析了。
  • 2、避免用 union all 拼接成一条 sql,导致这条 sql 里的绑定变量过多。
  • 3、兼容 MyBatis 的用法,在使用上更简单了,因为不需要用 MyBatis 的 for 循环,由插件来做循环;用 union all 方式的话,Java 代码要控制批的大小,需要一个循环,MyBatis 里需要一个,用于拼接 union all。
  • 4、一个对于性能有益的是:每次调用 MyBatis 的 insert 语句,都需要调用一次 Connection.prepareStatement 方法,也就需要访问数据库一次,如果对语句没有缓存的话;用插件的话,不管多少记录,只需要一次。
  • 这个插件对性能提升不会很显著,在我们的开发数据库上测试时,以 1000 条为 1 批,已有数据量 160w,插入 40w 数据,用 union all 的方式平均一条需要 32ms,用插件大概是 27ms。


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

    《深入理解 Scala》第四章 — 面向对象编程

    限制在对象或特质的 body 里初始化逻辑的代码

    在编译特质的时候, Scala 创建一个接口/实现对(interface/implementation pair,接口的类名是特质的名字,实现的类名是特质的名字加上 $class),接口用于 JVM 交互,实现则是一组静态方法,在类实现特质时可以用到。

    $ cat Test.scala
    trait Application {
        def main(args: Array[String]){
        }
    }
    
    object Test extends Application {
        println("hello world!")
    }
    
    // 编译后的接口和实现
    $ javap -c Test{,\$.class}
    //  接口类
    Compiled from "Test.scala"
    public final class Test {
      public static void main(java.lang.String[]);
        Code:
           0: getstatic     #16                 // Field Test$.MODULE$:LTest$;
           3: aload_0
           4: invokevirtual #18                 // Method Test$.main:([Ljava/lang/String;)V  // 转发调用实现类的方法
           7: return
    }
    
    //  实现类
    Compiled from "Test.scala"
    public final class Test$ implements Application {
      public static final Test$ MODULE$;
    
      // 静态初始化块,实例化实现类
      public static {};
        Code:
           0: new           #2                  // class Test$
           3: invokespecial #14                 // Method "<init>":()V
           6: return
    
      public void main(java.lang.String[]);
        Code:
           0: aload_0
           1: aload_1
           2: invokestatic  #21                 // Method Application$class.main:(LApplication;[Ljava/lang/String;)V
           5: return
    }
    

    目前,静态初始化代码块里的方法不能应用 HopSpot 优化。

    继续阅读

    《深入理解 Scala》第三章 — 来点样式 — 编码规范

    编码规范的目标可以归纳为三类:

    • 代码发现:作用在于使工程师能够轻松地推理代码,看懂其他工程师的意图。
    • 代码一致性:作用在于使整个项目的编码风格保持一致。
    • 错误防止:指那些有助于在生产代码里避免 bug 的样式规则。

    设计编码规范应该采取的方法:

    1. 从错误防止规范开始;
    2. 开发一套代码发现相关的规范,应与团队使用的开发环境匹配;
    3. 最后是代码一致性规范,应考虑到自动化工具支持的需要。

    空悬操作符是指一行代码的最后一个非空字符是一个操作符,有助于编译器判断语句真正结束的位置。

    名称重整(name mangling):是指编译器修改或修饰类名或方法名,以便把它编译到底层的平台上。Scala 里名称重整用在嵌套类或辅助方法上。

    当 Scala 必须生成匿名函数或匿名类时,Scala 会生成一个名字,包括函数或类所处的类名、字符串 annofun 和一个数字, 用 $ 符号连接起来。

    编译器也会对默认参数应用名称重整措施。Scala 里默认参数也是被编码成方法的,方法名为 default 加上用来表示参数在函数里的出现顺序的序号。这出现在方法名字空间(namespace)里而不是类名字空间里。方法名$default$序号

    继续阅读

    《深入理解 Scala》第二章 — 核心规则

    在 REPL 里做实验

    优先擦用面向表达式编程

    表达式返回值;语句执行代码,但不返回值。Scala 大部分语句都返回其最后一个表达式的值作为结果。

    • 不变性(immutability):指对象或变量赋值后就不再改变状态。
    • 可变性(mutability):指对象或变量在其生命周期中能够被改变或操纵。

    优先选择不变性

    Scala 里的所有变量都是指向对象的引用。把变量声明为 val 参数意味着它是个不变的“引用”。

    所有的方法参数都是不变引用,类参数默认是不变引用。

    创建可变引用的唯一方法是使用 var 引用。

    Scala 里不变性很重要,它有助于程序员推理代码,在定义判等或写并发程序时尤其明显。

    默认情况下,Scala 用对象位置判等法和散列。对象位置判等法用对象在内存中的位置来作为判等的唯一因素。

    Scala 提供了 ## 方法来对应 hashCode 方法,== 方法来对应 equals 方法。hashCode 和 equals 方法总是成对实现。

    继续阅读