2024年1月

场景描述:

前一阵子接手的新项目中需要使用2个数据源。

一个叫行云数据库,一个叫OceanBase数据库。

就是说,我有时候查询要查行云的数据,有时候查询要查 OceanBase 的数据,咋办?

废话不多说, 下面以mysql为例,开整。

一、环境依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.3.0</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.28</version>
</dependency>

二、实现思路

在进行下一步之前,我们必须要知道
SpringBoot 自动配置的相关原理
,因为之前,我们大多是单数据源。

# 配置mysql数据库
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/blog?serverTimezone=Asia/Shanghai&allowMultiQueries=true
    username: root
    password: root

从前只是会用,会配置,甚至都是别人给配置的自己直接拿来用。

但是要搞懂
动态数据源
就必须要先搞懂
自动配置

0.o?让我看看怎么个事儿之SpringBoot自动配置

现在我们要实现
多数据源
,并且可以
自动切换

也就是我 A 查询连接的是行云数据库。

而我 B 查询却连接的是 OceanBase 数据库。

怎么办?

那第一个肯定就不能再使用DataSourceAutoConfigurtation了。

我直接反手一个 exclude 。

@EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class})

然后呢?

Spring boot想得很周到,它提供了AbstractRoutingDataSource 抽象类。

这个类能
根据用户定义的规则选择当前的数据源


有同学要问了:

AbstractRoutingDataSource 是什么东西?

AbstractRoutingDataSource 是一个抽象类。

它继承了 AbstractDataSource 抽象类。

而 AbstractDataSource 实现了 DataSource 接口。

也就是说:
AbstractRoutingDataSource 他实际上就是一个DataSource 。

AbstractRoutingDataSource 中有两个关键方法。

setTargetDataSources(Map<Object, Object> targetDataSources)

第一个方法见名知意,
设置目标数据源
(复数也就是多个)。

protected abstract Object determineCurrentLookupKey();

第二个方法是
仅有的一个
抽象方法,需要开发者具体实现,也可以见名知意,
决定当前使用(目标数据源中的)哪个

我们要做的是什么?

我们准备 2 个数据源,全部配置好放进 Map<Object, Object> targetDataSources 里备用。

我们继承 AbstractRoutingDataSource 并实现抽象方法 determineCurrentLookupKey() 。

当我们继承AbstractRoutingDataSource时我们自身也是一个数据源。

对于数据源必然有连接数据库的动作。

只是AbstractRoutingDataSource的getConnection()方法里实际是调用determineTargetDataSource()返回的数据源的getConnection()方法。

这样我们可以在执行查询之前,设置使用的数据源。

实现可动态路由的数据源,在每次数据库查询操作前执行。

它的抽象方法 determineCurrentLookupKey() 决定使用哪个数据源。

我知道肯定有人看不懂要上嘴脸了:

Talk is cheap, show me the FUCKING code !!!

2.1 配置文件

spring:
  datasource:
    dynamic:
      db1:
        driverClassName: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/db1?serverTimezone=Asia/Shanghai&allowMultiQueries=true
        username: root
        password: root
      db2:
        driverClassName: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/db2?serverTimezone=Asia/Shanghai&allowMultiQueries=true
        username: root
        password: root

2.2 自定义动态数据源

DynamicDataSource继承AbstractRoutingDataSource。

public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceContextHolder.getDataSource();
    }
}

这里的determineCurrentLookupKey方法,需要返回一个数据源。

又有同学问了:

DynamicDataSourceContextHolder 又是什么东西?

public class DynamicDataSourceContextHolder {
    public static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();

    public static String getDataSource() {
        return CONTEXT_HOLDER.get();
    }

    public static void setDataSource(String dataSource) {
        CONTEXT_HOLDER.set(dataSource);
    }

    public static void clearDataSource() {
        CONTEXT_HOLDER.remove();
    }
}

看到 Context 应该很熟悉了,跟程序上下文有关。

它的作用就是你查询数据库的时候用哪个数据源,就 setDataSource 哪个。

还有点懵?没事,继续往下看。

