薛定谔的风口猪

站在巨人的肩膀上学习,猪都能看得很远

大学生与烧饼小贩

最近写技术的东西比较多,刚好也在看《黑客与画家》,里面讲到的关于如何创造财富这一论点引起了我极大的共鸣和思考,觉得既然博客折腾起来了,就简单整理下,写下点东西。刚好最近大学生校园招聘的季节,就从这个话题切入扯点东西吧。

在当今社会,什么东西都离不开钱,似乎没有钱,就生存不下去了(似乎的确是这样子)。然后,我们从出生那天起,就被安排着如何成为一个会赚钱的人。

上一个好的小学,报读各种英语培训班、奥数班,目的是为了考上好的中学。在中学和无数人死磕排名,好像排后了一名就世界末日一般的前景,每个人都被用成绩贴上了各种标签,无他,为了高考考个好成绩。

高考罢,要挑一个好的学校,读一门好的专业。好的学校就是就业率高,好的专业就是毕业容易找到好的工作,好的工作就是钱多活少又稳定。呵呵。没有一个人会问学校的学术氛围,也没有几个人会考虑孩子/自己到底喜欢的是什么,毕竟学生已经被训练为不会思考的机器,一辈子都在同一条起跑线上竞争,不需要思考,只需要永远的跑,跟着前面跑,跟着大家跑。所以,突然有了选择的时候,反而迷茫了,下意识的也只会跟着大众走。于是,金融、计算机、工程这些专业变得炙手可热。随之而来的,随便一所不沾边的学校都乱开一通专业。

在这个时候,你会发现,家长、学校变化都十分巨大。学校不再有很多的竞争逼着你走,虽然学生会丑陋的政治斗争依旧十分激烈,但我们可以不参加。而家长似乎也对你的成绩不那么过问了。无他,因为这么多年来,似乎已经到了收割的季节——到了毕业那一年,任务就完成了——孩子成为了一个大学生,理应是一个会赚钱的人咯!

在这个时候,才发现,就业竞争是如此的激烈。几万人应聘那零丁的几个职位,挤破头来最后拿个3 4千的工作。所有人都不知道这一辈是对呢?还是错?反正都这样干,那就都这样吧。毕业了也就那样了,无无聊聊朝九晚五,挤着公交地铁,天天看着房价涨、工资甚至还赶不上物价。眼看着街边一个卖烧饼的收入都一万多了,才可能回想,这一辈子到底得到了什么?

其实这也难怪,一个毕业后只能对着电脑打打字、写几份文件的,比不上一个卖烧饼的,又有什么出奇的?

但肯定很多人出来骂娘了,这TM不公平啊!凭啥!

问题就在这里,很多大学生总觉得这不公平、家长们也觉得无奈:农民工兄弟做的是体力活,我做的好歹他们不会做,说得自己做的东西像战斗机一样是什么高科技。坦白说,即便是如我们这种码农一样的职业,也就用着美国人的电脑,在美国人的系统上用美国人的语言模仿着美国人的代码,也不见得多么高端。

这里似乎有一个误区,我们总潜意识的想着用付出去衡量收获,这可能和读书时代我们宣扬“一分耕耘,一分收获”有关。这完全是一个错误的观点。在市场经济下,能交换收获的,只有输出。

这本来是一个很原始的道理,但随着历史的发展,被慢慢淡化了,所以有必要从头谈谈。

在古时候,没有货币出现的时候,人们会使用物品交换的方式去获得自己所需的东西。如果我需要鸡蛋,但我没有鸡去生蛋,那么我可能用羊去换。找到一家需要羊的,又拥有鸡蛋可换的人,跟他们去交换。然后假如我需要另外的东西,就再找合适的人交换。

这样发展到后面,人们的需求越来越多,在局限的一个地方要找到双方都刚好互补需求的另外一个人,十分不容易。于是,一般等价物便出现了,这类东西的一个最重要的属性是稀有,一般说来是稀有金属,如金、银。人们可以用自己生产的物品交换这一等价物品(卖),然后用这个等价物去购买另外的商品。

再到后来,携带金属交换并不便利,而且计算也没那么灵活。便出现了货币,直至纸币。人们去工作去赚钱,赚到了钱就可以买自己需要的东西。

