Добро пожаловать в форум, Guest  >>   Войти | Регистрация | Поиск | Правила | В избранное | Подписаться
Все форумы / Caché, Ensemble, DeepSee, MiniM, IRIS, GT.M Новый топик    Ответить
 Организация обмена данными между серверами Cache' через Socket коннект (PuTTY.exe)  [new]
MyasnikovIA
Member

Откуда: Новосибирск
Сообщений: 65
Доброго времени суток.
Решил поделиться с сообществом альтернативной технологией обмена данными между серверами Cache’ и сторонними языками программирования, поддерживающие Socket коннекты.
https://github.com/MyasnikovIA/ZSrvController

Для начала, немного о причине появления этой технологи:
Появилась необходимость получать большие объемы данных между двумя серверами Cache’. Задача казалось бы простая, и решается стандартными методами, с применением вэб технологий, но при реализации, лично я столкнулся с подводными камнями. Главной проблемой стала ошибка “Таймаута”. Данные перед отправкой очень долго подготавливались. Можно включить мозг и начать оптимизировать предварительную подготовку данных, но было лень это делать, и я пошел другим путем.
Был разработан контроллер, позволяющий прямое Socket соединение между серверами, а за одно и между любыми средствами разработки поддерживающие сокеты.

Сервер поддерживает авторизацию и следующие команды:
   "ip"         -  Получить IP адрес клиента со стороны сервера
   "run: w $h"  -  Выполнить однострочную команду
   "cls"        -  Очистить буфер команд на стороне сервера
   "read"       -  Прочитать буфер команд записанные на стороне сервера
   "run"        -  Выполнить команды записанные в буфере на стороне сервера
   "runline"    -  Выполнить построчно команды записанные в буфере на стороне сервера
   "key"        -  Получить ключ сесии
   "exit"       -  Разорвать соединение
   "ping"       -  тестовый запрос (после него произойдёт разрыв соединения)
   "gl"         -  показать имя  глобал в котором хранится буфер
   "gl:^GBuff"  -  Установить имя  глобала в котором хранится буфер
   "setapp:AppName"- Указываем название приложение из которого подключились 
   Все остальные строки записываются в буфер  на стороне сервера.


Проверить работу можно применив PuTTY.exe

Если кого заинтересует этот контроллер, и появятся идеи, как улучшить код, буду рад внести изменения.


Пример подключения между серверами, и выполнения MUMPS команды (взят из документации к классу)
      s obj=##class(%ZMSrv.Controller).%New()
      s obj.Host="127.0.0.1"
      s obj.Port=6006
      s obj.UserName="_SYSTEM"
      s obj.UserPass="SYS"
      s obj.NameSpace="USER"
      if obj.Connect()=1 {
         w !,obj.Send("ip")     
         w !,obj.Send("run: w $ZU(110)")       
         ; Наполняем буфер командами Cache'
         d obj.Send(" for a=1:1:100  d ")       
         d obj.Send(" .   w $JOB_"":""_a         ")      
         d obj.Send(" .   w $c(13,10) ")     
         w !,obj.Send("run") ; запустить выполнение команд в буфере
         d obj.Send("cls")   ; Очистить буфер на стороне сервера 
         w !,"SessionKey: "_obj.SessionKey
      }else{
         w !,"Error: "_obj.Error,!
      }
      d obj.DisConnect()
      q


Получить объект по ID
          s obj=##class(%ZMSrv.Controller).%New()
          s obj.Host="127.0.0.1"
          s obj.Port=6006
          s obj.UserName="_SYSTEM"
          s obj.UserPass="SYS"
          s obj.NameSpace="SAMPLES"
          if obj.Connect()=1 {
            s ExternalObject=obj.GetObject("Cinema.Film","1" )  
            zw ExternalObject
          }else{
            w !,"Error: "_obj.Error,!
          }
          d obj.DisConnect()


Получить объекты из Query запроса
                  s obj=##class(%ZMSrv.Controller).%New()
                  ;   s obj.Host="127.0.0.1"
                  ;   s obj.Port=6006
                  ;   s obj.UserName="_SYSTEM"
                  ;   s obj.UserPass="SYS"
                    s obj.NameSpace="SAMPLES"
                    if obj.Connect()=1 {
                      if obj.SqlQuery("","%Dictionary.ClassDefinition:Summary" )'=1  q
                      for  {
                          s resNext=obj.SqlQueryNext("")
                          q:resNext=0
                          if resNext="" {
                             w "Error: "_obj.Error,!
                             q
                          }
                          s re=obj.SqlQueryGet("")
                          zw re
                      }
                         
                    }else{
                      w !,"Error: "_obj.Error,!
                    }
                    d obj.DisConnect()
                    q
