Compojure hello world

断断续续学习了很久的Clojure,对语法也半生不熟的了,不过基本可以使用了,写点web的项目锻炼一下Clojure的熟练度。

###背景介绍
CompojureRing的中间件,实现了request的路由功能。

###建立项目
开发Clojure项目标配的Lein,使用Lein命令 lein new compojure-example,建立项目。

###添加依赖
初步建立项目之后,项目中只有Clojure-core的依赖,想要使用Compojure还需要添加相关的依赖。在project.clj中新增依赖:

1
2
3
4
[org.clojure/clojure "1.6.0"]
[compojure "1.1.6"]
[ring/ring-core "1.2.1"]
[ring/ring-jetty-adapter "1.2.1"]

增加完依赖,需要增加ring的一些插件,便于使用一些命令

1
:plugins [[lein-ring "0.8.10"]]

完成了上述配置之后,project.clj文件如下:

1
2
3
4
5
6
7
8
9
(defproject compojure-example "0.1.0"
:description "Example Compojure project"
:dependencies [[org.clojure/clojure "1.6.0"]
[compojure "1.1.6"]
[ring/ring-core "1.2.1"]
[ring/ring-jetty-adapter "1.2.1"]
[hiccup "1.0.0"]]
:plugins [[lein-ring "0.8.10"]]
:ring {:handler compojure.example.routes/app})

###routes
先写一个简单的hello world。

####增加引用

1
2
3
4
5
6
7
8
9
10
(ns compojure.example.routes
(:gen-class)
(:use compojure.core
ring.adapter.jetty
compojure.example.views
[hiccup.middleware :only (wrap-base-url)]
ring.adapter.jetty)
(:require [compojure.route :as route]
[compojure.handler :as handler]
[compojure.response :as response]))

ring.adapter.jetty为了将应用跑在jetty上,当然如果不需要运行在jetty上也可以不增加。

1
2
3
4
5
(defroutes app-routes
(GET "/" [] ("hello world")
(route/resources "/")
(route/not-found "Page not found")))
(run-jetty main-routes {:port 5050})

####静态资源
通过

1
(route/resources "/")

的方式可以访问项目中的静态文件(如js,css,html,img)等。

###run

####默认ring启动
现在使用命令

1
lein ring server

就可以运行项目,web项目将默认以3000启动。

####jetty 启动
使用如下命令:

1
2
3
lein deps
lein compile
lein uberjar

如上之后,在项目的target目录下会有XX-standalone.jar,使用命令

1
java -jar XX-standalone.jar

就可以在jetty中启动项目。

###Reference

Compojure wiki
Compojure API
Ring Spec
Clojure-doc Basic web Development

乐观锁(Optimistic Locking)与悲观锁(Pessimistic Locking)

###问题
首先来看看问题。数据表ATable结构如下

1
2
3
CREATE TABLE `FUN_Product` (
`ID` int(11) NOT NULL COMMENT '主键',
`Value` int(11) DEFAULT '0' COMMENT '对应产品引用ID'

现在用户UserA和UserB要同时更新表中的同一条记录,可能会出现如下的流程:

在相同的时间段内,UserA和UserB之间的更新是相互冲突的,那该怎么解决冲突,是合并两者的结果还是分先后?

###悲观锁
悲观锁,正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)[1]。

以MySQL InnoDB为例:

1
2
3
4
5
6
7
8
//0.开始事务
begin;/begin work;/start transaction; (三者选一就可以)
//1.查询信息
select value from Atable where ID=1 for update;
//2.修改value
update Atable set value=2;
//4.提交事务
commit;/commit work;

SQL中使用了select…for update的方式,就实现了悲观锁,在ATable表中ID为1的列就被锁定,其它的事务必须等本次事务提交之后才能执行。这样我们可以保证当前的数据不会被其它事务修改。
需要注意的是,在事务中,只有SELECT … FOR UPDATE 或LOCK IN SHARE MODE 同一笔数据时会等待其它事务结束后才执行,一般SELECT … 则不受此影响。

使用select…for update会把数据给锁住,不过我们需要注意一些锁的级别,MySQL InnoDB默认Row-Level Lock,所以只有「明确」地指定主键,MySQL 才会执行Row lock (只锁住被选取的数据) ,否则MySQL 将会执行Table Lock (将整个数据表单给锁住)。
这样在上述的问题,A必须等待B的事务结束之后,才能操作表,也就解决了可能出现的冲突。