在这里,一个概念容易被模糊——钱似乎就是财富。但这并不正确。钱只是换取、衡量财富的一种物品,它本身并不具备价值。假如突然有一天,银行印了一大堆钞票满天飞,那么你手上的钱瞬间就沦为废纸。

人类发明了一般等价物和后来的货币,为的不是创造财富,而是为了交换财富。所以,真正的财富,是我们用钱交换(买/卖)的东西,这里面说的很可能是一种物质的商品,也有可能是一种服务。

那么,理清楚这点之后,对于我们理解大学生就业、自己拿多少工资这种问题有什么作用呢?作用十分明显,乃至答案呼之欲出。我们去打工,不是卖自己的时间,自由、劳动力去交换钱(这些是表面的),深沉次的是,我们在出售“生产力”(并不是劳动力)。简单说,就是企业买了我们的某段时间、自由,并用我们的某些技能去为他们服务。而至于他们给多少钱,这并不取决于我们的服务时间、服务态度,而是取决于我们这段时间的“生产力”——能创造多少财富。

对,财富是可以创造出来的,这似乎每个人都懂,但似乎又不是。如果我们把钱=财富的话,这个命题就是假命题。因为钱的多少是由银行决定的,而银行把钱印到全世界都是,这并不增加社会的财富,反而让所有人都变穷了。明显,钱不是财富。那么什么是财富?

举一个例子,有一天,马浓再也敲不动代码了,然后被老板炒了。身上没有钱,穷得只剩下有一台破单车,还要不能跑的。然后他把它修了一下,能跑起来了,于是拿去卖了,获得了100块,够吃一个月的满头了!这一个过程中,马浓就创造了财富,他本来不拥有任何可以交换钱的物品(至少换不了100块),现在有了,因为他创造了一量价值100块(准确地说,价值1个月的馒头)的单车,这个世界因为马浓这一个行为,财富得到了上升。

所以,财富不是一定的,而是不断的变化的(人类历史就是不断创造财富的过程),这并不是一个零和游戏!马云他成功成为了中国首富,但没有从社会剥夺过一分一毫。相反,他让整个中国的财富迅速上升——很多店主依靠淘宝赚了一桶金,很多老百姓因为淘宝买到了更为便宜的衣服。

但这样一看,似乎只有生产,或者说只有物质生产才是创造财富的过程?也不对!再举个例子,淘宝卖的衣服,衣服本身是财富,但是他一开始并不能成为社会的财富,假如他生产出来的衣服一直都摆在库存没人穿,他们从其量也就一堆布料。

然后这时候,马浓去应聘做了一个物流人员,做起了把衣服从浙江发往广东的工作,使得广东的老百姓能买到浙江的衣服了。在这个过程中,假如没有马浓的输送,衣服无法成为你和我的财富。也就是说,服务也是一个创造财富的过程,由于物流人员的服务,我能够买到衣服,而卖家能卖到钱。这繁荣了整个淘宝,使得大家继续不断地创造更多的财富。而马浓也因此赚到了每月近万元的收入。

扯得有点远,我们回到大学生就业和卖烧饼的例子。大学生工作获得的工资,其实就是帮助企业去创造财富的过程,虽然这一过程似乎都被模糊为帮企业赚钱了。一个企业用4000块请你工作,是因为他觉得你能创造多余4000块的财富(假如一个月后人民币没有贬/升值)。这和市场买卖是一个意思,你值4000块,自然有人用4000买,如果你3000肯卖,肯定很多人愿意买。如果你希望自己能卖个10000块,最后肯定的结果就是没人肯买你(即便买了也会立刻退货),最后经过市场的催化,会逐渐趋向于你最终的“市值”。而没有任何一个人逼你去卖一家烧饼小摊的烧饼,然总有很多人愿意去买,因为他创造了烧饼这一财富,而你认为他卖得并不贵(坦白说,是挺贵的,但市场告诉你,他值这个价),最后他赚得了10000多的收入。

