无意间看到ITEYE推荐《大型网站技术架构:核心原理与案例分析》,一口气看完推荐的两部分节选,
深有体会,结合鄙人所维护网站,谈下对网站技术架构优化的一些方案以及个人看法。
首先,介绍重构前的网站架构模式:
1、同样应用系统功能采用分层,即应用层、服务层、数据层。这样分层结构,能使系统组织层次明显,以达到良好运作;
2、其次对应用层进行应用功能模块划分,并采用分布式部署(分WEB区、APP区:web区部署8个server,app区部署6个server),使用weblogic集群,提高系统可用性;
3、对页面前端的静态文件,如CSS、图片、js,采用CDN加速,提高系统响应能力;
网站规模:截止目前,网站仍继续保持日注册量在2.5万,日登录量在55万次的访问次,网站用户数逼近2千5百万。
以上的架构在应对如此规模的访问量上总显得力不从心,特别是在重构前APP上的server内存使用量总是逼近100%,长期处于高位。在对代码层面做优化后情况仍没有好转,最后决定对系统架构做重构,在对系统架构层面做了一番分析后,矛头直指系统的异步消息模块优化改造上。
经分析,承担系统消息服务模块的app server即承担既是消息生产者又是消息消费者的角色,而且异步请求都是主流程中的实时性、重要性相对不是很高的,则为独立搭建消息消费者服务提供方向。
搭建独立的消息服务app server,对保证APP端服务稳定,尽量减少关联系统异常造成的影响起到很大作用:
1、作为消息消费者,独立部署,即使消息阻塞,也能隔离影响
2、给关联系统提供接口服务,即使关联系统调用异常,也能隔离影响
3、可考虑作为后台定时任务服务器
网站的重构除了做架构消息队列分离外,前端也采用业界backboneJs前端MVC框架对页面做组织结构重构,后面有机会再另起文章写写相关内容。
在这里将自己亲身经过的重构拿出来跟大家分享下,很多时候很难说得清楚哪种重构方案适合解决哪种类型的问题,只要是能使用较小的代价,能快速解决问题的,往这个方向设计的方案基本就差不多了。
下面附上当初重构遇到问题已经解决方案:
问题1:Invalid Subject
BeanCreationException: [Security:090398]Invalid Subject:jmstest
错误说明:远程调用验证不通过
原因分析:client JVM把错误的subject传递给了Remote JVM
报错代码:JmsMessengerDS jmsService = (JmsMessengerDS) context.getBean(BizContextNames.JMS_MESSENGER_DS);
解决方法:
新建类JmsJndiTemplate继承JndiTemplate并将subject缓存,在鉴权代码如下:
Subject subject = ((JmsJndiTemplate) context.getBean("jndiJmsTemplate")).getSubject();
try {
Security.runAs(subject, new PrivilegedAction() {
public Object run() {
}
});
} catch (Exception ex) {}
问题2:请求无法发到目标集群
原因分析:app和message两个cluster会相互收到对方的组播包(同一个集群
中的instance才应该收到)。现在的情况是两个不同集群,提供不同服
务的集群能收到对方的组播包。app跟message在同一台主机,使用相同的IP跟端口。
所以需要将两个集群的端口换成不同。
如下是报错信息:
<2013-6-3 上午09时21分10秒 CST> <Error> <Cluster> <BEA-000110>
<Multicast socket receive error: java.io.OptionalDataException
java.io.OptionalDataException at
java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1285)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:322)
Atweblogic.cluster.MulticastManager.execute(MulticastManager.java:5
16) At
weblogic.kernel.ExecuteThread.execute(ExecuteThread.java:224)
at weblogic.kernel.ExecuteThread.run(ExecuteThread.java:183)>
问题3:服务器依赖问题
现象分析:原先设计逻辑在message server重启后,没有再重新lookup,造成APP server跟MESSAGE server之间存在依赖关系
解决方案:
新写JmsJndiObjectFactoryBean类替换JndiObjectFactoryBean类,当获取对象时判断jndiObject是否为空,如果为空,则再lookup一次
if (this.jndiObject == null) {
// 如果服务启动时,远程的JMS没有启动,afterPropertiesSet()调用会失败,jndiObject为null,
// 在这里补调一次
try {
afterPropertiesSet();
} catch (IllegalArgumentException e) {
logger.warn("远程服务可能还没有启动", e);
} catch (NamingException e) {
logger.warn("远程服务可能还没有启动", e);
}
}
return this.jndiObject;
问题4:请求还是落在本地集群
} 现象描述:当APP跟MESSAGE改成使用不同的端口,APP发出的请求还是落在MESSAGE上。
原因分析:可以从MESSAGE的console上分析看到,实际上APP中的消息已经发送到MESSAGE上JMS SERVER上,这是由于MESSAGE上的MessageDrivenBean获取请求解析对应的ActionID,而对ActionID的调用是通过PafaAC完成,此时需要把MESSAGE上的properties文件中关于PafaAC的T3地址配置成MESSAGE所在的服务地址即可
问题5:JMS重启后链接失效
现象描述:
当message server重启后,再发起交易会报发送失败错误:MessengerDS send message failure
原因分析:
QueueConnection作为单例,每个client和jms server只建立一次连接(start一次),相当socket通道打通,就一致不关闭
当jms server挂或者重启的时候,该连接则不可用了
所以在queueConnection.createQueueSession创建session时候,发现connection不可用,关闭以前的connection并设置connection为null,
下次请求时候,重新获取connection并重新start
// 发送之前进行初始化,已支持延迟加载
synchronized (this) {
if (queueConnection == null) {
try {
queueConnection = queueConnectionFactory
.createQueueConnection();
queueConnection.start();
} catch (JMSException jmsEx) {
queueConnection = null;
throw new RuntimeException(
START_CONNECTION_FAILURE);
}
}
}
改造上线后server内容消耗情况,相比之前有明显回落(7月分上线)