聊聊如何利用apollo与druid整合实现数据源动态热切

news2024/11/15 4:50:38

前言

本文的素材来源与某次和朋友技术交流,当时朋友就跟我吐槽说apollo不如nacos好用,而且他们还因为apollo发生过一次线上事故。

故事的背景大概是如下

前阵子朋友部门的数据库发生宕机,导致业务无法正常操作,当时朋友他们数据库信息是配置在apollo上,朋友的想法是当数据库宕机时,可以通过切换配置在apollo上的数据库信息,实现数据源热变更。但当他们数据库发生宕机时,朋友按他的想法操作,发现事情并不像他想象的那样,他们更换数据源后,发现业务服务连接仍然是旧的数据库服务,后面没办法他们只能联系dba处理。

后边我听了朋友的描述后,我就问他说,你们当时数据库热切是怎么做的,他的回答是:很简单啊,就把数据源信息配置在apollo上,如果要变更数据源,就直接在apollo的portal上变更一下啊。听了朋友话,我就问然后呢?朋友的回答是:什么然后?就没然后了啊。

通过那次交流,就有了今天的文章,今天我们就来聊聊apollo与druid整合实现数据源动态热切

实现核心思路

apollo的配置变更动态监听 + spring AbstractRoutingDataSource预留方法determineCurrentLookupKey来做数据源切换

在介绍实现核心逻辑之前,我们来聊一下配置中心

何为配置中心?

配置中心是一种统一管理各种应用配置的基础服务组件。他的核心是对配置的统一管理。他管理的范畴是配置,至于对配置有依赖的对象,比如数据源,他是不归配置中心来管理。为什么我会单独提这个?是因为朋友似乎陷入了一个误区,以为在apollo上变更了配置,这个配置依赖的数据源也会一起跟着变更

核心代码

1、创建动态数据源,代理原来的datasource

public class DynamicDataSource extends AbstractRoutingDataSource {

    public static final String DATASOURCE_KEY = "db";

    @Override
    protected Object determineCurrentLookupKey() {
        return DATASOURCE_KEY;
    }

    public DataSource getOriginalDetermineTargetDataSource(){
        return this.determineTargetDataSource();
    }
}

@Configuration
@EnableConfigurationProperties(BackupDataSourceProperties.class)
@ComponentScan(basePackages = "com.github.lybgeek.ds.switchover")
public class DynamicDataSourceAutoConfiguration {


    @Bean
    @ConditionalOnMissingBean
    @Primary
    @ConditionalOnClass(DruidDataSource.class)
    public AbstractDataSourceManger abstractDataSourceManger(DataSourceProperties dataSourceProperties, BackupDataSourceProperties backupDataSourceProperties){
        return new DruidDataSourceManger(backupDataSourceProperties,dataSourceProperties);
    }

    @Bean("dataSource")
    @Primary
    @ConditionalOnBean(AbstractDataSourceManger.class)
    public DynamicDataSource dynamicDataSource(AbstractDataSourceManger abstractDataSourceManger) {
        DynamicDataSource source = new DynamicDataSource();
        DataSource dataSource = abstractDataSourceManger.createDataSource(false);
        source.setTargetDataSources(Collections.singletonMap(DATASOURCE_KEY, dataSource));
        return source;
    }

}

这边有个需要注意的点就是DynamicDataSource的bean名称一定是需要为dataSource,目的是为了让spring默认的datasource取到的bean是DynamicDataSource

2、监听配置变更,并进行数据源切换

切换数据源

 @ApolloConfigChangeListener(interestedKeyPrefixes = PREFIX)
    public void onChange(ConfigChangeEvent changeEvent) {
        refresh(changeEvent.changedKeys());
    }

    /**
     *
     * @param changedKeys
     */
    private synchronized void refresh(Set<String> changedKeys) {
        /**
         * rebind configuration beans, e.g. DataSourceProperties
         * @see org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder#onApplicationEvent
         */
        this.applicationContext.publishEvent(new EnvironmentChangeEvent(changedKeys));

        /**
         * BackupDataSourceProperties rebind ,you can also do it in PropertiesRebinderEventListener
         * @see PropertiesRebinderEventListener
         */
        backupDataSourcePropertiesHolder.rebinder();

        abstractDataSourceManger.switchBackupDataSource();

    }
  @SneakyThrows
    @Override
    public void switchBackupDataSource() {
        if(backupDataSourceProperties.isForceswitch()){
            if(backupDataSourceProperties.isForceswitch()){
                log.info("Start to switch backup datasource : 【{}】",backupDataSourceProperties.getBackup().getUrl());
                DataSource dataSource = this.createDataSource(true);
                DynamicDataSource source = applicationContext.getBean(DynamicDataSource.class);
                DataSource originalDetermineTargetDataSource = source.getOriginalDetermineTargetDataSource();
                if(originalDetermineTargetDataSource instanceof DruidDataSource){
                    DruidDataSource druidDataSource = (DruidDataSource)originalDetermineTargetDataSource;
                    ScheduledExecutorService createScheduler = druidDataSource.getCreateScheduler();
                    createScheduler.shutdown();
                    if(!createScheduler.awaitTermination(backupDataSourceProperties.getAwaittermination(), TimeUnit.SECONDS)){
                        log.warn("Druid dataSource 【{}】 create connection thread force to closed",druidDataSource.getUrl());
                        createScheduler.shutdownNow();
                    }
                }
                //当检测到数据库地址改变时,重新设置数据源
                source.setTargetDataSources(Collections.singletonMap(DATASOURCE_KEY, dataSource));
                //调用该方法刷新resolvedDataSources,下次获取数据源时将获取到新设置的数据源
                source.afterPropertiesSet();

                log.info("Switch backup datasource : 【{}】 finished",backupDataSourceProperties.getBackup().getUrl());
            }
        }
    }

