Java泛型通配符extends与super

关键字说明

● ? 通配符类型

● <? extends T> 表示类型的上界,表示参数化类型的可能是T 或是 T的子类

● <? super T> 表示类型下界(Java Core中叫超类型限定),表示参数化类型是此类型的超类型(父类型),直至Object

####1. extends 示例

1
2
3
4
5
6
7
8
9
10
11
12

static class Food{}
static class Fruit extends Food{}
static class Apple extends Fruit{}
static class RedApple extends Apple{}

List<? extends Fruit> flist = new ArrayList<Apple>();
// complie error:
// flist.add(new Apple());
// flist.add(new Fruit());
// flist.add(new Object());
flist.add(null); // only work for null

List<? extends Frut> 表示 “具有任何从Fruit继承类型的列表”,编译器无法确定List所持有的类型,所以无法安全的向其中添加对象。可以添加null,因为null 可以表示任何类型。所以List 的add 方法不能添加任何有意义的元素,但是可以接受现有的子类型List 赋值。

Fruit fruit = flist.get(0);
Apple apple = (Apple)flist.get(0);

由于,其中放置是从Fruit中继承的类型,所以可以安全地取出Fruit类型。

flist.contains(new Fruit());    
flist.contains(new Apple());

在使用Collection中的contains 方法时,接受Object 参数类型,可以不涉及任何通配符,编译器也允许这么调用。

#####2. super 示例

List<? super Fruit> flist = new ArrayList<Fruit>();
flist.add(new Fruit());
flist.add(new Apple());
flist.add(new RedApple());
// compile error:
List<? super Fruit> flist = new ArrayList<Apple>();

List<? super Fruit> 表示“具有任何Fruit超类型的列表”,列表的类型至少是一个 Fruit 类型,因此可以安全的向其中添加Fruit 及其子类型。由于List<? super Fruit>中的类型可能是任何Fruit 的超类型,无法赋值为Fruit的子类型Apple的List.

// compile error:
Fruit item = flist.get(0);

因为,List<? super Fruit>中的类型可能是任何Fruit 的超类型,所以编译器无法确定get返回的对象类型是Fruit,还是Fruit的父类Food 或 Object.

小结

extends 可用于的返回类型限定,不能用于参数类型限定。

super 可用于参数类型限定,不能用于返回类型限定。

直观地讲,带有super超类型限定的通配符可以向泛型对象写入,带有extends子类型限定的通配符可以向泛型对象读取。——《Core Java》

zookeepr 单机伪集群配置

  1. 要在单机下搭建群集,简单的就是分别解压复制N个目录。

  2. 每个目录,分配配置对应的data 和 myid ,其中myid 用来表明当前启动是哪个zookeeper

  3. 配置文件的群集属性

server.1=127.0.0.1:2881:3881
server.2=127.0.0.1:2882:3883
server.3=127.0.0.1:2883:3883
server.4=127.0.0.1:2884:3884
server.5=127.0.0.1:2885:3885
  1. (我被坑的地方)每个zookeeper 服务有三个端口,

    clientPort=2181,这个用于指明给客户端应用连接的端(例如java 程序访问就是这个端口)

    server.1=127.0.0.1:2881:3881 ,其中2881 用于群集间的服务器进行数据通讯,
    3881 用于群集刚启动时

或leader 当机时 进行选举。

完整的配置文件(单机环境,5个实例的端口都要不一样)

# The number of milliseconds of each tick
tickTime=2000
# The number of ticks that the initial 
# synchronization phase can take
initLimit=10
# The number of ticks that can pass between 
# sending a request and getting an acknowledgement
syncLimit=5
# the directory where the snapshot is stored.
# do not use /tmp for storage, /tmp here is just 
# example sakes.
dataDir=D:/data/zookeeper/server5/data
# the port at which the clients will connect
clientPort=2185
#
# Be sure to read the maintenance section of the 
# administrator guide before turning on autopurge.
#
# http://zookeeper.apache.org/doc/current/zookeeperAdmin.html#sc_maintenance
#
# The number of snapshots to retain in dataDir
#autopurge.snapRetainCount=3
# Purge task interval in hours
# Set to "0" to disable auto purge feature
#autopurge.purgeInterval=1
server.1=127.0.0.1:2881:3881
server.2=127.0.0.1:2882:3883
server.3=127.0.0.1:2883:3883
server.4=127.0.0.1:2884:3884
server.5=127.0.0.1:2885:3885