Fair enough! 十分公平。也许你的工作并不比卖烧饼的轻松(不过很多的确比烧饼轻松得多),而且我们的前期投入也肯定比小贩们投入得多得多(无论教育费用,时间),但这就是市场给予我们的回应。一个人的收入,与他创造财富的能力相匹配,这是我能想到的最为公平且合理的资源配置方式。当然,除了创造财富外,还有另外一种获得财富的手段,那就是偷窃。这不包括真真意义上的偷,而还包括贪污、资源垄断,他们和偷窃都拥有一样的特点——没有创造财富而获取创造出来的财富,这在中国还十分的严重,但对比历史已经好得多得多。至少,以前的首富都是达官贵人,现在我们的首富却是马云。

那么,自己能拿多少钱?应该问的,不是自己付出了多少,自己的学历,自己的身份,而是问自己到底能创造多少财富,在市场化的社会里,最简单、最公平的获取财富的手段,就是创造财富了。

如何在eclipse中修改源目录路径

在我们使用Maven或者Gradle的时候,源码目录要求是:src/main/java。

但是如果我们直接用已经构建好了的eclipse项目,无论怎么新建文件夹,都不能构建出这样的源目录结构。eclipse会把main.java视为包(package)。

要解决这个问题,我们需要先让eclipse不要把src视为源目录(source folder)。方法:

右键src目录—>build path —> remove from build path.

这样以后,我们就可以建立我们的main和java文件夹在src下。 然后右键java文件夹—>select build path –> use as source folder, 这样就可以把源目录指向src/main/java了

浅谈网站性能优化

今天简单从上层的角度聊一下如何对有一个网站进行优化。

从用户在浏览器敲下回车,到数据回来,至少可以分为三个路径

  1. 在浏览器端,发送用户请求,并且接受服务器返回的响应数据进行页面渲染。
  2. 请求数据在网络进行传输,发送到服务器。服务器把响应数据在网络传输返回。
  3. 服务器端进行数据解析处理(访问文件,数据库等),最后返回响应数据。

我们把第一路径简要称为“前端”,第三路径称为“后端”,看看能在这三层如何对网站的性能做出优化。

前端的过程

1 . 本地DNS解析域名,得到IP地址(并将IP地址缓存起来),向目标IP发送请求(通常为HTTP)

以上过程可以优化的地方主要依靠减少DNS解析的次数。如果用户的浏览器设置了缓存,那么第二次访问相同域名的时候就不会请求DNS服务器了,而是直接用缓存中的IP发送请求。这主要依靠浏览器的相关设置,但我们也可以在页面告知浏览器需要做DNS的预取:

<meta http-equiv="x-dns-prefetch-control" content="on" />

2 . 浏览器得到相应数据做出渲染计算

在这阶段,浏览器主要做的是解析相应数据,创建DOM树,下载CSS样式应用到DOM树中,下载JS文件开始解析。

为了提高页面的访问速度,我们应该尽可能让CSS样式放到<head>中并且让下载js的语句放到<body>的末尾,这样就可以使得页面先渲染起来再执行js脚本,用户的等待时间将减小。

注:HTML5支持async属性支持脚本的异步执行,如:

<script type="text/javascript" src="demo_async.js" async="async"></script>

同时,我们可以设置浏览器的缓存,让浏览器下次防蚊时从缓存中读取内容,减小HTTP请求。

网络传输(第二阶段)

这是阶段的速度取决于网络情况,由于用户的请求的数据很小但往往接受的响应数据很大,所以这要求企业的网络带宽要有快的上行速度,这和用户的带宽是相反的。

后端过程

后端是主要可以发挥的地方,这里包括处理请求,访问数据库等资源的过程。

我们主要可以优化的地方有:

1 . 使用缓存,减缓数据库压力

我们应该尽可能使用缓存提高常用数据的读取数据,减少访问数据库的次数。现在分布式的场景下,可以使用Memcached搭建起分布式缓存。

2 . 使用异步操作代替同步操作,避免阻塞的等待时间,提高性能。

在高并发的情况,同步的请求操作(如数据库插入)会对数据库造成很大压力,同时也会导致用户的等待时间增长,我们应该尽可能使用异步的请求操作代替同步操作,以提高整体服务的响应速度,具体的操作将进入消息队列处理。最终的结果可以使用其他的方式告知用户,如邮件提醒的方式。

