分布式服务必备神器配置中心

446 阅读8分钟

努力学习了,可能还是看不懂;不努力学习,连看不懂的机会都没有,你细品。

1.前言

最近在看MySQL锁相关部分的知识,本打算由此来写一篇文章,好把自己理解的东西记录下来,同时也希望能传播给需要的小伙。无奈,内容过于复杂,处于似懂非懂的境界,于是就放弃了相关写作,以免误导大家(虽然没有太多的人看,嘿嘿嘿)。

不写MySQL,写写分布式服务必备神器配置中心,它不香嘛?

在日常开发中,相信你对数据库配置、MQ配置、Redis配置、开关配置等配置再熟悉不过了,相比数据库配置、MQ配置、Redis配置这些静态配置而言,开关配置属于动态配置,可能经常需要开启和关闭。

下面我会带着你回顾一下不堪回首的痛苦经历:产品经理找到了你,笑嘻嘻和你说:“小王帮忙把配置开关打开一下,业务需要”,小王同学开心的答应了,不就修改个开关配置嘛?分分钟给你搞定,让你见识一下真正的技术。于是,小王同学修改了配置,将应用发布上线,接着就开开心心地写起了日常bug。突然,耳边传来了熟悉的声音:“小王帮忙把配置开关关闭一下,业务说不需要这个功能了”,小王的内心:“我xxx,这变需求比变脸还快?”,即便内心不痛快,小王也不得不修改配置,重新发布应用,毕竟为了隔壁能幸福。

不甘于被产品经理继续折磨,小王想起了隔壁的小伙伴,经过与隔壁小伙伴一场激烈的探讨后,小王记住了一个关键的单词Apollo并开启了学习之旅。

2.Apollo介绍

Apollo(阿波罗)是携程框架部门研发的分布式配置中心,能够集中化管理应用不同环境、不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限、流程治理等特性,适用于微服务配置管理场景。

2.1 Apollo核心概念

  • 应用:开发者开发的项目
  • 环境:应用部署的环境,如:开发环境、测试环境、验收环境、线上环境
  • 集群:每个环境针对不同的数据中心部署的应用集群
  • namespace:相当于配置文件,主要用于对配置进行分类,比如Redis配置、数据库配置、kafka配置可以放在不同的命名空间

2.2 Apollo架构

从如上架构图可以看出存在如下角色:

  • config service:用于读取配置、推送配置,服务对象是apollo客户端
  • admin service:用于修改、发布配置,服务对象是portal
  • portal:apollo提供给用户进行配置操作的界面
  • client:用于从config service上拉取配置信息
  • eureka:用于服务的注册和发现
  • meta server:封装服务发现逻辑

2.3 Apollo核心流程

  • config service、admin service会将服务注册到eureka中

  • client、portal通过meta来发现服务

  • 用户通过portal来新增、修改配置

  • 用户点击配置发布,通过接口调用admin service

  • admin service保存配置的修改,并将变更信息插入到表中

  • config service扫描表中是否有最新的配置变更,如果有将变更推送给客户端

  • 客户端会定期从config service上拉取最新的配置信息

    根据如上的介绍,我们需要部署apollo服务和搭建自己的客户端

3. 环境搭建

环境搭建流程具体可以参照官网部分,本来是有环境搭建流程介绍,由于字数限制不得不删除。

环境搭建换完成之后,在浏览器中输入://localhost:8070 使用用户名:apollo 密码:admin登录,可以看到如下界面

4.实战

4.1 在Portal创建项目

项目的应用名称为:boot-example-apollo,对应的appid为:boot-example-apollo

⚠️此处创建项目时指定的appid一定要和客户端项目配置文件中的appid一直,否则无法正确读取配置信息

4.2 新增配置

首先点击右侧的新增配置按钮,保存之后,记得一定要点击发布

4.3 创建客户端项目

4.3.1 添加pom

<dependencies>
    <dependency>
        <groupId>com.ctrip.framework.apollo</groupId>
        <artifactId>apollo-client</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
</dependencies>

4.3.2 添加Aollo相关配置

server:
  port: 8081
app:
  id: boot-example-apollo # 指定应用的appid,在浏览器创建项目的时候指定
apollo:
  bootstrap:
    enabled: true # 在启动的时候注入application命名空间
  meta: http://localhost:8080 # 指定meta服务的地址
  cacheDir: /Users/pengli/software/idea/workspace/data/apollo-config-cache # 指定本地缓存文件地址

4.3.3 创建实体类

@Data
@ToString
@Component
public class TestJavaConfigBean {

