spring-cloud + 配置中心 demo 及分析

Demo

本文的 demo 参考:https://www.cnblogs.com/didispace/p/9188645.html 
和 https://github.com/qianyiwen2019/SpringCloud-Learning/tree/master/3-Edgware

  • 配置中心包括 3 部分

存储(配置数据存储的位置,默认为 git,还可以为 svn,本地存储,数据库),本例使用数据库
server(负责监听存储上的配置变化)
client (从 server 处定时拉取最新的配置)

  • 建库建表

create database `config-server-db`;
CREATE TABLE `properties` (
  `id` int(11) NOT NULL,
  `key` varchar(50) NOT NULL,
  `value` varchar(500) NOT NULL,
  `application` varchar(50) NOT NULL,
  `profile` varchar(50) NOT NULL,
  `label` varchar(50) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
  • server 的 application.properties

spring.application.name=config-server-db
server.port=10020

spring.profiles.active=jdbc
spring.cloud.config.server.jdbc.sql=SELECT `KEY`, `VALUE` from PROPERTIES where APPLICATION=? and PROFILE=? and LABEL=?
spring.datasource.url=jdbc:mysql://localhost:3306/config-server-db
spring.datasource.username=root
spring.datasource.password=
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
  • server 代码

package com.didispace.config.server.db;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;
import org.springframework.context.ApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;

@EnableConfigServer
@SpringBootApplication
public class ConfigServerBootstrap {

    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(ConfigServerBootstrap.class);
    }
}
  • 启动 server 并验证

插入测试配置数据

INSERT INTO properties VALUES(1, 'com.didispace.message', 'test-stage-master', 'config-client', 'stage', 'master');
INSERT INTO properties VALUES(2, 'com.didispace.message', 'test-online-master', 'config-client', 'online', 'master');
INSERT INTO properties VALUES(3, 'com.didispace.message', 'test-online-develop', 'config-client', 'online', 'develop');
INSERT INTO properties VALUES(4, 'com.didispace.message', 'hello-online-master', 'hello-service', 'online', 'master');
INSERT INTO properties VALUES(5, 'com.didispace.message', 'hello-online-develop', 'hello-service', 'online', 'develop');

测试 server 能正确监听到数据库里的配置数据

curl http://localhost:10020/config-client/stage/
{"name":"config-client","profiles":["stage"],"label":null,"version":null,"state":null,"propertySources":[{"name":"config-client-stage","source":{"com.didispace.message":"test-stage-master"}}]}

测试更新配置数据后,server 能监听到

update properties set value='test-stage-master-update' where application='config-client' and profile='stage' and label='master' and `key`='com.didispace.message';
curl http://localhost:10020/config-client/stage/
{"name":"config-client","profiles":["stage"],"label":null,"version":null,"state":null,"propertySources":[{"name":"config-client-stage","source":{"com.didispace.message":"test-stage-master-update"}}]}
  • Client 的 bootstrap.properties

spring.application.name=config-client
server.port=10025

spring.cloud.config.uri=http://localhost:10020/
spring.cloud.config.profile=stage
spring.cloud.config.label=master

management.security.enabled=false
  • Client 的代码

package com.didispace.config.client;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
public class ConfigClientApplication {

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

    @RefreshScope
    @RestController
    class TestController {

        @Value("${com.didispace.message}")
        private String message;

        @GetMapping("/test")
        public String test() {
            return message;
        }
    }
}

测试 client

curl http://localhost:10025/test
返回:test-stage-master-update

更新配置:update properties set value='test-stage-master-update-1' where application='config-client' and profile='stage' and label='master' and `key`='com.didispace.message';
curl http://localhost:10025/test
返回:test-stage-master-update,可见并未更新

强制 client 从 server 拉取配置: curl -X POST http://localhost:10025/refresh
curl http://localhost:10025/test
返回:test-stage-master-update-1

讨论

  • 怎么让 client 自动从 server 上刷新配置?