@Configuration
@EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class})
public class DynamicDataSourceConfig {
    @Bean("db1")
    @ConfigurationProperties(prefix = "spring.datasource.dynamic.db1")
    public DataSource db1() {
        return DataSourceBuilder.create().build();
    }
    @Bean("db2")
    @ConfigurationProperties(prefix = "spring.datasource.dynamic.db2")
    public DataSource db2() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    @Primary
    public DataSource dataSource() {
        Map<Object, Object> dataSourceMap = new HashMap<>(2);
        dataSourceMap.put("db1", db1());
        dataSourceMap.put("db2", db2());

        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        // 需要设置的多数据源
        dynamicDataSource.setTargetDataSources(dataSourceMap);
        // 主数据源/默认数据源
        dynamicDataSource.setDefaultTargetDataSource(db1());
        return dynamicDataSource;
    }
}

这是比较常见的自定义数据源配置了。

可以看到一共注册了3个数据源。

但是最后一个DynamicDataSource有 @Primary 注解,它表明这个数据源优先级更高。

DynamicDataSource中设置了dataSourceMap,也就是保存了 db1 和 db2。

以上我们动态数据源配置的工作就做完了。

我们以实际查询中的操作完整捋一遍这当中到底发生了什么!

@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;
    /**
     *使用db2数据源
     */
    public void saveUser(UserDto userDto) {
        DynamicDatasourceHolder.setDataSource("db2");
        User user = new User();
        user.setUName(userDto.getName());
        userMapper.insert(user);
        DynamicDatasourceHolder.removeDataSource("db2");
    }
}

首先,DynamicDatasourceHolder 设置了数据源 db2 。

CONTEXT_HOLDER 中就保存了一个 “db2” 字符串。

userMapper 进行数据库操作之前,MyBatis 框架替我们做了一些事。

其中一件事是
获取数据库连接

MyBatis 就在想:我得找个 DataSource ,因为DataSource 有getConnection() 方法。

谁是 DataSource ?

继承了 AbstractRoutingDataSource 的 DynamicDataSource 大声喊到:我是 !

开始连接数据库!

@Override
public Connection getConnection() throws SQLException {
	return determineTargetDataSource().getConnection();
}

连接哪个?

protected DataSource determineTargetDataSource() {
    // 哥,看这一行!
   Object lookupKey = determineCurrentLookupKey();
   DataSource dataSource = this.resolvedDataSources.get(lookupKey);
   return dataSource;
}

连接这个!

@Override
protected Object determineCurrentLookupKey() {
    return DynamicDataSourceContextHolder.getDataSource();
}

连接完成!

insert 完成!

removeDataSource("db2") !

每次这样用嫌麻烦?

办他!

三、动态数据源注解@DS

看这部分之前需要一些前置知识点:

Java注解,看完就会用

师爷,翻译翻译什么叫AOP

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface DS {
    String value() default "db1";
}
@Component
@Aspect
public class DynamicDataSourceAspect {

    @Pointcut("@annotation(com.example.demo.annotation.DS)")
    public void dynamicDataSourcePointCut() {

    }

    @Around("dynamicDataSourcePointCut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        String dataSourceKey = "db1";
        // 类上的注解
        Class<?> aClass = joinPoint.getTarget().getClass();
        DS annotation = aClass.getAnnotation(DS.class);

        // 方法上的注解
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        DS annotationMethod = signature.getMethod().getAnnotation(DS.class);

        if (Objects.nonNull(annotationMethod)) {
            dataSourceKey = annotationMethod.value();
        } else {
            dataSourceKey = annotation.value();
        }
        // 设置数据源
        DynamicDataSourceContextHolder.setDataSource(dataSourceKey);
        try {
             return joinPoint.proceed();
        }finally {
            DynamicDataSourceContextHolder.clearDataSource();
        }
    }
}

具体我就不讲解了,自己对照前两篇文章研究一下吧。


最后再补充一下,动态数据源实际上有现成的依赖包可以用,可以参考使用,地址如下:

https://github.com/baomidou/dynamic-datasource


往期推荐:


师爷,翻译翻译什么叫AOP


0.o?让我看看怎么个事儿之SpringBoot自动配置


终于搞懂动态代理了!


学会@ConfigurationProperties月薪过三千


不是银趴~是@Import!