###乐观锁
乐观锁( Optimistic Locking ) 相对悲观锁而言,乐观锁假设认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让返回用户错误的信息,让用户决定如何去做。那么我们如何实现乐观锁呢,一般来说有以下2种方式:

  • 使用数据版本(Version)记录机制实现,这是乐观锁最常用的一种实现方式。何谓数据版本?即为数据增加一个版本标识,一般是通过为数据库表增加一个数字类型的 “version” 字段来实现。当读取数据时,将version字段的值一同读出,数据每更新一次,对此version值加一。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的version值进行比对,如果数据库表当前版本号与第一次取出来的version值相等,则予以更新,否则认为是过期数据。
  • 乐观锁定的第二种实现方式和第一种差不多,同样是在需要乐观锁控制的table中增加一个字段,名称无所谓,字段类型使用时间戳(timestamp), 和上面的version类似,也是在更新提交的时候检查当前数据库中数据的时间戳和自己更新前取到的时间戳进行对比,如果一致则OK,否则就是版本冲突。
    乐观锁在更新数据时的流程会变成如下的模型:

###方案选择

悲观锁 乐观锁
独占数据,其他线程需要等待,不会出现修改的冲突,能够保证数据的一致性 其他线程可以同时读取数据,会出现冲突修改,需要上层逻辑解决冲突。
依赖于数据库的实现 但在出现冲突时,需要上层逻辑处理,一般进行重试,或者抛弃修改。
会降低效率,在线程较多时出现等待 能够保证高并发下的读取,效率较高
避免冲突 解决冲突

两种锁的选择并没有绝对的优劣,实际应用中根据项目需求判断;一般而言,对于读取频率很高而修改频率较少的需求可以采用乐观锁;数据很敏感且读取频率较低的可以采用悲观锁的方式。

###Reference

1.百度百科
2.mysql乐观锁总结和实践
3.mysql悲观锁总结和实践
4.Optimistic or pessimistic locking - Which one should you pick?
5.Transactions and Optimistic Locking

这是学习「深入理解Java虚拟机:JVM高级特性与最佳实践(第2版)」时记录的笔记,共享出来。


图片可以直接下载:下载地址

运行时数据区

程序计数器(ProgramCounter Register)

每个线程都有独立的程序计数器

一块较小的内存空间,可以看作当前线程所执行的字节码的行号指示器

虚拟机栈(VM Stack)

线程私有,生命周期与线程相同

每个方法执行时都会创建一个帧栈,用于存储局部变量表、操作数栈、动态链接、方法出口等信息

局部变量表存放了编译期可知的各种基本数据类型,对象引用和returnAddress

本地方法栈(Native Method Stack)

与虚拟机栈类似,但只为Native方法服务

方法区(Method Area)

用于存储已经被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码

Permanent Generation(保存虚拟机自己的静态(refective)数据

主要存放加载的Class类级别静态对象如class本身,method,field等等
permanent generation空间不足会引发full GC)

堆(Heap)

被所有线程共享的一块内存区域

也成为GC堆,是垃圾收集器管理的主要区域

内存分配与回收策略

堆内存布局
Eden:存放新生的对象

####### 对象优先分配至Eden区,当空间不足时,将触发MinorGC

####### MinorGC:新生代GC,指发生在新生代的拉圾收集动作,MinorGC非常频繁,一般回收速度也非常快

Survivor Space,主要用于存储垃圾回收之后的存活对象
Old Generation:用于存放生命周期较长的大对象

####### MajorGC/FullGC(老年代GC): 发生在老年代的GC,出现了MajorGC,通常伴随至少一次MinorGC,MajorGC速度通常比MinorGC慢10倍以上。

####### 大对象

####### 长期存活的对象

######## 对象每在Survivor经历一次MinorGC Age增加1,当增加到15时就直接晋升到老年代

######## 如果在Survivor空间中相同年龄所有对象大小的总和大于Suvivor空间的一半,年龄大于或等于该年龄段对象就可以直接进入老年代

内存布局

执行引擎

类加载

类加载时机(必须进行初始化的时机)

遇到new,getstatic,putstatic,invokestatic四条指令时,如果类没有进行初始化,需要初始化操作
使用new关键字初始化对象
读取或设置一个类的静态字段(被final修饰,或者编译器把结果放入静态池的静态字段除外)
调用一个类的静态方法时
当初始化一个类的时候,发现其父类还没有进行初始化
使用java.lang.reflect包的方法对类进行反射调用的时候
虚拟机启动时,用户需要指定要执行的主类,虚拟机会先初始化这个主类
当使用JDK7以上的动态语言支持

类加载过程

加载(Loading)
通过一个类的权限定名获取定义此类的二进制字节流

####### zip包等

####### 网络中获取

####### 运行时计算生成,用的最多的为动态代理技术