3、测试

   @Override
    public void run(ApplicationArguments args) throws Exception {
        while(true){
            User user = userService.getById(1L);
            System.err.println(user.getPassword());
            TimeUnit.SECONDS.sleep(1);
        }

    }

未切换前,控制台打印


切换后,控制台打印

总结

以上就是实现apollo与druid整合实现数据源动态热切的整体思路,但是实现中还存在有一点问题,就是存在老连接没做处理。虽然我在示例代码中没做处理,但代码里面预留了getOriginalDetermineTargetDataSource,可以通过getOriginalDetermineTargetDataSource来做额外一些操作。

本文的实现方式还可以使用apollo在github提供的case来实现,链接如下

https://github.com/apolloconfig/apollo-use-cases/tree/master/dynamic-datasource

他这个case在进行连接切换后,会对老的数据源进行连接清理。他里面的用数据源是HikariDataSource,如果你用apollo提供的case,当你是使用druid数据源时,我贴下druid的关闭部分源码


以及获取connection源码

这边有个注意点就是,当druid数据源进行关闭时,如果此时恰好有连接进来,此时就会报DataSourceDisableException,然后导致项目异常退出

最后说点额外的,之前朋友说apollo比nacos不好用啥的,我是持保留意见的,其实衡量一个技术的好坏,是要带上场景的,在某些场景,技术的具备的优势可能反而成了劣势

demo链接

https://github.com/lyb-geek/springboot-learning/tree/master/springboot-datasource-hot-switchover

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/187759.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【JavaEE进阶】锁的特性

目录 一、乐观锁&悲观锁 二、公平锁&非公平锁 三、可重入锁&非可重入锁 四、读写锁&互斥锁 互斥锁 读写锁 读写锁涉及的类:ReentrantReadWriteLock 读写锁的优势: 五、轻量级锁&重量级锁 六、CAS ①基于CAS实现原子类 下面&#xff0c;来一段CAS的伪…

举个栗子~Tableau 技巧(250):创建 KPI 指标突出显示表

上一个栗子发出后&#xff0c;有数据粉反馈&#xff1a;有什么办法可以让全年的销售数据分层显示哇&#xff1f;业绩表现好、一般和差的分别使用不同的底色。 这个需求&#xff0c;如果考核的是名次&#xff0c;可以使用 &#x1f330; 用颜色突出显示前N项(TopN)和后N项(Bott…

samba设置文件共享

前提说明本人使用的系统如下共享文件的系统&#xff1a;ubuntu 版本&#xff1a;18.04.6目标系统&#xff1a;windows11安装sambasudo apt-get install samba修改配置文件sudo vim /etc/samba/smb.conf文件末尾添加以下内容[share] # 共享的名称&#xff0c;可以自行定义 c…

完整数据分析流程:Python中的Pandas如何解决业务问题

开篇 作为万金油式的胶水语言&#xff0c;Python几乎无所不能&#xff0c;在数据科学领域的作用更是不可取代。数据分析硬实力中&#xff0c;Python是一个非常值得投入学习的工具。 这其中&#xff0c;数据分析师用得最多的模块非Pandas莫属&#xff0c;如果你已经在接触它了…

c#入门-异步方法

异步方法 如果一个操作会返回Task&#xff0c;那么用这个操作续接后续操作&#xff0c;也会得到Task。 也就是说Task具有传染性&#xff0c;最终拼凑出来的Task非常复杂。 使用异步方法&#xff0c;可以简化Task的拼凑。 async修饰 异步方法需要添加async修饰符。并且通常方…

【前端】Vue项目:旅游App-(15)home:网络请求house数据、动态并组件化展示house列表信息

文章目录目标过程与代码content组件请求数据&#xff1a;houseListrequeststore控制台输出动态加载更多列表数据house-item组件阶段1&#xff1a;数据传送阶段2&#xff1a;对着目标写样式house-item-v9house-item-v9&#xff1a;debughouse-item-v3阶段3&#xff1a;总体效果效…

Android ANR触发机制(二)

上一篇文章看了Service的ANR触发流程&#xff0c;现在看一下其他三种ANR触发流程。 1.BroadcastReceiver触发ANR BroadcastReceiver超时是位于ActivityManager线程中的BroadcastQueue.BroadcastHandler收到BROADCAST_TIMEOUT_MSG消息时触发。 广播队列分为foreground队列和b…

基于Java+SpringBoot+Vue前后端分离学生管理系统设计与实现