@Conditional+@Configuration有没有搞头?

本文深入研究了ANN的基本概念、发展背景、应用场景以及与人脑神经网络的关系。

关注TechLead,分享AI全维度知识。作者拥有10+年互联网服务架构、AI产品研发经验、团队管理经验,同济本复旦硕,复旦机器人智能实验室成员,阿里云认证的资深架构师,项目管理专业人士,上亿营收AI产品研发负责人

file

一、引言

file

ANN简介

人工神经网络(Artificial Neural Networks, ANN)是模仿生物神经网络(如大脑)的结构和功能的数学模型或计算模型,用于估计或逼近那些无法用传统算法精确表示的复杂函数关系。ANN是由大量互联互通的人工神经元组成,通过学习过程调整神经元间的连接权重,以实现特定的信号处理或行为模式。

研究背景与应用领域

发展背景

ANN的研究始于20世纪40年代,最初旨在通过机器模拟人脑的思考和认知过程。1950年代,首个简单的神经网络模型(感知机)被提出,但由于技术和理论限制,其发展一度陷入停滞。随着计算能力的提升和学习算法的进步,尤其是1980年代反向传播算法的提出,ANN开始快速发展,并逐渐成为深度学习和人工智能研究的核心。

应用场景

ANN在多个领域展现出强大的应用潜力和实际效果。其主要应用场景包括:

  1. 图像处理与计算机视觉
    :如图像识别、物体检测、图像分割等。
  2. 自然语言处理
    :包括语言翻译、情感分析、文本生成等。
  3. 语音识别与生成
    :例如智能助手中的语音交互系统。
  4. 金融领域
    :如股市预测、风险评估、算法交易等。
  5. 医疗诊断
    :辅助诊断、病理图像分析、基因数据分析等。
  6. 游戏和娱乐
    :游戏AI的开发、内容生成等。
  7. 自动驾驶技术
    :车辆环境感知、决策制定等。

ANN的广泛应用归功于其强大的学习能力和适应性,能够处理和分析大量复杂的数据,从而在上述领域发挥重要作用。

二、ANN背后的人脑神经网络

file

人脑神经网络的专业描述

人脑神经网络是由大约860亿个神经元组成的复杂网络。每个神经元通过突触与其他神经元相连,形成了一张巨大的、高度动态的网络。神经元通过电化学信号进行交流,这些信号在神经网络中传递和处理信息。关键在于,神经元之间的连接强度(突触权重)是可变的,这种可塑性是学习和记忆的生物学基础。

神经元的结构

  • 细胞体
    :包含核心的生物化学机制。
  • 树突
    :接收来自其他神经元的信号。
  • 轴突
    :将信号传输到其他神经元。
  • 突触
    :神经元间的连接点,通过神经递质实现信号传递。

信号处理

  • 激活
    :当一个神经元接收到足够的激励信号时,会触发动作电位,向其他神经元传递信息。
  • 抑制
    :某些信号会减弱神经元的活动,降低其发放动作电位的概率。

思考和认知过程

人类的思考和认知过程是极度复杂的,涉及到记忆、注意力、感知、语言等多个方面。大脑通过高效的信息处理机制,能够快速响应外部刺激、做出决策并学习新知识。这一过程在很大程度上依赖于神经元间连接的可塑性和网络结构的动态调整。

认知功能的实现

  • 学习与记忆
    :通过改变突触的强度(突触可塑性)实现。
  • 决策制定
    :大脑在处理信息时会评估不同行动的潜在后果。
  • 感知与处理
    :对外部信息(如视觉、听觉信号)进行编码和解析。

对机器学习算法的启示

人脑神经网络对ANN的设计和发展有深远的影响。机器学习算法,尤其是深度学习,从人脑中汲取灵感,尝试模拟以下几个方面:

  1. 分层处理
    :大脑通过不同层级处理不同复杂度的信息,ANN中的多层架构正是此思想的体现。
  2. 权重调整
    :与神经元突触的可塑性相似,ANN在学习过程中通过调整权重来提升性能。
  3. 并行处理
    :大脑能同时处理大量信息,类似地,ANN也采用并行计算来提高效率。
  4. 非线性激活
    :神经元的激活函数在ANN中以非线性激活函数的形式体现,增强模型的表达能力。