附上完整的配置文件 https://swtools.googlecode.com/git/linux/zookeeper/zookeeper.zip

git 常用命令

生成SSH公钥

cd ~/.ssh
ssh-keygen -t rsa -C "your_email@youremail.com"
cat ~/.ssh/id_rsa.pub  显示公钥

配置全局用户名

git config --global user.name "username"
git config --global user.email "xxxx@email.com" 
git config -l  查看所有配置参数

克隆仓库

git clone git://github.com/jquery/jquery.git

项目初始化

mkdir project_dir 创建项目目录
git init  初始化项目
touch README 生成readme 的空文件
> README 【或】生成readme 的空文件

git add -A  添加当前文件夹所有文件    
git commit -m '[comment]'    进行提交
git commit -a  提交所有修改的文件(不包括新建的文件)
git commit -am '[comment]'  全部提交
git status  查看git状态

建立git仓库

mkdir project.git
cd project.git
git --bare init 

删除文件

git rm [file_name] 移除文件(包括路径)
git rm -r -f [dir_name]  遍历移除文件夹的所有内容

工作目录中添加一个叫”.gitignore”的文件,用来忽略某些文件。

日志

git log 查看git日志
git log -p 查看详细日志
git log --pretty=oneline 查看日志显示在一行

远程操作

git remote -v 查看远程仓库
git remote add origin git@github.com:xxxx.git 添加远程仓库
git remote rm origin 移动远程仓库
git remote set-url --push origin [new_url] 修改远程仓库
git remote show origin 显示远程库origin的资源 
git push origin master
git pull origin master

分支操作

git branch 查看本地所有分支
git branch -a 查看所有分支[包括远程]
git branch -r 查看远程所有分支 

git branch feature1 master 从主分支master创建分支feature1
git branch -m old_branch_name new_branch_new 分支改名

git checkout [branch] 切换到分支
git checkout -b [branch] 创建并切换到分支
git checkout -t origin/dev 切换到远程分支

删除分支
git branch -d [branch_name] 删除一个分支
git branch -D [branch_name] 强制删除未合并的分支
git push origin :branch_remote_name 删除远程分支
git branch -r -d branch_remote_name 删除远程分支

分支合并(将dev分支合并到master)    
git checkout master
git pull origin master
git merge dev  执行合并
git push origin master    

标签

git tag 列出所有标签
git tag [tag_name] 打标签
git tag [tag_name] -a "comment" 打标签并加注释
git tag -d [tag_name] 删除本地的标签

git pull origin --tags 获取远程的所有标签
git push origin --tags 推送本地的标签到远程
git push origin [tag_name] 将标签推送到远程
git push origin :refs/tags/tag_name 删除远程标签

比较
git diff 查看尚末缓存的更新
git diff [commit_version] [commit_version] 显示两次提交的区别
git diff master dev 显示两个分支的差异
git diff master 显示工作目录与master分支的差异
git diff –cached 显示当前索引与上次提交的差异

版本管理

git reset --hard HEAD 撤消一个合并
git revert [commit_version] 还原某个版本的修改
git reset [commit_version] 将工作目录还原到某个版本
git checkout [branch_name] [commit_version] 签出某个提交版本的分支
git reabse 重新定义分支的版本状态

git rebase

子模块

git submodule add  url path  添加子模块

gitignore

顶层工作目录中添加一个叫”.gitignore”的文件,用来忽略某些文件。

例子:

# 以'#' 开始的行,被视为注释.
# 忽略掉所有文件名是 foo.txt 的文件.
foo.txt
# 忽略所有生成的 html 文件,
*.html
# foo.html是手工维护的,所以例外.
!foo.html
# 忽略所有.o 和 .a文件.
*.[oa]

参考资料

使用JMX管理和监控JVM

JMX(Java Management Extensions,即Java管理扩展)是一个为应用程序、设备、系统等植入管理功能的框架。JMX可以跨越一系列异构操作系统平台、系统体系结构和网络传输协议,灵活的开发无缝集成的系统、网络和服务管理应用。

配置jmx

1. 用户名密码的方式

复制模板文件