    @Value("${timeout:100}")
    private int timeout;

    @Value("${batch:200}")
    private int batch;
}

4.3.4创建启动类

@SpringBootApplication
@EnableApolloConfig
public class ApolloApplication {

    private static TestJavaConfigBean testJavaConfigBean;

    @Autowired
    public void setTestJavaConfigBean(TestJavaConfigBean testJavaConfigBean) {
        ApolloApplication.testJavaConfigBean = testJavaConfigBean;
    }

    public static void main(String[] args) {
        SpringApplication.run(ApolloApplication.class, args);

        System.out.println("TestJavaConfigBean======>" + testJavaConfigBean);
    }
}

4.3.5启动项目

启动项目之后可以看到如下打印内容:

TestJavaConfigBean======>TestJavaConfigBean(timeout=500, batch=300)

4.3.6 查看本地缓存文件

cat boot-example-apollo+default+application.properties
#Persisted by DefaultConfig
#Tue Feb 25 16:22:49 CST 2020
batch=300
timeout=500

由此可以看出Apollo会在本质缓存我们的配置信息,以保证在服务不可用的情况下仍然可以读取配置信息

4.4 以@ConfigurationProperties方式注入配置

4.4.1 创建实体类

@ConfigurationProperties(prefix = "redis.cache")
@Component
@Data
@ToString
public class SampleRedisConfig {
    private int expireSeconds;
    private int commandTimeout;
}

4.4.2 修改配置类

@Configuration
@EnableConfigurationProperties(value = SampleRedisConfig.class)
public class AppConfig {

    @Bean
    public TestJavaConfigBean testJavaConfigBean() {
        return new TestJavaConfigBean();
    }
}

4.4.3 在Apollo上添加相关配置

4.4.4 运行程序

SampleRedisConfig======>SampleRedisConfig(expireSeconds=1000, commandTimeout=2000)

可以看到该种方式也可以读取到相应的配置信息,此时你不妨试试,在Portal修改配置,看看你的应用程序是不是可以读取到最新的配置。测试之后你会发现客户端并不能读取到最新的配置,此情况就需要实现自动刷新功能,从而是客户端读取到最新的配置属性。

4.5 关于@ConfigurationProperties实现自动刷新方式

4.5.1 在实体类上添加@RefreshScope注解

@ConfigurationProperties(prefix = "redis.cache")
@Component
@RefreshScope
@Data
@ToString
public class SampleRedisConfig {
    private int expireSeconds;
    private int commandTimeout;
}

4.5.2 创建自动刷新配置类

@Component
@Slf4j
public class AppRefreshConfig {

    @Autowired
    private RefreshScope refreshScope;

    @Autowired
    private SampleRedisConfig sampleRedisConfig;

    @ApolloConfigChangeListener
    public void onChange(ConfigChangeEvent changeEvent) {
        log.info("before refresh {}", sampleRedisConfig.toString());
        refreshScope.refresh("sampleRedisConfig");
        log.info("after refresh {}", sampleRedisConfig.toString());
    }
}

4.6 读取非默认命名空间配置

4.6.1 创建命名空间

在Apollo服务上创建一个名为dataSource的命名空间

4.6.2 修改配置文件

server:
  port: 8081
app:
  id: boot-example-apollo # 指定应用的appid,在浏览器创建项目的时候指定
apollo:
  bootstrap:
    enabled: true # 在启动的时候注入application命名空间
    namespaces: application,order-service.dataSource
  meta: http://localhost:8080 # 指定meta服务的地址
  cacheDir: /Users/pengli/software/idea/workspace/data/apollo-config-cache # 指定本地缓存文件地址

注意:apollo.bootstrap.enabled = true和apollo.bootstrap.namespaces = application,order-service.dataSource这两个配置必须同时存在,源码分析如下:

4.6.3 创建实体类

@Component
@Data
@ToString
public class DatasourceConfig {

    @Value("${spring.datasource.url:''}")
    private String url;

    @Value("${spring.datasource.username:''}")
    private String userName;

    @Value("${spring.datasource.password:''}")
    private String password;
}

4.6.4 运行项目

DatasourceConfig======>DatasourceConfig(url=jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true, userName=root, password=root)

5.结束语

学习到这里,小王虽然已经变成了老王,内心却在窃喜:“从此以后再也不用担心产品经理提修改配置的需求了,你改任你改,我心自翱翔”。

本次的分享到此就结束了,对应的项目代码你可以参照boot-example-apollo,针对有问题或者有疑问的地方,欢迎在评论区留言探讨,期待与你下次相见。