springboot(五)读写分离,多个读库,Druid监控--待整理
在spring-cloud-sleuth的META-INF里的spring.factories里设置了一下:
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.cloud.sleuth.autoconfig.TraceEnvironmentPostProcessor
这样,TraceEnvironmentPostProcessor被配置在了ioc容器初始化之前。spring-cloud-sleuth-core包的org.springframework.cloud.sleuth.annotation.TraceEnvironmentPostProcessor.java
public class TraceEnvironmentPostProcessor implements EnvironmentPostProcessor {
private static final String PROPERTY_SOURCE_NAME = "defaultProperties";
private static final String SPRING_AOP_PROXY_TARGET_CLASS = "spring.aop.proxyTargetClass";
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment,
SpringApplication application) {
Map<String, Object> map = new HashMap<String, Object>();
// This doesn't work with all logging systems but it's a useful default so you see
// traces in logs without having to configure it.
if (Boolean.parseBoolean(environment.getProperty("spring.sleuth.enabled", "true"))) {
map.put("logging.pattern.level",
"%5p [${spring.zipkin.service.name:${spring.application.name:-}},%X{X-B3-TraceId:-},%X{X-B3-SpanId:-},%X{X-Span-Export:-}]");
}
// TODO: Remove this in 2.0.x. For compatibility we always set to true
if (!environment.containsProperty(SPRING_AOP_PROXY_TARGET_CLASS)) {
map.put(SPRING_AOP_PROXY_TARGET_CLASS, "true");
}
addOrReplace(environment.getPropertySources(), map);
}
//...
}
在TraceEnvironmentPostProcessor的postProcessEnvironment()方法里保证了两件事情:
1、设置了spring.aop.proxyTargetClass参数为true保证了cglib代理的开启,并加入了日志的追踪打印的模板。
2、而后在配置类TraceAutoConfiguration中生成了Tracer,默认实现为DefaultTraces,作用为正式创建一个工作单元span。
紧随其后的配置类为SleuthAnnotationAutoConfiguration,见spring-cloud-sleuth-core包的org.springframework.cloud.sleuth.annotation.SleuthAnnotationAutoConfiguration.java
@Configuration
@ConditionalOnBean(Tracer.class)
@ConditionalOnProperty(name = "spring.sleuth.annotation.enabled", matchIfMissing = true)
@AutoConfigureAfter(TraceAutoConfiguration.class)
@EnableConfigurationProperties(SleuthAnnotationProperties.class)
public class SleuthAnnotationAutoConfiguration { @Bean
@ConditionalOnMissingBean
SpanCreator spanCreator(Tracer tracer) {
return new DefaultSpanCreator(tracer);
}
//...
@Bean
SleuthAdvisorConfig sleuthAdvisorConfig() {
return new SleuthAdvisorConfig();
}
}
此处根据之前生成的Tracer进一步创建了其包装类SpanCreator,并最重要的是生成了代理的配置类SleuthAdvisorConfig。
class SleuthAdvisorConfig extends AbstractPointcutAdvisor implements BeanFactoryAware {
private Advice advice;
private Pointcut pointcut;
private BeanFactory beanFactory;
@PostConstruct
public void init() {
this.pointcut = buildPointcut();
this.advice = buildAdvice();
if (this.advice instanceof BeanFactoryAware) {
((BeanFactoryAware) this.advice).setBeanFactory(this.beanFactory);
}
}
//...
}
其初始化方法中,完成了切面与切面增强的创建。
其buildPointcut()方法:
private Pointcut buildPointcut() {
return new AnnotationClassOrMethodOrArgsPointcut();
}
/**
* Checks if a class or a method is is annotated with Sleuth related annotations
*/
private final class AnnotationClassOrMethodOrArgsPointcut extends
DynamicMethodMatcherPointcut {
@Override
public boolean matches(Method method, Class<?> targetClass, Object... args) {
return getClassFilter().matches(targetClass);
}
@Override public ClassFilter getClassFilter() {
return new ClassFilter() {
@Override public boolean matches(Class<?> clazz) {
return new AnnotationClassOrMethodFilter(NewSpan.class).matches(clazz) ||
new AnnotationClassOrMethodFilter(ContinueSpan.class).matches(clazz);
}
};
}
}
private final class AnnotationClassOrMethodFilter extends AnnotationClassFilter {
private final AnnotationMethodsResolver methodResolver;
AnnotationClassOrMethodFilter(Class<? extends Annotation> annotationType) {
super(annotationType, true);
this.methodResolver = new AnnotationMethodsResolver(annotationType);
}
@Override
public boolean matches(Class<?> clazz) {
return super.matches(clazz) || this.methodResolver.hasAnnotatedMethods(clazz);
}
}
显而易见,切面的匹配通过目标类是否满足使用了NewSpan或者ContinueSpan注解。
切面的增强则通过buildAdvice来构造Interceptor来通过代理使用invoke()方法来完成生成span的目的。
private Advice buildAdvice() {
return new SleuthInterceptor();
}
class SleuthInterceptor implements IntroductionInterceptor, BeanFactoryAware {
private static final Log logger = LogFactory.getLog(MethodHandles.lookup().lookupClass());
private static final String CLASS_KEY = "class";
private static final String METHOD_KEY = "method";
private BeanFactory beanFactory;
private SpanCreator spanCreator;
private Tracer tracer;
private SpanTagAnnotationHandler spanTagAnnotationHandler;
private ErrorParser errorParser;
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
Method method = invocation.getMethod();
if (method == null) {
return invocation.proceed();
}
Method mostSpecificMethod = AopUtils
.getMostSpecificMethod(method, invocation.getThis().getClass());
NewSpan newSpan = SleuthAnnotationUtils.findAnnotation(mostSpecificMethod, NewSpan.class);
ContinueSpan continueSpan = SleuthAnnotationUtils.findAnnotation(mostSpecificMethod, ContinueSpan.class);
if (newSpan == null && continueSpan == null) {
return invocation.proceed();
}
Span span = tracer().getCurrentSpan();
String log = log(continueSpan);
boolean hasLog = StringUtils.hasText(log);
try {
if (newSpan != null) {
span = spanCreator().createSpan(invocation, newSpan);
}
if (hasLog) {
logEvent(span, log + ".before");
}
spanTagAnnotationHandler().addAnnotatedParameters(invocation);
addTags(invocation, span);
return invocation.proceed();
} catch (Exception e) {
if (logger.isDebugEnabled()) {
logger.debug("Exception occurred while trying to continue the pointcut", e);
}
if (hasLog) {
logEvent(span, log + ".afterFailure");
}
errorParser().parseErrorTags(tracer().getCurrentSpan(), e);
throw e;
} finally {
if (span != null) {
if (hasLog) {
logEvent(span, log + ".after");
}
if (newSpan != null) {
tracer().close(span);
}
}
}
}
//...
}
当通过代理走到此处的invoke()方法说明此时涉及到了与别的服务的调用,需要生成新的spanId,那么就在这里newSpan注解生成新的spanId,如果该方法实现了ContinueSpan注解,那么就在现有的spanId。
如果采用newSpan注解,那么这里需要通过之前的在配置类中生成的tracer的getCurrentSpan()方法获取当前的span。
具体的实现在SpanContextHolder的getCurrentSpan()方法中。
class SpanContextHolder {
private static final Log log = org.apache.commons.logging.LogFactory
.getLog(SpanContextHolder.class);
private static final ThreadLocal<SpanContext> CURRENT_SPAN = new NamedThreadLocal<>(
"Trace Context");
/**
* Get the current span out of the thread context
*/
static Span getCurrentSpan() {
return isTracing() ? CURRENT_SPAN.get().span : null;
}
通过ThreadLoacl来获得当前的span,也就是说,当新的trace请求到来时,可以通过ThreadLoacl来存储。
紧接着通过spanCreator的createSpan()方法来证实获得新的span。
@Override public Span createSpan(MethodInvocation pjp, NewSpan newSpanAnnotation) {
String name = StringUtils.isEmpty(newSpanAnnotation.name()) ?
pjp.getMethod().getName() : newSpanAnnotation.name();
String changedName = SpanNameUtil.toLowerHyphen(name);
if (log.isDebugEnabled()) {
log.debug("For the class [" + pjp.getThis().getClass() + "] method "
+ "[" + pjp.getMethod().getName() + "] will name the span [" + changedName + "]");
}
return createSpan(changedName);
}
private Span createSpan(String name) {
if (this.tracer.isTracing()) {
return this.tracer.createSpan(name, this.tracer.getCurrentSpan());
}
return this.tracer.createSpan(name);
}
Span的名字在注解中的名字和方法名中有限选择前者,而后根据通过tracer的createSpan()来获得span。
@Override
public Span createSpan(String name, Span parent) {
if (parent == null) {
return createSpan(name);
}
return continueSpan(createChild(parent, name));
}
如果此时没有任何span存在,那么直接通过createSpan().
@Override
public Span createSpan(String name) {
return this.createSpan(name, this.defaultSampler);
} @Override
public Span createSpan(String name, Sampler sampler) {
String shortenedName = SpanNameUtil.shorten(name);
Span span;
if (isTracing()) {
span = createChild(getCurrentSpan(), shortenedName);
}
else {
long id = createId();
span = Span.builder().name(shortenedName)
.traceIdHigh(this.traceId128 ? createTraceIdHigh() : 0L)
.traceId(id)
.spanId(id).build();
if (sampler == null) {
sampler = this.defaultSampler;
}
span = sampledSpan(span, sampler);
this.spanLogger.logStartedSpan(null, span);
}
return continueSpan(span);
}
这里可以看到spanId的构造,如果当时是首次构建spanId,那么首先会创建一个traceId,作为本次跟踪流 的id。并与第一次的spanID相同。
但是,此时若是已经存在span,也就是说这并不是第一次,那么就没有必要将traceId设为该次创建的spanId,而是在createChild()方法中,记录当前的traceId为原来收到的traceId,并将收到的spanId作为parentId,并将savedSpan指向原来的span,重新生成一个spanId,并将新的span作为当前的span。
在完成了span的创建后,则会经过sample的判断,此次是否要使用span记录,可以根据配置修改sample的类型,如果采用了百分比类型的,那么可能不会记录下来,完全复制一份span,但是把其exportable属性改为false。
springboot(五)读写分离,多个读库,Druid监控--待整理的更多相关文章
- SpringBoot Mybatis 读写分离配置(山东数漫江湖)
为什么需要读写分离 当项目越来越大和并发越来大的情况下,单个数据库服务器的压力肯定也是越来越大,最终演变成数据库成为性能的瓶颈,而且当数据越来越多时,查询也更加耗费时间,当然数据库数据过大时,可以采用 ...
- springboot实现读写分离(基于Mybatis,mysql)
近日工作任务较轻,有空学习学习技术,遂来研究如果实现读写分离.这里用博客记录下过程,一方面可备日后查看,同时也能分享给大家(网上的资料真的大都是抄来抄去,,还不带格式的,看的真心难受). 完整代码:h ...
- SpringBoot数据库读写分离之基于Docker构建主从数据库同步实例
看了好久的SpringBoot结合MyBatista实现读写,但是一直没有勇气实现他,今天终于接触到了读写分离的东西,读写分离就是讲读操作执行在Slave数据库(从数据库),写操作在Master数据库 ...
- Mysql8.0主从复制搭建,shardingsphere+springboot+mybatis读写分离
1.安装mysql8.0 首先需要在192.167.3.171上安装JDK. 下载mysql安装包,https://dev.mysql.com/downloads/,找到以下页面下载. 下载后放到li ...
- 搭建基于springboot轻量级读写分离开发框架
何为读写分离 读写分离是指对资源的修改和读取进行分离,能解决很多数据库瓶颈,以及代码混乱难以维护等相关的问题,使系统有更好的扩展性,维护性和可用性. 一般会分三个步骤来实现: 一. 主从数据库搭建 信 ...
- 快速掌握mongoDB(五)——读写分离的副本集实现和Sharing介绍
1 mongoDB副本集 1 副本集简介 前边我们介绍都是单机MongoDB的使用,在实际开发中很少会用单机MongoDB,因为使用单机会有数据丢失的风险,同时单台服务器无法做到高可用性(即当服务器宕 ...
- Sharding+SpringBoot+Mybatis 读写分离
基于Sharding JDBC的读写分离 1.引入pom.xml <dependencies> <!-- mybatis --> <dependency> < ...
- SpringBoot 整合 MyCat 实现读写分离
MyCat一个彻底开源的,面向企业应用开发的大数据库集群.基于阿里开源的Cobar产品而研发.能满足数据库数据大量存储:提高了查询性能.文章介绍如何实现MyCat连接MySQL实现主从分离,并集成Sp ...
- 重新学习Mysql数据13:Mysql主从复制,读写分离,分表分库策略与实践
一.MySQL扩展具体的实现方式 随着业务规模的不断扩大,需要选择合适的方案去应对数据规模的增长,以应对逐渐增长的访问压力和数据量. 关于数据库的扩展主要包括:业务拆分.主从复制.读写分离.数据库分库 ...
随机推荐
- LG4980 【模板】Polya定理
题意 题目描述 给定一个$n$个点,$n$条边的环,有$n$种颜色,给每个顶点染色,问有多少种本质不同的染色方案,答案对$10^9+7$取模 注意本题的本质不同,定义为:只需要不能通过旋转与别的染色方 ...
- 下载并安装oracle 11g客户端
之所以一直没安装成功是因为找不到安装程序,找到的那些要么没有安装程序,要么安装时出错或者安装后用不了,反正一大堆问题 先给个oracle客户端的下载链接 https://pan.baidu.com/s ...
- 日志信息log
#include<syslog.h> //建立一个到系统日志的连接 //ident参数指向字符串,syslog()输出的每条信息都会包含这个字符串,这个参数的取值通常是程序名 //log_ ...
- linux前后台任务的切换以及执行暂停
command & 把command命令放到后台执行 ctrl+z 暂停该任务,并且放到后台 jobs 查看任务 bg n 把jobs号码为n的任务放到后台执行 fg n 把jobs号码为n的 ...
- day 61 pymysql
Python3连接MySQL 本文介绍Python3连接MySQL的第三方库--PyMySQL的基本使用. PyMySQL介绍 PyMySQL 是在 Python3.x 版本中用于连接 MySQL 服 ...
- 03关于C的数组指针
一.数组与指针有什么关系 (1)地址 数组名就是一个地址,指向一个数组的开端. 从实验结果我们可以看出前三个地址都是一样的,都是数组的开始地址,而最后一个是数组的第二个元素,因为是整型数据,地址加4. ...
- async await 的 实质 本质
async await 的 实质 就是 用 “状态机” 来 取代 函数层层调用 . async await 的 本质 是 语法糖, 和 提高性能 什么的 没什么关系 . 为了避免理解歧义, 我把 ...
- linux之 awk
简介awk是一个强大的文本分析工具,相对于grep的查找,sed的编辑,awk在其对数据分析并生成报告时,显得尤为强大.简单来说awk就是把文件逐行的读入,以空格为默认分隔符将每行切片,切开的部分再进 ...
- git 项目配置用户名、邮箱的方法
git 项目配置用户名.邮箱的方法 单个仓库里,配置用户名.邮箱: git config user.name "姓名" git config user.email "邮箱 ...
- vue-cli 3.x 使用
vue-cli 3.x 安装:npm install -g @vue/cli vue-cli 3.x 常用命令 vue-cli 3.x 常用命令 命令含义 vue --help vue-cli 3.x ...