用Mnesia为cache增加分布式支持

一:分布式缓存

1.选取通信策略

在设计分布式程序时,可供选择的通信方式主要有两种:异步通信和同步通信。采用异步通信时,发送方无须等待任何确认或应答。而在采用同步通信时,发送方会处于挂起状态,直至收到回复为止((即便只是“收到,多谢”之类的确认性回复)。Erlang消息传递的基本形式就是异步的,因为这种形式最为简单灵活:一般来说异步通信更适合分布式编程,而且同步通信总可以利用成对的异步请求/响应消息来模拟(gen_server:call/3就是这样做的)。

(1)异步通信:

异步通信有时也被称为“即发即忘”(fire and forget)或“即发即盼”(send and pray)式通信。消息一上路,发送方便撒手不管,继续干活。如果预期远端进程应该给出应答,发送方随后会伺机检查应答消息,如下图所示。一般来说,能否在指定时间内收到应答并不影响发送方
后续的工作,至少部分工作不会受到影响。

 

 即发即忘的异步通信:消息一发完,发送方便撒手不管,继续干活。应答消息将被单独发回。

异步通信的开销很低,是计算机系统间一种良好的基本通信形式。由于省去了各种检查、扫描、验证、计时等杂务,异步通信非常之快,尤其适用于创建简单而直观的系统。我们的建议是,除非万不得已,否则请尽量采用异步通信。 

(2)同步通信:

在同步通信中,每条消息都需要一个应答(哪怕只是一条用于确认消息送达的回执)。在收应答之前发送方会被挂起,什么也做不了。由于发送方在等待应答的过程中处于阻塞状态,这通信策略又被称做阻塞式通信。典型的同步信息交换过程如下图所示:

 同步阻塞式通信:在收到应答之前,发送方会被挂起。即使发送方的后续工作并不严格依赖于该应答,也会被迫中断,中断时长不短于消息往返一个来回所需的时间

同步通信最显著的缺陷就是在收到响应之前发送方什么都做不了(在分布式环境下,这段时间至少是网络中两台计算机之间消息传递时延的两倍)。另一方面,它的优势也很明显,那就是可以轻易地让系统在某一活动中保持同步。

2. 同步缓存和异步缓存

(1)同步缓存:同步模式提供的一致性保障与异步模式有所不同。在这种模式下,只有在所有缓存实例都拿到会话数据之后,系统才会告知用户登录成功。也就是说执行插入操作的函数必须先收到所有缓存实例的确认消息才能进行下一步动作。如图:

(2)异步缓存:不保证插入操作完成时系统状态的一致性并不意味着插人操作会频繁出错或是执行速度很慢一只是无法得到百分之百的一致性保障罢了。总体来看,服务的整体状态有可能会出现临时的不一致,而且在某个较低的概率下用户有可能会感知到这种不一致。如图所示:

但总的来说,其实就是同步缓存能保持一致,却得等全部完成才能进行下一步,而异步缓存虽有可能出现缓存不是百分百一致,但却可以各完成各的。

 3.分布式表

在分布式模式中,每一张表都需要进行映射,因为不同模块上的表内数据需要同步,这样才能进行在不同服务器直接实现访问和调用,主要映射关系如下图所示:

二: 用Mnesia实现分布式数据存储

Mnesia是一套轻量级的软实时分布式数据存储系统,支持冗余复制和事务,特别适合于存储离散的Erlang数据块,尤其擅长RAM中的数据存储。Mnesia天生支持Erlang,Erlang?数据无须任何格式转换便可原封不动地存人其中。有鉴于此,它自然就成为了缓存应用首选的数据库方案。

1.建立项目数据库

在Mnesia中,表项可由普通Erlang记录定义。我们可以用下面代码罗列:

%% 项目数据库的记录定义
-record(user, {id, name}).
-record(project, {title, description}).
-record(contributor, {user_id, title}).

建立数据库主要分为以下几个步骤:

  1. 初始化 Mnesia
  2. 启动节点
  3. 建立数据库模式
  4. 启动 Mnesia
  5. 建立数据库表
  6. 向新建的表中录入数据
  7. 对数据做一些基本查询

 2初始化数据库

(1)启动节点:在使用Mnesial时,请按如下方式启动Erlang节点:

erl -mnesia dir '"/tmp/mnesia_store"'-name mynode

(2) 建立数据库模式:只需要在本地节点上建立数据库模式即可:

(ming@erlware.org)1>mnesia:create_schema ([node()]).

(3) 启动Mnesia:

调用mnesia:start()便可手动启动Mnesia。Mnesia运行起来之后,可以调用mnesia:info()来核实数据库的基本信息,如数据库中现存多少张表,当前与多少个节点相连等:

(ming@derlware.org)2>mnesia:start ()
ok
(ming@erlware.org)3>mnesia:info().
---> Processes holding locks <---
---> Processes waiting for locks <---
---> Participant transactions <---
---> Coordinator transactions <---
---> Uncertain transactions <---
---> Active tables <---
schema    : with 1    records occupying 422    words of mem
===> System info in version "4.4.8", debug level none <===
opt_disc. Directory "/tmp/mnesia"is used.
use fallback at restart = false
running db nodes   = [mynode@erlware.org]
stopped db node s  = []
master node tables = []
remote             = []
ram_copies         = []
disc_copies        = [schema]
disc_only_copies   = []
[{ming@erlware.org,disc_copies}] = [schema]
2 transactions committed,0 aborted,0 restarted,0 logged to disc
0 held locks,0 in queue;0 local transactions,0 remote
0 transactions waits for other nodes: []
ok

用这种方法可以很方便地核实线上系统的配置,例如各节点间的全连通状况及一切是否配置
完好等。现在数据库系统已经初始化完毕,可以开始编写应用代码了,第一步是建表。

3.建表

建表操作完全可以直接在Erlang shell中进行,但由于shell对记录的支持很有限,这样做会有点儿别扭。可以用下面代码所示:

mnesia:create_table(Name, Options).-record(user, {id, name}).
mnesia:create_table(user, [{attributes, record_info(fields, user)}, {type, bag}]).mnesia:write(#user{id=Id, name=Name}).
mnasia:read(user, Id).
mnesia:transaction(Fun).
mnesia:dirty_write(#user{id=Id, name=Name}).%% record_info/2 不是真正意义上的函数,它只在编译器有效(和记录语法中的#一样),在运行期或在 Erlang shell 中无法调用它。其他的默认选项:
1、表既可读也可写
2、表仅驻留于 RAM 中
3、表中存储的记录与表同名
4、表的类型为 set
5、加载优先级为0
6、local_content 标记被置为 falseMnesia 表类型:
1、set
2、ordered_set
3、bagMnesia 存储类型:
1、ram_copies
2、disc_copies
3、disc_only_copies    % 不支持ordered_set不同节点上的表可以有不同的存储类型,甚至支持运行时修改。

Options 是一张 {Name, Value} 选项列表,在所有选项之中,最重要的一个是 attributes,该选项用于指定表中所存记录的字段名。如果没有它,Mnesia 会假定记录中仅有两个字段,分别为 key 和 val。表的主键永远都是记录的第一个字段。

不过,为了更好理解,我们还是打算编写一个小模块来完成这项工作,代码如下:

%% Mnesia建表模块
-record(user, {id, name}).
-record(project, {title, description}).
-record(contributor, {user_id, title}).init_tables()->mnesia:create_table(user, [{attributes, record_info(fields, user)}]),mnesia:create_table(project, [(attributes, record_info(fields,project)}]),mnesia:create_table(contributor,                        [{type, bag}, {attributes, record_info(fields, contributor)}]).

4.向表中录入数据

其他人在插入数据时是没有必要了解表的详情的,这些细节应该由API函数隐藏起来。添加API函数的同时也多出了一个校验机会,你可以在插人数据之前对数据进行一些一致性检查。比如说,新添加的用户至少要参与一个项目,并且不允许将用户加为尚不存在的项目的参与人。添加用户和项目的代码如下所示:

%% 数据插人函数
insert_user(Id, Name, ProjectTitles) when ProjectTitles =/= [] ->User = #user(idId,nameName},Fun = fun() ->%% 向表中写入用户记录mnesia:write(User),lists:foreach(fun(Title) ->[#project(title = Title)] = mnesia:read(project, Title),%% 插入参与人记录mnesia:write(#contributor{user_id = Id, project_title = Title})end,ProjectTitles)end,mnesia:transaction(Fun).%% 设置事务
insert_project(Title, Description) ->mnesia:dirty_write(#project{title = Title, description = Description}).

5.查询

QLC是一套通用查询接口,适用于ETS表、Mnesia表等各种具有表的特征的东西。通过实现相应的QLC适配器,甚至可以将QLC用在自定义的表结构上。在使用QLC之前,首先要用
mnesia:table(TableName)函数建立一个Mnesia表句柄,该句柄将被用作QLC的输人参数。然后,就可以用普通的列表速构语法来实现各种过滤和聚合操作了。例如,我们可以这样做:

mnesia:select(user, [{#user{id='$1', name=zh}, [], ['$1']}]).{Head, Condition, Results}
Condition 罗列作用于该匹配条件上的额外约束条件
Result 描述要从匹配到的每条记录中生成什么样的结果项式'_'    仅限于在 Head 部分使用,无所谓,任意值都可以
'$_'   仅限于在 Result 和 Condition 中使用,与查询条件相匹配的整条记录
'$$'   仅限于在 Result 和 Condition 中使用,等价于依此罗列出在 Head 部分匹配的所有变量

具体操作列如下面所示:

mnesia:transaction(fun() ->Table = mnesia:table(user),QueryHandle = qlc:q([U#user.id || U <- Table, U#user.name =:= martin]),qlc:eval(QueryHandle)end)

相较于select和匹配规范,QLC是一套更为优雅的查询接口。就可读性而言上述代码比起之前的版本要清晰得多,代码的目的一目了然:首先从Mnesia的user表中找出所有U#user,name等于martin的用户记录;然后从其中的每个记录u中取出U#user.id,形成最终的结果列表。

三:基于Mnesia的分布式缓存

学习了Mnesia的基础知识,现在总算可以开始开发了。要想让设计中的缓存正常运转,还有以下工作要做:

(1)用Mnesial取代ETS;
(2)让缓存能够识别出其他节点,从而进行必要的通信;
(3)让缓存具备资源探测能力;
(4)动态复制Mnesia表。

1.用Mnesia取代ETS

我们可以编写以下模块:

 sc_store模块关键函数有四个:
(1)init/0
 (2)insert/2
(3)lookup/1
(4)delete/1 

首先是用于设置ETS的init/0,现在你得用它来设置Mnesia表。我们后续还要进一步改造该函数,以便封装和冗余复制相关的逻辑;这些暂且放在一边,先把表建好再说。

(1)编写init/0:

init()->mnesia:start(),mesia:create_table(key_to_pid,[{index,[pid]},{attributes, record_info(fields, key_to_pid)}]).

注:这是一张驻留在RAM中的普通set型表,键不可重复

(2) 编写insert/2:

insert(Key,Pid) ->mnesia:dirty_write(#key_to_pid{key = Key, pid = Pid}).

注:在对表进行更新时我们用的是dirty._write.。在这里Mnesia只负责最简单的键值存储,根本用不到事务。之所以选用Mnesia,主要是考虑到它的冗余复制功能。

(3)编写lookup/1:

lookup(Key)->case mnesia:dirty_read(key_to_pid, Key) of[{key_to_pid,Key,Pid)] -> {ok, Pid};[] -> {error, not_found}end.

注:由于是set型表,结果中最多只有一条记录,用dirty_read就够了。

(4)编写delete/1:

delete(Pid) ->%% 按pid查询表项case mnesia:dirty_index_read(key_to_pid,Pid, #key_to_pid.pid) of[#key_to_pid{} = Record] -> mnesia:dirty_delete_object(Record);%% 查询出错也应返回ok_ ->okend.

注:在执行删除操作时可能会出现两种情况:要么键原本就不存在(可能已经被删掉了),要么
键存在并被成功删除。无论是哪种情况,都应该返回σk。

2.让缓存识别出其他节点

在该方案下新节点将通过两个长期运行的空白Erlang节点来加人预先约定的集群。这两个节点不执行任何用户代码(因而几乎永远不会宕机)。你只需要启动它们,给它们分配恰当的节点名,并设置好用于集群认证的cookie就可以了:

erl -name contactl -setcookie xxxxxxxx
erl -name contact2 -setcookie xxxxxxxx

新启动的缓存节点将按事先配置的节点名去pig这两个节点,如图所示:

 3.用资源探测定位其他缓存实例

你需要建立应用的目录结构,并编写相应的.app文件、_app模块,和sup模块(用于启动资源探测服务器)。全部搞定之后,应该得到两个目录结构并列的应用,如下所示:

lib|- simple_cache|- src|- ebin|- resource_discovery|- src|- ebin|- ...

此外,还需要给simple_cache增加两个依赖项,即resource_discoverymnesia.代码如下:

%% 编写后的simple_cache.app文件
{application,simple_cache,[{description,"A simple caching system"},{vsn, "0.3.0"},{modules, [simple_cache,sc_app,sc_sup,sc_element_sup,sc_store,sc_element,sc_event,sc_event_logger]},{registered, [sc_sup]},%% 加入了新的依赖项{applications, [kernel,sasl,stdlib,mnesia,resource_discovery]),{mod, {sc_app,[]}}
]}.

最后一步就是向集群中的其余节点发起资源交换请求,接着耐心等待一段时间,直至资源信息交换完毕(这套资源探测系统具有较强的异步性,因而等待是必要的):

resource_discovery:trade_resources(),
timer:sleep (?WAIT_FOR_RESOURCES),

很明显,这段代码也应该加到sc_app:start/2函数中,位置紧随ensure_contact()调用
之后。代码如下:

%% 资源探测相关的修改(sc_app.erl)
-define(WAIT_FOR_RESOURCES,2500).start(_StartType, _StartArgs) ->ok = ensure_contact(),resource_discovery:add_local_resource(simple_cache,node()),resource_discovery:add_target_resource_type(simple_cache),resource_discovery:trade_resources(),timer:sleep (?WAIT_FOR_RESOURCES),sc_store:init(),case sc_sup:start_link()of{ok, Pid} ->{ok, Pid};Error ->Errorend.

4.动态复制Mnesia表

前面初始化节点什么的就不再过多描述了,直接上代码:

%% 连接其他Mnesia节点并
-define(WAIT_FOR_TABLES,5000).add_extra_nodes([Node T]) ->case mnesia:change_config(extra_db_nodes,[Node])of{ok,[Node]} ->%% 用远程数据库的模式替换本地模式mnesia:add_table_copy(schema,node(), ram_copies),mnesia:add_table_copy(key_to_pid,node(), ram_copies),Tables = mnesia:system_info(tables),mnesia:wait_for_tables(Tables, ?WAIT_FOR_TABLES);_ ->%% 继续尝试其他节点add_extra_nodes(T)end.

这样就已经实现了用Mnesia实现分布式数据存储。测试时记得启动几个它所依赖的应用:

1> application:start(sasl).
ok2> mnesia:start().
ok3> application:start(resource_discovery).
ok4> application:start(simple_cache).
ok

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/203228.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Stable Diffusion AI绘画系列【17】:绘本童话风格场景

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推…

【南京站-EI会议征稿中】第三届网络安全、人工智能与数字经济国际学术会议(CSAIDE 2024)

第三届网络安全、人工智能与数字经济国际学术会议&#xff08;CSAIDE 2024&#xff09; 2024 3rd International Conference on Cyber Security, Artificial Intelligence and Digital Economy 第三届网络安全、人工智能与数字经济国际学术会议&#xff08;CSAIDE 2024&…

制作蓝牙小车

制作控制蓝牙小车app 想制作一个蓝牙小车&#xff0c;通过手机app程序操控小车运行&#xff0c;制作分三个部分&#xff08;app制作&#xff0c;蓝牙小车硬件制作&#xff0c;小车程序制作&#xff09;&#xff0c;先完成第一个部分app制作&#xff0c;本次app是通过androidstu…

MongoDB知识总结

这里写自定义目录标题 MongoDB基本介绍MongoDB基本操作数据库相关集合相关增删改查 MongoDB基本介绍 简单介绍 MongoDB是一个基于分布式文件存储的数据库。由C语言编写。旨在为WEB应用提供可扩展的高性能数据存储解决方案。 MongoDB是一个介于关系数据库和非关系数据库之间的产…

【Hive】——数据仓库

1.1 数仓概念 数据仓库&#xff08;data warehouse&#xff09;&#xff1a;是一个用于存储&#xff0c;分析&#xff0c;报告的数据系统 目的&#xff1a;是构建面向分析的集成化数据环境&#xff0c;分析结果为企业提供决策支持 特点&#xff1a; 数据仓库本身不产生任何数据…

Spring Boot学习随笔-SpringBoot的引言,回顾传统SSM开发

学习视频&#xff1a;【编程不良人】2021年SpringBoot最新最全教程 第一章、传统SSM开发回顾以及问题 Spring SpringMVC Mybatis SSM 实现一个简单功能 员工添加、查询… SSM项目简单实现 项目 需求分析 —>概要设计 —>&#xff08;库表设计&#xff09; —> 详细…

从零开始的c语言日记day40——字符函数和字符串函数——内存函数

常用函数介绍 求字符串长度 strlen 长度不受限制的字符串函数 Strcpy Strcat strcmp 长度受限制的字符串函数介绍 strncpy strncat strncmp 字符串查找 Strstro strtok 错误信息报告 strerror 字符操作 内存操作函数 memcpy memmove memset Memcmp 使用Asser…

点击el-tree小三角后去除点击后的高亮背景样式,el-tree样式修改

<div class"videoTree" v-loading"loadingTree" element-loading-text"加载中..." element-loading-spinner"el-icon-loading" element-loading-background"rgba(0, 0, 0, 0.8)" > <el-tree :default-expand-all&q…

鸿蒙4.0开发笔记之ArkTS语法基础之应用生命周期与页面中组件的生命周期(十六)

文章目录 一、应用生命周期二、生命周期函数定义三、生命周期五函数练习 一、应用生命周期 1、定义 应用生命周期就是代表了一个HarmonyOS应用中所有页面从创建、开启到销毁等过程的全生命周期。查看路径如下&#xff1a; Project/entry/src/main/ets/entryability/EntryAbili…

17、XSS——session攻击

文章目录 一、session攻击简介二、主要攻击方式2.1 预测2.2 会话劫持2.3 会话固定 一、session攻击简介 session对于web应用是最重要&#xff0c;也是最复杂的。对于web应用程序来说&#xff0c;加强安全性的首要原则就是&#xff1a;不要信任来自客户端的数据&#xff0c;一定…

Spring Boot与Mybatis基础配置(手动写增删改查)

一、 配置 1.新建项目 1.项目基础配置 解释&#xff1a;记得把这个改成start.aliyun.com要不没有java8也就是jdk1.8 2.项目依赖配置 2.配置maven 配置前&#xff1a; 配置后&#xff1a; 3.创建子项目并配置父子项目pom.xml 配置父pom.xml 声明当前项目不是要打成jar包的…

NFC和蓝牙在物联网中有什么意义?如何选择?

#NFC物联网# #蓝牙物联网# 在物联网中&#xff0c;NFC和蓝牙有什么意义&#xff1f; NFC在物联网中代表近场通信技术。它是一种短距离、高频的无线通信技术&#xff0c;可以在近距离内实现设备间的数据传输和识别。NFC技术主要用于移动支付、电子票务、门禁、移动身份识别、防…

利用阿里云 DDoS、WAF、CDN 和云防火墙为在线业务赋能

在这篇博客中&#xff0c;我们将详细讨论使用阿里云 CDN 和安全产品保护您的在线业务所需的步骤。 方案描述 创新技术的快速发展为世界各地的在线业务带来了新的机遇。今天的人们不仅习惯了&#xff0c;而且依靠互联网来开展他们的日常生活&#xff0c;包括购物、玩游戏、看电…

【python VS vba】(7) python与numpy (建设ing)

目录 1 numpy 的基本介绍 2 numpy里的两种新数据类型&#xff1a;ndarray 和 matrix 2.1 numpy特殊的数据类型 2.1.1 python的数据类型 2.1.2 首先 python原生的list 和 tuple 2.1.3 numpy的数据类型 2.2 np.matrix() 或者 np.mat() 2.2.1 首先&#xff0c;两种写法相…

基于PicGo实现Typora图片自动上传GitHub

文章目录 一. 引言二. 原理三. 配置3.1 GitHub 设置3.2 下载配置 PicGo3.3 配置 Typora3.4 使用 一. 引言 Typora是一款非常好的笔记软件&#xff0c;但是有一个比较不好的地方&#xff1a;默认图片是存放在本地缓存中。这就会导致文件夹一旦被误删或电脑系统重装而忘记备份文件…

18、XSS——cookie安全

文章目录 1、cookie重要字段2、子域cookie机制3、路径cookie机制4、HttpOnly Cookie机制5、Secure Cookie机制6、本地cookie与内存cookie7、本地存储方式 一般来说&#xff0c;同域内浏览器中发出的任何一个请求都会带上cookie&#xff0c;无论请求什么资源&#xff0c;请求时&…

西南科技大学C++程序设计实验六( 继承与派生一)

一、实验目的 1. 理解不同继承属性对派生类访问基类成员的区别 2. 掌握单继承程序编写 二、实验任务 1、调试下列程序,并在对程序进行修改后再调试,指出调试中的出错原因(该题中A为基类,B为派生类,B以public方式继承A) 重点:理解不同继承方式数据的访问权限,派生类…

.kann勒索病毒解密方法|勒索病毒解决|勒索病毒恢复|数据库修复

导言&#xff1a; 在数字化的今天&#xff0c;.kann勒索病毒等数字威胁正日益猖狂&#xff0c;给个人和企业的数据安全带来了巨大威胁。本文将深入介绍.kann勒索病毒的特征&#xff0c;提供有效的数据恢复方法&#xff0c;并分享一些预防措施&#xff0c;助您更好地在数字世界…

Java利用TCP实现简单的双人聊天

一、创建新项目 首先创建一个新的项目&#xff0c;并命名为聊天。然后创建包&#xff0c;创建两个类&#xff0c;客户端&#xff08;SocketClient&#xff09;和服务器端&#xff08;SocketServer&#xff09; 二、实现代码 客户端代码&#xff1a; package 聊天; import ja…

Memory-augmented Deep Autoencoder for Unsupervised Anomaly Detection 论文阅读

Memorizing Normality to Detect Anomaly: Memory-augmented Deep Autoencoder for Unsupervised Anomaly Detection 摘要1.介绍2.相关工作异常检测Memory networks 3. Memory-augmented Autoencoder3.1概述3.2. Encoder and Decoder3.3. Memory Module with Attention-based S…