5 дек 17, 12:28    [21006836]     Ответить | Цитировать Сообщить модератору
 Re: Организация обмена данными между серверами Cache' через Socket коннект (PuTTY.exe)  [new]
eduard93
Member

Откуда:
Сообщений: 158
MyasnikovIA, а вы не смотрели в сторону асинхронного взаимодействия?
5 дек 17, 13:44    [21007188]     Ответить | Цитировать Сообщить модератору
 Re: Организация обмена данными между серверами Cache' через Socket коннект (PuTTY.exe)  [new]
MyasnikovIA
Member

Откуда: Новосибирск
Сообщений: 65
eduard93,

Честно говоря нет. Даже не представляю как это сделать. Если подкините идею , буду очень признателен.
Дело в том что данная технология работает еще в одном проекте. И если есть более гибкое решение, почему бы не модернизировать.
5 дек 17, 16:34    [21008084]     Ответить | Цитировать Сообщить модератору
 Re: Организация обмена данными между серверами Cache' через Socket коннект (PuTTY.exe)  [new]
Alexey Maslov
Member

Откуда: СПб
Сообщений: 1520
Немного напомнило "MiniM InterConnect".

Как и ваша разработка, это скорее [ещё одна] технология удалённого выполнения кода, чем обмена большими объёмами данных. Если просто обходить глобал по $Query() и на другой стороне принимать, данные будут передаваться медленно по сравнению, например, с ECP. Обмен можно ускорить, если передавать глобал блоками (либо разобравшись, как работают %GOF/%GIF, либо используя блоки собственного формата). Приходилось делать и то, и другое, последнее, правда, пока не реализовано в виде обмена через сокеты.

Асинхронность: наверное, имелось в виду использование некоего фонового многопоточного механизма, такого как $system.WorkMgr.*, чтобы параллельно передавать сразу несколько глобалов; это, конечно, может ускорить пакетную передачу, но всё равно она завершится не раньше, чем передастся самый большой глобал.
5 дек 17, 17:08    [21008232]     Ответить | Цитировать Сообщить модератору
 Re: Организация обмена данными между серверами Cache' через Socket коннект (PuTTY.exe)  [new]
MyasnikovIA
Member

Откуда: Новосибирск
Сообщений: 65
Alexey Maslov,

Спасибо. я на днях попытаюсь разобраться с этими технологиями.

А вообще существует стандартный метод передачи данных предусмотренный компанией Intersystems? (конечно же кроме журналов и файлов)
Чтобы решать задачи аналогичной:
1) Подключится к уделенному серверу
2) Инициировать процесс
3) Дождаться окончания работы процесса
4) Забрать результат работы с удаленного сервера

И желательно без ограничений по времени и объеме данных на любом этапе :)
5 дек 17, 18:14    [21008441]     Ответить | Цитировать Сообщить модератору
 Re: Организация обмена данными между серверами Cache' через Socket коннект (PuTTY.exe)  [new]
Alexey Maslov
Member

Откуда: СПб
Сообщений: 1520
MyasnikovIA,

Готового "стандартного" метода не знаю. Обычно решается комбинацией самописного кода + ECP для удалённого доступа к глобалам. Мои терзания трёхлетней давности на эту тему отражены здесь.

Текущая реализация API Util.Proc сильно отличается от той, что была опубликована вместе со статьёй: ради надёжности и скорости работы её пришлось гораздо сильнее привязать к нашему базовому инструментарию (qWORD). В итоге без qWORD теперь работают лишь функции удалённого выполнения кода (Util.Proc::CCM) и запуска/контроля за удалёнными процессами (Util.Proc::RunJob, CheckJob).

Хочется это переписать, но времени пока нет. Сейчас бы делал на сокетах, и возможно добавил бы "экспресс-копирование" глобалов между произвольными серверами (блочное), не используя ECP. Да, и писал бы на стандартном M[umps].

Вспомнил байку, услышанную когда-то от пожилого сантехника. Учитель ему говорил: "Паша, делай хорошо. Х%ёво и так получится".
6 дек 17, 11:21    [21010111]     Ответить | Цитировать Сообщить модератору
 Re: Организация обмена данными между серверами Cache' через Socket коннект (PuTTY.exe)  [new]
kalin
Member

