背景

  有一套特定规格的应用(程序+数据库),当有业务需求时,就需要多部署应用,并且所有的应用都使用一个共同的后台来管理。应用新增后,如何通知后台更新连接串成了一个关键的问题。于是就产生了使用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管理数据库连接串的更多相关文章

  1. [奇思异想]使用RabbitMQ实现定时任务

    背景 工作中经常会有定时任务的需求,常见的做法可以使用Timer.Quartz.Hangfire等组件,这次想尝试下新的思路,使用RabbitMQ死信队列的机制来实现定时任务,同时帮助再次了解Rabb ...

  2. c# 扩展方法奇思妙用基础篇八:Distinct 扩展(转载)

    转载地址:http://www.cnblogs.com/ldp615/archive/2011/08/01/distinct-entension.html 刚看了篇文章 <Linq的Distin ...

  3. c# 扩展方法奇思妙用

    # 扩展方法出来已久,介绍扩展方法的文章也很多,但都是笼统的.本人最近一直在思考扩展方法的应用,也悟出了一些,准备将这最近一段时间对扩展方法的思考,写成一个系列文章.每个文章只介绍一个应用方面,篇幅不 ...

  4. c# 扩展方法奇思妙用基础篇八:Distinct 扩展

    刚看了篇文章 <Linq的Distinct太不给力了>,文中给出了一个解决办法,略显复杂. 试想如果能写成下面的样子,是不是更简单优雅 var p1 = products.Distinct ...

  5. 池化技术(二)HikariCP是如何管理数据库连接的?

    基于依赖程序的版本信息:HikariCP:3.3.1               驱动程序mysql-connector-java:8.0.17 上一篇:Druid是如何管理数据库连接的 零.类图和流 ...

  6. 池化技术(一)Druid是如何管理数据库连接的?

    基于依赖程序的版本信息:druid:1.1.16               驱动程序mysql-connector-java:8.0.17 下一篇:HikariCP是如何管理数据库连接的 零.类图& ...

  7. c# 扩展方法奇思妙用集锦

    本文转载:http://www.cnblogs.com/ldp615/archive/2009/08/07/1541404.html 其中本人觉得很经典的:c# 扩展方法奇思妙用基础篇五:Dictio ...

  8. Zookeeper管理多个HBase集群

    zookeeper是hbase集群的"协调器".由于zookeeper的轻量级特性,因此我们可以将多个hbase集群共用一个zookeeper集群,以节约大量的服务器.多个hbas ...

  9. Mysql系列九:使用zookeeper管理远程Mycat配置文件、Mycat监控、Mycat数据迁移(扩容)

    一.使用zookeeper管理远程Mycat配置文件 环境准备: 虚拟机192.168.152.130: zookeeper,具体参考前面文章 搭建dubbo+zookeeper+dubboadmin ...

随机推荐

  1. C语言的第一堂课

    感觉茂哥讲了很多,但是有些输入的语句还是没能记住 刚讲的都是概念,看来需要看一下表格,以及C语言的基础 有些意思还不能够理解 略显尴尬 回去把C语言书的各种概念理解一下……

  2. linux环境下安装lnmp出现php安装失败

    找到lnmp1.5/include/version.sh文件打开修改 #Freetype_Ver='freetype-2.7' 为 #Freetype_Ver='freetype-2.9'即可

  3. python通过套接字来发送接收消息

    案例如下: 1.启动一个服务端套接字服务 2.启动一个客户端套接字服务 3.客户端向服务端发送一个hello,服务端则回复一个word,并打印 参考地址:https://www.cnblogs.com ...

  4. Python菜鸟快乐游戏编程_pygame(1)

    Python菜鸟快乐游戏编程_pygame(博主录制,2K分辨率,超高清) https://study.163.com/course/courseMain.htm?courseId=100618802 ...

  5. CentOS7_防火墙

    1.查看防火墙状态 firewall-cmd --state 2.启动防火墙 systemctl start firewalld.service 3.关闭防火墙 systemctl stop fire ...

  6. Springboot-async(异步)初识

    通过@Async注解实现一个简单的异步任务处理 首先,假设一个全自动化的工厂车间每天需要开启四台互不影响的机器开关来完成生产量,于是车间主任A委派“同步甲”和“异步乙”轮 流完成每天打开机器开关的任务 ...

  7. MyISAM与InnoDB的区别是什么?

    1. 存储结构 MyISAM:每个MyISAM在磁盘上存储成三个文件.第一个文件的名字以表的名字开始,扩展名指出文件类型..frm文件存储表定义.数据文件的扩展名为.MYD (MYData).索引文件 ...

  8. Python高级笔记(四) -- 多继承_方法解析顺序表MRO

    1. 多继承以及MRO顺序 1.1 单独调用父类的方法 # -*- encoding=utf-8 -*- class Parent(object): def __init__(self, name): ...

  9. file 多次上传附件功能完善

    之前解决了一个页面中的单个附件上传问题,使用的是 id 定位.但是一个页面中,可能存在多个附件上传的地方,这时候如果继续使用 id,会出问题. 我依旧会上传一个附件.附件链接地址: https://f ...

  10. Nodejs一键实现微信内打开网页url自动跳转外部浏览器访问的功能

    前言 现如今微信对第三方推广链接的审核是越来越严格了,域名在微信中分享转发经常会被拦截,一旦被拦截用户就只能复制链接手动打开浏览器粘贴才能访问,要不然就是换个域名再推,周而复始.无论是哪一种情况都会面 ...