####### 其他文件生成,典型如JSP

####### 数据库中读取

将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
在内存中生成一个代表这个类的java.lang.class对象,作为方法区这个类的各种数据的访问入口
验证(Verification)
文件格式验证
原数据验证
字节码验证
符号引用验证
准备(preparation)
正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区分配
分配内存的变量仅为类变量(static修饰的变量),而不包括实例变量
解析(Resolution) :
某些情况下可以在初始化之后开始,为了支持Java的运行时绑定
虚拟机将常量池内的符号引用替换为直接引用的过程

####### 符号引用

####### 直接引用

初始化(Initialization)

类加载器

虚拟机角度
Bootstrap ClassLoader(c++实现,虚拟机的一部分)
其他类加载器(独立于虚拟机存在)
开发者角度
Bootstrap ClassLoader(启动类加载器)

####### 负责将/lib目录中的,或者被-Xbootclasspath参数指定的路中,且是虚拟机识别的类库加载到虚拟机内存中

Extension ClassLoader(扩展类加载器)

####### 加载/lib/ext目录,或者被java.ext.dirs系统变量指定的路径内的所有类库

####### 开发者可直接使用

Application ClassLoader(应用程序类加载器)
双亲委派模型
将类加载请求委派给父类加载器完成,父类同样如此
当父加载器无法完成加载时,子加载器才尝试自己加载
破坏双亲委派模型

虚拟机字节码执行引擎

栈帧

局部变量表
变量值存储空间,存放方法参数和方法内定义的局部变量
Variable Slot为最小单位
对于64位数据类型,虚拟机以高位对齐的方式分配两个连续的Slot
操作数栈
Last In First Out栈
动态连接
每个都包含一个指向运行时常量池中该栈帧所属方法的引用
方法返回地址
其他额外信息

本地库接口

垃圾收集

垃圾收集算法

标记-清除算法

复制算法

目前的商业虚拟机都使用此算反回收新生代

标记-整理算法

多用于回收老年代

分代收集算法

垃圾收集器

Serial收集器

必须停止其他所有的工作线程
Client模式下新生代的默认收集器
简单高效
只使用一个CPUhuo一条收集线程

SerialOld 收集器

Serial收集器的老年代版本

ParNew收集器

Serial的多线程版本

Parallel Scavenge收集器

新生代收集器
达到可控的吞吐量
复制算法

ParallelOld收集器

ParallelScavenge的老年代版本
使用多线程和标记-整理算法

CMS 收集器

依获取最短回收停顿时间为目标的收集器
ConcurrentMark Sweep (标记-清除算法)
初始标记

####### 标记GC Root能直接关联到的对象,速度很快(Stop the world

并发标记

####### 发生在GC Roots tracking阶段,可与用户线程一同运行

重新标记

####### 修正并发标记期间,用户程序继续运行而导致标记产生变动的那一部分对象的标记记录(Stop the world)

并发清除
无法清除浮动垃圾(Floating Garbage),可能出现ConCurrent Mode Failure
标记-清除算法容易导致碎片,在分配大对象时,可能需要FullGC

G1收集器

面向服务器端的收集器
特点
并行与并发
分代收集
可预测的停顿
空间整合

####### 整体看基于“标记-整理”算法

####### 局部看基于“复制”算法实现

####### 收集后不会产生内存空间碎片

将整个java 堆划分为多个大小相等的独立区域(Region)

判断对象已死?

引用计数法

可达性分析

GCRoots?
主要在全局性的引用(常量,或类静态属性)
执行上下文中(如栈帧里的本地变量表)
分析时,需要Stop the world,暂停所有线程的执行

GC类型

MinorGC:新生代GC,指发生在新生代的拉圾收集动作,MinorGC非常频繁,一般回收速度也非常快

MajorGC/FullGC(老年代GC): 发生在老年代的GC,出现了MajorGC,通常伴随至少一次MinorGC,MajorGC速度通常比MinorGC慢10倍以上。

团队问题的思考

image

团队经过了上半年的创业期,发生了很多事情,也做出来很多,但是其中的很多问题更值得深思。

####1.测试

#####问题

  • 自测不充分

    提测时出现不能保证冒烟测试的问题,与QA沟通不充分,test case项目之间不沟通,容易导致功能遗漏。

  • QA测试存在隐患

    QA在测试过程与开发沟通不够,出现一些功能点的遗漏,而且会因为开发自身实现的问题会遗留一些难以复现的bug。

#####方案

