小东子的个人技术专栏

重点关注Android、Java、智能硬件、JavaEE、react-native,Swift,微信小程序


  • 首页

  • 归档

  • 标签
小东子的个人技术专栏

dubbo2.5-spring4-mybastis3.2-springmvc4-mongodb3.4-redis3.2整合(四)Spring AOP中使用log4j实现http请求日志入mongodb

发表于 2017-01-12   |   字数统计: 1,737(字)   |   阅读时长: 8(分)

1、面向切面编程的一些术语

  • 切面(Aspect):切面用于组织多个Advice,Advice放在切面中的定义。
  • 连接点(Jionpoint):程序执行过程中的明确的点,如方法的调用,或异常的抛出。在SpringAOP中,连接点总是方法的调用。
  • 增强的处理(Advice):AOP框架在特定的切入点执行的增强处理。处理有around、brefore和after等类型。
  • 切入点(Pointcut):可以插入增强处理的连接点。简而言之,当某个连接点满足指定的要求时,该连接点将添加增强处理,该连接点也就变成了切入点。
1
pointcut xxxPointcut():execution(void H*.say*())
  • 引入:将方法或字段添加到被代理的类中,Spring允许将新的接口引入到任何被调用的处理对象中。例如:你可以使用一个引入,使任何对象实现IsModified接口,以此来简化缓存。
  • 目标对象:被AOP框架进行增强处理的对象,也被称为被增强对象,如果AOP框架采用的是动态的AOP实现 ,那该对象就是一个被代理对象。
  • AOP代理:AOP框架创建的对象,简单说,代理就是对目标对象的加强,Spring中的AOP代理可以是JDK的动态代理,也可以是cglib的动态代理,前者是实现接口 的目标对象的代理,后者为不实现接口的目标对象的代理。
  • 织入(Weaving):将增强处理添加到目标对象中,并创一个被增强对象(AOP代理)的过程就是织入。织入有l两种实现方式—–编译时增强(AspectJ)和运行时增强(如Spring AOP)。Spring和其他纯Java AOP框架一样,在运行时织入。

2、AOP的基本概念

AOP是从程序运行角度考虑程序的流程,提取业务处理的过程的切面,AOP面向的是程序运行中各个步骤,希望以更好的方式来组合业务处理的各个步骤。

AOP框架并不会与特定的代码耦合,AOP框架能处理程序执行中特定的切入点(PointCut),而不是某个具体类的耦合。

3、AOP框架具有如下的两个特征。

  • 各个步骤之间良好的隔离。

  • 源代码无关。

    注:Spring的AOP支持允许将一些通用任务如安全、事务、日志等进行集中式处理。从而提高了更多的复用。

    4.如何在Spring中引入AOP功能

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${srping.version}</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.7.2</version>
</dependency>

5、实现Web层的日志切面

实现AOP的切面主要有以下几个要素:

  • 使用 @Aspect 注解将一个java类定义为切面类。
  • 使用 @Pointcut 定义一个切入点,可以是一个规则表达式,比如下例中某个package下的所有函数,也可以是一个注解等。
  • 根据需要在切入点不同位置的切入内容
  • 使用 @Before 在切入点开始处切入内容
  • 使用 @After 在切入点结尾处切入内容
  • 使用 @AfterReturning 在切入点return内容之后切入内容(可以用来对处理返回值做一些加工处理)
  • 使用 @Around 在切入点前后切入内容,并自己控制何时执行切入点自身的内容
  • 使用 @AfterThrowing 用来处理当切入内容部分抛出异常之后的处理逻辑
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
package com.lidong.util;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.apache.log4j.Logger;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import com.mongodb.BasicDBObject;
/**
1. 保存请求数据到mongodb
2. @author lidong
3. @createTime 2016年12月24日
*/
@Aspect
@Order(1)
@Component
public class WebLogAspect {
private Logger logger = Logger.getLogger("mongodb");
ThreadLocal<Long> startTime = new ThreadLocal<>();
HttpServletRequest request;
JoinPoint mJoinPoint;
@Pointcut("execution (* com.lidong.*.controller.*.*(..))")
public void webLog(){}
@Before("webLog()")
public void doBefore(JoinPoint joinPoint) throws Throwable {
startTime.set(System.currentTimeMillis());
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
request = attributes.getRequest();
mJoinPoint = joinPoint;
}
private BasicDBObject getBasicDBObject(HttpServletRequest request, JoinPoint joinPoint) {
BasicDBObject r = new BasicDBObject();
r.append("requestURL", request.getRequestURL().toString());
r.append("requestURI", request.getRequestURI());
r.append("queryString", request.getQueryString());
r.append("remoteAddr", request.getRemoteAddr());
r.append("remoteHost", request.getRemoteHost());
r.append("remotePort", request.getRemotePort());
r.append("localAddr", request.getLocalAddr());
r.append("localName", request.getLocalName());
r.append("method", request.getMethod());
r.append("headers", getHeadersInfo(request));
r.append("parameters", request.getParameterMap());
r.append("classMethod", joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
r.append("args", Arrays.toString(joinPoint.getArgs()));
return r;
}
/**
* 获取请求头部的信息
* @param request
* @return
*/
private Map<String, String> getHeadersInfo(HttpServletRequest request) {
Map<String, String> map = new HashMap<>();
Enumeration headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String key = (String) headerNames.nextElement();
String value = request.getHeader(key);
map.put(key, value);
}
return map;
}
@AfterReturning(returning = "ret", pointcut = "webLog()")
public void doAfterReturning(Object ret) throws Throwable {
BasicDBObject logInfo = getBasicDBObject(request, mJoinPoint);
// 处理完请求,返回内容
logInfo.append("response",ret.toString());
logInfo.append("spend_time", (System.currentTimeMillis() - startTime.get()));
logger.info(logInfo);
}
}

