用Mnesia为cache增加分布式支持

news2024/11/30 13:42:53

一:分布式缓存

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 标记被置为 false
 
Mnesia 表类型:
1、set
2、ordered_set
3、bag
 
Mnesia 存储类型:
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
            _ ->

                ok
    end.

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

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).
ok

2> mnesia:start().
ok

3> application:start(resource_discovery).
ok

4> application:start(simple_cache).
ok

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

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

相关文章

万宾科技智能水环境综合治理监测系统效果

水环境综合治理是一项旨在全面改善水环境质量的系统工程。它以水体为对象&#xff0c;综合考虑各种因素&#xff0c;通过科学规划和技术手段&#xff0c;解决水环境污染、生态退化等问题&#xff0c;核心理念是“统一规划、分步实施&#xff1b;标本兼治&#xff0c;重在治本&a…

用C语言实现链栈的基本操作

#include <stdio.h> #include <malloc.h> #define ElemType char//相当于ElemType等同于char类型 //链式结构 数据域指针域 typedef struct LinkStackNode//定义一个链栈的结构体类型 {ElemType data;//ElemType是链栈的元素类型&#xff0c;代表数据域struct Lin…

京东数据运营(京东API接口):10月投影仪店铺数据分析

鲸参谋监测的京东平台10月份投影仪市场销售数据已出炉&#xff01; 10月份&#xff0c;环同比来看&#xff0c;投影仪市场销售均上涨。鲸参谋数据显示&#xff0c;今年10月&#xff0c;京东平台投影仪的销量为16万&#xff0c;环比增长约22%&#xff0c;同比增长约8%&#xff1…

关于前端学习的思考-align-items的用法

垂直对齐只对&#xff0c;显示模式display&#xff1a;flex有效 所以只能是flex 盒子才能应用。 摆四张图片就能清晰认识&#xff0c;不再赘述。

2023 金砖国家职业技能大赛网络安全省赛二三阶段样题(金砖国家未来技能挑战赛)

2023 金砖国家职业技能大赛网络安全省赛二三阶段样题&#xff08;金砖国家未来技能挑战赛&#xff09; 第二阶段&#xff1a; 安全运营 **背景&#xff1a;**作为信息安全技术人员必须能够掌握操作系统加固与安全管控、防火 墙一般配置、常见服务配置等相关技能&#xff0c;利…

【Qt】在表格QTableWidget或者QTableView中,当主键Id存在时更新数据,不存在时添加数据解决方案

问题 有时在开发中&#xff0c;表格需要显示数据&#xff0c;每一行呢&#xff0c;需要记录对应的id。 当更新表格数据时&#xff0c;会根据id进行更新&#xff0c;id存在就更新行数据&#xff0c;不存在就添加一行新数据。 解决方案 如何知道id存在还是不存在呢&#xff1f…

芯片半导体科普

我们在日常工作和生活中&#xff0c;经常会使用到各种各样的电子或电器产品&#xff0c;例如电脑、手机、电视、冰箱、洗衣机等。 这些产品&#xff0c;如果我们把它拆开&#xff0c;都会看到类似下面这样的一块绿色板子。 有时候是蓝色或黑色的 大家都知道&#xff0c;这个绿…

玩转大数据9:机器学习在大数据分析中的应用

1. 引言 在大数据时代&#xff0c;机器学习在大数据分析中扮演着至关重要的角色。本文介绍机器学习在大数据分析中的重要性和应用场景&#xff0c;并探讨Java中可用的机器学习库和框架。 2. 机器学习的基本概念和算法 机器学习是当今人工智能领域的一个关键分支&#xff0c;…

GitHub工业级开源软件:基于网络的过程可视化(SCADA/HMI/仪表板)

GitHub工业级开源软件:基于网络的过程可视化(SCADA/HMI/仪表板)  作者:本站编辑  2023-11-25 06:52:35  117 大家好,我是 Fun-Fun君,每天介绍github上最有价值的开源项目 今天介绍 FUXA 基于网络的过程可视化(SCADA/HMI/仪表板)软件 github地址:…

论文笔记--A Fine-grained Interpretability Evaluation Benchmark for Neural NLP