需要一套开发与QA均可以接受的测试方案,开发自测不应该只是保证冒烟测试,应该更多的参与到QA的功能测试中,保证重要功能的通畅,而且可以对test case进行补充。一些较大项目或者功能,可以在test case完成时,QA与开发进行沟通,如一起看一遍test case用例,保证大家对功能的理解是一致的。

####2.开发

#####问题

  • code review缺失

    不能保证code review,一些功能没有code review,一些在最后上线才提交code review,导致项目代码较为混乱,而且容易出现难以发现的bug。

  • 上线较为随意

    很多上线无上线邮件,会出现一个项目一天内多次上线,不利于项目的安全

#####方案

开发在提测时同时提交code review,在修改测试bug时修正code review发现的问题。上线问题可以在每天站会时沟通,同一个项目的功能可以合并到一起上线。

####3.团队建设

#####问题

  • 周会不健全

    因为时间问题,没有稳定的周会。缺少团队性的总结,一个问题一个人遇到了在另一个人身上也遇到了。周会可以是一个解决团队问题的时间,也可以作为大家沟通的时间。

  • 缺少学习分享

    之前的新人学习基本是个人学习,或者口口相传式,未能形成体系。个人学习到的新技术,或者在实际工作中的技术心得未能分享给大家,不能发挥最大效果。

#####方案

规律的周会,形式可以让大家都能参与其中的,如可以抛出一个问题供大家讨论,或者某个人分享一些技术心得,或者遇到的苦难。

周会可以成为大家工作上的交流时间,团队的凝聚力更强些。一定的分享可以激励更好的学习,不断提供能力。

####缺少时间是否是最大的问题?

上面的问题都会隐含着一个最大的问题:时间不够。有时候甚至连开发功能的时间都不够,又哪能有足够的时间来自测,code review或者团队建设?

但是在实践中却发现,没有足够的自测导致测试时间变长,上线时间并没有能够减少,而且导致QA测试过程中,开发修bug的时间过长,不仅耽误了功能的上线,而且耽误了下个功能开发,不能形成一个良性的循环。

没有足够的code review,一次混乱的代码上线了,花在整理这些代码的时间非常大,而且一不小心还会导致功能的错误,重复工作量较大,由于不断的修复这种问题,又使得上线频繁。

也许时间并不是最大的问题,是不是我们的工作方式出现了问题,才导致时间这么紧张,或者两者兼而有之??

undertow介绍

undertow提供了blocking和non-blocking两种api,灵活高性能的服务器框架。

###特点

  • 轻量级

  • 嵌入式

  • 灵活

  • 支持Websocket

  • 支持Servlet 3.1

  • 支持HTTP2

###Demo code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class HelloWorldServer {

public static void main(final String[] args) {
Undertow server = Undertow.builder()
.addHttpListener(8080, "localhost")
.setHandler(new HttpHandler() {
@Override
public void handleRequest(final HttpServerExchange exchange) throws Exception {
exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain");
exchange.getResponseSender().send("Hello World");
}
}).build();
server.start();
}
}

更多example

更多特性待续……

###Releated

undertow website
undertow cookdoc

undertow doc

github

issue

Clojure function

今天记录一下Clojure的function.
Functions are first class object in Clojure.

####定义函数

#####defn
最常用的函数定义方式就是使用defn来定义,首先来写个hello world.