三、ANN的研究进展

人工神经网络(ANN)的发展历程可以划分为几个重要阶段,每个阶段都有其里程碑式的技术和理论贡献。以下是这些阶段的详细描述及其典型的技术代表。

初始阶段:感知机与早期神经网络

发展背景

  • 时间线
    :1940年代末至1960年代。
  • 关键贡献
    :这一时期的研究聚焦于模拟神经元的简单模型,试图理解大脑如何处理信息。

技术代表

  • 感知机(Perceptron)
    :由Frank Rosenblatt在1957年提出,是最早的神经网络模型之一,能够执行简单的分类任务。

发展阶段:反向传播与多层网络

发展背景

  • 时间线
    :1980年代。
  • 关键贡献
    :引入多层结构和反向传播算法,大大提升了神经网络的学习能力和复杂性。

技术代表

  • 反向传播算法(Backpropagation)
    :这一算法能够有效地训练多层前馈神经网络,由Rumelhart, Hinton和Williams于1986年提出。

扩展阶段:卷积神经网络与循环神经网络

发展背景

  • 时间线
    :1990年代至2000年代。
  • 关键贡献
    :引入了专门针对特定任务(如图像和序列数据处理)的网络架构。

技术代表

  • 卷积神经网络(CNN)
    :由Yann LeCun等人在1990年代发展,特别适用于图像处理任务。
  • 循环神经网络(RNN)
    :适用于处理序列数据,如时间序列分析和自然语言处理。

革新阶段:深度学习与大数据

发展背景

  • 时间线
    :21世纪初至今。
  • 关键贡献
    :利用大规模数据和强大的计算能力,深度学习模型在多个领域取得了显著成就。

技术代表

  • 深度学习框架
    :如TensorFlow, PyTorch等,使得构建复杂的神经网络模型变得更加容易。
  • 长短期记忆网络(LSTM)
    :一种特殊的RNN架构,有效解决了传统RNN中的梯度消失问题。
  • 生成对抗网络(GAN)
    :由Ian Goodfellow于2014年提出,用于生成逼真的图像等数据。

当前与未来趋势

发展背景

  • 时间线
    :当前至未来。
  • 关键贡献
    :更加注重网络的可解释性、效率以及在复杂任务中的应用。

技术代表

  • 注意力机制与Transformer模型
    :在自然语言处理领域尤其有效,如BERT和GPT系列。
  • 自监督学习
    :减少对标记数据的依赖,通过学习数据中的内在结构来提高学习效率。

四、脑科学是否是人工智能的未来钥匙?

在探索人工智能(AI)的未来发展路径时,脑科学作为一个重要的参考领域,提供了对智能本质的深刻洞见。

脑科学与人工智能的交叉

相似性与启示

  • 信息处理机制
    :人脑通过神经元网络高效处理信息,这为ANN的结构提供了启示。
  • 学习与适应
    :大脑展现出的学习和适应能力激发了深度学习算法的发展。
  • 决策与问题解决
    :人类决策过程中的复杂性和灵活性为AI系统设计提供了参考。

差异与挑战

  • 复杂性与规模
    :人脑的复杂性远超当前任何AI系统。
  • 意识与自主性
    :AI缺乏类似人类的意识和自主性,这是目前AI无法触及的领域。
  • 情感与社会交互
    :人类的情感和社会交互能力是AI难以完全复制的部分。

脑科学对AI的具体贡献

算法与架构创新

  • 脑科学的研究可以启发新的算法,例如模拟大脑的信息编码和处理方式。
  • 神经可塑性的原理可以用于改进神经网络的学习机制。

解释性与可靠性

  • 通过研究人脑处理信息的方式,可以提高AI的解释性。
  • 模拟人类的决策过程有助于提高AI系统的可靠性和鲁棒性。

人机交互

  • 理解人类的认知和感知过程有助于改进人机交互。

最新的研究