如何在两台机器上使用octopress

在现在云端时代,在多台机器上操作同一份文档是十分常见的需求。而需要多台机器上使用octopress,git提供了很好的支持。今天分享下如何使用git在多台机器上使用octopress.

准备工作:

之前写的“如何开始使用octopress”一样,要先安装相应的软件

  1. 安装Git
  2. 安装ruby,例如:Ruby 1.9.3-p194,并配置环境变量PATH 到rubyhome/bin
  3. 安装 Development Kit.如 DevKit-tdm-32-4.5.2-20111229-1559-sfx.exe and 解压到文件夹 C:/RubyDevKit.
  4. 建立一个文件夹,例如在C:/github

克隆项目

接下来我们需要把已经建好的博客项目clone下来。

克隆source分支

$ git clone -b source git@github.com:username/username.github.com.git octopress ##octopress 为你的项目文件夹

克隆master分支

$ cd octopress ##进入项目
$ git clone git@github.com:username/username.github.com.git _deploy ##克隆master分支到_deploy 

配置环境

$ gem install bundler
$ rbenv rehash    # If you use rbenv, rehash to be able to run the bundle command
$ bundle install
$ rake setup_github_pages

然后它会询问你的项目仓库的URL:

Enter the read/write url for your repository (For example, ‘git@github.com:your_username/your_username.github.com)

输入仓库的URL,这样你就完成了全新的一个本地博客副本。

更新变化(重要)

每次使用前,先确保拿到最新的文件

$ cd octopress  #进入项目目录
$ git pull origin source  # 更新本地source branch
$ cd ./_deploy  #进入_deploy目录
$ git pull origin master  # 更新本地master branch

提交

提交的时候,由于需要多台机器协作,需要把source分支push到origin中,这样另外一台机器才能拿到最新的源文件。

$ rake generate
$ git add .
$ git commit -am "提交评论" 
$ git push origin source  # 更新远程 source branch 
$ rake deploy             # 更新远程 master branch,并部署博文

另外的机器更新变化

在另外的机器上,就可以获取到相应的变化。

$ cd octopress  #进入项目目录
$ git pull origin source  # 更新本地source branch
$ cd ./_deploy  #进入_deploy目录
$ git pull origin master  # 更新本地master branch

谈谈Java同步机制

在多线程中,有两个核心的问题需要解决。一个是多个线程对于资源的竞争问题,我们需要同步,另外一个则是多个线程之间的交互。 今天简单谈谈Java的同步机制。

我们以一个十分简单的例子讲起。

int i=0
public int getNextI(){
    return ++i;
}

这是一个极为简单的方法,但是在多线程的环境中则增加了许多其他复杂的因素。

首先,++i不是一个原子操作。进行i+1的操作,是分多步的,最后重新赋值写给i。

其次,在多线程的环境中,每个线程都会有一个working memory, 所以如果我们启动一个新线程操作getNextI(),i的操作是出现在working memory 中的,最后操作完成后一段时间才会重新写入main memory.

这样第一个问题就是,如何保证i的可见性。也就是说,假如两个线程T1,T2。T1对i进行操作后把i变成了2,但在T1把2这个值写入main memory之前,T2是读取不到2这个值的,也就是说他读到的是老的数据。

volatile

这时候,我们就可以把i用volatile修饰。 volatile的变量,线程不会复制到working memory,而是直接在main memory上操作。

