现在的位置: 首页 > 综合 > 正文

分享舍得网开发过程

2013年10月27日 ⁄ 综合 ⁄ 共 23315字 ⁄ 字号 评论关闭

    application server采用j2ee架构技术,使用了免费的resin2.1.17搭建,并采用自行开发的缓存系统进行负载均衡,webserver 采用了重量级的apache 和轻量级的lighttpd。动态内容交由apache处理,静态内容(比如图片、css、js)等交由lighttpd处理。
 

    以下是主要配置参数和建议:

一、java应用服务器,建议用resin2.1.17,如果有钱,可以买一个resin3的license,否则resin3比resin2慢,也可以考虑使用Glassfish,这个和resin性能差别比较小,tomcat还是不行。 
如果缓存需要使用特别大的内存,建议用64位操作系统。32位jdk理论上最多使用4G内存,实际上只能用3G,3G还要分成两部分,一部分是native,剩下一部分才是Xmx这个参数定义的,配置不好 
就经常出错,系统会停顿6-10秒,甚至jvm会因为native内存不够而崩溃(实际这种情况linux有的是剩余内存),这里我应用我的一些配置经验,如下:(以下内容csdn死活审核不过,疯了)

 

     1:串行垃圾回收,也就是默认配置,完成10万request用时153秒,JVM参数配置如下:(略)

这种配置一般在resin启动24小时内似乎没有大问题,网站可以正常访问,但查看日志发现,在接近24小时时,Full GC执行越来越频繁,大约每隔3分钟就有一次Full GC,每次Full GC系统会停顿6秒左右,作为一个网站来说,用户等待6秒恐怕太长了,所以这种方式有待改善。MaxTenuringThreshold=7表示一个对象如果在救助空间移动7次还没有被回收就放入年老代,GCTimeRatio=19表示java可以用5%的时间来做垃圾回收,1/(1+19)=1 /20=5%。

 

 2:并行回收,完成10万request用时117秒,配置如下:(略) 
并行回收我尝试过多种组合配置,似乎都没什么用,resin启动3小时左右就会停顿,时间超过10 秒。也有可能是参数设置不够好的原因,MaxGCPauseMillis表示GC最大停顿时间,在resin刚启动还没有执行Full GC时系统是正常的,但一旦执行Full GC,MaxGCPauseMillis根本没有用,停顿时间可能超过20秒,之后会发生什么我也不再关心了,赶紧重启resin,尝试其他回收策略。 
  
    3:并发回收,完成10万request用时60秒,比并行回收差不多快一倍,是默认回收策略性能的2.5倍,配置如下:(略) 
这个配置虽然不会出现10秒连不上的情况,但系统重启3个小时左右,每隔几分钟就会有5秒连不上的情况,查看gc.log,发现在执行ParNewGC时有个promotion failed错误,从而转向执行Full GC,造成系统停顿,而且会很频繁,每隔几分钟就有一次,所以还得改善。UseCMSCompactAtFullCollection是表是执行Full GC后对内存进行整理压缩,免得产生内存碎片,CMSFullGCsBeforeCompaction=N表示执行N次Full GC后执行内存压缩。 
  
    4:增量回收,完成10万request用时171秒,太慢了,配置如下 :(略) 
似乎回收得也不太干净,而且也对性能有较大影响,不值得试。

 

5:并发回收的I-CMS模式,和增量回收差不多,完成10万request用时170秒。 
配置如下:(略)

采用了sun推荐的参数,回收效果不好,照样有停顿,数小时之内就会频繁出现停顿,什么sun推荐的参数,照样不好使。 
  
    6:递增式低暂停收集器,还叫什么火车式回收,不知道属于哪个系,完成10万request用时153秒。配置如下:(略) 
该配置效果也不好,影响性能,所以没试。 
  
   7:相比之下,还是并发回收比较好,性能比较高,只要能解决ParNewGC(并行回收年轻代)时的promotion failed错误就一切好办了,查了很多文章,发现引起promotion failed错误的原因是CMS来不及回收(CMS默认在年老代占到90%左右才会执行),年老代又没有足够的空间供GC把一些活的对象从年轻代移到年老代,所以执行Full GC。CMSInitiatingOccupancyFraction=70表示年老代占到约70%时就开始执行CMS,这样就不会出现Full GC了。SoftRefLRUPolicyMSPerMB这个参数也是我认为比较有用的,官方解释是softly
reachable objects will remain alive for some amount of time after the last time they were referenced. The default value is one second of lifetime per free megabyte in the heap,我觉得没必要等1秒,所以设置成0。配置如下:略

 

   8:第7的配置还有可能因为救助空间不够造成promotion failed,我索性把救助空间去掉,通过调整SurvivorRatio和MaxTenuringThreshold实现,设置如下,总之还是要根据实际情况做出适合的调整。下面是我的最终配置,已经非常稳定,如果改成64位系统,内存可以加大。但隔三差五重启resin还是有必要的,Perm空间总会变满的。

 

二、apache的一些配置和技巧。 
    安装apache时根据情况带几个参数,网上也有其他优化参数,但估计性能差不了多少,如下 
./configure --prefix=/usr/local/apache2.2.10 --enable-so --enable-deflate --enable-rewrite --enable-expires  
我做过测试,apache自带的mod_mem_cache不太好使,最大使用内存不到200M时就会有问题,内存使用量突然下降,所以 
图片服务器建议不要用apache,用lighttpd或者nginx会更好。lighttpd+memcached的方式我就没配成功过,nginx+memcached能从缓存 
里获取,但不能set到缓存,所以我用的是lighttpd+mod_mem_cache(需要把lighttpd打个补丁),稍后作详细介绍。