脑科学与人工智能(AI)之间的关系日益显现为研究的重要领域,它为理解和发展AI技术提供了深刻的见解和潜在的突破。最近的研究强调了这种关系的几个关键方面,突出了脑科学作为推动AI发展的关键因素的潜力。

  1. AI与人脑中的记忆形成机制
    :一个重要的研究领域是AI与人脑记忆形成机制之间的惊人相似性。这种相似性不仅是表面的,它延伸到了记忆巩固中的非线性这一关键方面,这对于人类般的记忆处理在AI系统中至关重要。人类认知机制与AI设计之间的这种融合不仅对创造高效和高性能的AI系统充满希望,而且还通过AI模型为大脑的运作提供了宝贵的窗口。

  2. AI在神经科学研究中的应用
    :AI正在成为神经科学研究中不可或缺的工具。它帮助神经科学家测试假设并分析复杂的神经影像数据。用于执行智能任务的AI模型正在引领对大脑如何管理类似过程的新概念和理解。这种互利关系表明了一种相互促进的关系,其中一个领域的进步推动了另一个领域的发展。

  3. 脑启发的AI构建
    :将脑启发的非线性纳入AI构建标志着模拟人类记忆过程的重要进步。目前这一领域的研究重点是如何将人类认知机制启发和塑造AI设计。目标不仅是复制,还要基于人脑复杂工作的理解和创新。

  4. 未来方向
    :展望未来,AI与脑科学的交汇拥有巨大的潜力。它可以改变我们对大脑健康的理解,对抗疾病,并开发受人类智能的多样性和深度启发的AI技术。神经科学原理与AI研究的持续融合可能会导致更复杂、适应性更强、效率更高的AI系统。

关注TechLead,分享AI全维度知识。作者拥有10+年互联网服务架构、AI产品研发经验、团队管理经验,同济本复旦硕,复旦机器人智能实验室成员,阿里云认证的资深架构师,项目管理专业人士,上亿营收AI产品研发负责人
如有帮助,请多关注
TeahLead KrisChang,10+年的互联网和人工智能从业经验,10年+技术和业务团队管理经验,同济软件工程本科,复旦工程管理硕士,阿里云认证云服务资深架构师,上亿营收AI产品业务负责人。

前言✨

想学习WPF,但是看视频教程觉得太耗时间,直接看文档又觉得似懂非懂,因此想通过看Demo代码+文档的方式进行学习。

准备✨

微软官方其实提供了WPF的一些Demo,地址为:
microsoft/WPF-Samples: Repository for WPF related samples (github.com)

将其克隆到本地,有很多的Demo代码:

image-20240129141054183

新建一个空白解决方案:

image-20240129141201234

添加现有项目:

image-20240129141238024

选择Data Binding 中的 SimpleBinding:

image-20240129141537501

项目文件如下所示:

image-20240129141951318

查看SimpleBinding这个Demo的效果:

SimpleBindingDemo的效果

学习这个Demo✨

数据绑定的概念

首先需要知道数据绑定的基本概念,数据绑定是在应用 UI 与其显示的数据之间建立连接的过程。 如果绑定具有正确的设置,并且数据提供适当的通知,则在数据更改其值时,绑定到该数据的元素会自动反映更改。 数据绑定还意味着,如果元素中数据的外部表示形式发生更改,则基础数据可以自动进行更新以反映更改。 例如,如果用户编辑
TextBox
元素中的值,则基础数据值会自动更新以反映该更改。

现在再来看一下,数据绑定的模型图:

image-20240129142805099

现在我们根据这张模型图,学习这个Demo。

这个模型图表示数据绑定至少需要三个东西:

  • 绑定目标
  • 绑定
  • 绑定源

现在我们分别看看这个demo里面,这三个东西在哪?

绑定目标

我们发现这个demo的绑定目标是元素TextBox的Text属性与TextBlock的Text属性。

绑定

image-20240129143549091

这是一个C#类,查找在该Demo中,哪里出现了Binding?

发现在MainWindow.xaml中出现了Binding,有两处地方,分别是:

  <TextBox>
      <TextBox.Text>
          <Binding Source="{StaticResource MyDataSource}" Path="PersonName"
 UpdateSourceTrigger="PropertyChanged"/>
      </TextBox.Text>
  </TextBox>

 <TextBlock Text="{Binding Source={StaticResource MyDataSource}, Path=PersonName}"/>

