管理已部署存档中的应用程序状态
此示例说明如何管理部署到 MATLAB® Production Server™ 应用程序存档中的持久数据。它使用 MATLAB Production Server用于 MATLAB 函数执行的 RESTful API 和 JSON 将 MATLAB 应用程序的一个或多个实例连接到部署在服务器上的存档。
MATLAB Production Server 工作进程是无状态的。持久性通过在服务器上部署的多次调用 MATLAB 代码之间缓存数据来提供一种维持状态的机制。多个工作进程可以访问缓存的数据。
该示例描述了两个工作流。
在将应用程序部署到服务器之前,在 MATLAB 桌面环境中测试应用程序功能的测试工作工作流。
使用活动的 MATLAB Production Server 实例来部署存档的部署工作流。
为了演示如何使用持久性,此示例使用了旅行商问题,其中涉及寻找城市之间最短的路由。此实现将持久的 MATLABgraph
(MATLAB) 对象存储在数据缓存中。城市构成图的节点,城市之间的距离构成与图边相关的权重。在这个示例中,该图是一个完全图。测试工作流使用本地版本的路由查找功能。部署工作流使用打包到存档中并部署到服务器的路由查找功能。MATLAB 应用程序调用路由查找函数。这些函数从缓存中读取图形数据并将图形数据写入缓存。
示例的代码位于
,其中 $MPS_INSTALL
/client/matlab/examples/persistence/TravelingSalesman$MPS_INSTALL
是安装 MATLAB Production Server 的位置。
要托管使用 Production Server Compiler 创建的可部署存档,您必须安装与用于创建存档的 MATLAB 版本兼容的 MATLAB Runtime 版本。有关详细信息,请参阅MATLAB Production Server 支持的 MATLAB Runtime 版本。
步骤 1:编写使用持久性函数的 MATLAB 代码
编写函数来初始化持久数据
编写一个函数来检查数据缓存中是否存在城市和距离的图形。如果图形不存在,则从包含距离数据的 Excel® 电子表格创建它并将其写入缓存。由于每次只有一个 MATLAB Production Server 工作进程可以执行此写入操作,因此使用同步锁来确保数据初始化只发生一次。
使用
mps.cache.connect
连接到存储距离数据的缓存,如果不存在则创建它。在写入操作期间,使用mps.sync.mutex
获取互斥锁。一旦数据写入缓存,就释放锁。使用
loadDistanceData
函数初始化距离数据。function tf = loadDistanceData(connectionName, cacheName) c = mps.cache.connect(cacheName,'Connection',connectionName); tries = 0; while isKey(c,'Distances') == false && tries < 6 lk = mps.sync.mutex('DistanceData','Connection',connectionName); if acquire(lk,10) if isKey(c,'Distances') == false g = initDistanceData('Distances.xlsx'); c.Distances = g; end release(lk); end tries = tries + 1; end tf = isKey(c,'Distances'); end
编写函数来读取持久数据
编写一个函数从数据缓存中读取距离数据图。因为从缓存中读取数据是幂等操作,所以不需要使用同步锁。使用
mps.cache.connect
连接到缓存,然后检索图形。从缓存中读取图形并使用
listDestinations
函数将其转换为元胞数组。使用
findRoute
函数计算最短路由。使用最近邻算法,从给定城市开始并反复访问下一个最近的城市,直到访问过所有城市。function destinations = listDestinations() c = mps.cache.connect('TravelingSalesman','Connection','ScratchPad'); if loadDistanceData('ScratchPad','TravelingSalesman') == false error('Failed to load distance data. Cannot continue.'); end g = c.Distances; destinations = table2array(g.Nodes); end
function [route,distance] = findRoute(start,destinations) c = mps.cache.connect('TravelingSalesman','Connection','ScratchPad'); if loadDistanceData('ScratchPad','TravelingSalesman') == false error('Failed to load distance data. Cannot continue.'); end g = c.Distances; route = {start}; distance = 0; current = start; while ~isempty(destinations) minDistance = Inf; nextSegment = {}; for n = 1:numel(destinations) [p,d] = shortestpath(g,current,destinations{n}); if d < minDistance nextSegment = p(2:end); minDistance = d; end end current = nextSegment{end}; distance = distance + minDistance; destinations = setdiff(destinations,current); route = [ route nextSegment ]; end end
编写函数来修改持久数据
编写一个函数来添加新城市。添加城市会修改存储在数据缓存中的图形。因为此操作需要写入缓存,所以使用步骤 1 中描述的
mps.sync.mutex
函数进行锁定。添加城市后,通过确认每对城市之间的距离已知来检查图形是否仍然完整。使用
addDestination
函数添加城市。添加一个城市会添加一个新的图形节点name
以及将该节点连接到图中所有现有节点的新边。新添加的边的权重由向量distances
给出。destinations
是一个字符向量元胞数组,其中包含图中其他城市的名称。function count = addDestination(name, destinations, distances) count = 0; c = mps.cache.connect('TravelingSalesman','Connection','ScratchPad'); if loadDistanceData('ScratchPad','TravelingSalesman') == false error('Failed to load distance data. Cannot continue.'); end lk = mps.sync.mutex('DistanceData','Connection','ScratchPad'); if acquire(lk,10) g = c.Distances; newDestinations = setdiff(g.Nodes.Name, destinations); if ~isempty(newDestinations) error('MPS:Example:TSP:MissingDestinations', ... 'Add distances for missing destinations: %s', ... strjoin(newDestinations,', ')); end src = repmat({name},1,numel(destinations)); g = addedge(g, src, destinations, distances); c.Distances = g; release(lk); count = numnodes(g); end end
编写 MATLAB 应用程序来调用路由查找函数
编写一个 MATLAB App,将步骤 2 和 3 中描述的函数包装在各自的代理函数中。该应用程序允许您指定主机和端口。为了测试,当主机为空且端口值为 0 时,调用本地版本的路由查找函数。对于部署工作流,在指定主机和端口上运行的服务器上调用已部署的函数。使用
webwrite
(MATLAB) 函数向服务器发送 HTTP POST 请求。有关如何编写应用程序的更多信息,请参阅使用 App 设计工具创建并运行简单 App (MATLAB)。
分别为
findRouteProxy
、addDestinationProxy
和listDestinationProxy
函数编写代理函数findRoute
、addDestination
和listDestination
。function destinations = listDestinationsProxy(app) if isempty(app.HostEditField.Value) && ... app.PortEditField.Value <= 0 destinations = listDestinations(); return; end listDestinations_OPTIONS = weboptions('MediaType','application/json','Timeout',60,'ContentType','raw'); listDestinations_HOST = app.HostEditField.Value; listDestinations_PORT = app.PortEditField.Value; noInputJSON = '{ "rhs": [], "nargout": 1 }'; destinations_JSON = webwrite(sprintf('http://%s:%d/TravelingSalesman/listDestinations', ... listDestinations_HOST,listDestinations_PORT), noInputJSON, listDestinations_OPTIONS); if iscolumn(destinations_JSON), destinations_JSON = destinations_JSON'; end destinations_RESPONSE = mps.json.decoderesponse(destinations_JSON); if isstruct(destinations_RESPONSE) error(destinations_RESPONSE.id,destinations_RESPONSE.message); else if nargout > 0, destinations = destinations_RESPONSE{1}; end end end
function [route,distance] = findRouteProxy(app,start,destinations) if isempty(app.HostEditField.Value) && ... app.PortEditField.Value <= 0 [route,distance] = findRoute(start,destinations); return; end findRoute_OPTIONS = weboptions('MediaType','application/json','Timeout',60,'ContentType','raw'); findRoute_HOST = app.HostEditField.Value; findRoute_PORT = app.PortEditField.Value; start_destinations_DATA = {}; if nargin > 0, start_destinations_DATA = [ start_destinations_DATA { start } ]; end if nargin > 1, start_destinations_DATA = [ start_destinations_DATA { destinations } ]; end route_distance_JSON = webwrite(sprintf('http://%s:%d/TravelingSalesman/findRoute', ... findRoute_HOST,findRoute_PORT), ... mps.json.encoderequest(start_destinations_DATA,'nargout',nargout), findRoute_OPTIONS); if iscolumn(route_distance_JSON), route_distance_JSON = route_distance_JSON'; end route_distance_RESPONSE = mps.json.decoderesponse(route_distance_JSON); if isstruct(route_distance_RESPONSE) error(route_distance_RESPONSE.id,route_distance_RESPONSE.message); else if nargout > 0, route = route_distance_RESPONSE{1}; end if nargout > 1, distance = route_distance_RESPONSE{2}; end end end
function count = addDestinationProxy(app, name, destinations,distances) if isempty(app.HostEditField.Value) && ... app.PortEditField.Value <= 0 count = addDestination(name, destinations,distances); return; end addDestination_OPTIONS = weboptions('MediaType','application/json','Timeout',60,'ContentType','raw'); addDestination_HOST = app.HostEditField.Value; addDestination_PORT = app.PortEditField.Value; name_destinations_distances_DATA = {}; if nargin > 0, name_destinations_distances_DATA = [ name_destinations_distances_DATA { name } ]; end if nargin > 1, name_destinations_distances_DATA = [ name_destinations_distances_DATA { destinations } ]; end if nargin > 2, name_destinations_distances_DATA = [ name_destinations_distances_DATA { distances } ]; end count_JSON = webwrite(sprintf('http://%s:%d/TravelingSalesman/addDestination', ... addDestination_HOST,addDestination_PORT), ... mps.json.encoderequest(name_destinations_distances_DATA,'nargout',nargout), addDestination_OPTIONS); if iscolumn(count_JSON), count_JSON = count_JSON'; end count_RESPONSE = mps.json.decoderesponse(count_JSON); if isstruct(count_RESPONSE) error(count_RESPONSE.id,count_RESPONSE.message); else if nargout > 0, count = count_RESPONSE{1}; end end end
步骤 2:在测试工作流中运行示例
在 MATLAB 桌面环境中测试示例代码。为此,将位于
的所有文件复制到系统上的可写文件夹中,例如 $MPS_INSTALL
/client/matlab/examples/persistence/TravelingSalesman/tmp/persistence_example
。启动 MATLAB 桌面并使用 cd
(MATLAB) 命令将当前工作目录设置为 /tmp/persistence_example
。
为了测试目的,使用 mps.cache.control
函数从 MATLAB 桌面控制持久性服务。此函数返回一个 mps.cache.Controller
对象,该对象管理本地持久性服务的生命周期。
为使用 Redis™ 持久性提供程序的本地持久性服务创建
mps.cache.Controller
对象。>> ctrl = mps.cache.control('ScratchPad', 'Redis', 'Port', 8675);
当处于活动状态时,此控制器将启用名为
ScratchPad
的连接。连接名称将缓存链接到持久性服务中的存储位置。mps.cache.connect
函数需要连接名称来创建数据缓存。MATLAB Production Server 管理员在缓存配置文件mps_cache_config
中设置连接名称。有关详细信息,请参阅配置服务器以使用 Redis。通过在 MATLAB 桌面会话中使用相同的连接名称,您可以使您的代码从开发阶段转移到测试阶段再到生产阶段而无需进行任何更改。使用
start
启动持久性服务。>> start(ctrl);
启动使用持久性服务的
TravelingSalesman
路由查找应用程序。>> TravelingSalesman
应用程序以主机和端口的默认值启动。
点击加载城市以加载城市列表。使用开始菜单设置起始位置并使用 >> 和 << 按钮选择和清除要访问的城市。点击计算路径可显示一条访问所有城市的路由。
关闭应用程序时,使用
stop
停止持久性服务。停止持久性服务将删除该服务存储的数据。>> stop(ctrl);
步骤 3:在部署工作流中运行示例
要在部署工作流中运行示例,请将位于
的所有文件复制到系统中的可写文件夹,例如 $MPS_INSTALL
/client/matlab/examples/persistence/TravelingSalesman/tmp/persistence_example
。启动 MATLAB 桌面并使用 MATLAB cd
(MATLAB) 命令将当前工作目录设置为 /tmp/persistence_example
。
部署工作流管理 MATLAB 桌面环境之外的持久性服务的生命周期,并调用部署到服务器的存档中打包的路由查找函数。
创建一个 MATLAB Production Server 实例
使用
mps-new
从系统命令行创建服务器。有关详细信息,请参阅使用命令行创建服务器实例。如果您尚未设置服务器环境,请参阅mps-setup
了解更多信息。在文件夹
server_1
中创建一个新服务器tmp
。mps-new /tmp/server_1
或者,使用 MATLAB Production Server 仪表板创建服务器。有关详细信息,请参阅设置并登录 MATLAB Production Server 仪表板。
创建持久性服务连接
可部署存档需要一个名为
ScratchPad
的持久性服务连接。使用仪表板创建ScratchPad
连接,或将示例目录中的mps_cache_config
文件复制到服务器实例的config
目录中。如果config
目录中已经有一个mps_cache_config
文件,请对其进行编辑,添加ScratchPad
连接,如mps_cache_config
示例中指定的那样。使用 Production Server Compiler App 创建可部署存档并将其部署到服务器
打开 Production Server 存档编译器
MATLAB 工具条:在 App 选项卡的应用程序部署 下,点击 Production Server 存档编译器 。
MATLAB 命令提示符:输入
productionServerArchiveCompiler
。
在 Exported Functions 字段中,添加
findRoute.m
、listDestinations.m
和addDestination.m
。将存档重命名为
TravelingSalesman
。在 Custom Requirements 下,添加
Distances.xlsx
。点击打包。
生成的可部署存档
TravelingSalesman.ctf
位于工程的输出文件夹中。将TravelingSalesman.ctf
文件复制到服务器的auto_deploy
文件夹(本例中为/tmp/server_1/auto_deploy
进行托管。
启动服务器实例
使用
mps-start
从系统命令行启动服务器。或者,使用仪表板启动服务器。mps-start -C /tmp/server_1
启动持久服务
使用
mps-cache
从系统命令行启动持久性服务。或者,使用仪表板启动并附加持久性服务。mps-cache start -C /tmp/server_1 --connection ScratchPad
测试 App
启动使用持久性服务的
TravelingSalesman
路由查找应用程序。>> TravelingSalesman
该应用程序以主机和端口的空值启动。请参阅位于
main_config
服务器配置文件server_name/config
以获取 MATLAB Production Server 实例的主机和端口值。对于此示例,在/tmp/server_1/config
找到配置文件。在应用程序中输入主机和端口值。点击加载城市以加载城市列表。使用开始菜单设置起始位置并使用 >> 和 << 按钮选择和清除要访问的城市。点击计算路径可显示一条访问所有城市的路由。
测试环境工作流和部署环境工作流的结果相同。
另请参阅
mps.cache.Controller
(MATLAB Compiler SDK) | mps.cache.DataCache
(MATLAB Compiler SDK) | mps.sync.TimedMATFileMutex
(MATLAB Compiler SDK) | mps.sync.TimedRedisMutex
(MATLAB Compiler SDK) | mps.cache.control
(MATLAB Compiler SDK) | mps.cache.connect
(MATLAB Compiler SDK) | mps.sync.mutex
(MATLAB Compiler SDK)