注意:在WebLogAspect切面中,分别通过doBefore和doAfterReturning两个独立函数实现了切点头部和切点返回后执行的内容,若我们想统计请求的处理时间,就需要在doBefore处记录时间,并在doAfterReturning处通过当前时间与开始处记录的时间计算得到请求处理的消耗时间。

6、实现Web层的日志的日志保存到mongodb数据库中

6.1.通过自定义appender实现

思路:log4j提供的输出器实现自Appender接口,要自定义appender输出到MongoDB,只需要继承AppenderSkeleton类,并实现几个方法即可完成。

  • 引入mongodb的驱动,在pom.xml中引入下面依赖
1
2
3
4
5
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver</artifactId>
<version>3.2.2</version>
</dependency>
  • 实现MongoAppender

编写MongoAppender类继承AppenderSkeleton,实现如下:

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
package com.lidong.util;
import org.apache.log4j.AppenderSkeleton;
import org.apache.log4j.spi.LoggingEvent;
import com.mongodb.BasicDBObject;
import com.mongodb.MongoClient;
import com.mongodb.MongoClientURI;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
/**
* 自定义appender实现输出日志MongoDB
* @author lidong
*
*/
public class MongoAppender extends AppenderSkeleton {
private MongoClient mongoClient;
private MongoDatabase mongoDatabase;
private MongoCollection<BasicDBObject> logsCollection;
/**
* mongodb连接的url
*/
private String connectionUrl;
/**
* 数据库的名称
*/
private String databaseName;
/**
* 集合的名称
*/
private String collectionName;
@Override
protected void append(LoggingEvent loggingEvent) {
if(mongoDatabase == null) {
MongoClientURI connectionString = new MongoClientURI(connectionUrl);
mongoClient = new MongoClient(connectionString);
mongoDatabase = mongoClient.getDatabase(databaseName);
logsCollection = mongoDatabase.getCollection(collectionName, BasicDBObject.class);
}
//将日志插入到集合
logsCollection.insertOne((BasicDBObject) loggingEvent.getMessage());
}
@Override
public void close() {
if(mongoClient != null) {
mongoClient.close();
}
}
@Override
public boolean requiresLayout() {
return false;
}
public MongoClient getMongoClient() {
return mongoClient;
}
public void setMongoClient(MongoClient mongoClient) {
this.mongoClient = mongoClient;
}
public MongoDatabase getMongoDatabase() {
return mongoDatabase;
}
public void setMongoDatabase(MongoDatabase mongoDatabase) {
this.mongoDatabase = mongoDatabase;
}
public MongoCollection<BasicDBObject> getLogsCollection() {
return logsCollection;
}
public void setLogsCollection(MongoCollection<BasicDBObject> logsCollection) {
this.logsCollection = logsCollection;
}
public String getConnectionUrl() {
return connectionUrl;
}
public void setConnectionUrl(String connectionUrl) {
this.connectionUrl = connectionUrl;
}
public String getDatabaseName() {
return databaseName;
}
public void setDatabaseName(String databaseName) {
this.databaseName = databaseName;
}
public String getCollectionName() {
return collectionName;
}
public void setCollectionName(String collectionName) {
this.collectionName = collectionName;
}
}

6.3 在log4j.prperties中配置

1
2
3
4
5
6
# mongodb输出
log4j.logger.mongodb=INFO, mongodb
log4j.appender.mongodb=com.lidong.util.MongoAppender
log4j.appender.mongodb.connectionUrl=mongodb://127.0.0.1:27017
log4j.appender.mongodb.databaseName=logs
log4j.appender.mongodb.collectionName=logs_request

6.4在spring-mvc.xml中开启统一处理请求日志