参考以下,需要引入消息总线和 rabbitmq

http://www.ityouknow.com/springcloud/2017/05/26/springcloud-config-eureka-bus.html
https://blog.csdn.net/dh554112075/article/details/90577039
http://www.apgblogs.com/springcloud-configserverrefresh/

总体来说,就是配置更新后,只需要对着某个 client 触发一次 bus-refresh, curl -X POST http://localhost:10025/actuator/bus-refresh,配置就会借助 rabbitmq 推向所有 client。而不必对着每个 client 触发 curl -X POST http://localhost:10025/refresh

  • 其他配置中心,如阿里的 diamond 是怎么做的?它并不需要依赖额外的消息系统。
  • server 怎么自动从数据库拉取最新配置的?

spring-cloud-config git 地址  https://github.com/spring-cloud/spring-cloud-config

首先看入口:

spring 的注解通常用来装载某些 bean,并在装载过程中起到初始化作用。

@EnableConfigServer
@SpringBootApplication
public class ConfigServerBootstrap {
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(ConfigServerConfiguration.class)
public @interface EnableConfigServer {

}
@Configuration
public class ConfigServerConfiguration {
	class Marker {}

	@Bean
	public Marker enableConfigServerMarker() {
		return new Marker();
	}
}

Marker 唯一被引用的地方在 ConfigServerAutoConfiguration 类(需要在源代码中看)

@Configuration
@ConditionalOnBean(ConfigServerConfiguration.Marker.class)
@EnableConfigurationProperties(ConfigServerProperties.class)
@Import({ EnvironmentRepositoryConfiguration.class, CompositeConfiguration.class, ResourceRepositoryConfiguration.class,
		ConfigServerEncryptionConfiguration.class, ConfigServerMvcConfiguration.class, TransportConfiguration.class })
public class ConfigServerAutoConfiguration {

}

ConfigServerAutoConfiguration  又引入了好几个依赖。与监听配置的存储相关的是 EnvironmentRepositoryConfiguration

@Configuration
@Import({ JdbcRepositoryConfiguration.class, VaultRepositoryConfiguration.class, SvnRepositoryConfiguration.class,
		NativeRepositoryConfiguration.class, GitRepositoryConfiguration.class,
		DefaultRepositoryConfiguration.class })
public class EnvironmentRepositoryConfiguration {

可见这里 有 JdbcRepositoryConfiguration(以数据库作存储),NativeRepositoryConfiguration(本地文件作存储),GitRepositoryConfiguration(Git 作存储)等等。

而 DefaultRepositoryConfiguration 就是 GitRepositoryConfiguration。

@Configuration
@Profile("git")
class GitRepositoryConfiguration extends DefaultRepositoryConfiguration {
}

那么 JdbcRepositoryConfiguration 里边是怎么实现的呢?

@Configuration
@Profile("jdbc")
class JdbcRepositoryConfiguration {
	@Bean
	public JdbcEnvironmentRepository jdbcEnvironmentRepository(JdbcTemplate jdbc) {
		return new JdbcEnvironmentRepository(jdbc);
	}
}

看  JdbcEnvironmentRepository 的实现:

@ConfigurationProperties("spring.cloud.config.server.jdbc")
public class JdbcEnvironmentRepository implements EnvironmentRepository, Ordered {

	private static final String DEFAULT_SQL = "SELECT KEY, VALUE from PROPERTIES where APPLICATION=? and PROFILE=? and LABEL=?";
	private int order = Ordered.LOWEST_PRECEDENCE - 10;
	private final JdbcTemplate jdbc;
	private String sql = DEFAULT_SQL;
	private final PropertiesResultSetExtractor extractor = new PropertiesResultSetExtractor();

	public JdbcEnvironmentRepository(JdbcTemplate jdbc) {
		this.jdbc = jdbc;
	}
	
	public void setSql(String sql) {
		this.sql = sql;
	}

	public String getSql() {
		return this.sql;
	}


class PropertiesResultSetExtractor implements ResultSetExtractor<Map<String, String>> {

