[奇思异想]使用Zookeeper管理数据库连接串
背景
有一套特定规格的应用(程序+数据库),当有业务需求时,就需要多部署应用,并且所有的应用都使用一个共同的后台来管理。应用新增后,如何通知后台更新连接串成了一个关键的问题。于是就产生了使用ZooKeeper管理数据库连接串的奇思异想。具体方案如下:
1. 运维负责搭建数据库,并执行初始化脚本,然后把对应的数据库配置刷入ZooKeeper;
2. 运维完成App(1...N)的部署,App(1...N)从ZooKeeper读取对应的数据库配置;
3. 后台监听ZooKeeper,更新数据库配置到后台应用内存。
环境准备
1. 安装Zookeeper
docker pull zookeeper:3.4.13
docker run --name zookeeper -d -p 2181:2181 zookeeper:3.4.13
2. 安装Mysql
docker pull mysql:5.7
docker run --name mysql -e MYSQL_ROOT_PASSWORD=root -p 3306:3306 -d mysql:5.7
docker run --name mysql2 -e MYSQL_ROOT_PASSWORD=root -p 3307:3306 -d mysql:5.7
docker run --name mysql3 -e MYSQL_ROOT_PASSWORD=root -p 3308:3306 -d mysql:5.7
3. 初始化数据库
- CREATE DATABASE test;
- USE test;
- CREATE TABLE `table` (
- `id` int(11) NOT NULL,
- `name` varchar(50) NOT NULL,
- PRIMARY KEY (`id`)
- );
分别在各个数据库插入测试数据
mysql:
- USE test;
- INSERT INTO `table` (id, name) VALUES (1, 'A1');
- INSERT INTO `table` (id, name) VALUES (2, 'B1');
- INSERT INTO `table` (id, name) VALUES (3, 'C1');
mysql2:
- USE test;
- INSERT INTO `table` (id, name) VALUES (1, 'A2');
- INSERT INTO `table` (id, name) VALUES (2, 'B2');
- INSERT INTO `table` (id, name) VALUES (3, 'C2');
mysql3:
- USE test;
- INSERT INTO `table` (id, name) VALUES (1, 'A3');
- INSERT INTO `table` (id, name) VALUES (2, 'B3');
- INSERT INTO `table` (id, name) VALUES (3, 'C3');
4. 基于数据库生成POCO
Install-Package MySql.Data.EntityFrameworkCore -Version 8.0.13
Scaffold-DbContext "server=127.0.0.1;port=3306;user=root;password=123456;database=test" MySql.Data.EntityFrameworkCore -OutputDir DataAccess -f
5. 引用ZooKeeper相关组件
Install-Package ZooKeeperNetEx -Version 3.4.12.1
核心代码
1. ZookeeperOption:从appsettings中读取ZooKeeper相关配置
- public class ZookeeperOption
- {
- public ZookeeperOption(IConfiguration config)
- {
- if (config == null)
- {
- throw new ArgumentNullException(nameof(config));
- }
- var section = config.GetSection("zookeeper");
- section.Bind(this);
- }
- public string ConnectionString { get; set; }
- public int Timeout { get; set; }
- }
2. ZookeeperServiceCollectionExtensions:注册ZooKeeper服务
- public static class ZookeeperServiceCollectionExtensions
- {
- public static IServiceCollection AddZookeeper(this IServiceCollection services, IConfiguration config)
- {
- if (services == null)
- {
- throw new ArgumentNullException(nameof(services));
- }
- if (config == null)
- {
- throw new ArgumentNullException(nameof(config));
- }
- services.AddOptions();
- var option = new ZookeeperOption(config);
- var zookeeper = new org.apache.zookeeper.ZooKeeper(option.ConnectionString, option.Timeout * , new DefaultWatcher());
- services.Add(ServiceDescriptor.Singleton(zookeeper));
- return services;
- }
- }
- public class DefaultWatcher : Watcher
- {
- public override Task process(WatchedEvent @event)
- {
- return Task.CompletedTask;
- }
- }
3. ZookeeperHandler:ZooKeeper初始化及目录变化处理类,并把数据库连接信息写入程序内存
- public interface IZookeeperHandler
- {
- Task InitAsync();
- Task RefreshAsync();
- }
- public class ZookeeperHandler: IZookeeperHandler
- {
- private readonly org.apache.zookeeper.ZooKeeper _zooKeeper;
- private readonly IMemoryCache _cache;
- public ZookeeperHandler(org.apache.zookeeper.ZooKeeper zooKeeper, IMemoryCache cache)
- {
- _zooKeeper = zooKeeper;
- _cache = cache;
- }
- public async Task InitAsync()
- {
- await RefreshAsync();
- }
- public async Task RefreshAsync()
- {
- var connDic = new Dictionary<string, string>();
- var isExisted = await _zooKeeper.existsAsync("/connections");
- if (isExisted == null)
- {
- await _zooKeeper.createAsync("/connections", null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
- }
- var connResult = await _zooKeeper.getChildrenAsync("/connections", new ConnectionWatcher(this));
- foreach (var conn in connResult.Children)
- {
- var connData = await _zooKeeper.getDataAsync($"/connections/{conn}/value");
- var connStr = Encoding.UTF8.GetString(connData.Data);
- connDic[conn] = connStr;
- }
- _cache.Set("connections", connDic);
- }
- }
4. ConnectionWatcher:监听者,内容变化时调用ZookeeperHandler的RefreshAsync()方法,其中,变化只通知一次,因此需要再次建立监听
- public class ConnectionWatcher : Watcher
- {
- private readonly IZookeeperHandler _zookeeperService;
- public ConnectionWatcher(IZookeeperHandler zookeeperService)
- {
- _zookeeperService = zookeeperService;
- }
- public override async Task process(WatchedEvent @event)
- {
- var type = @event.get_Type();
- if (type != Event.EventType.None)
- {
- await _zookeeperService.RefreshAsync();
- }
- }
- }
5. ZookeeperApplicationBuilderExtensions:初始化
- public static class ZookeeperApplicationBuilderExtensions
- {
- public static IApplicationBuilder UseZookeeper(this IApplicationBuilder app)
- {
- var service = app.ApplicationServices.GetRequiredService<IZookeeperHandler>();
- service.InitAsync().Wait();
- return app;
- }
- }
6. ContextProvider:根据Id从内存中读取对应的数据库连接串,并提供DbContext实例
- public interface IContextProvider
- {
- TestContext GetContext(string id);
- }
- public class ContextProvider : IContextProvider
- {
- private readonly IMemoryCache _cache;
- public ContextProvider(IMemoryCache cache)
- {
- _cache = cache;
- }
- public TestContext GetContext(string id)
- {
- var dic = _cache.Get<Dictionary<string, string>>("connections");
- var connectionStr = dic[id];
- var optionsBuilder = new DbContextOptionsBuilder<TestContext>();
- optionsBuilder.UseMySQL(connectionStr);
- return new TestContext(optionsBuilder.Options);
- }
- }
效果演示
1. 刚开始没有任何连接信息
2. 添加一个连接信息
3. 查询连接对应的数据
4. 再添加两个连接信息
5. 查看ZooKeeper的信息
docker run -it --rm --link zookeeper:zookeeper zookeeper:3.4.13 zkCli.sh -server zookeeper
ls /connections
get /connections/1/value
get /connections/2/value
get /connections/3/value
补充说明
因为把数据库连接信息写到了程序内存中,因此,如果当ZooKeeper出现了故障:
1. 老的(正在运行)应用正在使用的数据库不会受到影响,但无法监听到数据库信息的变化;
2. 新的应用无法启动。
ZooKeeper恢复后:
1. 老的(正在运行)应用会重连,重新监听到数据库信息的变化
2. 新的应用可以成功启动。
源码地址
https://github.com/ErikXu/zookeeper-connection-management
[奇思异想]使用Zookeeper管理数据库连接串的更多相关文章
- [奇思异想]使用RabbitMQ实现定时任务
背景 工作中经常会有定时任务的需求,常见的做法可以使用Timer.Quartz.Hangfire等组件,这次想尝试下新的思路,使用RabbitMQ死信队列的机制来实现定时任务,同时帮助再次了解Rabb ...
- c# 扩展方法奇思妙用基础篇八:Distinct 扩展(转载)
转载地址:http://www.cnblogs.com/ldp615/archive/2011/08/01/distinct-entension.html 刚看了篇文章 <Linq的Distin ...
- c# 扩展方法奇思妙用
# 扩展方法出来已久,介绍扩展方法的文章也很多,但都是笼统的.本人最近一直在思考扩展方法的应用,也悟出了一些,准备将这最近一段时间对扩展方法的思考,写成一个系列文章.每个文章只介绍一个应用方面,篇幅不 ...
- c# 扩展方法奇思妙用基础篇八:Distinct 扩展
刚看了篇文章 <Linq的Distinct太不给力了>,文中给出了一个解决办法,略显复杂. 试想如果能写成下面的样子,是不是更简单优雅 var p1 = products.Distinct ...
- 池化技术(二)HikariCP是如何管理数据库连接的?
基于依赖程序的版本信息:HikariCP:3.3.1 驱动程序mysql-connector-java:8.0.17 上一篇:Druid是如何管理数据库连接的 零.类图和流 ...
- 池化技术(一)Druid是如何管理数据库连接的?
基于依赖程序的版本信息:druid:1.1.16 驱动程序mysql-connector-java:8.0.17 下一篇:HikariCP是如何管理数据库连接的 零.类图& ...
- c# 扩展方法奇思妙用集锦
本文转载:http://www.cnblogs.com/ldp615/archive/2009/08/07/1541404.html 其中本人觉得很经典的:c# 扩展方法奇思妙用基础篇五:Dictio ...
- Zookeeper管理多个HBase集群
zookeeper是hbase集群的"协调器".由于zookeeper的轻量级特性,因此我们可以将多个hbase集群共用一个zookeeper集群,以节约大量的服务器.多个hbas ...
- Mysql系列九:使用zookeeper管理远程Mycat配置文件、Mycat监控、Mycat数据迁移(扩容)
一.使用zookeeper管理远程Mycat配置文件 环境准备: 虚拟机192.168.152.130: zookeeper,具体参考前面文章 搭建dubbo+zookeeper+dubboadmin ...
随机推荐
- 大规模使用 Apache Kafka 的20个最佳实践
必读 | 大规模使用 Apache Kafka 的20个最佳实践 配图来源:书籍<深入理解Kafka> Apache Kafka是一款流行的分布式数据流平台,它已经广泛地被诸如New Re ...
- PHP 消息队列 详解
前言:之前做过的一些项目中有时候会接触到消息队列,但是对消息队列并没有一个很清楚的认知,本篇文章将会详细分析和归纳一些笔记,以供后续学习. 一.消息对列概念 从本质上说消息对列就是一个队列结构的中间件 ...
- 浅谈JS中逗号运算符的用法
阅读本文的前提是,你能区分什么是表达式,什么是语句.还有明确运算符和运算数都是些啥东西.所谓的表达式就是一个JavaScript的"短语",JavaScript的解释器可以计算它, ...
- selenium自动化测试python
一.环境部署 1.selenium安装 pip3 install selenium 1.安装浏览器驱动 WebDriver 需要通过浏览器驱动来与浏览器交互,以下列出几种常用的浏览器驱动下载地址: C ...
- 关于snmp octet string和普通string问题
我是获取的Octet String用String输出,输出的是一连串的2个16进制数 空格.:然后想对输出结果操作,得到我想要的值. 解决方案:private static string exchan ...
- python放弃篇(Django/爬虫)
第一篇:Django系列 第二篇:爬虫系列 待续……
- 光刻技术的原理和EUV光刻技术前景
本文转载自微信公众号 半导体技术天地, 链接 https://mp.weixin.qq.com/s/EEBkSQ_Yc8RYFO18VpO8ow
- react native( rn) 中关于navigationOptions中headerRight 获取navigation的问题 rn
使用以下代码获取navigation static navigationOptions = ({ navigation, navigationOptions }) => { const { pa ...
- 《.NET手札》
第一记 .net是一个平台,即.NET Framework平台 c#是基于.net平台的开发语言 .net的两种交互模式: B/S : 即浏览器(Browser)/服务器模式(Server) ...
- mysql的The user specified as a definer (”@’%') does not exist 的解决办法
两种可能: 1.用户权限不够 赋给用户所有权限试试 mysql> grant all privileges on *.* to root@"%" identified by ...