这两处都是在 XAML 中声明绑定。

 <TextBox>
      <TextBox.Text>
          <Binding Source="{StaticResource MyDataSource}" Path="PersonName"
 UpdateSourceTrigger="PropertyChanged"/>
      </TextBox.Text>
  </TextBox>

这种使用的是
对象元素语法

<TextBlock Text="{Binding Source={StaticResource MyDataSource}, Path=PersonName}"/>

这种使用的是
标记扩展

从这里我们接触到了
Binding
类的三个属性,
Source

Path

UpdateSourceTrigger

那它们分别是什么意思呢?

先来看下
Source

image-20240129144554369

我们发现这个demo里,
Source="{StaticResource MyDataSource}

StaticResource MyDataSource
又是什么呢?

在Window.Resources标签下有一行代码:

image-20240129144901173

<local:Person x:Key="MyDataSource" PersonName="Joe"/>

<Window.Resources>
标签在XAML中用于定义可以在整个窗口中重用的资源。这些资源可以是各种类型,包括样式、数据源、数据模板、颜色、画刷等。

<local:Person x:Key="MyDataSource" PersonName="Joe"/>
这行代码创建了一个Person对象,并将其命名为"MyDataSource",该对象的PersonName属性设置为"Joe",这样你就可以在窗口的其他地方通过这个键来引用这个对象。

因此
Source={StaticResource MyDataSource}
的意思就是将Binding对象的Source属性设置为键为"MyDataSource"的资源,在这里也就是一个Person对象。

再来看看
Path

image-20240129150048051

表示绑定源属性的路径。

Path="PersonName"
表示绑定源是刚刚那个Person对象的PersonName属性。

最后再看看
UpdateSourceTrigger

image-20240129150426493

这个属性可能就会难理解一点。

它表示当绑定目标怎么样时,绑定源的值应该被更新。

它的类型为枚举类型,有以下几个值:

含义
Default 绑定目标属性的默认
UpdateSourceTrigger
值。 大多数依赖属性的默认值为
PropertyChanged
,而
Text
属性的默认值为
LostFocus
Explicit 仅在调用
UpdateSource()
方法时更新绑定源。
LostFocus 每当绑定目标元素失去焦点时,都会更新绑定源。
PropertyChanged 每当绑定目标属性发生更改时,都会更新绑定源。
 <Label>Enter a Name:</Label>
 <TextBox>
     <TextBox.Text>
         <Binding Source="{StaticResource MyDataSource}" Path="PersonName"
UpdateSourceTrigger="PropertyChanged"/>
     </TextBox.Text>
 </TextBox>

我们会发现在这个demo中,
TextBox.Text
设置了
UpdateSourceTrigger
属性。

这是因为大多数依赖项属性的默认值为
PropertyChanged
,而
Text
属性的默认值为
LostFocus
。在这个demo中,我们不希望失去焦点才更新源数据,而是一发生改变就更新绑定源,因此设置
UpdateSourceTrigger
属性为
PropertyChanged

总结一下,我们遇到的关于Binding类的三个属性
Source

Path

UpdateSourceTrigger

属性名 含义
Source 获取或设置要用作绑定源的对象。
Path 获取或设置绑定源属性的路径。
UpdateSourceTrigger 获取或设置一个值,它可确定绑定源更新的时机。

现在我们已经介绍了绑定目标、绑定就差绑定源了。

绑定源

在数据绑定中,绑定源对象是指用户从其获取数据的对象。

在这个demo中,绑定源是一个Person对象。

查看Person类的代码:

using System.ComponentModel;

namespace SimpleBinding
{
    // This class implements INotifyPropertyChanged
    // to support one-way and two-way bindings
    // (such that the UI element updates when the source
    // has been changed dynamically)
    public class Person : INotifyPropertyChanged
    {
        private string _name;

        public Person()
        {
        }

        public Person(string value)
        {
            _name = value;
        }

        public string PersonName
        {
            get { return _name; }
            set
            {
                _name = value;
                // Call OnPropertyChanged whenever the property is updated
                OnPropertyChanged("PersonName");
            }
        }

