在ubuntu上部署CouchBase集群,在.Net Core 项目中使用
一、添加集群
首先在两台机器上安装CouchBase,现在我的两台机器IP为10.10.10.126和10.10.10.175
安装好之后,访问http://10.10.10.126:8091去新建Cluster ZYY1,如下:
现在可以看到,我的ZYY1只有一个节点;
二、添加节点并重新平衡
访问http://10.10.10.175:8091,如下:
选择加入已存在集群
这里报端口无法侦听,我用另外一种方式添加,在http://10.10.10.126:8091/ui/index.html#!/servers/list上,添加Server
添加后报错如下
查看后发现4369端口没有配置,加入后:
头疼了,百度了很多也没找到具体解决方案。
这里的原因是两台不同主机上的Docker无法互相通信。解决方案参考:https://www.cnblogs.com/liyuanhong/articles/5851251.html
这里需要执行重新平衡,请单击右上角的“ 重新平衡”按钮:
如下图,加入节点成功:
三、服务
Couchbase Server提供以下服务:
-
Data(数据):支持通过密钥指定的数据项的存储,设置和检索。
-
Query(查询):解析N1QL查询语言中指定的查询,执行查询并返回结果。查询服务与数据和索引服务交互。
-
Index(索引):创建索引,供查询和分析服务使用。
-
Search(搜索):创建专门用于全文搜索的索引。这支持语言感知搜索;允许用户搜索,例如单词
beauties
,并另外获得beauty
和的结果beautiful
。 -
Analytics(分析):支持联接,设置,聚合和分组操作;预计它们将很大,运行时间长且消耗大量的内存和CPU资源。
-
Eventing(事件处理):支持对数据更改进行近乎实时的处理:代码既可以响应文档变异,也可以按计时器安排执行。
通过多维扩展,可以相互独立地部署,维护和配置这些服务,以确保对不断变化的业务条件和紧急工作量需求做出最有效的持续响应。
服务是在每个节点的基础上设置的。每个节点最多可以运行一个服务实例。某些服务是相互依赖的,因此需要每个依赖项的至少一个实例在群集上运行(例如, 查询服务依赖于索引服务和数据服务)。初始化集群中的第一个节点时,为其分配的服务将 成为随后要添加到集群中的每个其他节点的默认分配。服务分配应基于工作量分析进行设计:如果期望特定服务处理繁重的工作量,则应为其分配更大的内存配额,并有可能作为节点上的唯一服务。
例如,下图显示了如何在开发集群的五个节点上平均分配四个服务(数据,索引,查询和搜索):
跨硬件资源灵活部署Couchbase Services的能力支持多维扩展,从而可以对集群进行微调,以最佳地处理紧急工作量需求。例如,如果遇到更大的搜索工作量要求,则可以删除一个或多个现有的非搜索节点,将其重新配置为运行搜索服务,然后重新添加到群集中。或者,可以将其他硬件资源(CPU,内存,磁盘容量)添加到群集中的目标节点,以支持关键服务的性能。这种提供服务的能力彼此独立,从而可以根据需要分别上下扩展其性能,从而在处理不断变化的业务需求以及重新部署现有资源以确保持续提高效率方面提供了最大的灵活性。
下面是我参考上图架构部署的我的CouchBase集群,如下图:
四、在.Net Core中使用CouchBase Server
开始使用之前,需要在NeGet安装CouchbaseNetClient1.基础操作
下面链接CouchBase服务器:
using Couchbase; using Couchbase.Authentication; using Couchbase.Configuration.Client; using Couchbase.Core; var cluster = new Cluster(new ClientConfiguration { Servers = new List<Uri> { new Uri("http://10.10.10.175:10000"), new Uri("http://10.10.10.175:10010"), new Uri("http://10.10.10.175:10020"), new Uri("http://10.10.10.175:10030"), new Uri("http://10.10.10.175:10040")} }); cluster.Authenticate(new PasswordAuthenticator("Administrator", "9op0(OP)")); services.AddSingleton<IBucket>(x => cluster.OpenBucket("TestZYY"));
运行时,出现如下错误:
有点懵,查了官方的文档,没找到什么问题!!!!
最后找到一个靠谱的:https://stackoverflow.com/questions/55598077/docker-aspnet-core-couchbase# 我是在另外的机器使用Docker安装的多个CouchBase,Docker内部分配了不同的子ip. 如果直接使用主机安装CouchBase,就没有这个问题了.
新建一个类Profile
public class Profile
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Type => "Profile";
}
下面我们在ProfileRepository中实现查找、更新、删除等操作
public class ProfileRepository { private readonly IBucket _bucket; public ProfileRepository(IBucket bucket) { _bucket = bucket; } public Dictionary<string, Profile> GetAll() { var request = QueryRequest.Create(@" SELECT META().id AS `key`, TOOBJECT(sb) as `value` FROM `TestZYY` as sb WHERE type='Profile';"); request.ScanConsistency(ScanConsistency.RequestPlus); var response = _bucket.Query<KeyValuePair<string, Profile>>(request); return response.Rows.ToDictionary(x => x.Key, x => x.Value); } public KeyValuePair<string, Profile> GetProfileByKey(string key) { var profile = _bucket.Get<Profile>(key).Value; return new KeyValuePair<string, Profile>(key, profile); } public void Save(KeyValuePair<string, Profile> model) { var doc = new Document<Profile> { Id = model.Key, Content = model.Value }; _bucket.Upsert(doc); } public void Delete(string key) { _bucket.Remove(key); } } // end::class[] }
在Couchbase中,使用以下操作之一存储文档:Upsert,Insert和Replace。这些操作都会将具有给定文档ID(键)的JSON文档写入数据库。对于文档的现有状态,更新方法在行为上有所不同:
-
如果在数据库中找不到给定的ID,Insert只会创建文档。
-
如果给定的ID已存在于数据库中,则Replace仅将替换文档。
-
Upsert将始终替换文档,而忽略该ID是否已存在。
可以使用get操作来检索文档,最后使用remove操作将其删除。 下面为几种更新操作的解释代码
Dictionary<string,object> kvStore;
void Insert(string docId, object value) {
if (!kvStore.ContainsKey(docId)) {
kvStore.Add(docId, value);
} else
{
throw new DocumentAlreadyExistsException();
}
}
void Replace(string docId, object value) {
if (kvStore.ContainsKey(docId)) {
kvStore.Add(docId, value);
} else {
throw new DocumentDoesNotExistException();
}
}
void Upsert(string docId, object value) {
kvStore.Add(docId, value);
}
object Get(string docId) {
if (kvStore.ContainsKey(docId)) {
return kvStore[docId];
} else {
throw new DocumentDoesNotExistException();
}
}
2.并发文档出冲突
Document属性:
名称 | 描述 |
---|---|
|
(每个存储桶)文档的唯一标识符。 |
|
文件的实际内容。 |
|
文档的CAS(比较和交换)值。 |
|
文档的到期时间。 |
|
突变后的可选MutationToken。 |
Cas(Compare And Swap)值是一个比较关键的值,每一次操作更新文档,CAS值都会发生更改。
服务器端CAS值实现如下:
uint Replace(string docid, object newvalue, uint oldCas=0) {
object existing = this.kvStore.get(docid);
if (!existing) {
throw DocumentDoesNotExist();
} else if (oldCas != 0 && oldCas != existing.cas) {
throw CasMismatch();
}
uint newCas = ++existing.cas;
existing.value = newValue;
return newCas;
}
假设以下两个代码块在不同的应用程序实例中同时执行:
进程1:
var result = bucket.Get<dynamic>("docid");
var new_doc = result.Value;
new_doc.field1 = "value1";
bucket.Replace("docid", new_doc);
进程2:
var result = buckt.Get<dynamic>("docid");
var new_doc = result.Value;
new_doc.field2 = "value2";
bucket.Replace("docid", new_doc);
检索文档:var result = bucket.Get<dynamic>("docid").Value;
将得到如下:
{“ field2”:“ value2”,“ a_field”:“ a_value”}
请注意field1
,即使应用程序将其插入到文档中也不存在。原因是因为线程2上的替换碰巧在线程1上的替换之后运行,但是线程1的替换是在线程2的获取之后执行的:由于线程2上文档的本地版本不包含field1( (因为线程#1的更新尚未存储在服务器上),因此通过执行替换,它实际上将覆盖线程1执行的替换。
在前面的示例中,我们看到对同一文档的并发更新可能会导致某些更新丢失。这不是因为Couchbase本身丢失了更新,而是因为应用程序没有意识到对该文档进行的更新,并且无意中覆盖了它们。
我们应该使用如下代码
var result = bucket.Get<dynamic>("docid");
var new_doc = result.Value;
var cur_cas = result.Cas;
new_doc.field1 = "value1";
var new_result = bucket.Replace("docid", new_doc, cas);
如果更新时候的文档CAS值与服务器上的CAS值不同,将会提示:服务器上的CAS不同。
参考文档:https://docs.couchbase.com/server/6.5/introduction/intro.html
https://docs.couchbase.com/dotnet-sdk/2.7/start-using-sdk.html