apache简单防DDOS攻击的配置,需要从网上找一个mod_evasive20模块,用/usr/local/apache/bin/apxs -cia mod_evasive20.c安装,一般情况下没有必要配置这玩意。 
<IfModule mod_evasive20.c> 
    DOSHashTableSize    10000   
    DOSPageCount        2 
    DOSSiteCount        50 
    DOSPageInterval     1 
    DOSSiteInterval     1 
    DOSBlockingPeriod   10 
    DOSEmailNotify      webmaster@xxx.com 
    DOSLogDir           /var/log/mod_dosevasive.log 
</IfModule>

apache防sql注入攻击的配置,需要安装mod_security模块 
<IfModule mod_security.c> 
    SecFilterEngine On 
    SecFilterCheckURLEncoding On 
    SecFilterForceByteRange 32 126 
    SecFilterCheckUnicodeEncoding On 
    SecServerResponseToken Off 
    SecAuditEngine RelevantOnly 
    SecAuditLog logs/audit_log 
    SecFilterDebugLog logs/modsec_debug_log 
    SecFilterDebugLevel 0 
    SecFilterDefaultAction "deny,log,status:406" 
    SecFilter /etc/*passwd 
    SecFilter /bin/*sh 
    SecFilter "/././" 
    SecFilter "<( |/n)*script" 
    SecFilter "<(.|/n)+>" 
    SecFilter "delete[[:space:]]+from" 
    SecFilter "insert[[:space:]]+into" 
    SecFilter "select.+from" 
    SecFilter "union[[:space:]]+from" 
    SecFilter "drop[[:space:]]" 
    SecFilterSelective "HTTP_USER_AGENT|HTTP_HOST" "^$" 
</IfModule>

加大apache最大连接数的配置,如果选择的是preworker工作方式。preworker和worker的区别就不讲了。apache2.0以上直接设置ServerLimit即可,apache2.0以前还要修改源代码才能使修改 
的ServerLimit生效,建议应用服务器用apache2.0吧,性能差不了多少。

<IfModule mpm_prefork_module> 
    ServerLimit           20000  #ServerLimit据说要放第一行 
    StartServers          50 
    MinSpareServers       50 
    MaxSpareServers       100 
    MaxClients            10000 
    MaxRequestsPerChild   10000 
</IfModule>

压缩传输的配置,这个做为网站来说非常重要,它是不压缩传输大小的20%左右,也就是说用户访问一个网站速度快了5倍,不配置不行,但是图片不能做压缩了。

 

<IfModule mod_deflate.c> 
 SetOutputFilter DEFLATE 
 DeflateCompressionLevel 3  
 DeflateFilterNote Input instream 
 DeflateFilterNote Output outstream 
 DeflateFilterNote Ratio ratio 
 LogFormat '"%h %l %u %t /"%r/" %>s %b /"%{Referer}i/" /"%{User-Agent}i/"" "%r" %{outstream}n/%{instream}n (%{ratio}n%%)' deflate 
 # Netscape 4.x has some problems... 
 BrowserMatch ^Mozilla/4 gzip-only-text/html 
 # Netscape 4.06-4.08 have some more problems 
 BrowserMatch ^Mozilla/4/.0[678] no-gzip 
 # MSIE masquerades as Netscape, but it is fine 
 #BrowserMatch /bMSIE !no-gzip !gzip-only-text/html 
 # NOTE: Due to a bug in mod_setenvif up to Apache 2.0.48 
 # the above regex won't work. You can use the following 
 # workaround to get the desired effect: 
 BrowserMatch /bMSI[E] !no-gzip !gzip-only-text/html 
 # Don't compress images, java scripts and style sheets 
 SetEnvIfNoCase Request_URI /.(?:gif|jpe?g|png|js|css)$ no-gzip dont-vary

 # Make sure proxies don't deliver the wrong content 
 # this needs mod_headers but it's very important 
 # so I don't add a IfModule around it 
 #Header append Vary User-Agent env=!dont-vary 
 #CustomLog logs/deflate_log.log deflate 
 #CustomLog "|/usr/local/cronolog/sbin/cronolog /usr/local/apache2.0.59_2/logs/www.shedewang.com.access.log.%Y%m%d" deflate env=!IMAG 
</IfModule>

apache配置mod_mem_cache,这个模块不太好使,建议不用 
<IfModule mod_cache.c> 
#CacheForceCompletion 100 
CacheDefaultExpire 3600 
CacheMaxExpire 86400 
CacheLastModifiedFactor 0.1 
CacheIgnoreNoLastMod on 
        <IfModule mod_mem_cache.c> 
                CacheEnable mem / 
                MCacheSize 2000000 
                MCacheMaxObjectCount 10000 
                MCacheMinObjectSize 1000 
                MCacheMaxObjectSize 512000 
                MCacheRemovalAlgorithm LRU 
        </IfModule> 
</IfModule>

CacheEnable: 启动 mod_cache,其后接两个参数。第一个参数指定快取的种类,应设为 mem (记忆体快取) 或 disk (磁碟快取) 之其一;第二个参数指定使用快取的 URI 路径,如果对整个网站 (或虚拟主机) 进行快取,简单指定为根目录(/) 即可。 
CacheForceCompletion: 这个值指定当 HTTP request 被取消时,内容的产生动作要完成的百分比;预设是 60(%)。 
CacheDefaultExpire: 指定快取的预设过期秒数;预设值是一小时 (3600)。 
CacheMaxExpire: 指定快取最大的过期秒数;预设值是一天 (86400)。 
CacheLastModifiedFactor: 用来从回应里 Last Modified 资讯算出 expire date。 
计算方式是:expire period (过期时距) = 最后更新后至今的时间间距 * CacheLastModifiedFactor 
而expire date = 目前时间 + expire period 
不过无论如何,过期时间不能超过 CacheMaxExpire 的设定值。

配置mod_expires模块

mod_expires可以减少10%左右的重复请求,让重复的用户对指定的页面请求结果都CACHE在本地,根本不向服务器发出请求,这一点特别实用在图片服务器上。

mod_expires的安装配置: 
<IfModule mod_expires.c> 
  # turn on the module for this directory 
  ExpiresActive on

  # cache common graphics for 3 days 
  ExpiresByType image/jpg "access plus 365 days" 
  ExpiresByType image/gif "access plus 365 days" 
  ExpiresByType image/jpeg "access plus 365 days" 
  ExpiresByType image/png "access plus 365 days"

</IfModule>

四:图片服务器的安装 
图片服务器是相对比较容易的,也是最稳定的服务器了,我建议用lighttpd或者nginx,不要用apache。 
lighttpd不加cache也可以撑到50-80m没秒,如果比这个流量还大,建议用lighttpd+mod_mem_cache或者 
lighttpd+memcached,但是后者我没有配成功,启动不了。nginx和memcached比较容易整合,但是又不能自动 
set到memcached里去,还要自己用php或者java程序把图片放入到memcached里去,不够智能。我用的是lighttpd1.4.20+mod_mem_cache,可以 
迟到4G多内存,比apache强多了。安装lighttpd的mod_mem_cache补丁如下 
tar -xzvf lighttpd-1.4.20.tar.gz 
cp lighttpd-1.4.20.mod_mem_cache.patch lighttpd-1.4.20 
patch -p0 < lighttpd-1.4.20.mod_mem_cache.patch   
sh autogen.sh    
./configure --prefix=/usr/local/lighttpd-1.4.20 
make && make install

配置文件增加如下段: 
server.modules              = ( 
#                               "mod_rewrite", 
#                               "mod_redirect", 
#                               "mod_alias", 
                                "mod_access", 
#                               "mod_cml", 
#                               "mod_trigger_b4_dl", 
#                               "mod_auth", 
#                               "mod_status", 
#                               "mod_setenv", 
#                               "mod_fastcgi", 
#                               "mod_proxy", 
#                               "mod_simple_vhost", 
#                               "mod_evhost", 
#                               "mod_userdir", 
#                               "mod_cgi", 
#                               "mod_compress", 
#                               "mod_ssi", 
#                               "mod_usertrack", 
                                "mod_expire", 
#                               "mod_secdownload", 
#                               "mod_rrdtool", 
                                "mod_accesslog", 
                                "mod_mem_cache" )  #注意加上这个

#然后增加如下配置段                                 
mem-cache.filetypes = ("application/x-javascript", "text/css", "text/html", "text/javascript", "image/jpg", "image/jpeg", "image/gif", "image/png", "image/bmp") 

mem-cache.enable = "enable"

#4096M 
mem-cache.max-memory = 4096

#512k 
mem-cache.max-file-size = 512

#lru count 
mem-cache.lru-remove-count = 100000

#1 day 
mem-cache.expire-time = 1440 
 

五:nginx可以做负载均衡,按照IP做负载均衡就不用管分布式session的问题,这里就不做讲述了,需要的时候查查就行。

六:网上说的lighttpd+squid+apache可以怎么怎么加速网站,我配置了一下,根本没用,第一层的lighttpd只要必须经过squid,即使squid的缓存里有也会很慢,还没有直接一个lighttpd快。 
下面附上squid3.0配置成功的配置文件,可以实现缓存,如果没有缓存可以往81这个端口转发。 
#visible_hostname www.abc.com  
visible_hostname localhost 
http_port 3128 vhost vport 
cache_mem 2048 MB 
maximum_object_size_in_memory 2048 KB 
memory_replacement_policy lru 
#cache_dir ufs /tmp 512 16 256  
cache_dir ufs /usr/local/squid/var/cache 2048 16 256 
max_open_disk_fds 0 
minimum_object_size 0 KB 
maximum_object_size 32768 KB 
logformat combined %>a %ui %un [%tl] "%rm %ru HTTP/%rv" %Hs %<st "%{Referer}>h" "%{User-Agent}>h" %Ss:%Sh 
access_log /usr/local/squid/var/logs/access_log combined 
cache_log /usr/local/squid/var/logs/cache.log 
pid_filename /usr/local/squid/var/logs/squid.pid 
#cache_store_log none  
cache_peer 127.0.0.1 parent 81 0 no-query no-digest originserver name=www 
#cache_peer_domain www www.abc.com 
cache_peer_domain localhost 
cache_peer_access www allow all 
http_access allow all 
acl QUERY urlpath_regex .exe 
cache deny QUERY 
cache_effective_user nobody 
cache_effective_group nobody


---------------------------------------------------------------------------------------------------------------------------------


舍得网(shedewang.com)的开发暂时告一段落,一个人用时不到1个月,java底层代码16902行,jsp代码27685行,共计44587行。整个开发过程遇到过许多问题,但最后都解决了。下面把我在开发中遇到的所有问题和解决办法列出,本人水平一般,仅供同僚们参考。


系统构架:redhat AS4/apache2.0.59/resin2.1.17/jdk6.0 u2/hibernate3.0/lucene2.2/urlrewrite3.0.4,数据库用得是mysql4.1.15,数据库缓存是构架在hibernate之上的,是一个只有794行的java类,但这个java类却做了数据库对象缓存、列表缓存、update缓冲、自动删除列表缓存,还提供了数据库查询、更新、插入的所有操作,它节省了我一半以上的开发时间。一个获取含有五个查询条件获取列表的方法只用不到10行代码就可以了。

问题一:做数据库缓存时遇到的问题。Map在并发遍历时会报ConcurrentModificationException,即使使用Collections.synchronizedList把Map包起来还是会报这个异常,这个问题很简单,解决办法也简单。第一种解决办法是不要用Map的iterator来遍历,而是用Set(Map.keySet方法)的toArray方法来遍历,这种办法虽然会损耗一定的性能和内存,但比在方法前加synchronized好得多;第二种解决办法用jdk5.0以后的ConcurrentHashMap来实现。(我的数据库的缓存用的是apache的LRUMap,用第一种解决办法,第二种解决办法我也准备好了,随时可以更换)

问题二:jfreecharts在Linux上不能显示中文,这个问题没有费多长时间就解决了,上网一搜就搞定,解决方法如下:
到网上下载一个linux下的ttf字体,本例用的是zysong.ttf
1.确认%JavaHome%/jre/lib/fonts目录下存在zysong.ttf
2.在%JavaHome%/jre/lib/fonts目录下执行"ttmkfdir -o fonts.dir"命令,重新生成fonts.dir文件
3.确认/usr/share/fonts/zh_CN/TrueType目录存在,如果不存在则mkdir创建
4.确认/usr/share/fonts/zh_CN/TrueType目录下存在zysong.ttf
5.在%JavaHome%/jre/lib目录下,执行 cp fontconfig.RedHat.3.properties.src fontconfig.properties
6.重起resin,OK。

问题三:linux下的too many open files错误,这个问题比较严重,AS4默认打开文件数是1024,如果超过这个数,resin就自动down掉了,非常恶心。解决办法如下:
echo 65536 > /proc/sys/fs/file-max
编辑/etc/sysctl.conf 文件,编辑行 fs.file-max = 65536
编辑文件/etc/security/limits.conf,增加行 * - nofile 65536
用ulimit -a 查看,如果看到行open files (-n) 65536就说明对了

问题四:内存泄露,表现出来的特征是CPU占到99.9%,内存由10%左右经过几个小时后慢慢涨到50%,最后死掉。做java的人知道,这个问题非常痛苦,而且没有很好的解决办法,因为直接看代码很难看出来。我原来一直以为问题会出现在缓存上,但仔细想想apache的LRUMap不至于产生内存泄露,尤其我设置了LRUMap最大长度只有10000,10000个内存对象能有多大,后来发现是SmartUpload的问题,改成apache的FileUpload子项目就可以了。另外,我在设置jvm参数时增加了-Xmx2048m
-Xms2048m -Xmn768m -Xss512k -XX:+UseParallelGC -XX:ParallelGCThreads=4 -XX:+UseParallelOldGC -XX:+UseAdaptiveSizePolicy这些参数,可以回收年老区的内存,现在比较稳定,一般内存占到27%左右就不会再涨了,可能这些参数还不是最优的,有待探索。另外查找内存泄露的软件JProbe我也玩了玩,的确看出其他代码没有明显内存泄露。

问题五:搜索分词。一个用户在用舍得网时反映,看到有“啤酒”和“茅台酒”,为什么搜“酒”搜不出来,原因很简单,“啤酒”和“茅台酒”是单独一个词,lucene写入的时候没有再把它拆开,所以必须要搜“啤酒”或“茅台酒”才能搜出来,这在技术上合理,但是用户觉得不合理。所以我改进了搜索算法,把中国3万多个汉字也加到词库中,而且在写入和搜索时用不同的分词算法,如“我喜欢喝啤酒”在写入时会分成“我+喜欢+喝+啤酒+喜+欢+啤+酒”,而在搜索时这句话会被分词为“我+喜欢+喝+啤酒”,这样,用户搜“啤酒”能搜到,搜“酒”也能搜到,而对应另外一句话“这人啤气不好,总喝酒”搜“啤”和“酒”都能搜到,但搜“啤酒”却搜不到,似乎有点意思。但是这么分词也会有点小问题,就是搜索的结果不太人性化。(我的中文词库加成语加汉字共50多万个,比起一般网上十来二十万要丰富得多,不过这没什么大用)

问题六:URL链接“静态化”。本想直接用apache的URL Rewrite来实现,发现不太可能,于是改用urlrewirte实现,配置没什么难度,但是要注意resin的web-app里须增加一行配置<servlet-mapping url-pattern='*.htm' servlet-name='plugin_match'/>,这样apache才会把htm结尾的请求转交给resin,否则apache报404错误,这在一般structs项目中都会提到。现在看到的舍得网http://shedewang.com/pg_5_c_2_index.htm,其实就是http://shedewang.com/index.jsp?pg=5&c=2,呵呵,没什么特别的。

问题七:IE6/IE7/FF的适配,这体现在许多细节上,如FF的回车事件捕获,IE7的href=#页面会移动等等问题上,多测几次,多上网找找也就都解决了。很多人开发网站似乎不太会管FF能不能看,但好歹我也在SP混过几年,做WAP的时候要适配10来款手机,做web适配三五个浏览器不算什么。

问题八:linux自身的bug。远程连接mysql时有时mysql似乎会重起,这个问题似乎是linux自身的bug,好像和解析有点关系导致mysql崩溃。解决办法:启动mysql增加一个参数,如下:/usr/local/mysql/bin/mysqld_safe --user=mysql --skip-name-resolve &

问题九:hiberate配置文件的问题,配置不好的话总是会报NESTED Exception,或者多用户并发的时候报错。我想一般人都遇到过了,增加一个c3p0的配置段,尤其注意max_statements设置稍微大一点,原来我设置为100的时候10个用户同时创建记录就会出错。
<property name="hibernate.connection.provider_class">org.hibernate.connection.C3P0ConnectionProvider</property>
<property name="hibernate.c3p0.max_size">200</property>
<property name="hibernate.c3p0.min_size">20</property>
<property name="hibernate.c3p0.timeout">3600</property>
<property name="hibernate.c3p0.max_statements">1000</property>
<property name="hibernate.c3p0.idle_test_period">300</property>
<property name="hibernate.c3p0.acquire_increment">5</property>
<property name="hibernate.c3p0.validate">false</property>

问题十:ajax的运用问题。提交大文本时似乎不能用prototype提供的Ajax.Updater,要自己创建一个AJAX对象,然后把内容send过去,具体js代码可以参考舍得网的comm.js,另外用jsp获取Ajax提交的内容如果是乱码,把jsp改成UTF-8编码即可。AJAX运用好了的确可以增加用户感受,而且可以让代码的松散耦合性更好,可以把一大段逻辑写在一个小的jsp里面。

Postfix邮件服务器配置和上传图片缩放更是我遇到最困难的问题,一言难尽,有兴趣的朋友可以加MSN:bruce_lau@163.com了解了解。


修改版在http://topic.csdn.net/u/20080315/11/14e7a35b-585b-496e-a206-59735528bada.html

原帖在这里
http://topic.csdn.net/u/20080124/16/edff1fef-ff1f-41e0-96a2-2707f99f3b21.html

不知道为什么原来的帖子被删除了,我用心写这么高水准的帖子,不应该被删才对。该帖在原来的基础上做了修正和增加。

舍得网(shedewang.com)的开发暂时告一段落,一个人用时不到1个月,java底层代码16902行,jsp代码27685行,共计44587行。整个开发过程遇到过许多问题,但最后都解决了。下面把我在开发中遇到的所有问题和解决办法列出,供参考。

系统构架:redhat AS4/apache2.0.59/resin2.1.17/jdk6.0 u2/hibernate3.0/lucene2.2/urlrewrite3.0.4,数据库用得是mysql4.1.15,数据库缓存是构架在hibernate之上的,是一个只有794行的java类,但这个java类却做了数据库对象缓存、列表缓存、update缓冲、自动删除列表缓存,还提供了数据库查询、更新、插入的所有操作,它节省了我一半以上的开发时间。一个获取含有五个查询条件获取列表的方法只用不到10行代码就可以了。【增加:现在我已经把这个数据库操作工具改成分布式了,可以随便增加java应用服务器实现负载均衡,并且各服务器之间可以实现缓存同步,这样,即使每日用户达到百万级别,我的构架也是可以支持的,我将在下面详细说明我的构架,仅供参考。许多人要求我开源,我觉得现在系统还没有经过大规模用户的并发验证,还不是时候,以后我会考虑开源的,我的代码没有太多高深的算法和设计模式,主要用到了模板模式,但全是用心写出来的。

问题一:做数据库缓存时遇到的问题。HashMap在并发遍历时会报ConcurrentModificationException,即使使用Collections.synchronizedList把Map包起来还是会报这个异常,这个问题很简单,解决办法也简单。第一种解决办法是不要用Map的iterator来遍历,而是用Set(Map.keySet方法)的toArray方法来遍历,这种办法虽然会损耗一定的性能和内存,但比在方法前加synchronized好得多;第二种解决办法用jdk5.0以后的ConcurrentHashMap来实现。【修正:经过测试和验证,第一种方法不行,也就是并发操作MAP而且要求遍历的时候只能用ConcruuentHashMap,在此要感谢写ConcurrentHashMap的专家们。】【增加:数据库的缓存有很多种办法,对于单个对象的缓存比较容易,直接用HashMap都可以,但是对于列表的缓存就比较复杂了,网上说到的memcachedb+memcache_engine或者berkeleydb应该都是做不到列表自动缓存,因为增加一条记录后会影响许多列表的排序,所以什么时候删除列表缓存是个比较头痛的问题,我的解决办法是列表的缓存的key便包含了查询条件信息。如一个表T有字段A,B,C,对应T.java有域A,B,C,那么查询一个A=1
and B=2 and C>0 的组合条件的列表的key就是A=1#B=2#C>0,这样,如果增加了一个对象T,其中T.A=1,T.B=3,T.C=0,显然上面列表查询条件包含了条件B=2,而增加的对象B=3,那么无论如何这个新增加的对象T都不可能在这个列表中,也就是说不用删除这个列表,只有增加的对象T满足T.A=1、T.B=2、T.C>0时该列表才需要重新从数据库中获取,以此可以推出更新、删除一个T对象时什么时候需要删除什么列表。】【增加:至于分布式,我用到了memcached来做远程缓存,利用UDP报文来保持各服务器之间的缓存同步。如获取一个对象:首先在本机缓存中获取,如果没有再去远程memcached
server中获取,如果还没有才从数据库中获取并同时放入memcached server 和本机缓存,这样,不管有多少台服务器在做负载均衡,对于一个对象T,只需要在数据库中读取一次,其他所有服务器都可以共享了,数据库查询的压力几乎没有,当然如果插入和修改的次数达到每秒几千次,我可能会用到mysql-proxy,但目前看来,几十万的用户/天的量都用不着,用个好点的数据库服务器即可】【不谦虚的说,我的这套分布式缓存方案是比较强大的,都是用心写出来的,虽然代码只有2874行。实例:如要获取一个id为6789的用户,只要用User
u =UserManager.getInstance().getById("6789");一句话即可,所有缓存逻辑都已经包含在里面了。】

问题二:jfreecharts在Linux上不能显示中文,这个问题没有费多长时间就解决了,上网一搜就搞定,解决方法如下:
到网上下载一个linux下的ttf字体,本例用的是zysong.ttf
1.确认%JavaHome%/jre/lib/fonts目录下存在zysong.ttf
2.在%JavaHome%/jre/lib/fonts目录下执行"ttmkfdir -o fonts.dir"命令,重新生成fonts.dir文件
3.确认/usr/share/fonts/zh_CN/TrueType目录存在,如果不存在则mkdir创建
4.确认/usr/share/fonts/zh_CN/TrueType目录下存在zysong.ttf
5.在%JavaHome%/jre/lib目录下,执行 cp fontconfig.RedHat.3.properties.src fontconfig.properties

6.重起resin,OK。

问题三:linux下的too many open files错误,这个问题比较严重,AS4默认打开文件数是1024,如果超过这个数,resin就自动down掉了,非常恶心。解决办法如下:

echo 65536 > /proc/sys/fs/file-max
编辑/etc/sysctl.conf 文件,编辑行 fs.file-max = 65536
编辑文件/etc/security/limits.conf,增加行 * - nofile 65536
用ulimit -a 查看,如果看到行open files (-n) 65536就说明对了

问题四:内存泄露,表现出来的特征是CPU占到99.9%,内存由10%左右经过几个小时后慢慢涨到50%,最后死掉。做java的人知道,这个问题非常痛苦,而且没有很好的解决办法,因为直接看代码很难看出来。我原来一直以为问题会出现在缓存上,但仔细想想apache的LRUMap不至于产生内存泄露,尤其我设置了LRUMap最大长度只有10000,10000个内存对象能有多大,后来发现是SmartUpload的问题,改成apache的FileUpload子项目就可以了。另外,我在设置jvm参数时增加了-Xmx2048m
-Xms2048m -Xmn768m -Xss512k -XX:+UseParallelGC -XX:ParallelGCThreads=4 -XX:+UseParallelOldGC -XX:+UseAdaptiveSizePolicy这些参数,可以回收年老区的内存,现在比较稳定,一般内存占到27%左右就不会再涨了,可能这些参数还不是最优的,有待探索。另外查找内存泄露的软件JProbe我也玩了玩,的确看出其他代码没有明显内存泄露。【修正:上面的参数配置狗P用都没有,内存总是一直在涨。我还尝试了用其他不同方法去回收内存,结果都不太好使,直接用jvm默认的回收方式是最好的,也就是只配置两个参数-Xmx768M
-Xms768M效果最好,这样java才真的可以一次性回收几百兆的内存。】

问题五:搜索分词。一个用户在用舍得网时反映,看到有“啤酒”和“茅台酒”,为什么搜“酒”搜不出来,原因很简单,“啤酒”和“茅台酒”是单独一个词,lucene写入的时候没有再把它拆开,所以必须要搜“啤酒”或“茅台酒”才能搜出来,这在技术上合理,但是用户觉得不合理。所以我改进了搜索算法,把中国3万多个汉字也加到词库中,而且在写入和搜索时用不同的分词算法,如“我喜欢喝啤酒”在写入时会分成“我+喜欢+喝+啤酒+喜+欢+啤+酒”,而在搜索时这句话会被分词为“我+喜欢+喝+啤酒”,这样,用户搜“啤酒”能搜到,搜“酒”也能搜到,而对应另外一句话“这人啤气不好,总喝酒”搜“啤”和“酒”都能搜到,但搜“啤酒”却搜不到,似乎有点意思。但是这么分词也会有点小问题,就是搜索的结果不太人性化。(我的中文词库加成语加汉字共50多万个,比起一般网上十来二十万要丰富得多,不过这没什么大用)

问题六:URL链接“静态化”。本想直接用apache的URL Rewrite来实现,发现不太可能,于是改用urlrewirte实现,配置没什么难度,但是要注意resin的web-app里须增加一行配置 <servlet-mapping url-pattern='*.htm' servlet-name='plugin_match'/>,这样apache才会把htm结尾的请求转交给resin,否则apache报404错误,这在一般structs项目中都会提到。现在看到的舍得网http://shedewang.com/pg_5_c_2_index.htm,其实就是http://shedewang.com/index.jsp?pg=5&c=2,呵呵,没什么特别的。

问题七:IE6/IE7/FF的适配,这体现在许多细节上,如FF的回车事件捕获,IE7的href=#页面会移动等等问题上,多测几次,多上网找找也就都解决了。很多人开发网站似乎不太会管FF能不能看,但好歹我也在SP混过几年,做WAP的时候要适配10来款手机,做web适配三五个浏览器不算什么。

问题八:linux自身的bug。远程连接mysql时有时mysql似乎会重起,这个问题似乎是linux自身的bug,好像和解析有点关系导致mysql崩溃。解决办法:启动mysql增加一个参数,如下:/usr/local/mysql/bin/mysqld_safe --user=mysql --skip-name-resolve &

问题九:hiberate配置文件的问题,配置不好的话总是会报NESTED Exception,或者多用户并发的时候报错。我想一般人都遇到过了,增加一个c3p0的配置段,尤其注意max_statements设置稍微大一点,原来我设置为100的时候10个用户同时创建记录就会出错。

<property name="hibernate.connection.provider_class">org.hibernate.connection.C3P0ConnectionProvider </property>

<property name="hibernate.c3p0.max_size">200 </property>
<property name="hibernate.c3p0.min_size">20 </property>
<property name="hibernate.c3p0.timeout">3600 </property>
<property name="hibernate.c3p0.max_statements">1000 </property>
<property name="hibernate.c3p0.idle_test_period">300 </property>
<property name="hibernate.c3p0.acquire_increment">5 </property>
<property name="hibernate.c3p0.validate">false </property>

问题十:ajax的运用问题。提交大文本时似乎不能用prototype提供的Ajax.Updater,要自己创建一个AJAX对象,然后把内容send过去,具体js代码可以参考舍得网的comm.js,另外用jsp获取Ajax提交的内容如果是乱码,把jsp改成UTF-8编码即可。AJAX运用好了的确可以增加用户感受,而且可以让代码的松散耦合性更好,可以把一大段逻辑写在一个小的jsp里面。

问题十一:网上似乎还有很多人在为自己选择应用服务器的事发愁,我只相信自己的眼睛,就拿tomcat6最新版和resin2.1.17做测试,50个用户并发的情况下tomcat慢4-5倍,我还能指望tomcat在更多的用户并发下发挥作用吗?我用resin2.1.17,很快!resin3 和resin Pro只有在配置好缓存和native的时候可能才有用,但对于我这种情况,用resin3和resin Pro不一定会更快。

做技术、做管理、做事都是在做人,有很多刚入门的新手,好多人都问我到底用struts还是spring还是什么MVC构架,其实这些都不是问题,只要养成自己的一套开发方式即可,但做技术要有深厚的基本功。像一个武林高手,就拿李小龙来说,他懂空手道、跆拳道、刀枪钯棍都研究过,但真正打架的时候就那么几招,但他有力量、脚步和无与伦比的速度,这些都是基本功;对于篮球高手来说,他甚至不需要懂街球那些花骚的玩法,但实战的时候他也就用几招而已,像科比,他最常用的就是远程投篮和突破,真正打球时没必要在地上一边打滚一边运球,当然,科比也是非常有力量的一个球员。

email:bruce_lau@163.com,欢迎指教!

呵呵,多谢各位支持。做技术其实挺辛苦的,做的是最牛的事情,位置却比较低,要忍各种各样的人,不容易。至于有多少代码是自己写的,这个不用怀疑,4万多行代码至少有一半以上是手写的,除了代码,服务器的Linux操作系统和所有用到的软件都是我一人安装配置的。我想说什么来着,我想说,技术在各个行业里都不是最重要的,要把技术、产品、销售结合起来,实现利润最大化才是最重要也是最难的,我一直在努力,但有时事情不是我个人能控制,倍感苦恼。如果有一天运气降临让我能突破人生发展的中级阶段,我一定选择单干,相信别人而不如相信自己。自勉,共勉。

改一点,经过实践,关于问题一,只能用第二种解决办法,并发遍历只能用ConcurrentHashMap,在这里要谢谢写ConcurrentHashMap的专家们提供了这么好的工具;关于问题四:我试过了许多中参数配置,最好的是只设置Xmx和Xms参数,并把这两个值设置成一样大,我的配置是-Xms768M -Xmx768M,这样我才每天都可以看到java稳定的回收内存,当内存使用量到达20%左右时(20%x4G约为800多M),java会启动垃圾回收,内存占用量可以从20%喀嚓就降到12%,以后趋于稳定。

最近我已经把缓存做成分布式了,实现了两级分布式缓存,速度应该是相当的快,只要带宽足够,5台服务器撑个100万用户/天应该不是问题,如果还不够再增加java应用服务器即可,该几行配置即可。

网上有很多介绍分布式缓存的解决方案,用到比较多得是memcached。用memcached来缓存一行数据(单个对象)比较有效,但是列表的缓存还是比较头痛得事。比如新插入了一行记录,默认active字段值是0,通过后台把active改成1后才能显示,这个修改影响了列表和排序,这种情况任何自动缓存都做不到智能获取(网上介绍的memcache_engine,memcachedb,berkeley DB应该都不行),而我写的分布式系统还可以缓存列表。

做程序也是做人。如果我的这些代码经过大规模并发操作没有问题,我会把它开源的!

这么多人回帖,我就再罗嗦两句吧。开源之事我会考虑的,但是现在程序没有经过巨大压力的验证,不知道稳定性如何,所以现在开源还不到时候;另一方面,我的程序写得特别简单,可能“高手们”都不懈一顾。我是简单实用型的,不追求华丽的设计模式和设计思想,比如在做WAP网站时,为了适配各种屏幕的手机,图片大小和格式有几十种(128x128 jpg的176x144jpg的 160x120 gif的等等),许多高手可能会考虑用很复杂的java算法或者到处找开源项目来实现图片处理,最后发现K数大小和清晰度都没法得到保证,就连某些拿了几千万美元投资的WAP网站也做不出来,而我的一个256行的java程序就可以实现图片格式转换(jpg
gif png随便转)、图片裁切、图片等比缩放,连图片锐化、质量百分比都可以做,处理完的图片K数小而且非常清晰。我又想说什么来着,我虽然写这么技术化的帖子,但我并不认为技术是多么了不起,我做的东西别人用点心都能做,但如果不用心,再厉害的程序员也想不到。做官要看曾国藩,做生意要学胡雪岩,做程序员要像史玉柱啊。个人浅见,附一张我的架构设计图,仅供参考

理论上,上图的构架可以支撑数百万用户/天的巨大规模应用(动态页面),因为java服务器有数据库缓存而且可以再增加数个,squid代理服务器可以改成硬件支持,mysql服务器可以改成多个服务器(简单的按表拆分即可),lighttpd也可以再增加数个。但是,这些都是理想状态,能不用分布式的时候就不要用分布式,别把问题想复杂了。透露一点代码,其实从数据库中获取对象很简单,代码如下:

protected BaseDbOm getById(Long id,Class omClass)
{
//第一步:在本地内存中寻找
if(id==null)return null;
BaseDbOm om = null;

if (RECORDS_CACHE.containsKey(id))
{
if(Config.getInstance().isLog())
{
logger.info("对象存在于本地内存中:"+omClass.getName()+"#"+id);
}
return (BaseDbOm) RECORDS_CACHE.get(id);
}

//第二步:去memcached server中查找
if(Config.getInstance().useMemCached())
{
BaseDbOm bdo = this.getFromMemCachedServer(omClass.getName()+"#"+id);
if(bdo!=null)
{
if(Config.getInstance().isLog())
{
logger.info("对象存在于远程缓存中:"+omClass.getName()+"#"+id);
}
RECORDS_CACHE.put(id, bdo);
return bdo;
}
}

//第三步:去读数据库
Session s = HibernateUtil.currentSession();
try
{
Transaction tx= s.beginTransaction();
om = (BaseDbOm) s.get(omClass, id);
tx.commit();

if(Config.getInstance().isLog())
{
if(om!=null)logger.info("对象存在于数据库中:"+omClass.getName()+"#"+id);
else logger.info("对象不存在于数据库中:"+omClass.getName()+"#"+id);
}
}
catch(HibernateException he)
{
he.printStackTrace();
}
finally
{
HibernateUtil.closeSession();
}

//第四步:放入本地缓存
RECORDS_CACHE.put(id, om);

//第五步:放入memcached server远程缓存中
set2MemCachedServer(om);

return om;
}

创建一个数据对象的代码也非常简单,如下:
public BaseDbOm create(BaseDbOm o)
{
if(o==null)return null ;

//这里的对象必须是BaseDbOm
//o.setCreateTime(new Long(System.currentTimeMillis()));
//o.setUpdateTime(new Long(System.currentTimeMillis()));

//SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
//o.setCreateDate(sdf.format(new Date()));
//o.setUpdateDate(sdf.format(new Date()));

Session s = HibernateUtil.currentSession();
try
{
Transaction tx= s.beginTransaction();
s.save(o);
tx.commit();

//刚创建的纪录放入缓存中
RECORDS_CACHE.put(o.getId(),o);

//放入远程缓存中
set2MemCachedServer(o);

//删除列表缓存
removeListCache(o,true);

if(Config.getInstance().isDbLog())
{
logger.info("创建数据库对象:"+o);
}
return o;
}
catch(HibernateException he)
{
he.printStackTrace();
return null;
}
finally
{
HibernateUtil.closeSession();
}
}

修改一个对象的代码稍微复杂,放入远程缓存的位置不能错,如下:
public void update(BaseDbOm om)
{
if (om == null) return ;
//om.setUpdateTime(new Long(System.currentTimeMillis()));

//SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
//om.setUpdateDate(sdf.format(new Date()));

//重新设置远程服务器的缓存
set2MemCachedServer(om);

//分布式删除对象缓存
removeFromCache(om.getId(),false,true);

Session s = HibernateUtil.currentSession();
try
{
Transaction tx= s.beginTransaction();
s.update(om);
tx.commit();

//删除列表缓存
removeListCache(om,true);

if(Config.getInstance().isDbLog())
{
logger.info("修改数据库对象:"+om);
}
}
catch(HibernateException e)
{
e.printStackTrace();
}
finally
{
HibernateUtil.closeSession();
}
}

以上代码均在一个叫BaseDbManager.java里,注意这里的BaseDbOm是一个基本数据库对象,其他新增加的数据对象只要extends BaseDbOm即可,新增加的UserManager需要extends BaseDbManager,那么增加了一个User只需一行代码 super.create(u);够简单了吧?

抱歉!评论已关闭.