1
2
3
4
<!-- web统一管理 -->
<aop:aspectj-autoproxy proxy-target-class="true"/>
<bean class="com.lidong.util.WebLogAspect" />

6.5在数据库中查看保存的日志

这里写图片描述

代码地址

小东子的个人技术专栏

dubbo2.5-spring4-mybastis3.2-springmvc4-mongodb3.4-redis3.2整合(三)使用Spring AOP实现mysql的读写分离

发表于 2017-01-12   |   字数统计: 3,046(字)   |   阅读时长: 14(分)

1. 为什么要进行(主从复制)读写分离

分布式环境下数据库的读写分离策略是解决数据库读写性能瓶颈的一个关键解决方案,更是最大限度了提高了应用中读取 (Read)数据的速度和并发量。

在进行数据库读写分离的时候,我们首先要进行数据库的主从配置,最简单的是一台Master和一台Slave(大型网站系统的话,当然会很复杂,这里只是分析了最简单的情况)。通过主从配置主从数据库保持了相同的数据,我们在进行读操作的时候访问从数据库Slave,在进行写操作的时候访问主数据库Master。这样的话就减轻了一台服务器的压力。

在进行读写分离案例分析的时候。首先,配置数据库的主从复制,使用mysqlreplicate命令快速搭建 Mysql 主从复制。

2.MySQL主从复制的原理

2.1MySQL主从复制的原理

这里写图片描述

2.2MySQL主从复制的基本过程

MySQL主从复制的两种情况:同步复制和异步复制,实际复制架构中大部分为异步复制。复制的基本过程如下:

  1. Slave上面的IO进程连接上Master,并请求从指定日志文件的指定位置(或者从最开始的日志)之后的日志内容。
  2. Master接收到来自Slave的IO进程的请求后,负责复制的IO进程会根据请求信息读取日志指定位置之后的日志信息,返回给Slave的IO进程。返回信息中除了日志所包含的信息之外,还包括本次返回的信息已经到Master端的bin-log文件的名称以及bin-log的位置。
  3. Slave的IO进程接收到信息后,将接收到的日志内容依次添加到Slave端的relay-log文件的最末端,并将读取到的Master端的 bin-log的文件名和位置记录到master-info文件中,以便在下一次读取的时候能够清楚的告诉Master“我需要从某个bin-log的哪个位置开始往后的日志内容,请发给我”。
  4. Slave的Sql进程检测到relay-log中新增加了内容后,会马上解析relay-log的内容成为在Master端真实执行时候的那些可执行的内容,并在自身执行。

3.开始MySQL5.7.12的主从复制教程:

3.1 MySQL5.6开始主从复制有两种方式:基于日志(binlog);基于GTID(全局事务标示符)。

需要注意的是:GTID方式不支持临时表!所以如果你的业务系统要用到临时表的话就不要考虑这种方式了,至少目前最新版本MySQL5.6.12的GTID复制还是不支持临时表的。

所以此篇教程主要是告诉大家如何通过日志(binlog)方式做主从复制!

3.2 MySQL官方提供的MySQL Replication教程:

http://dev.mysql.com/doc/refman/5.6/en/replication.html

这个官方教程强烈建议大家阅读。

3.3、准备工作:

  1. 配置MySQL主从复制(读写分离)之前,需要在主从两台服务器先安装好MySQL5.7。
  2. 目前最新的MySQL5.7 GA版本是MySQL5.7.12(点此下载MySQL5.7.12源码包)。
  3. 需要注意如下两点:

(1)如果你需要用于生产环境,安教程安装MySQL时不要急着做mysql启动操作。建议把mysql初始化生成的/usr/local/mysql/mysql.cnf删除,然后把你优化好的mysql配置文件my.cnf放到/etc下。
(2)建议主备两台服务器在同一局域网,主备两台数据库网络需要互通。

  1. 我的所使用的环境:

主数据库IP:192.168.0.104

从数据库IP:192.168.0.105

3.4 修改主数据库的的配置文件:

1
2
3
4
5
6
7
8
9
10
1 [mysqld]
2 server-id=1
3 log-bin=mysqlmaster-bin.log
4 sync_binlog=1
5 #注意:下面这个参数需要修改为服务器内存的70%左右
6 innodb_buffer_pool_size = 512M
7 innodb_flush_log_at_trx_commit=1
8 sql_mode=STRICT_TRANS_TABLES,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION,NO_AUTO_VALUE_ON_ZERO
9 lower_case_table_names=1
10 log_bin_trust_function_creators=1

修改之后要重启mysql服务:

1
# /etc/init.d/mysql restart

3.5、修改从数据库的的配置文件:

注意:(server-id配置为大于主数据库的server-id即可)