cp $JAVA_HOME/jre/lib/management/jmxremote.password.template /opt/conf/jmxreomte/jmxremote.password

配置示例

# The "monitorRole" role has password "QED".
# The "controlRole" role has password "R&D".
monitorRole QED
controlRole R&D

权限配置


chown nobody.nobody jmxremote.password
chmod 600 jmxremote.password

其中,chmod 600 jmxremote.password 等同于 chmod u=rw,g=-,o=- jmxremote.password

resin中的配置

1. 默认开启jmxremote
<cluster-default>
    <!-- sets the content root for the cluster, relative to server.root -->
    <root-directory>.</root-directory>

    <server-default>
    <!--
        - The JVM arguments
    -->
        <jvm-arg>-d64</jvm-arg>
        <jvm-arg>-Xmx4096m</jvm-arg>
        <jvm-arg>-Xms4096m</jvm-arg>
        <jvm-arg>-Xmn1536m</jvm-arg>
        <jvm-arg>-Xss2m</jvm-arg>
        <jvm-arg>-Xdebug</jvm-arg>
        <jvm-arg>-XX:MaxPermSize=512m</jvm-arg>
        <jvm-arg>-Dcom.sun.management.jmxremote</jvm-arg>
        <jvm-arg>-Dcom.sun.management.jmxremote.ssl=false</jvm-arg>
        <jvm-arg>-Dcom.sun.management.jmxremote.authenticate=true</jvm-arg>
2.具体实例监听的IP与端口
<cluster id="cluster-01">
    <server id="server-01" address="127.0.0.1" port="6801">
        <http address="192.168.1.10" port="8081" />
        <jvm-arg>-Djava.rmi.server.hostname=192.168.1.10</jvm-arg>
        <jvm-arg>-Dcom.sun.management.jmxremote.port=9081</jvm-arg>
        <jvm-arg>-Dcom.sun.management.jmxremote.password.file=/opt/conf/jmxremote/jmxremote.password</jvm-arg>
    </server>

#####3. 命令行配置

JMXREMOTE=" -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false -Djava.rmi.server.hostname=192.168.1.10 -Dcom.sun.management.jmxremote.port=9081 "
java ${JMXREMOTE} Main

参考资料

BTrace 简介

BTrace就是一个可以在不改代码、不重启应用的情况下,动态的查看程序运行细节的工具。

官方网站http://kenai.com/projects/btrace

下载地址 http://kenai.com/projects/btrace/downloads/directory/releases/current

以下示例代码来源于《深入理解Java虚拟机》

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

/**
 *
 * btrace pid TracingScript.class
 * 例如:btrace 8056 TracingScript.class
 * @author Caijianfeng
 *
 */
public class BTraceTest {

    public int add(int a,int b){
        int c = a + b;
        return c;
    }

    public static void main(String[] args) throws IOException {
        BTraceTest bTraceTest = new BTraceTest();
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
        for (int i = 0; i < 10; i++) {
            bufferedReader.readLine();
            int result = bTraceTest.add(i, i+1);
            System.out.println(result);
        }

    }
}

Btrace 脚本

import static com.sun.btrace.BTraceUtils.jstack;
import static com.sun.btrace.BTraceUtils.println;
import static com.sun.btrace.BTraceUtils.str;
import static com.sun.btrace.BTraceUtils.strcat;

import com.sun.btrace.annotations.BTrace;
import com.sun.btrace.annotations.Kind;
import com.sun.btrace.annotations.Location;
import com.sun.btrace.annotations.OnMethod;
import com.sun.btrace.annotations.Return;
import com.sun.btrace.annotations.Self;

@BTrace
public class TracingScript{

    @OnMethod(
        clazz = "BTraceTest",
        method = "add",
        location = @Location(Kind.RETURN)
    )
    public static void func(@Self BTraceTest instance,int a,int b,@Return int result){
        println("调用堆栈:");
        jstack();
        println(strcat("方法参数A:",str(a)));
        println(strcat("方法参数B:",str(b)));
        println(strcat("方法结果:",str(result)));
    }

}

Btrace 在调试resin 等容器里的代码可能由于采用的是 contentClassLoader 的原因而无法代码成功。

代码库: http://code.google.com/p/swtools/source/browse/#git%2Fjava%2Fbtrace-test