	@Override
	public Map<String, String> extractData(ResultSet rs)
			throws SQLException, DataAccessException {
		Map<String, String> map = new LinkedHashMap<>();
		while (rs.next()) {
			map.put(rs.getString(1), rs.getString(2));
		}
		return map;
	}

}

在上面  extractData() 和 setSql(), getSql() 分别打上断点,启动 server。访问 server
curl http://localhost:10020/config-client/stage/

  • client 怎么从 server 拉取配置的?

延伸阅读1:配置中心的意义是什么?

zookeeper 能实现服务发现,其 watcher 机制又能提供集中管理配置的功能,那为什么还需要单独的配置中心中间件呢?
以下内容来自  https://www.jianshu.com/p/2d79ac04d2dc     你还在使用zookeeper做配置中心吗?(侵删)

配置中心关注点

弱依赖 应用应该弱依赖于配置中心,如果配置中心挂掉,不能影响应用的正常运行,所以客户端应该缓存配置数据
配置存储应该做好容灾备份 配置中心的数据是不能丢失
推送时延和推送成功率 zookeeper 的 watcher 机制并不能保证每一次配置的更改都推送给客户端
灰度 zookeeper来做配置中心,做灰度是一件很难的事情,不是说不能做,是需要客户端去配合过滤一些不必要的数据,而这种造成了网络的垃圾开销及客户端的复杂度
集中入口及变更审计能力  

zookeeper 管理配置局限性

配置数据大小 zookeeper 默认 1M
配置数据环境属性不同的问题

在现实中,配置数据是要具有环境属性的,相当一部分配置在不同的环境必须设定不同的值,但是也有相当的另一部分配置在不同的环境要设定为完全一致的值。所以从某个应用的视角看,其100个配置项,可能有50个是每个环境要不同的值的,而另50个是不区分环境;而zookeeper只要把数据写入了znode中,必然会把数据通知给各个客户端,这种做法带来了两个问题:
1)垃圾网络开销;
2)应用需要按照规则进行过滤不需要的配置信息;

推送时延和推送成功率 zookeeper 的 watcher 机制并不能保证每一次配置的更改都推送给客户端

延伸阅读2:diamond 与 zookeeper 有何异同?

以下内容来自 https://yq.aliyun.com/articles/25656  diamond 与 zookeeper 有何不同,做成表格对比(侵删)