1
2
3
4
5
6
7
8
9
10
1 [mysqld]
2 server-id=2
3 log-bin=mysqlslave-bin.log
4 sync_binlog=1
5 #注意:下面这个参数需要修改为服务器内存的70%左右
6 innodb_buffer_pool_size = 512M
7 innodb_flush_log_at_trx_commit=1
8 sql_mode=STRICT_TRANS_TABLES,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION,NO_AUTO_VALUE_ON_ZERO
9 lower_case_table_names=1
10 log_bin_trust_function_creators=1

修改之后要重启mysql:

1
# /etc/init.d/mysql restart

附一个我已优化过的从数据库配置文件:点此下载

3.6、SSH登录到主数据库:

3.6.1. 在主数据库上创建用于主从复制的账户(192.168.0.104换成你的从数据库IP):

1
2
1 # mysql -uroot -p
2 mysql> GRANT REPLICATION SLAVE ON *.* TO 'repl'@'192.168.0.104' IDENTIFIED BY 'repl';

3.6.2. 主数据库锁表(禁止再插入数据以获取主数据库的的二进制日志坐标):

mysql> FLUSH TABLES WITH READ LOCK;

3.6.3. 然后克隆一个SSH会话窗口,在这个窗口打开MySQL命令行:

这里写图片描述

在这个例子中,二进制日志文件是mysqlmaster-bin.000001,位置是332,记录下这两个值,稍后要用到。

3.6.4 在主数据库上使用mysqldump命令创建一个数据快照:

mysqldump -uroot -p -h127.0.0.1 -P3306 –all-databases –triggers –routines –events >all.sql

注意:接下来会提示你输入mysql数据库的root密码,输入完成后,如果当前数据库不大,很快就能导出完成。

3.6.5 解锁第2.6.2步主数据的锁表操作:

mysql> UNLOCK TABLES;

3.7、SSH登录到从数据库:

(1)通过FTP、SFTP或其他方式,将上一步备份的主数据库快照all.sql上传到从数据库某个路径,例如我放在了/home/lidong/目录下;

(2)从导入主的快照:

cd /home/lidong

# mysql -uroot -p -h127.0.0.1 -P3306 < all.sql

注意:接下来会提示你输入mysql数据库的root密码,输入完成后,如果当前数据库不大,很快就能导入完成。

(3)给从数据库设置复制的主数据库信息(注意修改MASTER_LOG_FILE和MASTER_LOG_POS的值):

1
2
3
4
5
6
7
8
9
# mysql -uroot -p
mysql> CHANGE MASTER TO MASTER_HOST='192.168.100.2',MASTER_USER='repl',MASTER_PASSWORD='repl',MASTER_LOG_FILE='mysqlmaster-bin.000001',MASTER_LOG_POS=332;
# 然后启动从数据库的复制线程:
mysql> START slave;
# 接着查询数据库的slave状态:
mysql> SHOW slave STATUS \G
# 如果下面两个参数都是Yes,则说明主从配置成功!
Slave_IO_Running: Yes
Slave_SQL_Running: Yes

(4)接下来我们可以在主数据库上创建数据库、表、插入数据,然后看从数据库是否同步了这些操作。

一主一从的主从复制就实现了。

4、实现读写分离的两种方法

  1. 第一种方式是我们最常用的方式,就是定义2个数据库连接,一个是MasterDataSource,另一个是SlaveDataSource。更新数据时我们读取MasterDataSource,查询数据时我们读取SlaveDataSource。这种方式很简单。
  2. 第二种方式动态数据源切换,就是在程序运行时,把数据源动态织入到程序中,从而选择读取主库还是从库。主要使用的技术是:Annotation,spring AOP ,反射。

    5.Spring AOP 实现MySQL读写分离的具体实现

    5.1创建用于配置动态分配的读写的数据源ChooseDataSource.java

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
31
32
33
34
35
36
37
38
39
package com.lidong.util.aspect;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* 获取数据源,用于动态切换数据源
*/
public class ChooseDataSource extends AbstractRoutingDataSource {
public static Map<String, List<String>> METHOD_TYPE_MAP = new HashMap<String, List<String>>();
/**
* 实现父类中的抽象方法,获取数据源名称
* @return
*/
protected Object determineCurrentLookupKey() {
return DataSourceHandler.getDataSource();
}
// 设置方法名前缀对应的数据源
public void setMethodType(Map<String, String> map) {
for (String key : map.keySet()) {
List<String> v = new ArrayList<String>();
String[] types = map.get(key).split(",");
for (String type : types) {
if (StringUtils.isNotBlank(type)) {
v.add(type);
}
}
METHOD_TYPE_MAP.put(key, v);
}
}
}