###参考资料

图片排行算法

图片排行算法,如何生成所谓的热门图片

● 相关的因子

  1. 图片的上传时间 (created_at)
  2. 图片的收藏数(likes)
  3. 最后收藏时间(last_liked)
  4. 图片上传者的权威性(认证用户?用户等级? user_rank) (传图越多的用户,用户等级比例越高)
  5. 图片的评论数(comment_count)
  6. 最后评论时间(last_commented)
  7. 图片的浏览数(page_view)
  8. 图片被分享的次数(share_count)
  9. 图片最后被分享的时间(last_shared)
  10. 图片被转存的次数(copy_count)
  11. 图片的质量(文件大小、尺寸大小)
  12. 图片的分类(图片用户所在的标签:摄影用户优先在摄影标签中排名)

● 计算公式

参考:stackoverflow 的算法
http://www.ruanyifeng.com/blog/2012/03/ranking_algorithm_stack_overflow.html

为了简化因子,我们把所有最后有相关更新时间的因子都视为一次投票行为 last_voted

时间因素
age = Math.round((now - created_at) /3600) 实现按小时排行
last_voted = Math.round((now - last_voted) /3600)

用户等级的因素:上传的图片数,原创数,认证用户,注册时间,活跃度。
在暂时没有数据的情况下,全部初始化为1.

图片的质量根据尺寸相对标准尺寸计算,其中800 600 是定义的标准尺寸。
photo_quality = photo_width
photo_height /800 * 600

图片的上升因子:
dividend = log10(page_view 4) + (likes + copy_count + comment_count + share_count 5)

+ user_rank + photo_quality 

因为分享是个非常主动行为,被分享过去说明希望自己社交圈子里的朋友也看到。

时间衰减:
divisor = pow( ((age + 1) - (age - last_voted)/2) , 1.5 )

最后得分:
score = dividend / divisor

● 更新机制

  1. 实时更新:有触发上述因子 就马上更新计数?并发修改问题?
  2. 定时更新:把有触发过上述因子的图片,放入一个队列,过一个小时取出队列里的图片进行计算,然后更新。
  3. 问题是那些在一小时内,或者说一天内没有进行操作过的图片,不会随着时间衰减?

● 存储机制

由于原有的数据没有这些信息,如果需要记录可能需要修改表结构,为了省去这个步骤。
可以采用mongodb 来存储分数与排名信息,所有图片的分数初始化为0.

简单介绍相册网站的架构

我们团队正在开发一个相册类产品,希望打造一个基于存储为中心的相册服务。

产品的基本架构如下:

###1. 负载均衡 LVS + Keepalived

● 抗负载能力强(performance)
LVS工作方式的逻辑是非常之简单,而且工作在网络4层仅做请求分发之用,没有流量的转发。 相比nginx而言有更强的并发能力,默认配置能支持到10万并发

● 可伸缩性(scalability)
当服务的负载增长时,为获得更高的吞吐量,在LVS中增加real-servers来满足需求,其开销只是线性增长,且不降低服务质量。

● 高可用性(availability)
keepalived 可以实现服务器池对象的健康检查,负载均衡之间的自动失败切换(failover).从而保证LVS负载均衡本身的高可性。如果LVS中某real-server由于需要升级或其它原因而停止服务,其退出以及恢复工作,并不会造成整个LVS对客户端服务的中断。(例如对nginx进行在线升级操作)

● 成本低廉(Low cost)
购买F5 BIG-IP 和 NetScalar 等硬件负载均衡交换机动则需要十几万甚至数十万人民币。而基于LVS最基本的需求就是两台普通服务器。

★如何保持session?
为了保证服务器的伸缩性,我们的服务器是无状态的,也就是任何一台web server 都可以被其它web server 替代。

我们采用了基于加密 cookie 的session 来保持用户的访问状态。(当然也可以采用LVS 的会话保持机制来解决这个问题。)

###2. RPC机制: ICE + Protobuf + Zookeeper

● ICE 支持多平台,跨语言(语言中立),支持主流的语言,如Java/C++/Python 等。支持多种协议(TCP/UDP/SSL),自带集群管理,异步和同步都支持,并且文档齐全。 ICE的使用局限性:由于语言中立的需要,不接受直接返回null值,我们使用返回一个NULL Object 解决;不支持方法重载,那就再起一个方法名吧;ICE服务端抛出的Runtime 异常将不能被客户端捕获,可以采用声明式异常或者自定义返回CodeMsg的方式解决。