  zookeeper diamond

数据持久性

既可以有持久性数据,也有非持久性数据 只有持久性数据
数据拉取 客户端对相应的数据path注册Watcher,当数据有更新的时候,服务器会有事件通知,注意,这个通知仅仅是告诉客户端对应的数据有更新了,具体数据内容需要客户端根据自己的情况来决定是否需要获取最新数据 客户端每隔15s轮询服务器,比对数据是否更新,从而获取最新数据
数据实时性 由数据拉取模式知:ZooKeeper比Diamond高一些  
服务器数据存储 所有运行时数据都是存储在内存中,客户端的所有读写操作都是针对这份内存数据来进行的。同时,内存中的数据,ZK会以快照的形式dump到指定文件中去,配合事务日志,帮助服务器在下次重启的时候,能够加载正确的数据到内存中去

以mysql数据库为中心,所有在mysql中的数据都是最新的,客户端的所有写请求,都会首先写入数据库,同时会dump数据到Server的本地文件中,所有读请求都是直接走这个静态文件

数据模型 Diamond的数据都是以行组织的,这也更便于它使用mysql来管理数据。Diamond的基本数据结构包含dataid,group和content,根据group,可以将一组相关的数据组合起来。

ZooKeeper中,使用树形结构来组织数据,每个节点类型于一个文件系统的路径,一个节点下面也可以创建多个子节点来规则一些相关的数据。

容灾 在容灾方面,diamond做得相当的完备:
1. 所有客户端的读请求,都是直接读取服务器端的本地静态文件,因此,即使数据库挂了,都不会影响diamond的读服务。而读服务在所有使用diamond的应用场景中,占到了绝大部分。
2. Diamond客户端还保存了数据的快照,客户端每次从服务器成功获取数据后,都会把这份数据保存到本地文件系统中,称为快照文件。这个快照文件是为了防止在服务器无法获取数据的时候,能够在这个快照中获取数据。
3. 客户端还会有一个容灾目录,变个容灾目录是在服务器完全不可用的时候,运维人员可以手动在这个容灾目录中创建相关目录结构的数据,diamond就就会优先从这个目录中获取数据。
4. 说到这里,我们就可以给diamond的数据获取优先级作一个总结:
首先都会从容灾目录中获取数据——无法从容灾目录获取数据的话,就通过网络到服务器请求数据——如果无法从服务器获取数据,那么就从本地的snapshot中获取数据。
ZooKeeper实现了paxos算法,有效的解决了分布式单点问题。以一个3台机器构成的集群为例,任意一台ZK挂掉,都不会影响集群的数据一致性。
总结:在容灾方面,diamond有很大的优势,也符合了diamond的稳定性要求。
数据大小 Diamond对单个数据的大小,没有严格的限制,通常2M左右的数据大小都是没有问题的 由于全量数据都是存储在内存中,并且需求进行集群机器间的数据两步,所以对单个数据的大小有严格的限制,默认单个数据节点的最大数据大小是1M
  支持对数据的追加与聚合功能,即对同一个dataid的写入操作,可以设置为追加 只有覆盖写

延伸阅读3:Nacos 初览

https://github.com/alibaba/nacos    阿里开源的配置中心中间件
https://blog.csdn.net/youanyyou/article/details/85774066         Nacos vs Spring Cloud

相对于 Spring Cloud Eureka 来说,Nacos 更强大。
Nacos = Spring Cloud Eureka + Spring Cloud Config
Nacos 可以与 Spring, Spring Boot, Spring Cloud 集成,并能代替 Spring Cloud Eureka, Spring Cloud Config。
通过 Nacos Server 和 spring-cloud-starter-alibaba-nacos-config 实现配置的动态变更。
通过 Nacos Server 和 spring-cloud-starter-alibaba-nacos-discovery 实现服务的注册与发现。

https://juejin.im/post/5c887d1ee51d4558a2344baa           Nacos 入门

延伸阅读4:常见的配置中心

参考  https://www.jianshu.com/p/a639f9ac71c7  市场上常见的几种配置中心 (侵删)

spring cloud config

执行流程:

  1. 用户在Git中update和commit配置,Git hooks脚本回调;
  2. Spring Cloud Config Server通过回掉,获取更新配置;
  3. 用户在其中任一一台机器上执行配置刷新操作 (图中为 "post /bus/refresh")
  4. 目标机 (App A)在Config Server中拉取最新配置,并发送消息到消息总线中,通知其他机器配置被变更;
  5. 所有其他机器通过消息总线获知配置刷新后,在Config Server中拉取最新配置。

缺点:

  1. 依赖系统中消息中间件(RabbitMQ或kafka)、spring 消息总线调度器spring bus。
  2. 配置中心无降级处理、可用性不高。
  3. 无友好界面操作、基于git管理的第三方工具
  4. 整个链路较长、时效性无法保证。
  5. 推送记录追踪较为复杂
zookeeper  
ACM (diamond)

执行流程:

  1. 用户在ACM工作台更新相关配置;
  2. Diamond-server持久化存储配置、集群设置缓存;
  3. 所有Diamond-client同步轮询Diamond-server最新更改信息、判断是否有变化、有变化则拉取最新变化配置。
  4. 将最新配置文件写入到本地文件缓存。

缺点:

  1. 依赖阿里云Diamond服务、无法自行维护
发布了34 篇原创文章 · 获赞 0 · 访问量 1386

猜你喜欢

转载自blog.csdn.net/justsomebody126/article/details/103673856