├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── Rabbit.Zookeeper.sln ├── _config.yml ├── src ├── Rabbit.Zookeeper │ ├── Delegates.cs │ ├── IZookeeperClient.cs │ ├── Implementation │ │ ├── NodeEntry.cs │ │ └── ZookeeperClient.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── Rabbit.Zookeeper.csproj │ └── ZookeeperClientOptions.cs └── Rabbit.Zookeeper_NET4 │ ├── Properties │ └── AssemblyInfo.cs │ ├── Rabbit.Zookeeper_NET4.csproj │ └── packages.config └── tests └── Rabbit.Zookeeper.Tests ├── Properties └── AssemblyInfo.cs ├── Rabbit.Zookeeper.Tests.csproj └── ZookeeperClientTests.cs /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opendb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | *.VC.db 84 | *.VC.VC.opendb 85 | 86 | # Visual Studio profiler 87 | *.psess 88 | *.vsp 89 | *.vspx 90 | *.sap 91 | 92 | # TFS 2012 Local Workspace 93 | $tf/ 94 | 95 | # Guidance Automation Toolkit 96 | *.gpState 97 | 98 | # ReSharper is a .NET coding add-in 99 | _ReSharper*/ 100 | *.[Rr]e[Ss]harper 101 | *.DotSettings.user 102 | 103 | # JustCode is a .NET coding add-in 104 | .JustCode 105 | 106 | # TeamCity is a build add-in 107 | _TeamCity* 108 | 109 | # DotCover is a Code Coverage Tool 110 | *.dotCover 111 | 112 | # NCrunch 113 | _NCrunch_* 114 | .*crunch*.local.xml 115 | nCrunchTemp_* 116 | 117 | # MightyMoose 118 | *.mm.* 119 | AutoTest.Net/ 120 | 121 | # Web workbench (sass) 122 | .sass-cache/ 123 | 124 | # Installshield output folder 125 | [Ee]xpress/ 126 | 127 | # DocProject is a documentation generator add-in 128 | DocProject/buildhelp/ 129 | DocProject/Help/*.HxT 130 | DocProject/Help/*.HxC 131 | DocProject/Help/*.hhc 132 | DocProject/Help/*.hhk 133 | DocProject/Help/*.hhp 134 | DocProject/Help/Html2 135 | DocProject/Help/html 136 | 137 | # Click-Once directory 138 | publish/ 139 | 140 | # Publish Web Output 141 | *.[Pp]ublish.xml 142 | *.azurePubxml 143 | # TODO: Comment the next line if you want to checkin your web deploy settings 144 | # but database connection strings (with potential passwords) will be unencrypted 145 | *.pubxml 146 | *.publishproj 147 | 148 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 149 | # checkin your Azure Web App publish settings, but sensitive information contained 150 | # in these scripts will be unencrypted 151 | PublishScripts/ 152 | 153 | # NuGet Packages 154 | *.nupkg 155 | # The packages folder can be ignored because of Package Restore 156 | **/packages/* 157 | # except build/, which is used as an MSBuild target. 158 | !**/packages/build/ 159 | # Uncomment if necessary however generally it will be regenerated when needed 160 | #!**/packages/repositories.config 161 | # NuGet v3's project.json files produces more ignoreable files 162 | *.nuget.props 163 | *.nuget.targets 164 | 165 | # Microsoft Azure Build Output 166 | csx/ 167 | *.build.csdef 168 | 169 | # Microsoft Azure Emulator 170 | ecf/ 171 | rcf/ 172 | 173 | # Windows Store app package directories and files 174 | AppPackages/ 175 | BundleArtifacts/ 176 | Package.StoreAssociation.xml 177 | _pkginfo.txt 178 | 179 | # Visual Studio cache files 180 | # files ending in .cache can be ignored 181 | *.[Cc]ache 182 | # but keep track of directories ending in .cache 183 | !*.[Cc]ache/ 184 | 185 | # Others 186 | ClientBin/ 187 | ~$* 188 | *~ 189 | *.dbmdl 190 | *.dbproj.schemaview 191 | *.pfx 192 | *.publishsettings 193 | node_modules/ 194 | orleans.codegen.cs 195 | 196 | # Since there are multiple workflows, uncomment next line to ignore bower_components 197 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 198 | #bower_components/ 199 | 200 | # RIA/Silverlight projects 201 | Generated_Code/ 202 | 203 | # Backup & report files from converting an old project file 204 | # to a newer Visual Studio version. Backup files are not needed, 205 | # because we have git ;-) 206 | _UpgradeReport_Files/ 207 | Backup*/ 208 | UpgradeLog*.XML 209 | UpgradeLog*.htm 210 | 211 | # SQL Server files 212 | *.mdf 213 | *.ldf 214 | 215 | # Business Intelligence projects 216 | *.rdl.data 217 | *.bim.layout 218 | *.bim_*.settings 219 | 220 | # Microsoft Fakes 221 | FakesAssemblies/ 222 | 223 | # GhostDoc plugin setting file 224 | *.GhostDoc.xml 225 | 226 | # Node.js Tools for Visual Studio 227 | .ntvs_analysis.dat 228 | 229 | # Visual Studio 6 build log 230 | *.plg 231 | 232 | # Visual Studio 6 workspace options file 233 | *.opt 234 | 235 | # Visual Studio LightSwitch build output 236 | **/*.HTMLClient/GeneratedArtifacts 237 | **/*.DesktopClient/GeneratedArtifacts 238 | **/*.DesktopClient/ModelManifest.xml 239 | **/*.Server/GeneratedArtifacts 240 | **/*.Server/ModelManifest.xml 241 | _Pvt_Extensions 242 | 243 | # Paket dependency manager 244 | .paket/paket.exe 245 | paket-files/ 246 | 247 | # FAKE - F# Make 248 | .fake/ 249 | 250 | # JetBrains Rider 251 | .idea/ 252 | *.sln.iml 253 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Rabbit ZooKeeper Extensions 2 | 3 | The project uses the [Apache ZooKeeper .NET async Client](https://www.nuget.org/packages/ZooKeeperNetEx/) component, in addition to providing the basic zk operation, but also additional encapsulation of the commonly used functions in order to allow. Net developers to better use zookeeper. 4 | ## Features 5 | 6 | 1. session expired 7 | 2. Permanent watcher 8 | 3. Recursively delete nodes 9 | 4. Recursively create nodes 10 | 5. Cross-platform (support. Net core) 11 | 12 | ## Instructions for use 13 | ### Create connection 14 | 15 | IZookeeperClient client = new ZookeeperClient(new ZookeeperClientOptions("172.18.20.132:2181") 16 | { 17 | BasePath = "/", //default value 18 | ConnectionTimeout = TimeSpan.FromSeconds(10), //default value 19 | SessionTimeout = TimeSpan.FromSeconds(20), //default value 20 | OperatingTimeout = TimeSpan.FromSeconds(60), //default value 21 | ReadOnly = false, //default value 22 | SessionId = 0, //default value 23 | SessionPasswd = null //default value 24 | EnableEphemeralNodeRestore = true //default value 25 | }); 26 | ### Create node 27 | var data = Encoding.UTF8.GetBytes("2016"); 28 | 29 | //Fast create temporary nodes 30 | await client.CreateEphemeralAsync("/year", data); 31 | await client.CreateEphemeralAsync("/year", data, ZooDefs.Ids.OPEN_ACL_UNSAFE); 32 | 33 | //Fast create permanent nodes 34 | await client.CreatePersistentAsync("/year", data); 35 | await client.CreatePersistentAsync("/year", data, ZooDefs.Ids.OPEN_ACL_UNSAFE); 36 | 37 | //Full call 38 | await client.CreateAsync("/year", data, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT_SEQUENTIAL); 39 | 40 | //Recursively created 41 | await client.CreateRecursiveAsync("/microsoft/netcore/aspnet", data); 42 | ### Get node data 43 | IEnumerable data = await client.GetDataAsync("/year"); 44 | Encoding.UTF8.GetString(data.ToArray()); 45 | ### Get the child node 46 | IEnumerable children= await client.GetChildrenAsync("/microsoft"); 47 | ### Determine whether the node exists 48 | bool exists = await client.ExistsAsync("/year"); 49 | ### Delete the node 50 | await client.DeleteAsync("/year"); 51 | 52 | //Recursively deleted 53 | bool success = await client.DeleteRecursiveAsync("/microsoft"); 54 | ### update data 55 | Stat stat = await client.SetDataAsync("/year", Encoding.UTF8.GetBytes("2017")); 56 | ### Subscription data changes 57 | await client.SubscribeDataChange("/year", (ct, args) => 58 | { 59 | IEnumerable currentData = args.CurrentData; 60 | string path = args.Path; 61 | Watcher.Event.EventType eventType = args.Type; 62 | return Task.CompletedTask; 63 | }); 64 | ### Subscription node changes 65 | await client.SubscribeChildrenChange("/microsoft", (ct, args) => 66 | { 67 | IEnumerable currentChildrens = args.CurrentChildrens; 68 | string path = args.Path; 69 | Watcher.Event.EventType eventType = args.Type; 70 | return Task.CompletedTask; 71 | }); 72 | ## FAQ 73 | ### When will the "SubscribeDataChange" event be triggered? 74 | The event subscribed by the "SubscribeDataChange" method is triggered in the following cases: 75 | 76 | 1. The node is created 77 | 2. The node is deleted 78 | 3. Node data changes 79 | 4. zk connection re-successful 80 | 81 | ### When will the "SubscribeChildrenChange" event be triggered? 82 | The event subscribed by the "SubscribeChildrenChange" method is triggered in the following cases: 83 | 84 | 1. The node is created 85 | 2. The node is deleted 86 | 3. Node node changes 87 | 4. zk connection re-successful 88 | 89 | ### How do I distinguish the status of a node in the "xxxxChange" event? 90 | In the event trigger parameter will have a type "EventType" attribute "Type", through this attribute can clearly distinguish the reasons for node changes. 91 | 92 | ### Why write this program, it and "ZooKeeperEx" What is the difference? 93 | Officially provided components, only provide the basic api, in the normal use of the zk needs to do very complicated things, breed a lot of additional code and can not guarantee the correctness of its implementation. 94 | 95 | In the java language also has the official zk package package ZKClient, the current component is also a reference to this project. What are the specific packages that are available? Please refer to the section "Features Provided". 96 | -------------------------------------------------------------------------------- /Rabbit.Zookeeper.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26304.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{382072F7-0554-45BA-91FC-274126A3BB65}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{31979019-CD33-49AE-8F1F-1F89021C78D7}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Rabbit.Zookeeper", "src\Rabbit.Zookeeper\Rabbit.Zookeeper.csproj", "{48F4923D-B6A8-4C12-8A7B-706CCA73D465}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Rabbit.Zookeeper.Tests", "tests\Rabbit.Zookeeper.Tests\Rabbit.Zookeeper.Tests.csproj", "{621F5DAC-2FAF-4D1F-AD7E-69585BB9E86E}" 13 | EndProject 14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Rabbit.Zookeeper_NET4", "src\Rabbit.Zookeeper_NET4\Rabbit.Zookeeper_NET4.csproj", "{27CD3CC5-E3CE-4B96-AC9F-4080D6FF87FD}" 15 | EndProject 16 | Global 17 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 18 | Debug|Any CPU = Debug|Any CPU 19 | Release|Any CPU = Release|Any CPU 20 | EndGlobalSection 21 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 22 | {48F4923D-B6A8-4C12-8A7B-706CCA73D465}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {48F4923D-B6A8-4C12-8A7B-706CCA73D465}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {48F4923D-B6A8-4C12-8A7B-706CCA73D465}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {48F4923D-B6A8-4C12-8A7B-706CCA73D465}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {621F5DAC-2FAF-4D1F-AD7E-69585BB9E86E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {621F5DAC-2FAF-4D1F-AD7E-69585BB9E86E}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {621F5DAC-2FAF-4D1F-AD7E-69585BB9E86E}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {621F5DAC-2FAF-4D1F-AD7E-69585BB9E86E}.Release|Any CPU.Build.0 = Release|Any CPU 30 | {27CD3CC5-E3CE-4B96-AC9F-4080D6FF87FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {27CD3CC5-E3CE-4B96-AC9F-4080D6FF87FD}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {27CD3CC5-E3CE-4B96-AC9F-4080D6FF87FD}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {27CD3CC5-E3CE-4B96-AC9F-4080D6FF87FD}.Release|Any CPU.Build.0 = Release|Any CPU 34 | EndGlobalSection 35 | GlobalSection(SolutionProperties) = preSolution 36 | HideSolutionNode = FALSE 37 | EndGlobalSection 38 | GlobalSection(NestedProjects) = preSolution 39 | {48F4923D-B6A8-4C12-8A7B-706CCA73D465} = {382072F7-0554-45BA-91FC-274126A3BB65} 40 | {621F5DAC-2FAF-4D1F-AD7E-69585BB9E86E} = {31979019-CD33-49AE-8F1F-1F89021C78D7} 41 | {27CD3CC5-E3CE-4B96-AC9F-4080D6FF87FD} = {382072F7-0554-45BA-91FC-274126A3BB65} 42 | EndGlobalSection 43 | EndGlobal 44 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /src/Rabbit.Zookeeper/Delegates.cs: -------------------------------------------------------------------------------- 1 | using org.apache.zookeeper; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | 5 | namespace Rabbit.Zookeeper 6 | { 7 | /// 8 | /// 连接状态变更事件参数。 9 | /// 10 | public class ConnectionStateChangeArgs 11 | { 12 | /// 13 | /// 连接状态。 14 | /// 15 | public Watcher.Event.KeeperState State { get; set; } 16 | } 17 | 18 | /// 19 | /// 节点变更参数。 20 | /// 21 | public abstract class NodeChangeArgs 22 | { 23 | /// 24 | /// 创建一个新的节点变更参数。 25 | /// 26 | /// 节点路径。 27 | /// 事件类型。 28 | protected NodeChangeArgs(string path, Watcher.Event.EventType type) 29 | { 30 | Path = path; 31 | Type = type; 32 | } 33 | 34 | /// 35 | /// 变更类型。 36 | /// 37 | public Watcher.Event.EventType Type { get; private set; } 38 | 39 | /// 40 | /// 节点路径。 41 | /// 42 | public string Path { get; private set; } 43 | } 44 | 45 | /// 46 | /// 节点数据变更参数。 47 | /// 48 | public sealed class NodeDataChangeArgs : NodeChangeArgs 49 | { 50 | /// 51 | /// 创建一个新的节点数据变更参数。 52 | /// 53 | /// 节点路径。 54 | /// 事件类型。 55 | /// 最新的节点数据。 56 | public NodeDataChangeArgs(string path, Watcher.Event.EventType type, IEnumerable currentData) : base(path, type) 57 | { 58 | CurrentData = currentData; 59 | } 60 | 61 | /// 62 | /// 当前节点数据(最新的) 63 | /// 64 | public IEnumerable CurrentData { get; private set; } 65 | } 66 | 67 | /// 68 | /// 节点子节点变更参数。 69 | /// 70 | public sealed class NodeChildrenChangeArgs : NodeChangeArgs 71 | { 72 | /// 73 | /// 创建一个新的节点子节点变更参数。 74 | /// 75 | /// 节点路径。 76 | /// 事件类型。 77 | /// 最新的子节点集合。 78 | public NodeChildrenChangeArgs(string path, Watcher.Event.EventType type, IEnumerable currentChildrens) : base(path, type) 79 | { 80 | CurrentChildrens = currentChildrens; 81 | } 82 | 83 | /// 84 | /// 当前节点的子节点数据(最新的) 85 | /// 86 | public IEnumerable CurrentChildrens { get; private set; } 87 | } 88 | 89 | /// 90 | /// 节点数据变更委托。 91 | /// 92 | /// ZooKeeper客户端。 93 | /// 节点数据变更参数。 94 | public delegate Task NodeDataChangeHandler(IZookeeperClient client, NodeDataChangeArgs args); 95 | 96 | /// 97 | /// 节点子节点变更委托。 98 | /// 99 | /// ZooKeeper客户端。 100 | /// 节点子节点变更参数。 101 | public delegate Task NodeChildrenChangeHandler(IZookeeperClient client, NodeChildrenChangeArgs args); 102 | 103 | /// 104 | /// 连接状态变更委托。 105 | /// 106 | /// ZooKeeper客户端。 107 | /// 连接状态变更参数。 108 | public delegate Task ConnectionStateChangeHandler(IZookeeperClient client, ConnectionStateChangeArgs args); 109 | } -------------------------------------------------------------------------------- /src/Rabbit.Zookeeper/IZookeeperClient.cs: -------------------------------------------------------------------------------- 1 | using org.apache.zookeeper; 2 | using org.apache.zookeeper.data; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Threading.Tasks; 6 | 7 | namespace Rabbit.Zookeeper 8 | { 9 | /// 10 | /// 一个抽象的ZooKeeper客户端。 11 | /// 12 | public interface IZookeeperClient : IDisposable 13 | { 14 | /// 15 | /// 具体的ZooKeeper连接。 16 | /// 17 | ZooKeeper ZooKeeper { get; } 18 | 19 | /// 20 | /// 客户端选项。 21 | /// 22 | ZookeeperClientOptions Options { get; } 23 | 24 | /// 25 | /// 等待zk连接到具体的某一个状态。 26 | /// 27 | /// 希望达到的状态。 28 | /// 最长等待时间。 29 | /// 如果成功则返回true,否则返回false。 30 | bool WaitForKeeperState(Watcher.Event.KeeperState states, TimeSpan timeout); 31 | 32 | /// 33 | /// 重试直到zk连接上。 34 | /// 35 | /// 返回类型。 36 | /// 执行的zk操作。 37 | /// 执行结果。 38 | Task RetryUntilConnected(Func> callable); 39 | 40 | /// 41 | /// 获取指定节点的数据。 42 | /// 43 | /// 节点路径。 44 | /// 节点数据。 45 | Task> GetDataAsync(string path); 46 | 47 | /// 48 | /// 获取指定节点下的所有子节点。 49 | /// 50 | /// 节点路径。 51 | /// 子节点集合。 52 | Task> GetChildrenAsync(string path); 53 | 54 | /// 55 | /// 判断节点是否存在。 56 | /// 57 | /// 节点路径。 58 | /// 如果存在则返回true,否则返回false。 59 | Task ExistsAsync(string path); 60 | 61 | /// 62 | /// 创建节点。 63 | /// 64 | /// 节点路径。 65 | /// 节点数据。 66 | /// 权限。 67 | /// 创建模式。 68 | /// 节点路径。 69 | /// 70 | /// 因为使用序列方式创建节点zk会修改节点name,所以需要返回真正的节点路径。 71 | /// 72 | Task CreateAsync(string path, byte[] data, List acls, CreateMode createMode); 73 | 74 | /// 75 | /// 设置节点数据。 76 | /// 77 | /// 节点路径。 78 | /// 节点数据。 79 | /// 版本号。 80 | /// 节点状态。 81 | Task SetDataAsync(string path, byte[] data, int version = -1); 82 | 83 | /// 84 | /// 删除节点。 85 | /// 86 | /// 节点路径。 87 | /// 版本号。 88 | Task DeleteAsync(string path, int version = -1); 89 | 90 | /// 91 | /// 订阅节点数据变更。 92 | /// 93 | /// 节点路径。 94 | /// 监听者。 95 | Task SubscribeDataChange(string path, NodeDataChangeHandler listener); 96 | 97 | /// 98 | /// 取消订阅节点数据变更。 99 | /// 100 | /// 节点路径。 101 | /// 监听者。 102 | void UnSubscribeDataChange(string path, NodeDataChangeHandler listener); 103 | 104 | /// 105 | /// 订阅连接状态变更。 106 | /// 107 | /// 监听者。 108 | void SubscribeStatusChange(ConnectionStateChangeHandler listener); 109 | 110 | /// 111 | /// 取消订阅连接状态变更。 112 | /// 113 | /// 监听者。 114 | void UnSubscribeStatusChange(ConnectionStateChangeHandler listener); 115 | 116 | /// 117 | /// 订阅节点子节点变更。 118 | /// 119 | /// 节点路径。 120 | /// 监听者。 121 | Task> SubscribeChildrenChange(string path, NodeChildrenChangeHandler listener); 122 | 123 | /// 124 | /// 取消订阅节点子节点变更。 125 | /// 126 | /// 节点路径。 127 | /// 监听者。 128 | void UnSubscribeChildrenChange(string path, NodeChildrenChangeHandler listener); 129 | } 130 | 131 | /// 132 | /// ZooKeeper客户端扩展方法。 133 | /// 134 | public static class ZookeeperClientExtensions 135 | { 136 | /// 137 | /// 创建短暂的节点。 138 | /// 139 | /// ZooKeeper客户端。 140 | /// 节点路径。 141 | /// 节点数据。 142 | /// 是否按顺序创建。 143 | /// 节点路径。 144 | /// 145 | /// 因为使用序列方式创建节点zk会修改节点name,所以需要返回真正的节点路径。 146 | /// 147 | public static Task CreateEphemeralAsync(this IZookeeperClient client, string path, byte[] data, bool isSequential = false) 148 | { 149 | return client.CreateEphemeralAsync(path, data, ZooDefs.Ids.OPEN_ACL_UNSAFE, isSequential); 150 | } 151 | 152 | /// 153 | /// 创建短暂的节点。 154 | /// 155 | /// ZooKeeper客户端。 156 | /// 节点路径。 157 | /// 节点数据。 158 | /// 权限。 159 | /// 是否按顺序创建。 160 | /// 节点路径。 161 | /// 162 | /// 因为使用序列方式创建节点zk会修改节点name,所以需要返回真正的节点路径。 163 | /// 164 | public static Task CreateEphemeralAsync(this IZookeeperClient client, string path, byte[] data, List acls, bool isSequential = false) 165 | { 166 | return client.CreateAsync(path, data, acls, isSequential ? CreateMode.EPHEMERAL_SEQUENTIAL : CreateMode.EPHEMERAL); 167 | } 168 | 169 | /// 170 | /// 创建节点。 171 | /// 172 | /// ZooKeeper客户端。 173 | /// 节点路径。 174 | /// 节点数据。 175 | /// 是否按顺序创建。 176 | /// 节点路径。 177 | /// 178 | /// 因为使用序列方式创建节点zk会修改节点name,所以需要返回真正的节点路径。 179 | /// 180 | public static Task CreatePersistentAsync(this IZookeeperClient client, string path, byte[] data, bool isSequential = false) 181 | { 182 | return client.CreatePersistentAsync(path, data, ZooDefs.Ids.OPEN_ACL_UNSAFE, isSequential); 183 | } 184 | 185 | /// 186 | /// 创建节点。 187 | /// 188 | /// ZooKeeper客户端。 189 | /// 节点路径。 190 | /// 节点数据。 191 | /// 权限。 192 | /// 是否按顺序创建。 193 | /// 节点路径。 194 | /// 195 | /// 因为使用序列方式创建节点zk会修改节点name,所以需要返回真正的节点路径。 196 | /// 197 | public static Task CreatePersistentAsync(this IZookeeperClient client, string path, byte[] data, List acls, bool isSequential = false) 198 | { 199 | return client.CreateAsync(path, data, acls, isSequential ? CreateMode.PERSISTENT_SEQUENTIAL : CreateMode.PERSISTENT); 200 | } 201 | 202 | /// 203 | /// 递归删除该节点下的所有子节点和该节点本身。 204 | /// 205 | /// ZooKeeper客户端。 206 | /// 节点路径。 207 | /// 如果成功则返回true,false。 208 | public static async Task DeleteRecursiveAsync(this IZookeeperClient client, string path) 209 | { 210 | IEnumerable children; 211 | try 212 | { 213 | children = await client.GetChildrenAsync(path); 214 | } 215 | catch (KeeperException.NoNodeException) 216 | { 217 | return true; 218 | } 219 | 220 | foreach (var subPath in children) 221 | { 222 | if (!await client.DeleteRecursiveAsync(path + "/" + subPath)) 223 | { 224 | return false; 225 | } 226 | } 227 | await client.DeleteAsync(path); 228 | return true; 229 | } 230 | 231 | /// 232 | /// 递归创建该节点下的所有子节点和该节点本身。 233 | /// 234 | /// ZooKeeper客户端。 235 | /// 节点路径。 236 | /// 节点数据。 237 | public static Task CreateRecursiveAsync(this IZookeeperClient client, string path, byte[] data) 238 | { 239 | return client.CreateRecursiveAsync(path, data, ZooDefs.Ids.OPEN_ACL_UNSAFE); 240 | } 241 | 242 | /// 243 | /// 递归创建该节点下的所有子节点和该节点本身。 244 | /// 245 | /// ZooKeeper客户端。 246 | /// 节点路径。 247 | /// 节点数据。 248 | /// 权限。 249 | public static Task CreateRecursiveAsync(this IZookeeperClient client, string path, byte[] data, List acls) 250 | { 251 | return client.CreateRecursiveAsync(path, p => data, p => acls); 252 | } 253 | 254 | /// 255 | /// 递归创建该节点下的所有子节点和该节点本身。 256 | /// 257 | /// ZooKeeper客户端。 258 | /// 节点路径。 259 | /// 获取当前被创建节点数据的委托。 260 | /// 获取当前被创建节点权限的委托。 261 | public static async Task CreateRecursiveAsync(this IZookeeperClient client, string path, Func getNodeData, Func> getNodeAcls) 262 | { 263 | var data = getNodeData(path); 264 | var acls = getNodeAcls(path); 265 | try 266 | { 267 | await client.CreateAsync(path, data, acls, CreateMode.PERSISTENT); 268 | } 269 | catch (KeeperException.NodeExistsException) 270 | { 271 | } 272 | catch (KeeperException.NoNodeException) 273 | { 274 | var parentDir = path.Substring(0, path.LastIndexOf('/')); 275 | await CreateRecursiveAsync(client, parentDir, getNodeData, getNodeAcls); 276 | await client.CreateAsync(path, data, acls, CreateMode.PERSISTENT); 277 | } 278 | } 279 | 280 | /// 281 | /// 等待直到zk连接成功,超时时间为zk选项中的操作超时时间配置值。 282 | /// 283 | /// zk客户端。 284 | public static void WaitForRetry(this IZookeeperClient client) 285 | { 286 | client.WaitUntilConnected(client.Options.OperatingTimeout); 287 | } 288 | 289 | /// 290 | /// 等待直到zk连接成功。 291 | /// 292 | /// zk客户端。 293 | /// 最长等待时间。 294 | /// 如果成功则返回true,否则返回false。 295 | public static bool WaitUntilConnected(this IZookeeperClient client, TimeSpan timeout) 296 | { 297 | return client.WaitForKeeperState(Watcher.Event.KeeperState.SyncConnected, timeout); 298 | } 299 | } 300 | } -------------------------------------------------------------------------------- /src/Rabbit.Zookeeper/Implementation/NodeEntry.cs: -------------------------------------------------------------------------------- 1 | using org.apache.zookeeper; 2 | using org.apache.zookeeper.data; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | 8 | namespace Rabbit.Zookeeper.Implementation 9 | { 10 | internal class NodeEntry 11 | { 12 | #region Field 13 | 14 | private readonly IZookeeperClient _client; 15 | 16 | /// 17 | /// 数据变更多播委托。 18 | /// 19 | private NodeDataChangeHandler _dataChangeHandler; 20 | 21 | /// 22 | /// 子节点变更多播委托。 23 | /// 24 | private NodeChildrenChangeHandler _childrenChangeHandler; 25 | 26 | /// 27 | /// 节点的快照。 28 | /// 29 | private NodeSnapshot _localSnapshot = default(NodeSnapshot); 30 | 31 | #endregion Field 32 | 33 | #region Property 34 | 35 | public string Path { get; } 36 | 37 | #endregion Property 38 | 39 | #region Constructor 40 | 41 | public NodeEntry(string path, IZookeeperClient client) 42 | { 43 | Path = path; 44 | _client = client; 45 | } 46 | 47 | #endregion Constructor 48 | 49 | #region Public Method 50 | 51 | public async Task> GetDataAsync(bool watch = false) 52 | { 53 | var zookeeper = _client.ZooKeeper; 54 | var data = await zookeeper.getDataAsync(Path, watch); 55 | 56 | _localSnapshot.SetData(data?.Data); 57 | 58 | return data?.Data; 59 | } 60 | 61 | public async Task> GetChildrenAsync(bool watch = false) 62 | { 63 | var zookeeper = _client.ZooKeeper; 64 | var data = await zookeeper.getChildrenAsync(Path, watch); 65 | 66 | _localSnapshot.SetChildrens(data?.Children); 67 | 68 | return data?.Children; 69 | } 70 | 71 | public async Task ExistsAsync(bool watch = false) 72 | { 73 | var zookeeper = _client.ZooKeeper; 74 | var data = await zookeeper.existsAsync(Path, watch); 75 | 76 | var exists = data != null; 77 | 78 | _localSnapshot.SetExists(exists); 79 | 80 | return exists; 81 | } 82 | 83 | public async Task CreateAsync(byte[] data, List acls, CreateMode createMode) 84 | { 85 | var zooKeeper = _client.ZooKeeper; 86 | var path = await zooKeeper.createAsync(Path, data, acls, createMode); 87 | 88 | _localSnapshot.Create(createMode, data, acls); 89 | 90 | return path; 91 | } 92 | 93 | public Task SetDataAsync(byte[] data, int version = -1) 94 | { 95 | var zooKeeper = _client.ZooKeeper; 96 | var stat = zooKeeper.setDataAsync(Path, data, version); 97 | 98 | _localSnapshot.Update(data, version); 99 | 100 | return stat; 101 | } 102 | 103 | public async Task DeleteAsync(int version = -1) 104 | { 105 | var zookeeper = _client.ZooKeeper; 106 | await zookeeper.deleteAsync(Path, version); 107 | 108 | _localSnapshot.Delete(); 109 | } 110 | 111 | #region Listener 112 | 113 | public async Task SubscribeDataChange(NodeDataChangeHandler listener) 114 | { 115 | _dataChangeHandler += listener; 116 | 117 | //监控数据变化 118 | await WatchDataChange(); 119 | } 120 | 121 | public void UnSubscribeDataChange(NodeDataChangeHandler listener) 122 | { 123 | _dataChangeHandler -= listener; 124 | } 125 | 126 | public async Task> SubscribeChildrenChange(NodeChildrenChangeHandler listener) 127 | { 128 | _childrenChangeHandler += listener; 129 | 130 | //监控子节点变化 131 | return await WatchChildrenChange(); 132 | } 133 | 134 | public void UnSubscribeChildrenChange(NodeChildrenChangeHandler listener) 135 | { 136 | _childrenChangeHandler -= listener; 137 | } 138 | 139 | #endregion Listener 140 | 141 | #endregion Public Method 142 | 143 | #region Private Method 144 | 145 | /// 146 | /// 通知节点发生变化。 147 | /// 148 | /// zookeeper sdk监听事件参数。 149 | /// 是否是zk第一次连接上服务器。 150 | internal async Task OnChange(WatchedEvent watchedEvent, bool isFirstConnection) 151 | { 152 | //得到节点路径(如果只是状态发送变化则路径为null) 153 | var path = watchedEvent.getPath(); 154 | //是否是zk连接状态变更 155 | var stateChanged = path == null; 156 | 157 | //如果只是状态变更则进行状态变更处理 158 | if (stateChanged) 159 | { 160 | await OnStatusChangeHandle(watchedEvent, isFirstConnection); 161 | } 162 | else if (path == Path) //如果变化的节点属于自己 163 | { 164 | var eventType = watchedEvent.get_Type(); 165 | 166 | //是否属于数据变更 167 | var dataChanged = new[] 168 | { 169 | Watcher.Event.EventType.NodeCreated, 170 | Watcher.Event.EventType.NodeDataChanged, 171 | Watcher.Event.EventType.NodeDeleted 172 | }.Contains(eventType); 173 | 174 | if (dataChanged) 175 | { 176 | //如果子节点刚刚被创建并且该节点有注册子节点变更监听,则通知zk进行子节点监听(延迟监听) 177 | if (eventType == Watcher.Event.EventType.NodeCreated && HasChildrenChangeHandler) 178 | await _client.RetryUntilConnected(() => GetChildrenAsync(true)); 179 | 180 | //进行数据变更处理 181 | await OnDataChangeHandle(watchedEvent); 182 | } 183 | else 184 | { 185 | //进行子节点变更处理 186 | await OnChildrenChangeHandle(watchedEvent); 187 | } 188 | } 189 | } 190 | 191 | /// 192 | /// 是否有数据变更处理者。 193 | /// 194 | private bool HasDataChangeHandler => HasHandler(_dataChangeHandler); 195 | 196 | /// 197 | /// 是否有子节点变更处理者。 198 | /// 199 | private bool HasChildrenChangeHandler => HasHandler(_childrenChangeHandler); 200 | 201 | /// 202 | /// 状态变更处理。 203 | /// 204 | /// 205 | /// 是否是zk第一次连接上服务器。 206 | private async Task OnStatusChangeHandle(WatchedEvent watchedEvent, bool isFirstConnection) 207 | { 208 | //第一次连接zk不进行通知 209 | if (isFirstConnection) 210 | return; 211 | 212 | //尝试恢复节点 213 | await RestoreEphemeral(); 214 | 215 | if (HasDataChangeHandler) 216 | await OnDataChangeHandle(watchedEvent); 217 | if (HasChildrenChangeHandler) 218 | await OnChildrenChangeHandle(watchedEvent); 219 | } 220 | 221 | private async Task OnDataChangeHandle(WatchedEvent watchedEvent) 222 | { 223 | if (!HasDataChangeHandler) 224 | return; 225 | 226 | //获取当前节点最新数据的一个委托 227 | var getCurrentData = new Func>>(() => _client.RetryUntilConnected(async () => 228 | { 229 | try 230 | { 231 | return await GetDataAsync(); 232 | } 233 | catch (KeeperException.NoNodeException) //节点不存在返回null 234 | { 235 | return null; 236 | } 237 | })); 238 | 239 | //根据事件类型构建节点变更事件参数 240 | NodeDataChangeArgs args; 241 | switch (watchedEvent.get_Type()) 242 | { 243 | case Watcher.Event.EventType.NodeCreated: 244 | args = new NodeDataChangeArgs(Path, Watcher.Event.EventType.NodeCreated, await getCurrentData()); 245 | break; 246 | 247 | case Watcher.Event.EventType.NodeDeleted: 248 | args = new NodeDataChangeArgs(Path, Watcher.Event.EventType.NodeDeleted, null); 249 | break; 250 | 251 | case Watcher.Event.EventType.NodeDataChanged: 252 | case Watcher.Event.EventType.None: //重连时触发 253 | args = new NodeDataChangeArgs(Path, Watcher.Event.EventType.NodeDataChanged, await getCurrentData()); 254 | break; 255 | 256 | default: 257 | throw new NotSupportedException($"不支持的事件类型:{watchedEvent.get_Type()}"); 258 | } 259 | 260 | await _dataChangeHandler(_client, args); 261 | 262 | //重新监听 263 | await WatchDataChange(); 264 | } 265 | 266 | private async Task OnChildrenChangeHandle(WatchedEvent watchedEvent) 267 | { 268 | if (!HasChildrenChangeHandler) 269 | return; 270 | 271 | //获取当前节点最新的子节点信息 272 | var getCurrentChildrens = new Func>>(() => _client.RetryUntilConnected( 273 | async () => 274 | { 275 | try 276 | { 277 | return await GetChildrenAsync(); 278 | } 279 | catch (KeeperException.NoNodeException) 280 | { 281 | return null; 282 | } 283 | })); 284 | 285 | //根据事件类型构建节点子节点变更事件参数 286 | NodeChildrenChangeArgs args; 287 | switch (watchedEvent.get_Type()) 288 | { 289 | case Watcher.Event.EventType.NodeCreated: 290 | args = new NodeChildrenChangeArgs(Path, Watcher.Event.EventType.NodeCreated, 291 | await getCurrentChildrens()); 292 | break; 293 | 294 | case Watcher.Event.EventType.NodeDeleted: 295 | args = new NodeChildrenChangeArgs(Path, Watcher.Event.EventType.NodeDeleted, null); 296 | break; 297 | 298 | case Watcher.Event.EventType.NodeChildrenChanged: 299 | case Watcher.Event.EventType.None: //重连时触发 300 | args = new NodeChildrenChangeArgs(Path, Watcher.Event.EventType.NodeChildrenChanged, 301 | await getCurrentChildrens()); 302 | break; 303 | 304 | default: 305 | throw new NotSupportedException($"不支持的事件类型:{watchedEvent.get_Type()}"); 306 | } 307 | 308 | await _childrenChangeHandler(_client, args); 309 | 310 | //重新监听 311 | await WatchChildrenChange(); 312 | } 313 | 314 | private async Task WatchDataChange() 315 | { 316 | await _client.RetryUntilConnected(() => ExistsAsync(true)); 317 | } 318 | 319 | private async Task> WatchChildrenChange() 320 | { 321 | return await _client.RetryUntilConnected(async () => 322 | { 323 | await ExistsAsync(true); 324 | try 325 | { 326 | return await GetChildrenAsync(true); 327 | } 328 | catch (KeeperException.NoNodeException) 329 | { 330 | } 331 | return null; 332 | }); 333 | } 334 | 335 | private static bool HasHandler(MulticastDelegate multicast) 336 | { 337 | return multicast != null && multicast.GetInvocationList().Any(); 338 | } 339 | 340 | private async Task RestoreEphemeral() 341 | { 342 | //没有开启恢复 343 | if (!_client.Options.EnableEphemeralNodeRestore) 344 | return; 345 | 346 | //节点不存在 347 | if (!_localSnapshot.IsExist) 348 | return; 349 | 350 | //不是短暂的节点 351 | if (_localSnapshot.Mode != CreateMode.EPHEMERAL && _localSnapshot.Mode != CreateMode.EPHEMERAL_SEQUENTIAL) 352 | return; 353 | 354 | try 355 | { 356 | await _client.RetryUntilConnected(async () => 357 | { 358 | try 359 | { 360 | return await CreateAsync(_localSnapshot.Data?.ToArray(), _localSnapshot.Acls, _localSnapshot.Mode); 361 | } 362 | catch (KeeperException.NodeExistsException) //节点已经存在则忽略 363 | { 364 | return Path; 365 | } 366 | }); 367 | } 368 | catch (Exception exception) 369 | { 370 | Console.WriteLine($"恢复节点失败,异常:{exception.Message}"); 371 | } 372 | } 373 | 374 | #endregion Private Method 375 | 376 | #region Help Type 377 | 378 | public struct NodeSnapshot 379 | { 380 | public bool IsExist { get; set; } 381 | public CreateMode Mode { get; set; } 382 | public IEnumerable Data { get; set; } 383 | public int? Version { get; set; } 384 | public List Acls { get; set; } 385 | public IEnumerable Childrens { get; set; } 386 | 387 | public void Create(CreateMode mode, byte[] data, List acls) 388 | { 389 | IsExist = true; 390 | Mode = mode; 391 | Data = data; 392 | Version = -1; 393 | Acls = acls; 394 | Childrens = null; 395 | } 396 | 397 | public void Update(IEnumerable data, int version) 398 | { 399 | IsExist = true; 400 | Data = data; 401 | Version = version; 402 | } 403 | 404 | public void Delete() 405 | { 406 | IsExist = false; 407 | Mode = null; 408 | Data = null; 409 | Version = null; 410 | Acls = null; 411 | Childrens = null; 412 | } 413 | 414 | public void SetData(IEnumerable data) 415 | { 416 | IsExist = true; 417 | Data = data; 418 | } 419 | 420 | public void SetChildrens(IEnumerable childrens) 421 | { 422 | IsExist = true; 423 | Childrens = childrens; 424 | } 425 | 426 | public void SetExists(bool exists) 427 | { 428 | if (!exists) 429 | { 430 | Delete(); 431 | return; 432 | } 433 | IsExist = true; 434 | } 435 | } 436 | 437 | #endregion Help Type 438 | } 439 | } -------------------------------------------------------------------------------- /src/Rabbit.Zookeeper/Implementation/ZookeeperClient.cs: -------------------------------------------------------------------------------- 1 | using org.apache.zookeeper; 2 | using org.apache.zookeeper.data; 3 | using System; 4 | using System.Collections.Concurrent; 5 | using System.Collections.Generic; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | 9 | #if !NET40 10 | 11 | using TaskEx = System.Threading.Tasks.Task; 12 | 13 | #endif 14 | 15 | namespace Rabbit.Zookeeper.Implementation 16 | { 17 | /// 18 | /// ZooKeeper客户端。 19 | /// 20 | public class ZookeeperClient : Watcher, IZookeeperClient 21 | { 22 | #region Field 23 | 24 | private readonly ConcurrentDictionary _nodeEntries = 25 | new ConcurrentDictionary(); 26 | 27 | private ConnectionStateChangeHandler _connectionStateChangeHandler; 28 | 29 | private Event.KeeperState _currentState; 30 | private readonly AutoResetEvent _stateChangedCondition = new AutoResetEvent(false); 31 | 32 | private readonly object _zkEventLock = new object(); 33 | 34 | private bool _isDispose; 35 | 36 | #endregion Field 37 | 38 | #region Constructor 39 | 40 | /// 41 | /// 创建一个新的ZooKeeper客户端。 42 | /// 43 | /// 连接字符串。 44 | /// 为空。 45 | public ZookeeperClient(string connectionString) 46 | : this(new ZookeeperClientOptions(connectionString)) 47 | { 48 | } 49 | 50 | /// 51 | /// 创建一个新的ZooKeeper客户端。 52 | /// 53 | /// 客户端选项。 54 | public ZookeeperClient(ZookeeperClientOptions options) 55 | { 56 | Options = options; 57 | ZooKeeper = CreateZooKeeper(); 58 | } 59 | 60 | #endregion Constructor 61 | 62 | #region Public Method 63 | 64 | /// 65 | /// 具体的ZooKeeper连接。 66 | /// 67 | public ZooKeeper ZooKeeper { get; private set; } 68 | 69 | /// 70 | /// 客户端选项。 71 | /// 72 | public ZookeeperClientOptions Options { get; } 73 | 74 | /// 75 | /// 等待zk连接到具体的某一个状态。 76 | /// 77 | /// 希望达到的状态。 78 | /// 最长等待时间。 79 | /// 如果成功则返回true,否则返回false。 80 | public bool WaitForKeeperState(Event.KeeperState states, TimeSpan timeout) 81 | { 82 | var stillWaiting = true; 83 | while (_currentState != states) 84 | { 85 | if (!stillWaiting) 86 | { 87 | return false; 88 | } 89 | 90 | stillWaiting = _stateChangedCondition.WaitOne(timeout); 91 | } 92 | return true; 93 | } 94 | 95 | /// 96 | /// 重试直到zk连接上。 97 | /// 98 | /// 返回类型。 99 | /// 执行的zk操作。 100 | /// 执行结果。 101 | public async Task RetryUntilConnected(Func> callable) 102 | { 103 | var operationStartTime = DateTime.Now; 104 | while (true) 105 | { 106 | try 107 | { 108 | return await callable(); 109 | } 110 | catch (KeeperException.ConnectionLossException) 111 | { 112 | #if NET40 113 | await TaskEx.Yield(); 114 | #else 115 | await Task.Yield(); 116 | #endif 117 | this.WaitForRetry(); 118 | } 119 | catch (KeeperException.SessionExpiredException) 120 | { 121 | #if NET40 122 | await TaskEx.Yield(); 123 | #else 124 | await Task.Yield(); 125 | #endif 126 | this.WaitForRetry(); 127 | } 128 | if (DateTime.Now - operationStartTime > Options.OperatingTimeout) 129 | { 130 | throw new TimeoutException($"Operation cannot be retried because of retry timeout ({Options.OperatingTimeout.TotalMilliseconds} milli seconds)"); 131 | } 132 | } 133 | } 134 | 135 | /// 136 | /// 获取指定节点的数据。 137 | /// 138 | /// 节点路径。 139 | /// 节点数据。 140 | public async Task> GetDataAsync(string path) 141 | { 142 | path = GetZooKeeperPath(path); 143 | 144 | var nodeEntry = GetOrAddNodeEntry(path); 145 | return await RetryUntilConnected(async () => await nodeEntry.GetDataAsync()); 146 | } 147 | 148 | /// 149 | /// 获取指定节点下的所有子节点。 150 | /// 151 | /// 节点路径。 152 | /// 子节点集合。 153 | public async Task> GetChildrenAsync(string path) 154 | { 155 | path = GetZooKeeperPath(path); 156 | 157 | var nodeEntry = GetOrAddNodeEntry(path); 158 | return await RetryUntilConnected(async () => await nodeEntry.GetChildrenAsync()); 159 | } 160 | 161 | /// 162 | /// 判断节点是否存在。 163 | /// 164 | /// 节点路径。 165 | /// 如果存在则返回true,否则返回false。 166 | public async Task ExistsAsync(string path) 167 | { 168 | path = GetZooKeeperPath(path); 169 | 170 | var nodeEntry = GetOrAddNodeEntry(path); 171 | return await RetryUntilConnected(async () => await nodeEntry.ExistsAsync()); 172 | } 173 | 174 | /// 175 | /// 创建节点。 176 | /// 177 | /// 节点路径。 178 | /// 节点数据。 179 | /// 权限。 180 | /// 创建模式。 181 | /// 节点路径。 182 | /// 183 | /// 因为使用序列方式创建节点zk会修改节点name,所以需要返回真正的节点路径。 184 | /// 185 | public async Task CreateAsync(string path, byte[] data, List acls, CreateMode createMode) 186 | { 187 | path = GetZooKeeperPath(path); 188 | 189 | var nodeEntry = GetOrAddNodeEntry(path); 190 | return await RetryUntilConnected(async () => await nodeEntry.CreateAsync(data, acls, createMode)); 191 | } 192 | 193 | /// 194 | /// 设置节点数据。 195 | /// 196 | /// 节点路径。 197 | /// 节点数据。 198 | /// 版本号。 199 | /// 节点状态。 200 | public async Task SetDataAsync(string path, byte[] data, int version = -1) 201 | { 202 | path = GetZooKeeperPath(path); 203 | 204 | var nodeEntry = GetOrAddNodeEntry(path); 205 | return await RetryUntilConnected(async () => await nodeEntry.SetDataAsync(data, version)); 206 | } 207 | 208 | /// 209 | /// 删除节点。 210 | /// 211 | /// 节点路径。 212 | /// 版本号。 213 | public async Task DeleteAsync(string path, int version = -1) 214 | { 215 | path = GetZooKeeperPath(path); 216 | 217 | var nodeEntry = GetOrAddNodeEntry(path); 218 | await RetryUntilConnected(async () => 219 | { 220 | await nodeEntry.DeleteAsync(version); 221 | return 0; 222 | }); 223 | } 224 | 225 | /// 226 | /// 订阅节点数据变更。 227 | /// 228 | /// 节点路径。 229 | /// 监听者。 230 | public async Task SubscribeDataChange(string path, NodeDataChangeHandler listener) 231 | { 232 | path = GetZooKeeperPath(path); 233 | 234 | var node = GetOrAddNodeEntry(path); 235 | await node.SubscribeDataChange(listener); 236 | } 237 | 238 | /// 239 | /// 取消订阅节点数据变更。 240 | /// 241 | /// 节点路径。 242 | /// 监听者。 243 | public void UnSubscribeDataChange(string path, NodeDataChangeHandler listener) 244 | { 245 | path = GetZooKeeperPath(path); 246 | 247 | var node = GetOrAddNodeEntry(path); 248 | node.UnSubscribeDataChange(listener); 249 | } 250 | 251 | /// 252 | /// 订阅连接状态变更。 253 | /// 254 | /// 监听者。 255 | public void SubscribeStatusChange(ConnectionStateChangeHandler listener) 256 | { 257 | _connectionStateChangeHandler += listener; 258 | } 259 | 260 | /// 261 | /// 取消订阅连接状态变更。 262 | /// 263 | /// 监听者。 264 | public void UnSubscribeStatusChange(ConnectionStateChangeHandler listener) 265 | { 266 | _connectionStateChangeHandler -= listener; 267 | } 268 | 269 | /// 270 | /// 订阅节点子节点变更。 271 | /// 272 | /// 节点路径。 273 | /// 监听者。 274 | public async Task> SubscribeChildrenChange(string path, NodeChildrenChangeHandler listener) 275 | { 276 | path = GetZooKeeperPath(path); 277 | 278 | var node = GetOrAddNodeEntry(path); 279 | return await node.SubscribeChildrenChange(listener); 280 | } 281 | 282 | /// 283 | /// 取消订阅节点子节点变更。 284 | /// 285 | /// 节点路径。 286 | /// 监听者。 287 | public void UnSubscribeChildrenChange(string path, NodeChildrenChangeHandler listener) 288 | { 289 | path = GetZooKeeperPath(path); 290 | 291 | var node = GetOrAddNodeEntry(path); 292 | node.UnSubscribeChildrenChange(listener); 293 | } 294 | 295 | #endregion Public Method 296 | 297 | #region Overrides of Watcher 298 | 299 | /// Processes the specified event. 300 | /// The event. 301 | /// 302 | public override async Task process(WatchedEvent watchedEvent) 303 | { 304 | if (_isDispose) 305 | return; 306 | 307 | var path = watchedEvent.getPath(); 308 | if (path == null) 309 | { 310 | await OnConnectionStateChange(watchedEvent); 311 | } 312 | else 313 | { 314 | NodeEntry nodeEntry; 315 | if (!_nodeEntries.TryGetValue(path, out nodeEntry)) 316 | return; 317 | await nodeEntry.OnChange(watchedEvent, false); 318 | } 319 | } 320 | 321 | #endregion Overrides of Watcher 322 | 323 | #region Implementation of IDisposable 324 | 325 | /// 执行与释放或重置非托管资源关联的应用程序定义的任务。 326 | public void Dispose() 327 | { 328 | if (_isDispose) 329 | return; 330 | _isDispose = true; 331 | 332 | lock (_zkEventLock) 333 | { 334 | TaskEx.Run(async () => 335 | { 336 | await ZooKeeper.closeAsync().ConfigureAwait(false); 337 | }).ConfigureAwait(false).GetAwaiter().GetResult(); 338 | } 339 | } 340 | 341 | #endregion Implementation of IDisposable 342 | 343 | #region Private Method 344 | 345 | private bool _isFirstConnectioned = true; 346 | 347 | private async Task OnConnectionStateChange(WatchedEvent watchedEvent) 348 | { 349 | if (_isDispose) 350 | return; 351 | 352 | var state = watchedEvent.getState(); 353 | SetCurrentState(state); 354 | 355 | if (state == Event.KeeperState.Expired) 356 | { 357 | await ReConnect(); 358 | } 359 | else if (state == Event.KeeperState.SyncConnected) 360 | { 361 | if (_isFirstConnectioned) 362 | { 363 | _isFirstConnectioned = false; 364 | } 365 | else 366 | { 367 | foreach (var nodeEntry in _nodeEntries) 368 | { 369 | await nodeEntry.Value.OnChange(watchedEvent, true); 370 | } 371 | } 372 | } 373 | 374 | _stateChangedCondition.Set(); 375 | if (_connectionStateChangeHandler == null) 376 | return; 377 | await _connectionStateChangeHandler(this, new ConnectionStateChangeArgs 378 | { 379 | State = state 380 | }); 381 | } 382 | 383 | private NodeEntry GetOrAddNodeEntry(string path) 384 | { 385 | return _nodeEntries.GetOrAdd(path, k => new NodeEntry(path, this)); 386 | } 387 | 388 | private ZooKeeper CreateZooKeeper() 389 | { 390 | return new ZooKeeper(Options.ConnectionString, (int)Options.SessionTimeout.TotalMilliseconds, this, Options.SessionId, Options.SessionPasswd, Options.ReadOnly); 391 | } 392 | 393 | private async Task ReConnect() 394 | { 395 | if (!Monitor.TryEnter(_zkEventLock, Options.ConnectionTimeout)) 396 | return; 397 | try 398 | { 399 | if (ZooKeeper != null) 400 | { 401 | try 402 | { 403 | await ZooKeeper.closeAsync(); 404 | } 405 | catch 406 | { 407 | // ignored 408 | } 409 | } 410 | ZooKeeper = CreateZooKeeper(); 411 | } 412 | finally 413 | { 414 | Monitor.Exit(_zkEventLock); 415 | } 416 | } 417 | 418 | private void SetCurrentState(Event.KeeperState state) 419 | { 420 | lock (this) 421 | { 422 | _currentState = state; 423 | } 424 | } 425 | 426 | private string GetZooKeeperPath(string path) 427 | { 428 | var basePath = Options.BasePath ?? "/"; 429 | 430 | if (!basePath.StartsWith("/")) 431 | basePath = basePath.Insert(0, "/"); 432 | 433 | basePath = basePath.TrimEnd('/'); 434 | 435 | if (!path.StartsWith("/")) 436 | path = path.Insert(0, "/"); 437 | 438 | path = $"{basePath}{path.TrimEnd('/')}"; 439 | return string.IsNullOrEmpty(path) ? "/" : path; 440 | } 441 | 442 | #endregion Private Method 443 | } 444 | } -------------------------------------------------------------------------------- /src/Rabbit.Zookeeper/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyConfiguration("")] 9 | [assembly: AssemblyCompany("")] 10 | [assembly: AssemblyProduct("Rabbit.Zookeeper")] 11 | [assembly: AssemblyTrademark("")] 12 | 13 | // Setting ComVisible to false makes the types in this assembly not visible 14 | // to COM components. If you need to access a type in this assembly from 15 | // COM, set the ComVisible attribute to true on that type. 16 | [assembly: ComVisible(false)] 17 | 18 | // The following GUID is for the ID of the typelib if this project is exposed to COM 19 | [assembly: Guid("48f4923d-b6a8-4c12-8a7b-706cca73d465")] 20 | -------------------------------------------------------------------------------- /src/Rabbit.Zookeeper/Rabbit.Zookeeper.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 支持断线重连,永久watcher,递归删除,递归创建等常用操作的,ZooKeeper客户端。 5 | ©RabbitHub. All rights reserved. 6 | Apache ZooKeeper .NET async Client Extensions 7 | majian 8 | netstandard1.6;net45 9 | true 10 | Rabbit.Zookeeper 11 | Rabbit.Zookeeper 12 | RabbitHub;Rabbit;Zookeeper;ZKClient;ZookeeperClient 13 | 1.优化接口的调用 14 | 2.支持短暂类型节点的恢复 15 | http://images.cnblogs.com/cnblogs_com/ants/862383/o_icon.png 16 | https://github.com/RabbitTeam/zookeeper-client 17 | https://github.com/RabbitTeam/zookeeper-client/blob/master/LICENSE 18 | git 19 | https://github.com/RabbitTeam/zookeeper-client 20 | $(PackageTargetFallback);dnxcore50 21 | 1.6.0 22 | false 23 | false 24 | false 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | $(DefineConstants);NET 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/Rabbit.Zookeeper/ZookeeperClientOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Rabbit.Zookeeper 4 | { 5 | /// 6 | /// ZooKeeper客户端选项。 7 | /// 8 | public class ZookeeperClientOptions 9 | { 10 | /// 11 | /// 创建一个新的ZooKeeper客户端选项。 12 | /// 13 | /// 14 | /// 为10秒。 15 | /// 为20秒。 16 | /// 为60秒。 17 | /// 为false。 18 | /// 为0。 19 | /// 为null。 20 | /// 为null。 21 | /// 为true。 22 | /// 23 | public ZookeeperClientOptions() 24 | { 25 | ConnectionTimeout = TimeSpan.FromSeconds(10); 26 | SessionTimeout = TimeSpan.FromSeconds(20); 27 | OperatingTimeout = TimeSpan.FromSeconds(60); 28 | ReadOnly = false; 29 | SessionId = 0; 30 | SessionPasswd = null; 31 | EnableEphemeralNodeRestore = true; 32 | } 33 | 34 | /// 35 | /// 创建一个新的ZooKeeper客户端选项。 36 | /// 37 | /// 连接字符串。 38 | /// 为空。 39 | /// 40 | /// 为10秒。 41 | /// 为20秒。 42 | /// 为60秒。 43 | /// 为false。 44 | /// 为0。 45 | /// 为null。 46 | /// 为null。 47 | /// 为true。 48 | /// 49 | public ZookeeperClientOptions(string connectionString) : this() 50 | { 51 | if (string.IsNullOrEmpty(connectionString)) 52 | throw new ArgumentNullException(nameof(connectionString)); 53 | 54 | ConnectionString = connectionString; 55 | } 56 | 57 | /// 58 | /// 连接字符串。 59 | /// 60 | public string ConnectionString { get; set; } 61 | 62 | /// 63 | /// 等待ZooKeeper连接的时间。 64 | /// 65 | public TimeSpan ConnectionTimeout { get; set; } 66 | 67 | /// 68 | /// 执行ZooKeeper操作的重试等待时间。 69 | /// 70 | public TimeSpan OperatingTimeout { get; set; } 71 | 72 | /// 73 | /// zookeeper会话超时时间。 74 | /// 75 | public TimeSpan SessionTimeout { get; set; } 76 | 77 | /// 78 | /// 是否只读,默认为false。 79 | /// 80 | public bool ReadOnly { get; set; } 81 | 82 | /// 83 | /// 会话Id。 84 | /// 85 | public long SessionId { get; set; } 86 | 87 | /// 88 | /// 会话密码。 89 | /// 90 | public byte[] SessionPasswd { get; set; } 91 | 92 | /// 93 | /// 基础路径,会在所有的zk操作节点路径上加入此基础路径。 94 | /// 95 | public string BasePath { get; set; } 96 | 97 | /// 98 | /// 是否启用短暂类型节点的恢复。 99 | /// 100 | public bool EnableEphemeralNodeRestore { get; set; } 101 | } 102 | } -------------------------------------------------------------------------------- /src/Rabbit.Zookeeper_NET4/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Rabbit.Zookeeper_NET4")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Rabbit.Zookeeper_NET4")] 13 | [assembly: AssemblyCopyright("Copyright © 2017")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("27cd3cc5-e3ce-4b96-ac9f-4080d6ff87fd")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /src/Rabbit.Zookeeper_NET4/Rabbit.Zookeeper_NET4.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {27CD3CC5-E3CE-4B96-AC9F-4080D6FF87FD} 8 | Library 9 | Properties 10 | Rabbit.Zookeeper_NET4 11 | Rabbit.Zookeeper_NET4 12 | v4.5.2 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | 24 | 25 | pdbonly 26 | true 27 | bin\Release\ 28 | TRACE 29 | prompt 30 | 4 31 | 32 | 33 | 34 | 35 | 36 | ..\..\packages\ZooKeeperNetEx.3.4.9.2\lib\net45\ZooKeeperNetEx.dll 37 | 38 | 39 | 40 | 41 | 42 | Implementation\NodeEntry.cs 43 | 44 | 45 | Implementation\ZookeeperClient.cs 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /src/Rabbit.Zookeeper_NET4/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /tests/Rabbit.Zookeeper.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyConfiguration("")] 8 | [assembly: AssemblyCompany("")] 9 | [assembly: AssemblyProduct("Rabbit.Zookeeper.Tests")] 10 | [assembly: AssemblyTrademark("")] 11 | 12 | // Setting ComVisible to false makes the types in this assembly not visible 13 | // to COM components. If you need to access a type in this assembly from 14 | // COM, set the ComVisible attribute to true on that type. 15 | [assembly: ComVisible(false)] 16 | 17 | // The following GUID is for the ID of the typelib if this project is exposed to COM 18 | [assembly: Guid("621f5dac-2faf-4d1f-ad7e-69585bb9e86e")] -------------------------------------------------------------------------------- /tests/Rabbit.Zookeeper.Tests/Rabbit.Zookeeper.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp1.1 5 | Rabbit.Zookeeper.Tests 6 | Rabbit.Zookeeper.Tests 7 | true 8 | 1.6.0 9 | $(PackageTargetFallback);dnxcore50 10 | 1.0.4 11 | false 12 | false 13 | false 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /tests/Rabbit.Zookeeper.Tests/ZookeeperClientTests.cs: -------------------------------------------------------------------------------- 1 | using org.apache.zookeeper; 2 | using Rabbit.Zookeeper.Implementation; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | using Xunit; 10 | 11 | namespace Rabbit.Zookeeper.Tests 12 | { 13 | public class ZookeeperClientTests 14 | { 15 | private readonly IZookeeperClient _client; 16 | 17 | public ZookeeperClientTests() 18 | { 19 | Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); 20 | _client = new ZookeeperClient(new ZookeeperClientOptions("172.18.20.132:2181") 21 | { 22 | SessionTimeout = TimeSpan.FromSeconds(20), 23 | OperatingTimeout = TimeSpan.FromSeconds(30) 24 | }); 25 | } 26 | 27 | [Fact] 28 | public async Task GetChildrenAsyncTest() 29 | { 30 | var childrens = await _client.GetChildrenAsync("/"); 31 | 32 | Assert.NotNull(childrens); 33 | 34 | Assert.True(childrens.Any()); 35 | 36 | childrens = await _client.GetChildrenAsync("/ApiRouteRoot"); 37 | Assert.NotNull(childrens); 38 | 39 | Assert.True(childrens.Any()); 40 | } 41 | 42 | [Fact] 43 | public async Task ExistsAsyncTest() 44 | { 45 | var result = await _client.ExistsAsync("/"); 46 | Assert.True(result); 47 | } 48 | 49 | [Fact] 50 | public async Task GetDataAsyncTest() 51 | { 52 | var data = await _client.GetDataAsync("/"); 53 | Assert.NotNull(data); 54 | 55 | data = await _client.GetDataAsync("/chanelInfo"); 56 | Assert.NotNull(data); 57 | } 58 | 59 | [Fact] 60 | public async Task ReconnectionTest() 61 | { 62 | Assert.True(await _client.ExistsAsync("/")); 63 | await Task.Delay(TimeSpan.FromSeconds(8)); 64 | Assert.True(await _client.ExistsAsync("/")); 65 | } 66 | 67 | [Fact] 68 | public async Task CreateTest() 69 | { 70 | var path = $"/{Guid.NewGuid():N}"; 71 | 72 | if (await _client.ExistsAsync(path)) 73 | await _client.DeleteAsync(path); 74 | 75 | await _client.CreateEphemeralAsync(path, Encoding.UTF8.GetBytes("abc")); 76 | 77 | var data = (await _client.GetDataAsync(path)).ToArray(); 78 | Assert.Equal("abc", Encoding.UTF8.GetString(data)); 79 | await _client.DeleteAsync(path); 80 | } 81 | 82 | [Fact] 83 | public async Task DeleteTest() 84 | { 85 | var path = $"/{Guid.NewGuid():N}"; 86 | 87 | if (await _client.ExistsAsync(path)) 88 | { 89 | await _client.DeleteAsync(path); 90 | } 91 | else 92 | { 93 | await _client.CreateEphemeralAsync(path, null); 94 | await Task.Delay(1000); 95 | if (await _client.ExistsAsync(path)) 96 | await _client.DeleteAsync(path); 97 | else 98 | Assert.True(false, "创建节点失败"); 99 | } 100 | Assert.False(await _client.ExistsAsync(path)); 101 | } 102 | 103 | [Fact] 104 | public async Task SubscribeDataChangeTest() 105 | { 106 | var path = $"/{DateTime.Now:yyyy_MM_dd_HH_mm_ss_ff}"; 107 | try 108 | { 109 | if (await _client.ExistsAsync(path)) 110 | await _client.DeleteAsync(path); 111 | 112 | var types = new List(); 113 | var waitEvent = new AutoResetEvent(false); 114 | 115 | await _client.SubscribeDataChange(path, (client, args) => 116 | { 117 | types.Add(args.Type); 118 | waitEvent.Set(); 119 | return Task.CompletedTask; 120 | }); 121 | 122 | //created 123 | await _client.CreateEphemeralAsync(path, null); 124 | waitEvent.WaitOne(10000); 125 | Assert.Equal(Watcher.Event.EventType.NodeCreated, types[0]); 126 | 127 | //modify 128 | await _client.SetDataAsync(path, new byte[] { 1 }); 129 | waitEvent.WaitOne(10000); 130 | Assert.Equal(Watcher.Event.EventType.NodeDataChanged, types[1]); 131 | 132 | //deleted 133 | await _client.DeleteAsync(path); 134 | waitEvent.WaitOne(10000); 135 | Assert.Equal(Watcher.Event.EventType.NodeDeleted, types[2]); 136 | } 137 | finally 138 | { 139 | if (await _client.ExistsAsync(path)) 140 | await _client.DeleteAsync(path); 141 | } 142 | } 143 | 144 | [Fact] 145 | public async Task SubscribeChildrenChangeTest() 146 | { 147 | var path = $"/{DateTime.Now:yyyy_MM_dd_HH_mm_ss_ff}"; 148 | var path2 = $"{path}/123"; 149 | try 150 | { 151 | if (await _client.ExistsAsync(path)) 152 | await _client.DeleteRecursiveAsync(path); 153 | 154 | var types = new List(); 155 | 156 | var semaphore = new Semaphore(0, 2); 157 | 158 | await _client.SubscribeDataChange(path, (client, args) => 159 | { 160 | if (args.Type == Watcher.Event.EventType.NodeCreated) 161 | semaphore.Release(); 162 | return Task.CompletedTask; 163 | }); 164 | await _client.SubscribeChildrenChange(path, (client, args) => 165 | { 166 | types.Add(args.Type); 167 | semaphore.Release(); 168 | return Task.CompletedTask; 169 | }); 170 | 171 | await _client.CreatePersistentAsync(path, null); 172 | semaphore.WaitOne(10000); 173 | await _client.CreatePersistentAsync(path2, null); 174 | semaphore.WaitOne(10000); 175 | Assert.Equal(Watcher.Event.EventType.NodeChildrenChanged, types[0]); 176 | } 177 | finally 178 | { 179 | if (await _client.ExistsAsync(path)) 180 | await _client.DeleteRecursiveAsync(path); 181 | } 182 | } 183 | 184 | /* [Fact] 185 | public async Task ReconnectionDataChangeTest() 186 | { 187 | var path = $"/{DateTime.Now:yyyy_MM_dd_HH_mm_ss_ff}"; 188 | 189 | if (await _client.ExistsAsync(path)) 190 | await _client.DeleteRecursiveAsync(path); 191 | 192 | await _client.CreateEphemeralAsync(path, null); 193 | 194 | var isChange = false; 195 | 196 | await _client.SubscribeDataChange(path, (client, args) => 197 | { 198 | if (args.Type == NodeListenerType.DataChanged) 199 | isChange = true; 200 | return Task.CompletedTask; 201 | }); 202 | 203 | await Task.Delay(TimeSpan.FromSeconds(15)); 204 | 205 | Assert.True(isChange); 206 | }*/ 207 | 208 | [Fact] 209 | public async Task UnSubscribeTest() 210 | { 211 | var path = $"/{DateTime.Now:yyyy_MM_dd_HH_mm_ss_ff}"; 212 | 213 | var count = 0; 214 | 215 | var waitEvent = new AutoResetEvent(false); 216 | NodeDataChangeHandler handler = (client, args) => 217 | { 218 | count++; 219 | waitEvent.Set(); 220 | return Task.CompletedTask; 221 | }; 222 | 223 | await _client.SubscribeDataChange(path, handler); 224 | 225 | await _client.CreateEphemeralAsync(path, null); 226 | 227 | waitEvent.WaitOne(10000); 228 | Assert.Equal(1, count); 229 | 230 | _client.UnSubscribeDataChange(path, handler); 231 | 232 | await _client.DeleteAsync(path); 233 | 234 | Assert.Equal(1, count); 235 | } 236 | 237 | [Fact] 238 | public async Task CreateRecursiveAndDeleteRecursiveTest() 239 | { 240 | var pathRoot = $"/{DateTime.Now:yyyy_MM_dd_HH_mm_ss_ff}"; 241 | var path = $"{pathRoot}/1/2"; 242 | if (await _client.ExistsAsync(pathRoot)) 243 | await _client.DeleteRecursiveAsync(pathRoot); 244 | 245 | await _client.CreateRecursiveAsync(path, null); 246 | Assert.True(await _client.ExistsAsync(path)); 247 | 248 | await _client.DeleteRecursiveAsync(pathRoot); 249 | if (await _client.ExistsAsync(pathRoot)) 250 | throw new Exception("删除失败"); 251 | } 252 | } 253 | } --------------------------------------------------------------------------------