博主介绍&#xff1a;✌全网粉丝3W&#xff0c;全栈开发工程师&#xff0c;从事多年软件开发&#xff0c;在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战✌ 博主作品&#xff1a;《微服务实战》专栏是本人的实战经验总结&#xff0c;《Spring家族及…

2023年二月份图形化二级打卡试题

活动时间 从2023年 2月1日至1月21日&#xff0c;每天一道编程题。 本次打卡的规则如下&#xff1a; &#xff08;1&#xff09;小朋友每天利用10~15分钟做一道编程题&#xff0c;遇到问题就来群内讨论&#xff0c;我来给大家答疑。 &#xff08;2&#xff09;小朋友做完题目后&…

数组中和为0的三个数

给你一个整数数组 nums &#xff0c;判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i ! j、i ! k 且 j ! k &#xff0c;同时还满足 nums[i] nums[j] nums[k] 0 。请你返回所有和为 0 且不重复的三元组。 注意: 答案中不可以包含重复的三元组。 示例 1: 输入: num…

了解SLI、SLO和SLA

了解SLI、SLO和SLA 概念解释 服务水平指标(SLI) SLI代表目前服务的状态&#xff0c;例如可以是最基本的接口成功率、p99响应时间&#xff0c;也可以是一些业务指标&#xff0c;例如用户投诉率之类的。是可量化&#xff0c;是可确定的。 服务水平目标(SLO) SLO是目标&#x…

【树】哈夫曼树和哈夫曼编码

哈夫曼&#xff08;Huffman&#xff09;树&#xff0c;又称最优树&#xff0c;是一类带权路径长度最短的树。最优二叉树&#xff08;哈夫曼树&#xff09;路径&#xff1a;从树中一个结点到另一个结点之间的分支构成这两个结点之间的路。路径长度:路径上的分支数目&#xff1b;…

mysql分组排序取组内第一的数据行获取分组后,组内排名第一或最后的数据行。

前言&#xff1a; group by函数后取到的是分组中的第一条数据&#xff0c;但是我们有时候需要取出各分组的最新一条&#xff0c;该怎么实现呢&#xff1f; 本文提供两种实现方式。 一、准备数据 DROP TABLE IF EXISTS tb_dept; CREATE TABLE tb_dept (id bigint(20) UNSIG…

chat聊天系统消息消费时遇到的问题及优化思路

前言 之前有段工作经历涉及到了chat相关&#xff0c;而消息的发送 -> 存储 -> 消费是由不同的团队负责的&#xff0c;因此消息如何再多个团队之间流通、以及通过什么介质传递都是需要考虑的问题。 之前我负责过一些消息消费的相关工作&#xff0c;消息发送团队将消息推…

【Linux】简介磁盘|inode|动静态库

目录一.简介磁盘1.磁盘的物理结构&#xff1a;2.磁盘存储方式&#xff1a;3.磁盘的逻辑抽象&#xff1a;二.inode&&文件系统1.inode文件属性&#xff08;inode&#xff09;内容&#xff08;data block&#xff09;为什么删除一个文件相比于写一个文件要快得多&#xff…

若依配置教程(二)集成积木报表JimuReport

积木报表配置官网 在搭建好若依环境成功运行以后&#xff0c;我们先在这个系统中加一个小功能&#xff1a;JimuReport积木报表&#xff0c;以下步骤&#xff0c;我们按照官网教程&#xff0c;详细配置一下&#xff1a; 1.在ruoyi-admin文件夹下的pom.xml加入jar包依赖&#x…

MLP多层感知机理解

目录 .1简介 .2例子 2.1模型 2.2 实例 2.2.1 问题描述 2.2.2 数学过程 .3 代码 3.1 问题描述 3.2 代码 references&#xff1a; .1简介 多层感知机是全连接的 可以把低维的向量映射到高维度 MLP整个模型就是这样子的&#xff0c;上面说的这个三层的MLP用公式总结起来…

C 语言零基础入门教程(二十)

C 预处理器 C 预处理器不是编译器的组成部分&#xff0c;但是它是编译过程中一个单独的步骤。简言之&#xff0c;C 预处理器只不过是一个文本替换工具而已&#xff0c;它们会指示编译器在实际编译之前完成所需的预处理。我们将把 C 预处理器&#xff08;C Preprocessor&#x…

练手好福利!20个Python实战项目含源代码【2023最新】

高效学习源代码的步骤&#xff1a;1.运行程序&#xff0c;观察表现2.运行源码&#xff0c;断点调试&#xff0c;从头跟一边源码的执行流程&#xff0c;注意函数堆栈3.画类图、流程图&#xff0c;先把遇到的重要类记录下来&#xff0c;表明各个类的关系4.记录问题&#xff0c;把…

Unity XR

一、几个Unity XR Interaction Toolkit学习地址 1.B站视频 https://www.bilibili.com/video/BV11q4y1b74z/?spm_id_from333.999.0.0&vd_source8125d294022d2e63a58dfd228a7fcf63 https://www.bilibili.com/video/BV13b4y177J4/?spm_id_from333.999.0.0&vd_source8…