        // Declare the event
        public event PropertyChangedEventHandler PropertyChanged;
        // Create the OnPropertyChanged method to raise the event
        protected void OnPropertyChanged(string name)
        {
            var handler = PropertyChanged;
            handler?.Invoke(this, new PropertyChangedEventArgs(name));
        }
    }
}

首先这个
Person
类实现了
INotifyPropertyChanged
接口,我们来看下
INotifyPropertyChanged
接口:

public interface INotifyPropertyChanged
{
    //
    // 摘要:
    //     Occurs when a property value changes.
    event PropertyChangedEventHandler? PropertyChanged;
}

包含一个PropertyChanged事件,因此该Person类必须实现接口的成员PropertyChanged。

当属性值被设置时,会调用OnPropertyChanged方法,从而触发事件。

INotifyPropertyChanged
接口定义了一个
PropertyChanged
事件,当一个属性的值发生变化时,你可以触发这个事件。WPF的数据绑定引擎会监听这个事件,当事件被触发时,它会更新绑定的UI元素的值。
如果你的数据源没有实现这个接口,那么当数据源的属性值发生变化时,WPF的数据绑定引擎将无法知道这个变化,因此它将无法更新UI元素的值。
这意味着,如果你的数据源的属性值在运行时可能会发生变化,并且你希望这些变化能够自动反映到UI上,那么你的数据源就需要实现
INotifyPropertyChanged
接口。

总结✨

通过这个小Demo,我们明白了WPF中的数据绑定的三要素,绑定对象、绑定、绑定源。在这个demo中,我们学会了如何在xaml中声明绑定,知道了Binding的三个属性
Source

Path

UpdateSourceTrigger
的含义,明白了数据源为什么要实现
INotifyPropertyChanged
接口,学会了WPF中数据绑定的基本用法,希望对你有所帮助。

一、内容提要

自知是人外有人,天外有天,相信对于Log4j2的异步日志打印早有老师或者同学已是熟稔于心,优化配置更是信手拈来,为了防止我在这里啰里八嗦的班门弄斧,我先将谜底在此公布:
log4j2.asyncQueueFullPolicy=Discard & log4j2.discardThreshold=ERROR
,这两个Log4j2配置在强依赖的RPC服务方系统或中间件系统出现问题且业务流量巨大时,对系统的快速止血和影响范围的控制有奇效。
要问为什么如此配置,是否还有高手?请见下文分晓。

二、现场还原

在2023年12月15日这“普通”的一天午后14点25分,我们收到了来自UMP的报警,接口服务可用率直接从100%干到了0.72%(该服务会直接影响到订单接单的相关流程,对客户体验有着重大影响)。我们顺藤摸瓜,并配合其他的服务监控定位到是强依赖系统出现了故障,导致其服务高度不可用,因此赶紧联系相关系统同事进行问题排查和处理。

大概14:33左右,服务端系统同事反馈已经机器扩容完成,由以下截图可见,确实在14:32左右服务可用率开始有抬头趋势,但是至此问题却并未终结,在14:36服务可用率开始急转直下(错误日志内容除了服务响应超时,服务端线程池满等异常以外,同时还收到了无服务的异常以及其他不相关服务调用的超时异常)。服务端系统同事反馈新扩容机器仅有极少数流量流入,如此“意外之喜”是我们没想到的,其后续排查和问题定位的过程分析也是该文成文的主要原因。最终我们通过操作客户端机器重启,服务得以完全恢复。

图2-1 服务端系统问题初现

图2-2 客户端服务监控可用率再次骤降

图2-3 客户端服务监控TP指标

系统JDK和相关依赖版本信息:

1.JDK:1.8.0_191
2.JSF:1.7.4-HOTFIX-T8
3.SLF4J-API:1.7.25
4.LOG4J-SLF4J-IMPL:2.18.0-jdsec.rc2
5.LOG4J2:2.18.0-jdsec.rc2
6.DISRUPTOR:3.4.2

三、问题点

1.

为何 服务可用率恢复到了一定程度后又掉下来了?
2.

为何 一个服务方的服务出现问题后,其他服务方的服务却也受到了影响?
3.

服务超时控制 为何 没有起作用?
4.

服务端出现问题 为何 需要客户端执行重启操作?

四、排查过程

