Zookeeper发布订阅之SpringBoot+Mybatis多数据源
1.前言
数据发布/订阅系统,即所谓的配置中心,顾名思义就是发布者将数据发布到Zookeeper的一个或一系列节点上,供订阅者进行数据订阅,进而达到动态获取数据的目的,实现配置信息的集中管理和数据的动态更新。
发布订阅一般有两种模式,分别是推(Push)和拉(Pull)模式。在推拉模式中,服务端主动将数据更新发送给所有订阅的客户端;而拉模式则是客户端主动发起请求来获取最新数据。Zookeeper采取推和拉相结合的方式:客户端会向相应的客户端发送Watcher事件通知,客户端接收到这个消息通知后,需要主动到服务端获取最新的数据。
“配置管理”的实际案例来展示Zookeeper在"数据发布/订阅"场景下的使用方式。
2.案例
将敏感的配置信息存放与Zookeeper中。启动项目时,自动从Zookeeper中获取。
Maven坐标:
1 <dependency> 2 <groupId>org.springframework.boot</groupId> 3 <artifactId>spring-boot-starter-web</artifactId> 4 </dependency> 5 <dependency> 6 <groupId>org.springframework.boot</groupId> 7 <artifactId>spring-boot-starter-jdbc</artifactId> 8 </dependency> 9 <dependency> 10 <groupId>org.projectlombok</groupId> 11 <artifactId>lombok</artifactId> 12 <optional>true</optional> 13 </dependency> 14 <dependency> 15 <groupId>org.apache.zookeeper</groupId> 16 <artifactId>zookeeper</artifactId> 17 <version>3.4.10</version> 18 </dependency> 19 <dependency> 20 <groupId>org.apache.curator</groupId> 21 <artifactId>curator-framework</artifactId> 22 <version>2.9.0</version> 23 </dependency> 24 <dependency> 25 <groupId>org.apache.curator</groupId> 26 <artifactId>curator-recipes</artifactId> 27 <version>2.9.0</version> 28 </dependency> 29 <dependency> 30 <groupId>org.mybatis</groupId> 31 <artifactId>mybatis</artifactId> 32 <version>3.4.3</version> 33 </dependency> 34 <dependency> 35 <groupId>org.mybatis</groupId> 36 <artifactId>mybatis-spring</artifactId> 37 <version>1.3.1</version> 38 </dependency> 39 <dependency> 40 <groupId>mysql</groupId> 41 <artifactId>mysql-connector-java</artifactId> 42 </dependency> 43 <dependency> 44 <groupId>com.zaxxer</groupId> 45 <artifactId>HikariCP</artifactId> 46 <version>2.7.4</version> 47 </dependency> 48 <dependency> 49 <groupId>org.springframework.boot</groupId> 50 <artifactId>spring-boot-starter-test</artifactId> 51 <scope>test</scope> 52 </dependency>
自动从Zookeeper中获取”配置信息“,并放入Enviroment中
配置ApplicationContextInitializer,项目在启动先调用该类,进行加载环境变量到Enviroment中去(这里采用在classpath路径下新建META-INF/spring.factories文件方式,其它两种方式为:
1.使用SpringApplication 对象,调用 addInitializers()
2.在配置文件中配置 context.initializer.classes =xx)
spring.factories文件
1 # Initializers 2 org.springframework.context.ApplicationContextInitializer=\ 3 com.example.zkconfig.config.GlobalConfigInit
GlobalConfigIn类进行一系列配置文件加载及Zk连接,我用的Curator框架
我的application.properties此刻内容
server.port=8076
#zk 配置信息
com.unconfig.info=/config/zk-config
application-dev 则对应具体连接机器
#zk com.zookeeper.url=zk://192.168.159.129:2181
从zk取配置信息放入spring环境
1 @Slf4j 2 public class GlobalConfigInit implements ApplicationContextInitializer<ConfigurableApplicationContext> { 3 4 @Override 5 public void initialize(ConfigurableApplicationContext configurableApplicationContext) { 6 ConfigurableEnvironment environment = configurableApplicationContext.getEnvironment(); 7 String zkUri = environment.getProperty("com.zookeeper.url"); 8 if (null == zkUri) { 9 log.warn("could not find zk uri,unconf not configured.."); 10 } else { 11 ZookeeperUtils zookeeperUtils = new ZookeeperUtils(); 12 zookeeperUtils.setZkUri(zkUri); 13 String unconf = environment.getProperty("com.unconfig.info"); 14 if (null != unconf) { 15 log.info("config uniconf .. {}", unconf); 16 ZkPropertySource propertySource = new ZkPropertySource("default", zookeeperUtils.getEnv(unconf)); 17 environment.getPropertySources().addLast(propertySource); 18 } else { 19 log.warn("unconf not configured.."); 20 } 21 } 22 } 23 24 public String getEnv(Environment environment) { 25 String zkUrl = environment.getProperty("com.zookeeper.url"); 26 if (null != zkUrl && !zkUrl.isEmpty()) { 27 return zkUrl; 28 } else { 29 String unConfig = environment.getProperty("com.unconfig.info"); 30 if (unConfig != null && !unConfig.isEmpty()) { 31 ZookeeperURI parse = ZookeeperURI.parse(unConfig); 32 String inferZkUrl = "zk://" + parse.getServers(); 33 System.setProperty("com.zookeeper.url", inferZkUrl); 34 log.info("inferred zk uri from unconf zk {}", inferZkUrl); 35 return inferZkUrl; 36 } else { 37 return null; 38 } 39 } 40 } 41 }
这里不再多说,接下来配置数据源。
1 @Configuration 2 @MapperScan(basePackages = {"com.example.zkconfig.dao.write"}, sqlSessionFactoryRef = "writeSqlSessionFactory") 3 public class WriteDataSourceConfig { 4 5 @Value("${write.datasource.mappers}") 6 private String location; 7 8 @Bean(name = "writeDataSource") 9 @ConfigurationProperties(prefix = "write.datasource") 10 public DataSource dataSource() { 11 return DataSourceBuilder.create().type(HikariDataSource.class).build(); 12 } 13 14 @Bean(name = "writeSqlSessionFactory") 15 public SqlSessionFactory writeSqlSessionFactory(@Qualifier("writeDataSource") DataSource dataSource) throws Exception { 16 SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); 17 sqlSessionFactoryBean.setDataSource(dataSource); 18 sqlSessionFactoryBean.setMapperLocations( 19 new PathMatchingResourcePatternResolver().getResources(location)); 20 return sqlSessionFactoryBean.getObject(); 21 } 22 23 @Bean(name = "writeTransactionManager") 24 public DataSourceTransactionManager writeTransactionManager(@Qualifier("writeDataSource") DataSource dataSource) { 25 return new DataSourceTransactionManager(dataSource); 26 } 27 }
而我实际在zookeeper节点配置的数据
测试走一波,启动成功。