本文记录springboot2集成shardingsphere4实现业务层读写分离,其余相关涉及组件 :mybatis-plus,hikari, postgresql , logback ,p6spy
1. pom文件引入shardingsphere4依赖
<dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>sharding-jdbc-spring-boot-starter</artifactId> <version>4.0.1</version> </dependency>
2.shardingsphere4相关配置项
1 spring: 2 shardingsphere: 3 datasource: 4 names: master,slave0,slave1 5 master: 6 type: com.zaxxer.hikari.HikariDataSource 7 driver-class-name: com.p6spy.engine.spy.P6SpyDriver 8 # 连接池配置项 9 jdbc-url: jdbc:p6spy:postgresql://a:5432/bb 10 username: postgres 11 password: postgres 12 autoCommit: false 13 maximum-pool-size: 10 14 validation-timeout: 5_000 15 connection-timeout: 30_000 16 idle-timeout: 600_000 17 max-lifetime: 1_800_000 18 slave0: 19 type: com.zaxxer.hikari.HikariDataSource 20 driver-class-name: com.p6spy.engine.spy.P6SpyDriver 21 jdbc-url: jdbc:p6spy:postgresql://b:5432/bb 22 username: postgres 23 password: postgres 24 autoCommit: false 25 maximum-pool-size: 10 26 validation-timeout: 5_000 27 connection-timeout: 30_000 28 idle-timeout: 600_000 29 max-lifetime: 1_800_000 30 slave1: 31 type: com.zaxxer.hikari.HikariDataSource 32 driver-class-name: com.p6spy.engine.spy.P6SpyDriver 33 jdbc-url: jdbc:p6spy:postgresql://c:5432/bb 34 username: postgres 35 password: postgres 36 autoCommit: false 37 maximum-pool-size: 10 38 validation-timeout: 5_000 39 connection-timeout: 30_000 40 idle-timeout: 600_000 41 max-lifetime: 1_800_000 42 # 读写分离配置项 43 masterslave: 44 load-balance-algorithm-type: round_robin # random 45 master-data-source-name: master 46 slave-data-source-names: slave0,slave1 47 name: ms 48 props: 49 sql: 50 show: true
3.p6spy相关配置项 (resources下创建spy.properties文件,内容如下)
1 #开启模块sql记录和长时sql记录 2 module.log=com.p6spy.engine.logging.P6LogFactory,com.p6spy.engine.outage.P6OutageFactory 3 logMessageFormat=com.p6spy.engine.spy.appender.CustomLineFormat 4 #自定义sql输出格式 5 customLogMessageFormat=%(currentTime) | TIME\uFF1A %(executionTime) ms | SQL\uFF1A %(sql) 6 #日志输出方式 7 appender=com.p6spy.engine.spy.appender.Slf4JLogger 8 excludecategories=info,debug,result,resultset 9 deregisterdrivers=true 10 dateformat=yyyy-MM-dd HH:mm:ss 11 driverlist=org.postgresql.Driver 12 #开启长时sql记录 13 outagedetection=true 14 #触发长时记录时限 15 outagedetectioninterval=2
4.logback相关配置(resource文件夹下创建logback-spring.xml内容如下)
1 <?xml version="1.0" encoding="UTF-8"?> 2 <configuration> 3 4 <!--日志格式应用spring boot默认的格式,也可以自己更改--> 5 <include resource="org/springframework/boot/logging/logback/defaults.xml"/> 6 7 <!--定义日志存放的位置,默认存放在项目启动的相对路径的目录--> 8 <springProperty scope="context" name="LOG_PATH" source="log.path" defaultValue="log"/> 9 <springProperty scope="context" name="ZONE" source="quick.zone" defaultValue="default"/> 10 <springProperty scope="context" name="APP_NAME" source="spring.application.name" defaultValue="app"/> 11 <springProperty scope="context" name="LOGSTASH_URL" source="logstash.url" defaultValue="localhost:4560"/> 12 13 <!-- ****************************************************************************************** --> 14 <!-- ****************************** 开发环境日志 ************************************ --> 15 <!-- ****************************************************************************************** --> 16 <springProfile name="local"> 17 <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> 18 <encoder> 19 <pattern>${CONSOLE_LOG_PATTERN}</pattern> 20 <charset>utf-8</charset> 21 </encoder> 22 </appender> 23 24 <!-- 日志记录器,日期滚动记录 --> 25 <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> 26 27 <!-- 正在记录的日志文件的路径及文件名 --> 28 <file>${LOG_PATH}/${ZONE}-${APP_NAME}-local.log</file> 29 30 <!-- 日志记录器的滚动策略,按日期,按大小记录 --> 31 <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> 32 33 <!-- 归档的日志文件的路径,%d{yyyy-MM-dd}指定日期格式,%i指定索引 --> 34 <fileNamePattern>${LOG_PATH}/all/${ZONE}-${APP_NAME}-%d{yyyy-MM-dd}.%i-local.log</fileNamePattern> 35 36 <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> 37 <maxFileSize>10MB</maxFileSize> 38 </timeBasedFileNamingAndTriggeringPolicy> 39 </rollingPolicy> 40 41 <!-- 追加方式记录日志 --> 42 <append>true</append> 43 44 <!-- 日志文件的格式 --> 45 <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> 46 <pattern>${FILE_LOG_PATTERN}</pattern> 47 <charset>utf-8</charset> 48 </encoder> 49 </appender> 50 51 <!--默认所有的包以warn--> 52 <root level="warn"> 53 <appender-ref ref="STDOUT"/> 54 <appender-ref ref="FILE"/> 55 </root> 56 57 <!--ShardingSphere打印sql用--> 58 <logger name="ShardingSphere-SQL" level="info"/> 59 <!--p6spy打印sql--> 60 <logger name="p6spy" level="info"/> 61 <!--屏蔽getRowIdLifetime未实作警告--> 62 <logger name="com.zaxxer.hikari.pool.ProxyConnection" level="error"/> 63 64 <!--各个服务的包在本地执行的时候,打开debug模式--> 65 <logger name="com.myproject" level="debug" additivity="false"> 66 <appender-ref ref="STDOUT"/> 67 <appender-ref ref="FILE"/> 68 </logger> 69 </springProfile> 70 71 <!-- ********************************************************************************************** --> 72 <!-- **** 放到服务器上不管在什么环境都只在文件记录日志,控制台(catalina.out)打印logback捕获不到的日志 **** --> 73 <!-- ********************************************************************************************** --> 74 <springProfile name="!local"> 75 76 <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> 77 <encoder> 78 <pattern>${CONSOLE_LOG_PATTERN}</pattern> 79 <charset>utf-8</charset> 80 </encoder> 81 </appender> 82 83 <!-- 日志记录器,日期滚动记录 --> 84 <appender name="FILE_ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender"> 85 86 <!-- 正在记录的日志文件的路径及文件名 --> 87 <file>${LOG_PATH}/quick/${ZONE}-${APP_NAME}-log-error.log</file> 88 89 <!-- 日志记录器的滚动策略,按日期,按大小记录 --> 90 <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> 91 92 <!-- 归档的日志文件的路径,%d{yyyy-MM-dd}指定日期格式,%i指定索引 --> 93 <fileNamePattern>${LOG_PATH}/quick/${ZONE}-${APP_NAME}-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern> 94 95 <!-- 除按日志记录之外,还配置了日志文件不能超过10M,若超过10M,日志文件会以索引0开始, 96 命名日志文件,例如log-error-2013-12-21.0.log --> 97 <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> 98 <maxFileSize>10MB</maxFileSize> 99 </timeBasedFileNamingAndTriggeringPolicy> 100 </rollingPolicy> 101 102 <!-- 追加方式记录日志 --> 103 <append>true</append> 104 105 <!-- 日志文件的格式 --> 106 <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> 107 <pattern>${FILE_LOG_PATTERN}</pattern> 108 <charset>utf-8</charset> 109 </encoder> 110 111 <!-- 此日志文件只记录error级别的 --> 112 <filter class="ch.qos.logback.classic.filter.LevelFilter"> 113 <level>error</level> 114 <onMatch>ACCEPT</onMatch> 115 <onMismatch>DENY</onMismatch> 116 </filter> 117 </appender> 118 119 <!-- 日志记录器,日期滚动记录 --> 120 <appender name="FILE_ALL" class="ch.qos.logback.core.rolling.RollingFileAppender"> 121 122 <!-- 正在记录的日志文件的路径及文件名 --> 123 <file>${LOG_PATH}/quick/${ZONE}-${APP_NAME}-log-all.log</file> 124 125 <!-- 日志记录器的滚动策略,按日期,按大小记录 --> 126 <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> 127 128 <!-- 归档的日志文件的路径,%d{yyyy-MM-dd}指定日期格式,%i指定索引 --> 129 <fileNamePattern>${LOG_PATH}/quick/${ZONE}-${APP_NAME}-all-%d{yyyy-MM-dd}.%i.log</fileNamePattern> 130 131 <!-- 除按日志记录之外,还配置了日志文件不能超过10M,若超过10M,日志文件会以索引0开始, 132 命名日志文件,例如log-error-2013-12-21.0.log --> 133 <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> 134 <maxFileSize>10MB</maxFileSize> 135 </timeBasedFileNamingAndTriggeringPolicy> 136 </rollingPolicy> 137 138 <!-- 追加方式记录日志 --> 139 <append>true</append> 140 141 <!-- 日志文件的格式 --> 142 <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> 143 <pattern>${FILE_LOG_PATTERN}</pattern> 144 <charset>utf-8</charset> 145 </encoder> 146 </appender> 147 148 <!-- 日志记录器,日期滚动记录 纪录TAM日志--> 149 <appender name="FILE_TAM" class="ch.qos.logback.core.rolling.RollingFileAppender"> 150 151 <!-- 正在记录的日志文件的路径及文件名 --> 152 <file>${LOG_PATH}/quick/${ZONE}-${APP_NAME}-log-tam.log</file> 153 154 <!-- 日志记录器的滚动策略,按日期,按大小记录 --> 155 <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> 156 157 <!-- 归档的日志文件的路径,%d{yyyy-MM-dd}指定日期格式,%i指定索引 --> 158 <fileNamePattern>${LOG_PATH}/quick/${ZONE}-${APP_NAME}-tam-%d{yyyy-MM-dd}.%i.log</fileNamePattern> 159 160 <!-- 除按日志记录之外,还配置了日志文件不能超过10M,若超过10M,日志文件会以索引0开始, 161 命名日志文件,例如log-error-2013-12-21.0.log --> 162 <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> 163 <maxFileSize>10MB</maxFileSize> 164 </timeBasedFileNamingAndTriggeringPolicy> 165 </rollingPolicy> 166 167 <!-- 追加方式记录日志 --> 168 <append>true</append> 169 170 <!-- 日志文件的格式 --> 171 <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> 172 <pattern>${FILE_LOG_PATTERN}</pattern> 173 <charset>utf-8</charset> 174 </encoder> 175 <!-- 此日志文件只记录error级别的 --> 176 <filter class="ch.qos.logback.classic.filter.LevelFilter"> 177 <level>error</level> 178 <onMatch>ACCEPT</onMatch> 179 <onMismatch>DENY</onMismatch> 180 </filter> 181 </appender> 182 183 <!-- 日志记录器,日期滚动记录 纪录AUDITLOG日志--> 184 <appender name="FILE_AUDIT" class="ch.qos.logback.core.rolling.RollingFileAppender"> 185 186 <!-- 正在记录的日志文件的路径及文件名 --> 187 <file>${LOG_PATH}/auditlog/${ZONE}-${APP_NAME}-log-auditLog.log</file> 188 189 <!-- 日志记录器的滚动策略,按日期,按大小记录 --> 190 <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> 191 192 <!-- 归档的日志文件的路径,%d{yyyy-MM-dd}指定日期格式,%i指定索引 --> 193 <fileNamePattern>${LOG_PATH}/auditlog/${ZONE}-${APP_NAME}-auditlog-%d{yyyy-MM-dd}.%i.log</fileNamePattern> 194 195 <!-- 除按日志记录之外,还配置了日志文件不能超过10M,若超过10M,日志文件会以索引0开始, 196 命名日志文件,例如log-error-2013-12-21.0.log --> 197 <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> 198 <maxFileSize>10MB</maxFileSize> 199 </timeBasedFileNamingAndTriggeringPolicy> 200 </rollingPolicy> 201 202 <!-- 追加方式记录日志 --> 203 <append>true</append> 204 205 <!-- 日志文件的格式 --> 206 <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> 207 <pattern>${FILE_LOG_PATTERN}</pattern> 208 <charset>utf-8</charset> 209 </encoder> 210 <!-- 此日志文件只记录info级别的 --> 211 <filter class="ch.qos.logback.classic.filter.LevelFilter"> 212 <level>info</level> 213 <onMatch>ACCEPT</onMatch> 214 <onMismatch>DENY</onMismatch> 215 </filter> 216 </appender> 217 218 <appender name="LOGSTASH" class="net.logstash.logback.appender.LogstashTcpSocketAppender"> 219 <destination>${LOGSTASH_URL}</destination> 220 <encoder charset="UTF-8" class="net.logstash.logback.encoder.LogstashEncoder" /> 221 </appender> 222 223 224 <!--记录到文件时,记录两类一类是error日志,一个是所有日志--> 225 <root level="warn"> 226 <appender-ref ref="STDOUT"/> 227 <appender-ref ref="FILE_ERROR"/> 228 <appender-ref ref="FILE_ALL"/> 229 </root> 230 231 <logger name="com.myproject" level="info"/> 232 <!--ShardingSphere打印sql用--> 233 <logger name="ShardingSphere-SQL" level="info"/> 234 <!--p6spy打印sql--> 235 <logger name="p6spy" level="info"/> 236 <!--屏蔽getRowIdLifetime未实作警告--> 237 <logger name="com.zaxxer.hikari.pool.ProxyConnection" level="error"/> 238 </springProfile> 239 240 </configuration>
5.集成结果
通过以上日志观察发现已经实现了业务层面读写分离和读库的轮询负载策略
通过以上日志发现已经实现了线上环境的完整sql记录,至此已达到预期效果,集成完毕
6.问题总结
1.从数据层方面shardingsphere4并没有实现主从库的数据同步,在其他方案的数据同步过程中主从数据不一致导致的数据差异是否满足应用场景还需具体评估.
2.本实例数据库采用postgres,目前最新版本的postgres数据库驱动也没有实现getRowIdLifetime()方法,shardingsphere4缓存数据源元数据的时候会去调用该方法,
驱动程序会抛出一个方法未实作的异常从下面代码可见框架捕获后不做处理并缓存该项为UNSUPPORTED,但是因为实例集成hikari连接池,hikar的ProxyConnection类会抛出一个一个警告如下图二,看起来像是项目启动报错,但实际并不影响,程序运行,因此在输出日志中将项屏蔽,具体配置在上面logback中
警告:
3.logback的配置中root节点默认开启warm级别日志,要想支撑 shardingsphere 和p6spy记录sql需要单独配置相应节点,开放对应日志级别,因 shardingsphere 打印出的不完整sql,除了查看数据库路由外并没有太大用处,而本项目当前并不关心数据库路由,因此将对应配置项设为false,并集成p6spy完成完整的sql记录
4.配置项中的数据库url连接项名称视具体连接池可能会有不同,本实例用连接池该名为jdbc-url,有些连接池叫url
7.部分源码分析
该主键的入口类,实现了EnvironmentAware接口,在DataSoureAutoConfiguration之前被执行对dataSource经常了包装处理用于支撑该框架的功能,下图为datasource实例化过程,该方法的第一个入参dataSourceClassName为配置项的type所以对连接池的配置项需要和type放在同一级,如文章开始配置项所示.
本实例只关心主从分离,其他源码部分相关类:
以上,为全部内容,后期发现问题再做补充.