有时候自己突然会冒出一些新的想法和工作思路,但是如果当时没有记录下来的话,往往很快就忘记。所以现在把这些想到的记录在这里,会比较杂,但有时间就可以把它们整理成比较详细的东西。

对 Web 系统增加访问统计

在我们现有的各个系统中很少有对访问信息(如 PV、浏览器信息等)做详细记录的,而这些数据其实对数据统计、业务分析、浏览器分析、性能优化等来说都是必备的。

现成的这类免费统计服务也挺多,CNZZ 是这方面国内比较专业的网站。使用第三方服务的一个缺陷是,只能使用提供的功能,无法实现个性化的数据分析。

添加统计脚本时,我希望能够做到:

  • 能够方便的给多个页面添加或删除这种脚本;
  • 能够方便的启用或停用统计脚本。

以下是一些解决方案:

  1. 如果页面采用的是 JSP,那么定义一个通用页面,将脚本写到这个页面,需要用到的页面 include 一下。
  2. 如果页面采用的是 freemarker 等模板,那么定义一个通用的宏,将脚本写到宏里,需要用到的页面引用一下。
  3. 如果页面采用了 sitemesh,那么只要在 decorate 页面中统一添加一下脚本,即可实现相关页面都添加。
  4. 采用过滤器来处理,过滤器支持匹配那些 URL 地址、排除那些 URL 地址、给匹配的页面添加什么内容的脚本。

第 3 种和第 4 种方式相对比较好,因为对页面的代码没有侵入性。

另外,可以通过读取一个系统配置项来判断是否加载统计脚本,这样就能通过改变配置选项来方便的启用或停用脚本。

需要能够监控到各个系统的运行状态

监控的内容包括:CPU、内存、IO、数据库连接池、Tomcat 线程池、应用线程池、在线用户数等。

扩展 OMC?应用提供监控接口?

需要方便的动态调整系统的日志级别,方便外网系统问题排查

如何避免各个应用系统都重复开发这样的功能?

集群环境下如何方便的查看每个节点的日志?

和同事讨论之后,一个设想是将日志以共享目录的方式(NFS)存放,每个节点将日志都存放到共享目录的节点目录下,然后通过一个独立的应用系统来提供日志的查看、下载等功能。比如,日志的目录结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
share/
|-- logs.app
| |-- server0
| | |-- background.log
| | `-- smsg.log
| |-- server1
| | |-- background.log
| | `-- smsg.log
| `-- server2
| |-- background.log
| `-- smsg.log
`-- logs.web
|-- server0
| |-- eis.log
| |-- ess.log
| |-- etoh.log
| |-- office.log
| `-- passport.log
|-- server1
| |-- eis.log
| |-- ess.log
| |-- etoh.log
| |-- office.log
| `-- passport.log
`-- server2
|-- eis.log
|-- ess.log
|-- etoh.log
|-- office.log
`-- passport.log

目录说明:

  • share 是顶级的共享日志目录,对于各个服务器节点来说,就像本地目录一样访问。
  • logs.app 是用于存放后台服务日志文件的目录。
  • logs.web 是用于存放 Web 应用日志文件的目录。
  • server0、server1、server2 为服务器节点目录,里面存放的是各个服务器节点上产生的日志。

规范各个系统的连接池配置、日志配置、Spring 数据库事务配置

编写和使用远程服务(RPC)时应该注意的问题。

问题场景:

  1. 某 Web 应用系统使用了 ThreadPool 来处理所有核心业务。
  2. 不少业务需要访问内网的一个远程的 UserService(RPC) 来获取用户信息。
  3. UserService 需要访问数据库。
  4. 数据库有时候会变慢,一些大查询需要 10 秒以上才能完成。

结果 4 造成 3 很多调用很久才能执行完,3 造成 2 的 RPC 调用阻塞,2 造成 1的 ThreadPool 堵塞,ThreadPool 不断有新任务加入,但是老的任务迟迟不能完成。因此对于最终用户的表现是很多请求没有响应。部分用户认为是网络原因会手工重复提交请求,这样会造成状况并进一步恶化。

上面的问题根本是没有意识到远程服务可能会超时或失败,把远程服务 RPC 调用当成一个本地调用来执行。

服务的提供方:

  • 对接口参数进行日志记录(debug 级别),方便出问题的时候进行排查。
  • 提供 echo、hello 等简单的接口测试方法,这样在接口调用失败时,可以比较方便的知道是否是网络问题引起。

服务的调用方:

  • 对 RPC 调用设置超时时间、并发连接数,超时时间一般包括两个:connectionTimeout、socketTimeout。
  • 如果对实时性要求不高,可以将 RPC 调用改为异步调用。

给 XFire 设置超时时间、并发连接数等:

1
2
3
4
5
6
7
8
9
10
11
12
13
XFire xfire = XFireFactory.newInstance().getXFire();
XFireProxyFactory factory = new XFireProxyFactory(xfire);
Service serviceModel = new ObjectServiceFactory().create(clazz);
service = factory.create(serviceModel, serviceURL);
// 获取客户端代理
Client client = Client.getInstance(service);
// 设置超时时间等参数
client.setProperty(CommonsHttpMessageSender.HTTP_TIMEOUT, String.valueOf(socketTimeout));
client.setProperty(CommonsHttpMessageSender.MAX_CONN_PER_HOST, String.valueOf(maxConnections));
client.setProperty(CommonsHttpMessageSender.MAX_TOTAL_CONNECTIONS, String.valueOf(maxConnections));
...

给 HttpInvoker 设置超时时间、并发连接数等:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
HttpInvokerProxyFactoryBean factoryBean = new HttpInvokerProxyFactoryBean();
factoryBean.setServiceUrl(serviceUrl);
factoryBean.setServiceInterface(serviceInterface);
CommonsHttpInvokerRequestExecutor executor = new CommonsHttpInvokerRequestExecutor();
connMgrParams = executor.getHttpClient().getHttpConnectionManager().getParams();
connMgrParams.setConnectionTimeout(connectionTimeout); // 设置连接超时时间
connMgrParams.setSoTimeout(socketTimeout); // 设置Socket超时时间
connMgrParams.setDefaultMaxConnectionsPerHost(maxConnections); // 设置每个Host的连接池的最大连接数
connMgrParams.setMaxTotalConnections(maxConnections); // 设置全局的连接池的最大连接数
factoryBean.setHttpInvokerRequestExecutor(executor);
...

系统应该提供某种方式,方便的查看到应用所部署的服务器,方便集群环境下的问题排查

比如可以类似家校互联这样的在某些关键性页面添加服务器标识。

如何方便的在集群环境下发布应用

集群环境多了,发布应用是个麻烦的事情。如果仍然采用手工发布,不仅效率低而且容易出错。

采用第三方工具?自己开发工具?