5.2 DataSourceAspect进行具体方法的AOP拦截

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package com.lidong.util.aspect;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component;
/**
* 切换数据源(不同方法调用不同数据源)
*/
@Aspect
@Component
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class DataSourceAspect {
protected Logger logger = LoggerFactory.getLogger(this.getClass());
@Pointcut("execution(* com.lidong.core.*.dao.*.*(..))")
public void aspect() {
}
/**
* 配置前置通知,使用在方法aspect()上注册的切入点
*/
@Before("aspect()")
public void before(JoinPoint point) {
String className = point.getTarget().getClass().getName();
String method = point.getSignature().getName();
logger.info(className + "." + method + "(" + StringUtils.join(point.getArgs(), ",") + ")");
try {
for (String key : ChooseDataSource.METHOD_TYPE_MAP.keySet()) {
for (String type : ChooseDataSource.METHOD_TYPE_MAP.get(key)) {
if (method.startsWith(type)) {
DataSourceHandler.putDataSource(key);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

5.3 数据源的Handler类 DataSourceHandler.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.lidong.util.aspect;
/**
* 数据源的Handler类
* @author lidong
*
*/
public class DataSourceHandler {
// 数据源名称线程池
public static final ThreadLocal<String> holder = new ThreadLocal<String>();
/**
* 在项目启动的时候将配置的读、写数据源加到holder中
*/
public static void putDataSource(String datasource) {
holder.set(datasource);
}
/**
* 从holer中获取数据源字符串
*/
public static String getDataSource() {
return holder.get();
}
}

5.4 spring-db.xml读写数据源配置

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/hms
url1=jdbc:mysql://192.168.0.105:3306/hms
username=root
password=123456
password1=
initialSize=0
maxActive=20
maxIdle=20
minIdle=1
maxWait=60000
timeBetweenEvictionRunsMillis=3000
minEvictableIdleTimeMillis=300000
maxPoolPreparedStatementPerConnectionSize=20
druid.filters=
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd">
<!-- 引入配置文件 <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="classpath:jdbc.properties" /> </bean> -->
<bean id="propertyConfigurer"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:jdbc.properties</value>
<value>classpath:mongo.properties</value>
<value>classpath:redis.properties</value>
</list>
</property>
</bean>
<bean id="statFilter" class="com.alibaba.druid.filter.stat.StatFilter"
lazy-init="true">
<property name="logSlowSql" value="true" />
<property name="mergeSql" value="true" />
</bean>
<bean id="readDataSource" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<property name="url" value="${url}" />
<property name="username" value="${username}" />
<property name="password" value="${password}" />
<property name="filters" value="stat" />
<property name="maxActive" value="20" />
<property name="initialSize" value="1" />
<property name="maxWait" value="60000" />
<property name="minIdle" value="1" />
<property name="timeBetweenEvictionRunsMillis" value="3000" />
<property name="minEvictableIdleTimeMillis" value="300000" />
<property name="validationQuery" value="SELECT 'x'" />
<property name="testWhileIdle" value="true" />
<property name="testOnBorrow" value="false" />
<property name="testOnReturn" value="false" />
<property name="poolPreparedStatements" value="true" />
<property name="maxPoolPreparedStatementPerConnectionSize"
value="20" />
</bean>
<bean id="writeDataSource" class="com.alibaba.druid.pool.DruidDataSource"
destroy-method="close" init-method="init" lazy-init="true">
<property name="driverClassName" value="${driver}" />
<property name="url" value="${url1}" />
<property name="username" value="${username}" />
<property name="password" value="${password1}" />
<property name="initialSize" value="${initialSize}" />
<property name="maxActive" value="${maxActive}" />
<property name="minIdle" value="${minIdle}" />
<property name="maxWait" value="${maxWait}" />
<property name="proxyFilters">
<list>
<ref bean="statFilter" />
</list>
</property>
<property name="filters" value="${druid.filters}" />
<property name="connectionProperties" value="password=${password1}" />
<property name="testWhileIdle" value="true" />
<property name="testOnBorrow" value="false" />
<property name="testOnReturn" value="false" />
<property name="validationQuery" value="SELECT 'x'" />
<property name="timeBetweenLogStatsMillis" value="60000" />
<property name="minEvictableIdleTimeMillis" value="${minEvictableIdleTimeMillis}" />
<property name="timeBetweenEvictionRunsMillis" value="${timeBetweenEvictionRunsMillis}" />
</bean>
<!-- 配置动态分配的读写 数据源 -->
<bean id="dataSource" class="com.lidong.util.aspect.ChooseDataSource"
lazy-init="true">
<property name="targetDataSources">
<map key-type="java.lang.String" value-type="javax.sql.DataSource">
<!-- write -->
<entry key="write" value-ref="writeDataSource" />
<!-- read -->
<entry key="read" value-ref="readDataSource" />
</map>
</property>
<property name="defaultTargetDataSource" ref="writeDataSource" />
<property name="methodType">
<map key-type="java.lang.String">
<!-- read -->
<entry key="read" value=",get,select,count,list,query" />
<!-- write -->
<entry key="write" value=",add,create,update,delete,remove," />
</map>
</property>
</bean>
</beans>

配置了readDataSource和writeDataSource两个数据源,但是交给
SqlSessionFactoryBean进行管理的只有dataSource,使用了com.lidong.util.aspect.ChooseDataSource来进行数据源的选择,默认的数据源是writeDataSource。methodType是定义了方法的关键字,那些是选择读库,那个是写库。

5.5 在Dao层通过切面选择数据源

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package com.lidong.core.user.service;
import java.util.List;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;
import com.lidong.api.service.user.IUserService;
import com.lidong.core.user.dao.IUserDao;
import com.lidong.model.user.User;
@Service("userService")
public class UserServiceImp implements IUserService {
@Resource
IUserDao mIUserDao;
@Override
public User getUserById(int userId) {
return mIUserDao.selectByPrimaryKey(userId);
}
@Override
public User getUserByUsername(String username) {
return mIUserDao.selectByPrimaryUsername(username);
}
@Override
public void addUser(User user) {
mIUserDao.insert(user);
}
@Override
public List<User> getAllUser() {
return mIUserDao.selectAllUsers();
}
@Override
public int delUserById(Integer userId) {
return mIUserDao.deleteByPrimaryKey(userId);
}
@Override
public int updateUser(User user) {
return mIUserDao.updateByPrimaryKey(user);
}
}

注意:通过看com.alibaba.druid.pool.DruidDataSourceStatLoggerImpl 是一分钟发一次心跳,监听写数据源有没有宕机。如果宕机会进行重连。

[代码地址]

小东子的个人技术专栏

dubbo2.5-spring4-mybastis3.2-springmvc4-mongodb3.4-redis3.2整合(二)之 JDBC连接池、监控组件 Druid

发表于 2017-01-12   |   字数统计: 913(字)   |   阅读时长: 4(分)

1 Druid简介

Druid是阿里巴巴开源平台上的一个项目,整个项目由数据库连接池、插件框架和SQL解析器组成。该项目主要是为了扩展JDBC的一些限制,可以让程序员实现一些特殊的需求,比如向密钥服务请求凭证、统计SQL信息、SQL性能收集、SQL注入检查、SQL翻译等,程序员可以通过定制来实现自己需要的功能。

Druid是目前最好的数据库连接池,在功能、性能、扩展性方面,都超过其他数据库连接池,包括DBCP、C3P0、BoneCP、Proxool、JBoss DataSource。Druid已经在阿里巴巴部署了超过600个应用,经过一年多生产环境大规模部署的严苛考验。

2.Druid的功能

1、替换DBCP和C3P0。Druid提供了一个高效、功能强大、可扩展性好的数据库连接池。

2、可以监控数据库访问性能,Druid内置提供了一个功能强大的StatFilter插件,能够详细统计SQL的执行性能,这对于线上分析数据库访问性能有帮助。

3、数据库密码加密。直接把数据库密码写在配置文件中,这是不好的行为,容易导致安全问题。DruidDruiver和DruidDataSource都支持PasswordCallback。

4、SQL执行日志,Druid提供了不同的LogFilter,能够支持Common-Logging、Log4j和JdkLog,你可以按需要选择相应的LogFilter,监控你应用的数据库访问情况。

5、扩展JDBC,如果你要对JDBC层有编程的需求,可以通过Druid提供的Filter机制,很方便编写JDBC层的扩展插件。

3.Druid怎么配置maven

1
2
3
4
5
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid-version}</version>
</dependency>

4.Druid支持的数据库

理论上说,支持所有有jdbc驱动的数据库。实际测试过的有


  1. mysql 支持,大规模使用
  2. oracle 支持,大规模使用
  3. sqlserver 支持
  4. postgres 支持
  5. db2 支持
  6. h2 支持
  7. derby 支持
  8. sqlite 支持
  9. sybase 支持

5.配置DruidDataSource

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
31
32
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<!-- 基本属性 url、user、password -->
<property name="url" value="${jdbc_url}" />
<property name="username" value="${jdbc_user}" />
<property name="password" value="${jdbc_password}" />
<!-- 配置初始化大小、最小、最大 -->
<property name="initialSize" value="1" />
<property name="minIdle" value="1" />
<property name="maxActive" value="20" />
<!-- 配置获取连接等待超时的时间 -->
<property name="maxWait" value="60000" />
<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="300000" />
<property name="validationQuery" value="SELECT 'x'" />
<property name="testWhileIdle" value="true" />
<property name="testOnBorrow" value="false" />
<property name="testOnReturn" value="false" />
<!-- 打开PSCache,并且指定每个连接上PSCache的大小 -->
<property name="poolPreparedStatements" value="true" />
<property name="maxPoolPreparedStatementPerConnectionSize" value="20" />
<!-- 配置监控统计拦截的filters -->
<property name="filters" value="stat" />
</bean>

通常来说,只需要修改initialSize、minIdle、maxActive。

如果用Oracle,则把poolPreparedStatements配置为true,mysql可以配置为false。分库分表较多的数据库,建议配置为false。

6.配置_StatFilter

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
31
32
<servlet>
<servlet-name>DruidStatView</servlet-name>
<servlet-class>com.alibaba.druid.support.http.StatViewServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>DruidStatView</servlet-name>
<url-pattern>/druid/*</url-pattern>
</servlet-mapping>
<filter>
<filter-name>DruidWebStatFilter</filter-name>
<filter-class>com.alibaba.druid.support.http.WebStatFilter</filter-class>
<init-param>
<param-name>exclusions</param-name>
<param-value>*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*</param-value>
</init-param>
<init-param>
<param-name>profileEnable</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>principalCookieName</param-name>
<param-value>USER_COOKIE</param-value>
</init-param>
<init-param>
<param-name>principalSessionName</param-name>
<param-value>USER_SESSION</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>DruidWebStatFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

7.结果展示

7.1.首页

这里写图片描述

7.2 SQL监控

这里写图片描述

代码地址

小东子的个人技术专栏

dubbo2.5-spring4-mybastis3.2-springmvc4-mongodb3.4-redis3.2整合(一)Dubbo的使用

发表于 2017-01-12   |   字数统计: 2,321(字)   |   阅读时长: 8(分)

1.Dubbo简介

DUBBO是一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,是阿里巴巴SOA服务化治理方案的核心框架,每天为2,000+个服务提供3,000,000,000+次访问量支持,并被广泛应用于阿里巴巴集团的各成员站点。

随着互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在必行,亟需一个治理系统确保架构有条不紊的演进。

这里写图片描述

单一应用架构

当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。
此时,用于简化增删改查工作量的 数据访问框架(ORM) 是关键。

垂直应用架构

当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,将应用拆成互不相干的几个应用,以提升效率。
此时,用于加速前端页面开发的 Web框架(MVC) 是关键。

###分布式服务架构
当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。
此时,用于提高业务复用及整合的 分布式服务框架(RPC) 是关键。

流动计算架构

当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。
此时,用于提高机器利用率的 资源调度和治理中心(SOA) 是关键。

2.需求

在大规模服务化之前,应用可能只是通过RMI或Hessian等工具,简单的暴露和引用远程服务,通过配置服务的URL地址进行调用,通过F5等硬件进行负载均衡。

(1) 当服务越来越多时,服务URL配置管理变得非常困难,F5硬件负载均衡器的单点压力也越来越大。

此时需要一个服务注册中心,动态的注册和发现服务,使服务的位置透明。

并通过在消费方获取服务提供方地址列表,实现软负载均衡和Failover,降低对F5硬件负载均衡器的依赖,也能减少部分成本。

(2) 当进一步发展,服务间依赖关系变得错踪复杂,甚至分不清哪个应用要在哪个应用之前启动,架构师都不能完整的描述应用的架构关系。

这时,需要自动画出应用间的依赖关系图,以帮助架构师理清理关系。

(3) 接着,服务的调用量越来越大,服务的容量问题就暴露出来,这个服务需要多少机器支撑?什么时候该加机器?

为了解决这些问题,第一步,要将服务现在每天的调用量,响应时间,都统计出来,作为容量规划的参考指标。

其次,要可以动态调整权重,在线上,将某台机器的权重一直加大,并在加大的过程中记录响应时间的变化,直到响应时间到达阀值,记录此时的访问量,再以此访问量乘以机器数反推总容量。

以上是Dubbo最基本的几个需求,更多服务治理问题参见:

http://code.alibabatech.com/blog/experience_1402/service-governance-process.html

3.架构

dubbo运行架构如下图示:

这里写图片描述
节点角色说明:

Provider: 暴露服务的服务提供方。
Consumer: 调用远程服务的服务消费方。
Registry: 服务注册与发现的注册中心。
Monitor: 统计服务的调用次调和调用时间的监控中心。
Container: 服务运行容器。
调用关系说明:

  • 服务容器负责启动,加载,运行服务提供者。
  • 服务提供者在启动时,向注册中心注册自己提供的服务。
  • 服务消费者在启动时,向注册中心订阅自己所需的服务。
  • 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
  • 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
  • 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。

    ####1.连通性:

  • 注册中心负责服务地址的注册与查找,相当于目录服务,服务提供者和消费者只在启动时与注册中心交互,注册中心不转发请求,压力较小
  • 监控中心负责统计各服务调用次数,调用时间等,统计先在内存汇总后每分钟一次发送到监控中心服务器,并以报表展示
  • 服务提供者向注册中心注册其提供的服务,并汇报调用时间到监控中心,此时间不包含网络开销
  • 服务消费者向注册中心获取服务提供者地址列表,并根据负载算法直接调用提供者,同时汇报调用时间到监控中心,此时间包含网络开销
  • 注册中心,服务提供者,服务消费者三者之间均为长连接,监控中心除外
  • 注册中心通过长连接感知服务提供者的存在,服务提供者宕机,注册中心将立即推送事件通知消费者
  • 注册中心和监控中心全部宕机,不影响已运行的提供者和消费者,消费者在本地缓存了提供者列表
  • 注册中心和监控中心都是可选的,服务消费者可以直连服务提供者

    2.健状性:

  • 监控中心宕掉不影响使用,只是丢失部分采样数据

  • 数据库宕掉后,注册中心仍能通过缓存提供服务列表查询,但不能注册新服务

  • 注册中心对等集群,任意一台宕掉后,将自动切换到另一台
    注册中心全部宕掉后,服务提供者和服务消费者仍能通过本地缓存通讯

  • 服务提供者无状态,任意一台宕掉后,不影响使用
  • 服务提供者全部宕掉后,服务消费者应用将无法使用,并无限次重连等待服务提供者恢复

3.伸缩性:

  • 注册中心为对等集群,可动态增加机器部署实例,所有客户端将自动发现新的注册中心
  • 服务提供者无状态,可动态增加机器部署实例,注册中心将推送新的服务提供者信息给消费者

    4.升级性:

    这里写图片描述
    当服务集群规模进一步扩大,带动IT治理结构进一步升级,需要实现动态部署,进行流动计算,现有分布式服务架构不会带来阻力:
    Deployer: 自动部署服务的本地代理。
    Repository: 仓库用于存储服务应用发布包。
    Scheduler: 调度中心基于访问压力自动增减服务提供者。
    Admin: 统一管理控制台。

    4.dubbo的调用方式

异步调用
基于NIO的非阻塞实现并行调用,客户端不需要启动多线程即可完成并行调用多个远程服务,相对多线程开销较小。

这里写图片描述

本地调用
本地调用,使用了Injvm协议,是一个伪协议,它不开启端口,不发起远程调用,只在JVM内直接关联,但执行Dubbo的Filter链。

5.duubo的用法

本地服务:(Spring配置)
local.xml

1
2
3
4
5
<bean id=“xxxService” class=“com.xxx.XxxServiceImpl” />
<bean id=“xxxAction” class=“com.xxx.XxxAction”>
<property name=“xxxService” ref=“xxxService” />
</bean>

远程服务:(Spring配置)
在本地服务的基础上,只需做简单配置,即可完成远程化:

将上面的local.xml配置拆分成两份,将服务定义部分放在服务提供方remote-provider.xml,将服务引用部分放在服务消费方remote-consumer.xml。
并在提供方增加暴露服务配置,在消费方增加引用服务配置。
如下:

remote-provider.xml

1
2
3
<bean id=“xxxService” class=“com.xxx.XxxServiceImpl” /> <!-- 和本地服务一样实现远程服务 -->
<dubbo:service interface=“com.xxx.XxxService” ref=“xxxService” /> <!-- 增加暴露远程服务配置 -->

remote-consumer.xml

1
2
3
4
5
<dubbo:reference id=“xxxService” interface=“com.xxx.XxxService” /> <!-- 增加引用远程服务配置 -->
<bean id=“xxxAction” class=“com.xxx.XxxAction”> <!-- 和本地服务一样使用远程服务 -->
<property name=“xxxService” ref=“xxxService” />
</bean>

6.dubbo统一管理控制台

这里写图片描述

今天就对dubbo做简单的说明。后面开始介绍如何整合dubbo2.5-spring4-mybastis3.2-springmvc4-mongodb3.4-redis3.2整合。

#代码地址

小东子的个人技术专栏

Hello World

发表于 2017-01-12   |   字数统计: 78(字)   |   阅读时长: 1(分)

Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.

Quick Start

Create a new post

1
$ hexo new "My New Post"

More info: Writing

Run server

1
$ hexo server

More info: Server

Generate static files

1
$ hexo generate

More info: Generating

Deploy to remote sites

1
$ hexo deploy

More info: Deployment

1…78910
李东

李东

细节决定成败,点滴铸就辉煌

46 文章
18 标签
© 2017 李东
由 Hexo 强力驱动
主题 - NexT.Pisces