如果上面的问题解决不了,大概我晚上睡觉的时候一掀开被窝也全是

为何

了,带着这些问题我们便开始了如下排查过程。

第一查

排查客户端机器是否出现由于GC导致的STW(stop the world)

由以下截图可见Young GC次数确有所升高(猜测应该有较多大对象产生),Full GC并未触发;堆内存,非堆内存的使用率处于正常水平;CPU使用率有所飙高且线程数略有增加,但是尚处于正常可接受范围内,目前看来问题时间段内机器的JVM指标并不能提供太多的定位线索。

图4-1-1 客户端服务器Young GC和Full GC情况

图4-1-2 客户端服务器堆内存和非堆内存情况

图4-1-3 客户端服务器CPU使用率和线程数情况

第二查

排查客户端机器磁盘情况,看是否存在日志阻塞的情况

通过以下截图可见,磁盘使用率,磁盘繁忙,磁盘写速度等磁盘指标看起来也尚属正常,由此暂时推断该问题与日志的相关性并不大。

注:这对我们后来问题排查的方向造成了误导,将罪魁祸首给很快排除掉了,其实再仔细看一下磁盘使用率的截图,在相对较短的时间内磁盘使用率却有了明显的增长,这是出现了“精英怪”呀,不过即便如此,目前也仅仅是猜测,并无充足的证据来支持定罪,接下来的排查才是重量级

图4-2-1 客户端服务器磁盘使用率情况

图4-2-2 客户端服务器磁盘繁忙情况

图4-2-3 客户端服务器磁盘写速度情况

第三查

查看问题时间段内客户端机器的内存状况(jmaq -dump)

通过
JSF-CLI-WORKER
关键字我们可以定位到JSF客户端线程状态,通过以下截图可以看出,JSF客户端线程虽然在执行不同的服务处理流程,但是都在执行WARN/ERROR日志打印的时候卡在了enqueue环节。

图4-3-1 JSF客户端线程状态1

图4-3-2 JSF客户端线程状态2

下面让我们来分析一下源码,具体看一下enqueue到底干了什么?

// enqueue源码分析
// 代码位置org.apache.logging.log4j.core.async.AsyncLoggerConfigDisruptor#enqueue 363
private void enqueue(final LogEvent logEvent, final AsyncLoggerConfig asyncLoggerConfig) {
        // synchronizeEnqueueWhenQueueFull() 是与另外一个要和大家分享的开关配置(AsyncLoggerConfig.SynchronizeEnqueueWhenQueueFull)有关,该配置用于指定当日志异步打印使用的RingBuffer满了以后,将多个日志线程的队列写入操作通过锁(queueFullEnqueueLock)调整为同步
        if (synchronizeEnqueueWhenQueueFull()) {
            // 同步锁:private final Object queueFullEnqueueLock = new Object();
            synchronized (queueFullEnqueueLock) {
                disruptor.getRingBuffer().publishEvent(translator, logEvent, asyncLoggerConfig);
            }
        } else {
            disruptor.getRingBuffer().publishEvent(translator, logEvent, asyncLoggerConfig);
        }
    }

由上述源码我们不妨大胆的假设一下:
JSF的客户端线程由于日志打印被阻塞而导致功能出现异常。
那么顺着这个思路,我们就顺藤摸瓜,看看RingBuffer里面到底存了啥。聚光灯呢?往这打!


当我们找到RingBuffer的时候,不禁倒吸一口凉气,这小小RingBuffer居然有1.61GB之巨,让我们更近一步到RingBuffer的entries里面看看,到底是什么样的压力让RingBufferの

前言

大家好,这里是白泽。
《Go语言的100个错误以及如何避免》
是最近朋友推荐我阅读的书籍,我初步浏览之后,大为惊喜。就像这书中第一章的标题说到的:“
Go: Simple to learn but hard to master
”,整本书通过分析100个错误使用 Go 语言的场景,带你深入理解 Go 语言。

我的愿景是以这套文章,在保持权威性的基础上,脱离对原文的依赖,对这100个场景进行篇幅合适的中文讲解。所涉内容较多,总计约 8w 字,这是该系列的第一篇文章,对应书中第1-10个错误场景。