volotile特别适用于多线程环境中某个循环的结束条件。 如 while(condition){//do someting}, 这里面的condition应该声明为volatile,这样每一个线程对condition的修改都会立刻被其他线程读取到。

synchronized

volatile只能保证变量的可见性,并不能保证i++的原子性。如果我们需要串行化的处理这个方法,我们需要谨慎使用volatile,而使用synchronized

假如T1,T2同时进入getNextI(),然后T1和T2都读到了1,然后分别的进行++i,最后有可能i只是变成了2。我们希望T1和T2是有秩序的访问这个方法,这时候我们要使用到Synchronized机制了。

我们可以改成

public synchronized int getNextI(){
    return i++;
}

这样在每个线程进入getNextI()之前,都会尝试去获取当前对象的intrinsic锁,并且只有一个线程可以获取。当结束该方法时候,就会释放,这样其他线程就可以获取到,这样就可以就可以保证T1和T2对方法操作是串行的。

注: 如果当前方法为静态方法,则锁是打在当前类的Class对象,而非对象本身。所以静态方法的控制和实例方法的控制是区分开来的。

但有些时候整个方法都加锁会影响性能,因为我们可能很多操作都不涉及共享资源,也就没有资源竞争的问题存在,所以synchronized 除了可以修饰方法,还可以修饰一段代码块,以便最小粒度的限制加锁的范围。当修饰代码块时候,必须要指定获取intrinsic锁的对象。如:

public  int getNextI(){
    synchronized(this){
        return i++;
    }
}

更多文档可参考:

http://docs.oracle.com/javase/tutorial/essential/concurrency/locksync.html

初探SOA

有些系统或者产品当发展得越来越大后,就会有很多子模块,子产品。但这些模块中却有着很多相同的需要解决的问题,即有许多共有的业务逻辑。例如,可能豆瓣系统下面的豆瓣读书,豆瓣电影,豆瓣小组等等的每个模块虽然都有着独特的功能,但都有对于用户信息的读写,或者用户评价的相关逻辑。

这时候,假如每个系统都要独自的单独维护着这些逻辑,会出现很多重复性的代码和逻辑,这就会导致这一部分共有的逻辑需要修改的时候,每个模块下的对应逻辑都要作相应的更改,会导致系统的维护成本大大上升。

为了解决的这一类问题,可采用提取公共逻辑去划分不同的系统的方法来提高系统的可重用性和可维护性。

还有另外一个问题是,随着系统的访问量逐步上升,由于单一系统需要处理所有的逻辑,必然会导致当访问量、数据量上升到一定程度后出现性能问题。这时候,我们可以把系统进行分解,以让独立的子系统分工处理其中某些具体的任务。

无论哪种问题,在我们把系统分解成了几个子系统之后,都要有一个最明显的问题需要解决:系统之间如何进行通信/交互。

显而易见的,系统可以通过网络进行通信:如基于HTTP,TCP+NIO,RMI或者WebService等等进行通信。同时,具体应该是同步还是异步通信,这也是一个需要考虑的问题。

那么问题就来了,当子系统越来越多,由于系统间的通信没有统一的标准,这将导致开发人员每需要访问一个子系统时,都可能需要使用/学习不同的交互方式,这大大增加了维护成本。

这时候,SOA就出现了,它是为了解决统一交互方式这一问题出出现的。

SOA(Service-oriented architecture),强调的是系统之间使用一个统一标准的服务方式进行交互,各个系统可以采用不同的语言,不同的框架实现,而交互则全部通过服务的方式进行。

SOA所带来的挑战如下:

  • 服务多级调用所带来的延时。

任何一个分布式的应用由于需要通过网络进行调用,除了成功和失败之外,都会多出一种状态:超时。而这在服务多级调用的时候会带来不少挑战:例如:A—>B—->C的过程中,可能会带啦大幅度的延时。为了解决高性能交互,需要完善调用的过程,例如当B服务执行的时如果已经超时,就没有必要调用C服务了,而应该直接抛出超时异常给A。

  • 调试/跟踪困难

如 A–>B–>C的过程,假如B报错了,调用者可能会认为B服务的问题,而B服务的开发人员则可能认为C的问题。而C则又可能认为这是网络的问题。这就导致了出现问题时候调试/跟踪困难

  • 更高的安全/检测要求

未拆分系统时候,安全和检测只需要在一个系统出现,而拆分后,每个系统都需要相应的安全控制和监测。

  • 现有应用移植

这是推广SOA的大挑战。应用越多,难度和时间越大

  • QoS(Quality of Service)的支持

每个服务提供者能支撑的访问量是有限的,因此设定服务的QoS非常重要,并且应该尽可能利用流量控制、机器资源分配等保障QoS

  • 高可用和高可伸缩的挑战

这是互联网应用必须做到的,而SOA由于承担了所有服务的交互,因此其在这两个指标上影响重大

  • 多版本和依赖管理

由于服务多起来后,需要对服务的依赖关系进行管理,以便升级时做相应的安排。

JVM垃圾回收算法

GC的原理是,找到不再被使用的对象(没有被引用),然后回收这些对象所占的内存。通常使用的是收集器的方式实现GC,主要有引用计数收集器和跟踪收集器

  • 引用计数收集器

顾名思义,引用计数收集器做的事是记录每个对象现在被引用的数量,而当计数器下降到0的时候,证明已经没有被引用了。就可以被回收了:如下图所示

引用计数收集器

当A释放了B的引用后,就可以回收B的所占用的内存。

但是引用计数需要对每一个对象赋值时进行计数器的增减,这有一定的消耗。更重要的是,他无法实现循环引用的场景。例如如果B和C相互引用,那么即使A释放了B和C的引用,也无法回收B和C。

- 跟踪收集器

采用集中的管理方式,全局记录数据的引用状态。基于既定的条件触发(如定时或者空间不足),执行时候需要从跟集合扫描对象的引用关系,这要有复制(Copying)、标记-清除(Mark-Sweep)和标记-压缩(Mark-Compact)三种实现算法。

ADB Server Didn’t ACK ,failed to Start Daemon 解决方法(小心风行客户端)

最近重新学习Android,但这两天遇到了极为奇怪的问题,突然之间,启动ADT的时候就报出下面的错误:

[2014-10-03 12:15:43 - adb] ADB server didn't ACK
[2014-10-03 12:15:43 - adb] * failed to start daemon *
[2014-10-03 12:15:43 - ddms] 'E:\Programming\ADT\adt-bundle-windows-x86_64-20140702\sdk\platform-tools\adb.exe,start-server' failed -- run manually if necessary

查了一下stackoverflow, 大多数的解决方案都是:

  1. 关掉eclipse
  2. 在任务管理器中把adb.exe关掉
  3. 进入adb所在目录,然后执行adb start-server,成功执行则问题解决

问题应该就解决了。但我的问题是, adb start-server 启动不起来!

最后发现了是端口占用的原因导致。

解决方法如下:

1.adb nodaemon server

查看不能执行的原因,输出:

cannot bind ‘tcp:5037’

2.定位到了是端口的问题!是5037端口被占用了!

3.netstat -ano | findstr 5037

查找谁占用了5037的进程,得到进程pid.

4.杀死该进程。

可以在任务管理器中杀死,或者使用命令: taskkill /pid 端口号 -f

最后发现是tfadb.exe这个程序占用了该端口。查询签名发现这是风行客户端!!

真是无比坑爹啊,安装了风行的同学真的要注意下。

数据库事务的隔离级别与并发控制

数据库拥有ACID四个基本属性。

其中的I(隔离性)要求并发事务中,事务的中间状态是无法被别的事务查看的。例如A账户转到B账户的事务中,不能让别的事务在B账户扣除了100块前查看到A事务增加了100块这个中间状态。

但出于性能的考虑,许多数据库都允许使用者配置隔离级别牺牲一定的隔离性换取并发性。

SQL定义了四种隔离级别:

  1. Read Uncommitted: 读取未提交的数据,即可读取其他事务修改了但是没有提交的数据。这是最低的隔离级别(会导致脏读)
  2. Read Committed:只能读取已经提交的数据。解决了脏读的问题但是没有解决“不可重复读”,即一个事务中多次读取的数据的内容可能是不一样的。例如,事务2在事务1开始后修改了A账户为150,而第一次读到A账户有100块,然后事务2提交,第二次读的时候就变成了150块。
  3. Repeatable Read:保证可以事务多次读取到的数据行的内容是一致的。但还是有可能导致幻读,即同一事务中,第二次读到的数据行中拥有第一次没有读取到的。
  4. Serializable:最高级别的隔离级别,即事务是可串行执行的,就像一个事务执行的时候没有别的事务执行一样。

并发控制

事务的锁分为读锁和写锁。允许为同一个元素增加多个读锁,但只允许加一个写锁,且写事务会阻塞读事务。

由于互联网的业务属性决定,读事务远远比写事务多得多。而加锁一定程度上阻碍了读的性能,对于读性能的优化是一个刚需。

现在有以下两种方法可以大大提高读取的效率而不需要加锁

写时复制(Copy-On-Write)

读操作不需要加锁,而当需要写操作的时候,以B+树为例:

1 拷贝:将从叶子到根的所有节点拷贝出来

2 对拷贝的内容进行修改。

3 提交后,原子地切换根节点指向新的节点。

这样读操作并不需要加锁,并不会被写操作所阻塞,但问题是写的时候需要拷贝结点,而且多个写操作是互斥的,一个时刻只能允许一个写操作

多版本并发控制(Multi-Version Concurrency Control,MVCC)

对于读操作也不需要加锁,原理是对于每行的数据维护多个数据版本。MySQL InnoDB的存储引擎为例,InnoDB对每行数据隐式地维护了两列——“最近被修改的事务号”和“被删除事务号”。

SELECT: 需要满足以下两个条件才能返回

  1. 行的修改版本号小于当前事务号。(证明事务开始前就被提交了)
  2. 行的删除号不存在,或者大于该事务号。(没有被删除,或者事务开始后才被删除的,保证可重复读) 在可重复读的隔离级别下,后开始的事务对数据的影响不应该被先前的事务看到,所以应该忽略后面事务的操作。

INSERT

直接把修改的事务号改为当前事务号

DELETE

直接把删除的事务号改为当前事务号。而不是真正的删除

UPDATE

更新行的时候,复制一份数据并修改最近修改的事务号为当前事务。

MVCC在读取数据时候不需要加锁,会通过对应的事务号返回需要的记录,大大提高了并发性。但由于维护了多个版本的数据,需要定期清理不再使用的数据。

怎么在Spring Controller里面返回404

由于大多的客户端和服务端是独立的(可能用不同语言编写),客户端无法获知服务端的异常,所以普通的异常处理并不足以提示客户端。而基于HTTP协议的服务,我们则需要按照服务端的异常而返回特定的状态码给客户端。

以返回404状态码为例,在Spring 的Controller里面我们可以有以下3种方式处理:

  1. 自定义异常+@ResponseStatus注解:

     //定义一个自定义异常,抛出时返回状态码404
     @ResponseStatus(value = HttpStatus.NOT_FOUND)
     public class ResourceNotFoundException extends RuntimeException {
         ...
     }
    
     //在Controller里面直接抛出这个异常
     @Controller
     public class SomeController {
         @RequestMapping(value="/video/{id}",method=RequestMethod.GET)
         public @ResponseBody Video getVidoeById(@PathVariable long id){
             if (isFound()) {
                 // 做该做的逻辑
             }
             else {
                 throw new ResourceNotFoundException();//把这个异常抛出 
             }
         }
     }
    
  2. 使用Spring的内置异常

    默认情况下,Spring 的DispatcherServlet注册了DefaultHandlerExceptionResolver,这个resolver会处理标准的Spring MVC异常来表示特定的状态码

      Exception                                   HTTP Status Code
      ConversionNotSupportedException             500 (Internal Server Error)
      HttpMediaTypeNotAcceptableException         406 (Not Acceptable)
      HttpMediaTypeNotSupportedException          415 (Unsupported Media Type)
      HttpMessageNotReadableException             400 (Bad Request)
      HttpMessageNotWritableException             500 (Internal Server Error)
      HttpRequestMethodNotSupportedException      405 (Method Not Allowed)
      MissingServletRequestParameterException     400 (Bad Request)
      NoSuchRequestHandlingMethodException        404 (Not Found)
      TypeMismatchException                       400 (Bad Request)
    
  3. 在Controller方法中通过HttpServletResponse参数直接设值

     //任何一个RequestMapping 的函数都可以接受一个HttpServletResponse类型的参数
     @Controller
     public class SomeController {
         @RequestMapping(value="/video/{id}",method=RequestMethod.GET)
         public @ResponseBody Video getVidoeById(@PathVariable long id ,HttpServletResponse response){
             if (isFound()) {
                 // 做该做的逻辑
             }
             else {
                 response.setStatus(HttpServletResponse.SC_NOT_FOUND);//设置状态码
             }
             return ....
         }
     }
    

更多详情: Spring MVC 文档