Откуда:
Сообщений: 261
MyasnikovIA,
В году этак 2000 писал с целью передачи большого объема данных из Cache в клиентское приложение. Там через socket из Cache командой write шел поток бинарных данных.
На сегодня для взаимодействия с Cache есть более продвинутые и унифицированные решения.
6 дек 17, 16:07    [21011647]     Ответить | Цитировать Сообщить модератору
 Re: Организация обмена данными между серверами Cache' через Socket коннект (PuTTY.exe)  [new]
eduard93
Member

Откуда:
Сообщений: 158
MyasnikovIA
eduard93,

Честно говоря нет. Даже не представляю как это сделать. Если подкините идею , буду очень признателен.
Дело в том что данная технология работает еще в одном проекте. И если есть более гибкое решение, почему бы не модернизировать.

+ Пример асинхронного REST взаимодействия:
/// Basic async REST example.
Class Utils.AsyncREST Extends %CSP.REST
{

Parameter CONTENTTYPE = "application/json";

Parameter CHARSET = "UTF-8";

Parameter UseSession As BOOLEAN = 1;

Parameter HandleCorsRequest = 1;

Parameter GLVN = "^AsyncREST";

XData UrlMap [ XMLNamespace = "http://www.intersystems.com/urlmap" ]
{
<Routes>
<Route Url="/StartTask" Method="GET" Call="StartTask"/>
<Route Url="/GetTask/:TaskId" Method="GET" Call="GetTask"/>
</Routes>
}

/// Get info about task
ClassMethod GetTask(TaskId As %Integer) As %Status
{
	Set PercentDone = ..TaskStatus(TaskId)
	
	If PercentDone = 100 {
		Set Result = ..GetCompletedTask(TaskId) 
	} Else {
		Set Result = {"TaskId" : (TaskId), "PercentDone":(PercentDone), "Alive":($Data(^$JOB(TaskId)))}
	}
	
	Write Result.%ToJSON()
	Quit $$$OK
}

/// Start new task and return result immediately
ClassMethod StartTask() As %Status
{
	Job ..Task()
	Write {"TaskId" : ($zchild)}.%ToJSON()
	Quit $$$OK
}

/// Actually do task
ClassMethod Task()
{
	Set Wait = 0 
	Set Increase = 1
	While Wait<=10 {
		Hang Increase
		Do ..LogTaskStatus( Wait * 10)
		Set Wait = Wait + Increase
			
	}
	
	Do ..LogTaskData("Some result")
}

/// Set current task progression
ClassMethod LogTaskStatus(PercentDone As %Integer(MINVAL=0,MAXVAL=100))
{
	Set:PercentDone>100 PercentDone = 100
	Set:PercentDone<0 PercentDone = 0
	
	Set @..#GLVN@($job) = PercentDone
	If PercentDone = 0 {
		Set @..#GLVN@($job, "Start") = $zdt($h, 3, 1, 3)
	} ElseIf PercentDone = 100 {
		Set @..#GLVN@($job, "End") = $zdt($h, 3, 1, 3)
	}
}

/// Specify data = task output
ClassMethod LogTaskData(Data)
{
	Set @..#GLVN@($job, "Data") = Data
}

/// Get current task progression
ClassMethod TaskStatus(TaskId As %Integer) [ CodeMode = expression ]
{
@..#GLVN@(TaskId)
}

/// Get info about completed task
ClassMethod GetCompletedTask(TaskId As %Integer) As %DynamicObject [ CodeMode = expression ]
{
{
	"TaskId": (TaskId), 
	"PercentDone": 100,
	"Start": (@..#GLVN@(TaskId, "Start")),
	"End": (@..#GLVN@(TaskId, "End")),
	"Data": (@..#GLVN@(TaskId, "Data")),
	"IsJobAlive": ($Data(^$JOB(TaskId)))
}
}

/// Remove complete task
ClassMethod ClearTask(TaskId As %Integer)
{
	Kill @..#GLVN@(TaskId)
}

}


Установка:
1. Загрузить код
2. Создать веб-приложение с REST брокером Utils.AsyncREST

Как работать:

Допустим веб-приложение называется /asyncREST.

Сначала запускаем новое задание по адресу http://localhost:57772/asyncREST/StartTask
В ответ нам придёт идентификатор задания:
{"TaskId":4168}


Далее с любой периодичностью опрашиваем http://localhost:57772/asyncREST/GetTask/<TaskId> (в нашем случае http://localhost:57772/asyncREST/GetTask/12608 ).
В ответ придёт текущий статус задания:
{
  "TaskId": "12608",
  "PercentDone": 20,
  "Alive": 1
}


Когда задение выполнится при очередном опросе придёт результат:
{
  "TaskId": "12608",
  "PercentDone": 100,
  "Start": "2017-12-06 23:18:55.000",
  "End": "2017-12-06 23:19:05.000",
  "Data": "Some result",
  "IsJobAlive": 0
}
6 дек 17, 23:25    [21013106]     Ответить | Цитировать Сообщить модератору
 Re: Организация обмена данными между серверами Cache' через Socket коннект (PuTTY.exe)  [new]
eduard93
Member

Откуда:
Сообщений: 158
В первом ответе сервера было:
{"TaskId":12608}
6 дек 17, 23:26    [21013109]     Ответить | Цитировать Сообщить модератору
 Re: Организация обмена данными между серверами Cache' через Socket коннект (PuTTY.exe)  [new]
eduard93
Member

Откуда:
Сообщений: 158
Поправил на GitHub код так, чтобы TaskId всегда был Integer в JSON.
6 дек 17, 23:28    [21013114]     Ответить | Цитировать Сообщить модератору
 Re: Организация обмена данными между серверами Cache' через Socket коннект (PuTTY.exe)  [new]
Alexey Maslov
Member

Откуда: СПб
Сообщений: 1520
eduard93
Пример асинхронного REST взаимодействия...
Эдуард,

новый Портал (v2018.1 / IRIS) будет его поддерживать?
8 дек 17, 12:09    [21017690]     Ответить | Цитировать Сообщить модератору
 Re: Организация обмена данными между серверами Cache' через Socket коннект (PuTTY.exe)  [new]
DAiMor
Member

Откуда: Volzhsky -> Moscow -> CZ, Brno
Сообщений: 2545
Alexey Maslov
eduard93
Пример асинхронного REST взаимодействия...
Эдуард,

новый Портал (v2018.1 / IRIS) будет его поддерживать?
Лично я не вижу препятствий. Там не настолько кардинальные измения. И в этом примере не используется ничего из сколько нибудь специфичного типа WorkMgr или Event.
8 дек 17, 12:48    [21017878]     Ответить | Цитировать Сообщить модератору
 Re: Организация обмена данными между серверами Cache' через Socket коннект (PuTTY.exe)  [new]
Alexey Maslov
Member

Откуда: СПб
Сообщений: 1520
DAiMor,

я не про пример, а про сам Портал. Сейчас он преимущественно синхронный. Например, просмотр переменных процесса заканчивается обычно нередко таймаутом.
8 дек 17, 14:38    [21018385]     Ответить | Цитировать Сообщить модератору
 Re: Организация обмена данными между серверами Cache' через Socket коннект (PuTTY.exe)  [new]
DAiMor
Member

Откуда: Volzhsky -> Moscow -> CZ, Brno
Сообщений: 2545
Alexey Maslov
DAiMor,

я не про пример, а про сам Портал. Сейчас он преимущественно синхронный. Например, просмотр переменных процесса заканчивается обычно нередко таймаутом.
Очень сомневаюсь, что там что-то делали в этом плане. Фокус думаю был больше на другом функционале. Портал давно правда уже не трогали.
8 дек 17, 15:06    [21018531]     Ответить | Цитировать Сообщить модератору
 Re: Организация обмена данными между серверами Cache' через Socket коннект (PuTTY.exe)  [new]
Alexey Maslov
Member

Откуда: СПб
Сообщений: 1520
DAiMor
...не используется ничего из сколько нибудь специфичного типа WorkMgr или Event...
Класс Event стабилизировался уже давно.
Что касается WorkMgr, то действительно были серьёзные изменения между 2015.1 и 2017.1. Что-то новое появилось в этих классах в 2018.1?
8 дек 17, 15:16    [21018564]     Ответить | Цитировать Сообщить модератору
 Re: Организация обмена данными между серверами Cache' через Socket коннект (PuTTY.exe)  [new]
eduard93
Member

Откуда:
Сообщений: 158
Будет работать начиная с 2016.2 как есть. Адаптировать для REST без динамических объектов (2014.2) или вообще без REST на CSP не представляется сложным. С выходом InterSystems IRIS Data Platform скорее всего будет работать как есть.
8 дек 17, 17:12    [21018987]     Ответить | Цитировать Сообщить модератору
Все форумы / Caché, Ensemble, DeepSee, MiniM, IRIS, GT.M Ответить