1
2
3
4
(defn hello-world
[name]
(println (name " say Hello,World!"))
(hello-world "brucefeng")

函数再控制台上将会输入“brucefeng say Hello,World!”
通常我们在使用函数是基本有一下两种,无参数和有参数的函数,其中有参数的函数又会出现单个参数和多个参数,每种函数只是在写法上有细微的差别。

  • 无参数函数

    1
    2
    (defn hello-world
    (println "Hello,World!"))
  • 单个参数函数
    最开始的函数就是这种方式。

  • 多个参数函数

    1
    2
    3
    4
    (defn hello-world-multiparam
    [firstname familyname]
    (println (str firstname familyname "say Hello World!")))
    (hello-world-multiparam "bruce" "feng")

当多个参数时,在函数内部可以使用1%,2%这种方式来调用第一个,第二个参数,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

(defn hello-world-multiparam
[firstname familyname]
(println (str %1 %2 "say Hello World!")))
(hello-world-multiparam "bruce" "feng")

(defn name doc-string? attr-map? [params*] prepost-map? body)
(defn name doc-string? attr-map? ([params*] prepost-map? body) + attr-map?)

```
- 多个参数函数
可以在参数列表中包含一个&,即可将剩余的参数放入一个序列,绑定到&后面的变量上
```clojure
(defn greeting
([] (greeting "World"))
([userName] (println (str "Hello " userName))))

#####函数注释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
        (defn hello-world
"this is a comment"
[name]
(println name " say Hello,World!"))
(doc hello-world)
```
#####函数metadata
```clojure
(defn hello-world
"this is a comment"
{:forstudy "yes"
:added 0.1}
[name]
(println name " say Hello,World!"))
(meta (var hello-world))

使用meta就可以查看函数的元数据了。

(meta (var hello-world))      

现在回头来看看defn的定义:

1
2
(defn name doc-string? attr-map? [params*] prepost-map? body)
(defn name doc-string? attr-map? ([params*] prepost-map? body) + attr-map?)

基本上已经涵盖了上面的集中情况。

#####defn-
(defn- name & decls)
defn-与defn功能一致,都是用于定义函数的,defn-定义的函数作用域是私有的,而defn定义的函数是公有的

####匿名函数
匿名函数就是没有名字的函数,在很多情况下,一个功能较为复杂,但是在其他地方的重用度较低,没有必要单独定义函数,这种时候就可以使用匿名函数。

1
2
(fn [] (println "hello,World!"))
(def plus-func (fn [x y] (+ x y)))

匿名函数可以配合def使用,就可以起到defn相同的作用:

1
2
(def plus-func (fn [x y] (+ x y)))
(println (plus-func 1 2))

当然匿名函数最大的作用不是为了和def一起使用取代defn的,在这里使用这种写法只是为了匿名函数的使用形式.
匿名函数的定义除了使用fn的方式,还可以使用#()的形式,%表示唯一的参数,%1、%2 ..依次表示第1、2、..个参数;%&表示所有参数

(def minus-func #(- %1 %2))
(#(/ %2 %1) 3 4)
(#(apply / %&) 3 5 7)   ;结果为3/5/7

####Reference

Clojure 学习入门(6)—— 函数定义
Clojure By Example

Rime(鼠须管)是一个支持Windows,Linux和Mac三个平台的输入法,也叫小狼毫、中州韻,被广大网友称做神级输入法。

###鼠须管的优缺点

####优点
>
快。
標點符號全,鍵位分佈合理。比如無論繁簡,都同時可以打出「」([ ])、『』(shift+ [ ])和“”(shift + ‘)——這裡希望提醒各位中文輸入法開發者,直角引號不是繁體中文專用的引號,不要把它們設定爲只有繁體狀態下才可以輸入。(包括 OS X 自帶輸入法都在犯這個錯誤。)其它標點的分佈也非常合理,選擇也很多。
幾種主流音形輸入法都有,選擇很多。
用字規範,對我這種識字不多但又希望多認識幾個字的人幫助很大。
候選字排序合理。這一點我在 FIT、IMKQIM 和 SunPinyin 上都覺得很彆扭,經常出現在第一個(即用空格可上字)的候選項是很「奇怪」的字。我不懂技術,沒法做出更準確的描述了。
交互界面簡單。幾乎所有第三方輸入法的界面都有一些多餘的設計元素,這裡弄個紋理,那裡放個圖標。鼠鬚管就不同,注意力可以全部放在文字上。同時又給用戶適當調整配色和字體的自由——雖然沒有圖形設置界面,其實稍微熟悉一下,自定義還是挺簡便的。我都可以做,相信絕大多數人都可以做。

参见 鼠鬚管(小狼毫、中州韻)输入法有什么优点,使用体验如何

####缺点
rime是一个开源输入法,也就有着开源软件的通病。

  • 图形化配置不全

rime模式使用.yaml格式的配置文件,相对入门用户及其不方便。不过这个问题已经有网友解决了。

  • 文档不足

和上个文档比较相关,如果能够提供足够的图形化配置,对于入门用户也就无需过多的文档了,毕竟只是个输入法,绝大部分的用户只是想流畅的输入。

  • bug修复较慢

###Mac上的Rime

###安装
下载地址 下载及安装

###配置工具

  • 配置文件
    选择了鼠须管之后,可以使用-> 用户设定 ,之后会进入Rime的配置文件目录。详细的可以查看 鼠须管,“神级”输入法
  • GUI配置工具
    SCU
    使用了这个配置工具之后,终于能够摆脱代码式的配置了,对于入门级用户是个巨大的福音。
    OTP

OTP

###删除Rime

Rime没有自动的删除功能,所以在Mac上只能通过手动命令的方式来删除了。

rm -rf /Library/Input\ Methods/Squirrel.app

就能成功删除了,用的不舒服能很快的删除。

Date: 2013-08-19 22:16:45
tag:others
category:esay
title:第一篇
又开始折腾博客了,这是第一次实用静态博客,折腾了好久也没折腾好。