● zookeeper 集群是一个去中心化的分布式集群,并且有watcher 机制,监听数据变更。可以作为分布式服务的配置与注册服务。

● protobuf 能够压缩数据量,高效编码、解码,减少网络传输 或 缓存层内存占用大小。 protobuf 原生不支持map,需要自定义结构体来实现,thrfit则可以原生支持map 等复杂结构。

★为什么是ICE 而不是thrift?
为什么是ICE 而不是thrift , 因为我们团队对ICE 比较熟悉并且有成功案例.
后续的改进中会考虑使用thrift ,可以和 tornado 无缝结合,很好的利用tornado 的异步特性。

###3. 图片处理服务:python + tornado

考虑到Java对图片压缩处理的效率低下,我们使用python 的 PIL (Python Imaging Library)来对图片进行压缩处理。

tornado 是facebook用于处理FriendFeed 的开源Web框架, 它是非阻塞式服务器,而且速度相当快。得利于其 非阻塞的方式和对 epoll 的运用,Tornado 每秒可以处理数以千计的连接,因此 Tornado 是实时 Web 服务的一个 理想框架。

###4. 利用好Cache 提升系统性能(节约每1ms)

● 依赖工具与数据说话,可监控与衡量的设计。
(通过Spring动态代理拦截产生日志,从而监控请求处理过程的每个方法的耗时,找出性能瓶颈)

● 尽可能的采用批量操作,一次性获取需要的全部数据。
(例如:利用redis 缓存的批量接口或pipeline,数据库jdbc的batch操作。)在获取feed 与专辑列表页面通过批量操作把几百次请求合并为几次请求,降低网络IO的开销,从耗时1秒多降低到10ms,性能提升了100倍。

● 切分好缓存的粒度,达到最佳的缓存利用率。
(把图片数据与图片的浏览数分开,把专辑图片id列表与图片对象缓存分开。)

● 只获取需要展现的数据。
(例如:在专辑列表页上需要展现专辑的4个小图,但并不需要获取这4个小图的浏览数信息,就减少不必要的请求与时间开销。)

● 短路逻辑优化。
优先考虑全部命中缓存的情况,直接获取数据,取不到或数据个数小于请求个数,再考虑数据抓取。

● 划分操作优先级。
主要操作优先执行,次要操作或耗时较长的操作通过发送消息队列,放到后台任务执行。
(例如:图片转存时,需要累计图片原作者的被转存数。)

● 提升程序中可并行的部分。
根据Amdahl定律,并发的性能受限于必须串行的比例。(例如:图片转存时,利用线程池并发请求云存储进行图片文件复制操作。)

● 建立多级的缓存。
页面层可以划分为好几个json请求,进行片断缓存,service 层可以将组织出来的视图数据进行缓存。(由于目前性能基本达标与缓存维护成本,这个暂末实施。)

通过以上原则,进行简单的优化后。基本上所有请求都在50ms 以内,90%请求响应时间在20ms 以内。

小结:以上所提到的优化措施都是在代码层面的优化,实际上优化应该先从产品需求、业务流程、系统架构上考虑,最后才是根据实际线上的监控日志,对频繁调用的耗时操作进行优化。

★ redis 那些事

● redis 我们没有配置持久化,redis 的持久化有可能导致CPU瞬间100%,从而产生较高的延迟。
● redis list 列表时的对象不支持LRU(或者类似MongoDB Caped Collection 的特性),或者可以借鉴花瓣网自己实现的redis.fpush 。
● 为了防止数据库没有内容时,频繁的穿透redis cache层,进行mysql数据库查询。@小小剑士 发明了 emptyable_list,即使数据库返回的列表是空的,仍然在redis 中建立一个标志位,说明已从数据库加载过数据,无需再去查询数据。
● 为了避免缓存失效时的雪崩效应,应该在加载数据时建立起一个分布式锁(可用memcached 或 zookeeper 实现),或者只是简单load 展现数据的情况可以让获取不到锁的请求线程直接返回,即使用户第一次没访问到数据,刷一下页面也就正常了。
● 期待redis 3.0 的cluster 功能。