论文笔记--A Fine-grained Interpretability Evaluation Benchmark for Neural NLP 1. 文章简介2. 文章概括3 文章重点技术3.1 数据收集3.2 数据扰动3.3 迭代标注和检查根因3.4 度量3.4.1 Token F1-score3.4.2 MAP(Mean Average Precision) 4. 文章亮点5. 原文传送门 1. 文章简…

联通宽带+老毛子Padavan固件 开启IP v6

联通宽带开启IP v6 参考&#xff1a; 联通宽带开启 IPV6 的方法_联通ipv6怎么开通-CSDN博客 个人宽带如何开启IPv6网络访问 - 知乎 (zhihu.com) 首先&#xff0c;你要确定当前你所在的地区运营商已经开通了IPV6&#xff0c;可以使用手机流量 IP查询(ipw.cn) | IPv6测试 | IPv…

人工智能学习8(集成学习之xgboost)

编译工具&#xff1a;PyCharm 文章目录 编译工具&#xff1a;PyCharm 集成学习XGBoost(Extreme Gradient Boosting)极端梯度提升树1.最优模型的构建方法XGBoost目标函数案例1&#xff1a;泰坦尼克号案例2&#xff1a;对奥拓集团差评进行正确分类。数据准备&#xff1a;1.第一种…

Baumer工业相机堡盟工业相机如何通过BGAPISDK将相机图像高速保存到电脑内存(C#)

Baumer工业相机堡盟工业相机如何通过BGAPISDK将相机图像高速保存到电脑内存&#xff08;C#&#xff09; Baumer工业相机Baumer工业相机图像保存到电脑内存的技术背景代码分析注册SDK回调函数BufferEvent声明可以存储相机图像的内存序列和名称在图像回调函数中将图像保存在内存序…

CSS新手入门笔记整理:CSS盒模型

在“CSS盒子模型”理论中&#xff0c;页面中的所有元素都可以看成一个盒子&#xff0c;并且占据着一定的页面空间。 一个页面由很多这样的盒子组成&#xff0c;这些盒子之间会互相影响&#xff0c;因此掌握盒子模型需要从两个方面来理解&#xff1a; 一是理解单独一个盒子的内…

持续集成交付CICD:Sonarqube自动更新项目质量配置

目录 一、实验 1.Sonarqube手动自定义质量规则并指定项目 2.Sonarqube自动更新项目质量配置 一、实验 1.Sonarqube手动自定义质量规则并指定项目 &#xff08;1&#xff09;自定义质量规则 ①新配置 ②更多激活规则③根据需求激活相应规则④已新增配置 ⑤ 查看 &#x…

自适应局部降噪滤波器的python实现-------冈萨雷斯数字图像处理

自适应局部降噪滤波器 自适应局部降噪滤波器是一种信号处理技术&#xff0c;用于降低图像或信号中的噪声水平&#xff0c;同时保留图像或信号的细节。其原理基于局部区域内信号的统计特性和噪声的特征&#xff0c;通过动态调整滤波器的参数来适应不同区域的信号和噪声属性。 自…

多平台展示预约的服装小程序效果如何

线下实体服装店非常多&#xff0c;主要以同城生意为主&#xff0c;但随着电商经济增长&#xff0c;传统线下自然流量变少&#xff0c;商家们会选择线上入驻平台开店获得更多线上用户&#xff0c;包括自建私域小程序等。 而除了直接卖货外&#xff0c;线上展示预约在服装行业也…

html通过CDN引入使用Vue和ElementUI

html通过CDN引入使用Vue和ElementUI 近期遇到个需求&#xff0c;就是需要在.net MVC的项目中&#xff0c;对已有的项目的首页进行优化&#xff0c;也就是写原生html和js。但是咱是一个写前端的&#xff0c;写html还可以&#xff0c;.net的话&#xff0c;开发也不方便&#xff0…

【开源】基于JAVA的APK检测管理系统

项目编号&#xff1a; S 038 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S038&#xff0c;文末获取源码。} 项目编号&#xff1a;S038&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 数据中心模块2.2 开放平台模块2.3 软…