Dans l'un de nos projets, nous avons utilisé IPC (communication inter-processus) sur les sockets. Un projet assez volumineux, un bot de trading, où de nombreux modules interagissaient les uns avec les autres. À mesure que la complexité augmentait, il devenait une question de surveillance de ce qui se passait dans les microservices. Nous avons décidé de créer notre propre application de suivi des flux de données en utilisant seulement deux bibliothèques de réaction et de rétablissement . Je voudrais partager notre approche avec vous.
Les microservices échangent des objets JSON entre eux, avec deux champs: nom et données. Le nom est l'identifiant auquel l'objet est destiné et le champ de données est la charge utile. Exemple:
{ name:'ticket_delete', data:{id:1} }
Étant donné que le service est assez rudimentaire et que les protocoles changent chaque semaine, la surveillance doit être aussi simple et modulaire que possible. Ainsi, dans l'application, chaque module doit afficher les données qui lui sont destinées, et ainsi en ajoutant, en supprimant des données, il faut obtenir un ensemble de modules indépendants pour surveiller les processus dans les microservices.
Alors, commençons. Par exemple, créons une application simple et un serveur Web. L'application comprendra trois modules. Ils sont indiqués par des lignes pointillées sur l'image. Boutons de contrôle de la minuterie, des statistiques et des statistiques.
Créons un simple serveur Web Socket.
/** src/ws_server/echo_server.js */
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8888 });
function sendToAll( data) {
let str = JSON.stringify(data);
wss.clients.forEach(function each(client) {
client.send(str);
});
}
//
setInterval(e=>{
let d = new Date();
let H = d.getHours();
let m = ('0'+d.getMinutes()).substr(-2);
let s = ('0'+d.getSeconds()).substr(-2);
let time_str = `${H}:${m}:${s}`;
sendToAll({name:'timer', data:{time_str}});
},1000);
. :
node src/ws_server/echo_server.js
. rollup .
rollup.config.js
import serve from 'rollup-plugin-serve';
import babel from '@rollup/plugin-babel';
import { nodeResolve } from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import hmr from 'rollup-plugin-hot'
import postcss from 'rollup-plugin-postcss';
import autoprefixer from 'autoprefixer'
import replace from '@rollup/plugin-replace';
const browsers = [ "last 2 years", "> 0.1%", "not dead"]
let is_production = process.env.BUILD === 'production';
const replace_cfg = {
'process.env.NODE_ENV': JSON.stringify( is_production ? 'production' : 'development' ),
preventAssignment:false,
}
const babel_cfg = {
babelrc: false,
presets: [
[
"@babel/preset-env",
{
targets: {
browsers: browsers
},
}
],
"@babel/preset-react"
],
exclude: 'node_modules/**',
plugins: [
"@babel/plugin-proposal-class-properties",
["@babel/plugin-transform-runtime", {
"regenerator": true
}],
[ "transform-react-jsx" ]
],
babelHelpers: 'runtime'
}
const cfg = {
input: [
'src/main.js',
],
output: {
dir:'dist',
format: 'iife',
sourcemap: true,
exports: 'named',
},
inlineDynamicImports: true,
plugins: [
replace(replace_cfg),
babel(babel_cfg),
postcss({
plugins: [
autoprefixer({
overrideBrowserslist: browsers
}),
]
}),
commonjs({
sourceMap: true,
}),
nodeResolve({
browser: true,
jsnext: true,
module: false,
}),
serve({
open: false,
host: 'localhost',
port: 3000,
}),
],
} ;
export default cfg;
main.js
.
/** src/main.js */
import React, { createElement, Component, createContext } from 'react';
import ReactDOM from 'react-dom';
import {Connect, Provider} from './store'
import Timer from './Timer/Timer'
const Main = () => (
<Provider>
<h1>ws stats</h1>
<Timer/>
</Provider>
);
const root = document.body.appendChild(document.createElement("DIV"));
ReactDOM.render(<Main />, root);
/** src/store.js */
import React, { createElement, Component, createContext } from 'react';
import createStoreFactory from 'redoor';
import * as actionsWS from './actionsWS'
import * as actionsTimer from './Timer/actionsTimer'
const createStore = createStoreFactory({Component, createContext, createElement});
const { Provider, Connect } = createStore(
[
actionsWS, // websocket actions
actionsTimer, // Timer actions
]
);
export { Provider, Connect };
. .
/** src/actionsWS.js */
export const __module_name = 'actionsWS'
let __emit;
// emit redoor
export const bindStateMethods = (getState, setState, emit) => {
__emit = emit
};
//
let wss = new WebSocket('ws://localhost:8888')
// redoor
wss.onmessage = (msg) => {
let d = JSON.parse(msg.data);
__emit(d.name, d.data);
}
. : . redoor . :
+------+ | emit | --- events --+--------------+----- ... ------+-------------> +------+ | | | v v v +----------+ +----------+ +----------+ | actions1 | | actions2 | ... | actionsN | +----------+ +----------+ +----------+
"" .
. Timer
Timer.js
actionsTimer.js
/** src/Timer/Timer.js */
import React from 'react';
import {Connect} from '../store'
import s from './Timer.module.css'
const Timer = ({timer_str}) => <div className={s.root}>
{timer_str}
</div>
export default Connect(Timer);
, timer_str
actionsTimer.js
. Connect
redoor.
/** src/Timer/actionsTimer.js */
export const __module_name = 'actionsTimer'
let __setState;
//
export const bindStateMethods = (getState, setState) => {
__setState = setState;
};
//
export const initState = {
timer_str:''
}
// "" "timer"
export const listen = (name,data) =>{
name === 'timer' && updateTimer(data);
}
//
function updateTimer(data) {
__setState({timer_str:data.time_str})
}
, "" timer
( listen
) .
redoor:
__module_name
- .
bindStateMethods
- setState
, .
initState
- timer_str
listen
- redoor.
. http://localhost:3000
npx rollup -c rollup.config.js --watch
. . . echo_server.js
/** src/ws_server/echo_server.js */
...
let g_interval = 1;
//
setInterval(e=>{
let stats_array = [];
for(let i=0;i<30;i++) {
stats_array.push((Math.random()*(i*g_interval))|0);
}
let data = {
stats_array
}
sendToAll({name:'stats', data});
},500);
...
. Stats
Stats.js
actionsStats.js
/** src/Stats/Stats.js */
import React from 'react';
import {Connect} from '../store'
import s from './Stats.module.css'
const Bar = ({h})=><div className={s.bar} style={{height:`${h}`px}}>
{h}
</div>
const Stats = ({stats_array})=><div className={s.root}>
<div className={s.bars}>
{stats_array.map((it,v)=><Bar key={v} h={it} />)}
</div>
</div>
export default Connect(Stats);
/** src/Stats/actionsStats.js */
export const __module_name = 'actionsStats'
let __setState = null;
export const bindStateMethods = (getState, setState, emit) => {
__setState = setState;
}
export const initState = {
stats_array:[],
}
export const listen = (name,data) =>{
name === 'stats' && updateStats(data);
}
function updateStats(data) {
__setState({
stats_array:data.stats_array,
})
}
/** src/store.js */
...
import * as actionsStats from './Stats/actionsStats'
const { Provider, Connect } = createStore(
[
actionsWS,
actionsTimer,
actionsStats //<-- Stats
]
);
...
:
Stats
Timer
, , . , ? .
g_interval . .
. interval
.
/** src/Stats/Stats.js */
...
import Buttons from './Buttons' //
...
const Stats = ({cxRun, stats_array})=><div className={s.root}>
<div className={s.bars}>
{stats_array.map((it,v)=><Bar key={v} h={it} />)}
</div>
<Buttons/> {/* */}
</div>
...
/** src/Stats/Buttons.js */
import React from 'react';
import {Connect} from '../store'
import s from './Stats.module.css'
const DATA_INTERVAL_PLUS = {
name:'change_interval',
interval:1
}
const DATA_INTERVAL_MINUS = {
name:'change_interval',
interval:-1
}
const Buttons = ({cxEmit, interval})=><div className={s.root}>
<div className={s.btns}>
<button onClick={e=>cxEmit('ws_send',DATA_INTERVAL_PLUS)}>
plus
</button>
<div className={s.len}>interval:{interval}</div>
<button onClick={e=>cxEmit('ws_send',DATA_INTERVAL_MINUS)}>
minus
</button>
</div>
</div>
export default Connect(Buttons);
:
actionsWS.js
/** src/actionsWS.js */
...
let wss = new WebSocket('ws://localhost:8888')
wss.onmessage = (msg) => {
let d = JSON.parse(msg.data);
__emit(d.name, d.data);
}
// ""
export const listen = (name,data) => {
name === 'ws_send' && sendMsg(data);
}
//
function sendMsg(msg) {
wss.send(JSON.stringify(msg))
}
Buttons.js
(cxEmit
) redoor. ws_send
"" actionsWS.js
. data
- : DATA_INTERVAL_PLUS
DATA_INTERVAL_MINUS
. { name:'change_interval', interval:1 }
/** src/ws_server/echo_server.js */
...
wss.on('connection', function onConnect(ws) {
// "" "change_interval"
// Buttons.js
ws.on('message', function incoming(data) {
let d = JSON.parse(data);
d.name === 'change_interval' && change_interval(d);
});
});
let g_interval = 1;
//
function change_interval(data) {
g_interval += data.interval;
// ,
sendToAll({name:'interval_changed', data:{interval:g_interval}});
}
...
Buttons.js. actionsStats.js "interval_changed
" interval
/** src/Stats/actionsStats.js */
...
export const initState = {
stats_array:[],
interval:1 //
}
export const listen = (name,data) =>{
name === 'stats' && updateStats(data);
// ""
name === 'interval_changed' && updateInterval(data);
}
//
function updateInterval(data) {
__setState({
interval:data.interval,
})
}
function updateStats(data) {
__setState({
stats_array:data.stats_array,
})
}
Nous avons donc trois modules indépendants, où chaque module ne surveille que son propre événement et ne l'affiche que lui. Ce qui est assez pratique lorsque la structure et les protocoles au stade du prototypage ne sont pas encore complètement clairs. Il est seulement nécessaire d'ajouter que puisque tous les événements ont une structure de bout en bout, nous devons clairement adhérer au modèle de création d'un événement, nous avons choisi ce qui suit pour nous-mêmes: ( MODULEN AME)_(FUNCTION NAME)_(VAR NAME)
.
J'espère que cela a été utile. Les codes sources du projet, comme d'habitude, sont sur le github.