###5. 数据库设计

用户信息不分表

用户所产生的图片、好友信息、新鲜事根据用户id 进行CRC32 Hash 分库分表。

★如何解决明星用户的粉丝数特别多,导致表的数据分布不均匀?
把用户的粉丝根据用户id 做shard后,再进行一次shard。 user –> user_followers_shard_map —> user_followers_data。

具体参见《Pinterest的数据库分片架构》

###6. 编写可维护性的代码—保持架构的简单

● 把握原好面向对象设计的原则。
Java 程序员应该了解的 10 个面向对象设计原则 http://www.iteye.com/news/24488

● 持续重构,编写可维护性的代码。
团队内部成员互相进行code-review, 重构那些看起来复杂晦涩的逻辑。

● 可测试性。
积极编写靠谱的单元测试,能够有效提升软件的质量。

这里非常感谢@小小剑士 童鞋对单元测试工作的推广与坚持。

● 编码规范。
简短的方法名,规范的命名规则。使代码逻辑更加清晰明了,代码更容易被别人读懂。更多内容请参考《代码大全》《重构》《代码整洁之道》

案例:

● 在原先处理redis 的缓存交互逻辑与 缓存业务逻辑耦合在一起,我们进行了单独的分离。使用XXXRedisDao 处理具体与Redis 交互的代码,简化了类的职责,XXXCachedDao 只负责缓存 与 数据库的逻辑部分。

● 原先每个ICE Service 层都编写着复杂的业务视图,例如展示一个图片需要这个图片的用户昵称、头像等业务逻辑。改造后的Service 层,只吐出业务的基本数据,如feed 中只包含photo_id 的字段,而最终需要展现的photo 其它信息,通过在web 层调用其它service 进行组装,并且可以根据不同的展现需要对业务视图进行复用或定制。

● 利用jafka 消息分发业务事件消息,解除强耦合。例如:新建用户的初始化设置,上传图片给好友发送Feed.

● 封装许多工具类或内部类,优先利用组合模式来实现,达到组件化模块化,整个架构更加清晰合理。

###7. 其它

我们使用了twitter 开放出来的 snowflake 来生成UUID.
使用supervisord 监控 tornado 的进程,从而保证系统的稳定性与可靠性。
在管理后台或广场推送等方面,使用了MongoDB 作为方便快捷的存储。
Web前端使用了jQuery,RequireJs,doT 等前端Javascript框架。
前端返回的json数据暂时没有做gzip,除chrome外浏览器端解压反而把dom ready 的时间变长。
使用git 进行源码管理,基于hudson + maven 进行持续集成和部署。
使用 facebook 开放出来的 scribe 进行日志收集。
使用开源工具Ganglia 和自己开发的支撑平台对在线服务进行监控与分析。

###8. 广告

我们在招聘,欢迎所有NB闪闪的 UI设计师、 Web前端工程师、Java工程师、Python工程师 加入我们!

联系邮箱:ryanchen@sohu-inc.com

欢迎使用我们的产品 http://pp.sohu.com ,或提出您的宝贵意见。


补充一下和朋友的讨论:

1. 为什么不用Nginx模块来处理和压缩上传的图片,会不会更快?

  • 我们的图片上传是利用tornado 服务端接收的内存里进行处理,没有写文件,速度应该有保证。(未经实际测试)
  • 除了处理图片上传压缩,还需要处理post 到云存储 或从云存储获取数据,调用ICE服务等业务逻辑。相比现有的nginx 图片压缩模块来说,需要进一步扩展。(淘宝就是nginx模块进行图片的压缩。)
  • 团队成员普通对C++不熟悉,而用python开发,易于上手,也容易进行修改维护。

2. LVS 和 HAProxy 有何区别?

来自抚琴煮洒的Blog中有一段话:

HAProxy可以对Mysql读进行负载均衡,对后端的MySQL节点进行检测和负载均衡,不过在后端的MySQL slaves数量超过10台时性能不如LVS,所以我向大家推荐LVS+Keepalived。

原文:软件级负载均衡器(LVS/HAProxy/Nginx)的特点简介和对比 http://andrewyu.blog.51cto.com/1604432/697466

引用请教的同事的回答就是HAProxy的性能没有LVS好,具体本人未做过验证。

参考资料