1
0
mirror of https://github.com/taigrr/nats.docs synced 2025-01-18 04:03:23 -08:00

added gateways, leafnodes, nats-account-server, fixed typos etc.

This commit is contained in:
Alberto Ricart
2019-05-20 09:15:03 -05:00
parent e832940daf
commit b33bd52668
19 changed files with 1212 additions and 8 deletions

76
gateways/README.md Normal file
View File

@@ -0,0 +1,76 @@
## Gateways
Gateways enable connecting one or more clusters together; they allow the formation of super clusters from smaller clusters. Cluster and Gateway protocols listen in different ports. Clustering is used for adjacent servers; gateways are for joining clusters together. Typically all cluster nodes will also be gateway nodes, but this is not a requirement.
Gateway configuration is similar to clustering:
- gateways have a dedicated port where they listen for gateway requests
- gateways gossip gateway members and remote discovered gateways
Unlike clusters, gateways:
- don't form a full mesh
- are bound by uni-directional connections
Gateways exist to:
- reduce the number of connections required between servers
- optimize the interest graph propagation
## Gateway Connections
A nats-server in a gateway role will specify a port where it will accept gateways connections. If the configuration specifies other _external_ `gateways`, the gateway will create one outbound gateway connection for each gateway in its configuration. It will also gossip other gateways it knows or discovered.
If the local cluster has three gateway nodes, this means there will be three outbound connections to each external gateway.
![Gateway Connections](simple.svg)
> In the example above cluster _A_ has configured gateway connections for _B_ (solid lines). B has discovered gateway connections to _A_ (dotted lines). Note that the number of outgoing connections always matches the number of gateways with the same name.
![Gateway Discovered Gateways](three_gw.svg)
> In this second example, again configured connections are shown with solid lines and discovered gateway connections are shown using dotted lines. Gateways _A_ and _C_ were both discovered via gossiping; _B_ discovered _A_ and _A_ discovered _C_.
A key point in the description above is that each node in the cluster will make a connection to a single node in the remote cluster — a difference from the clustering protocol, where every node is directly connected to all other nodes.
For those mathematically inclined, cluster connections are `N(N-1)/2` where _N_ is the number of nodes in the cluster. On gateway configurations, outbound connections are the summation of `Ni(M-1)` where Ni is the number of nodes in a gateway _i_, and _M_ is the total number of gateways. Inbound connections are the summation of `U-Ni` where U is the sum of all gateway nodes in all gateways, and N is the number of nodes in a gateway _i_. It works out that both inbound and outbound connection counts are the same.
The number of connections required to join clusters using clustering vs. gateways is apparent very quickly. For 3 clusters, with N nodes:
| Nodes per Cluster | Full Mesh Conns | Gateway Conns |
| ---: | ----: | ----: |
| 1 | 3 | 6|
| 2 | 15 | 12 |
| 3 | 36 | 18 |
| 4 | 66 | 24 |
| 5 | 105 | 30 |
| 30 | 4005 | 180 |
## Interest Propagation
Gateways propagate interest using three different mechanisms:
- Optimistic Mode
- Interest-only Mode
- Queue Subscriptions
### Optimistic Mode
When a publisher in _A_ publishes "foo", the _A_ gateway will check if cluster _B_ has registered _no_ interest in "foo". If not, it forwards "foo" to _B_. If upon receiving "foo", _B_ has no subscribers on "foo", _B_ will send a gateway protocol message to _A_ expressing that it has no interest on "foo", preventing future messages on "foo" from being forwarded.
Should a subscriber on _B_ create a subscription to "foo", _B_ knowing that it had previously rejected interest on _foo_, will send a gateway protocol message to cancel its previous _no interest_ on "foo" in _A_.
### Interest-only Mode
When a gateway on _A_ sends many messages on various subjects for which _B_ has no interest. _B_ sends a gateway protocol message for _A_ to stop sending optimistically, and instead send if there's known interest in the subject. As subscriptions come and go on _B_, _B_ will update its subject interest with _A_.
### Queue Subscriptions
When a queue subscriber creates a new subscription, the gateway propagates the subscription interest to other gateways. The subscription interest is only propagated _once_ per _Account_ and subject. When the last queue subscriber is gone, the cluster interest is removed.
Queue subscriptions work on _Interest-only Mode_ to honor NATS' queue semantics across the _Super Cluster_. For each queue group, a message is only delivered to a single queue subscriber. Only when a local queue group member is not found, is a message forwarded to a different interested cluster; gateways will always try to serve local queue subscribers first and only failover when a local queue subscriber is not found.
### Gateway Configuration
The [Gateway Configuration](gateway.md) document describes all the options available to gateways.

100
gateways/gateway.md Normal file
View File

@@ -0,0 +1,100 @@
## Gateway Configuration
The `gateway` configuration block is similar to a `cluster` block:
```yaml
gateway {
name: "A"
listen: "localhost:7222"
authorization {
user: gwu
password: gwp
}
gateways: [
{name: "A", url: "nats-gateway://gwu:gwp@localhost:7222"},
{name: "B", url: "nats-gateway://gwu:gwp@localhost:7333"},
{name: "C", url: "nats-gateway://gwu:gwp@localhost:7444"},
]
}
```
One difference is that instead of `routes` you specify `gateways`. As expected _self-gateway_ connections are ignored, so you can share gateway configurations with minimal fuzz.
Starting a server:
```text
> nats-server -c A.conf
[85803] 2019/05/07 10:50:55.902474 [INF] Starting nats-server version 2.0.0-RC11
[85803] 2019/05/07 10:50:55.902547 [INF] Git commit [not set]
[85803] 2019/05/07 10:50:55.903669 [INF] Gateway name is A
[85803] 2019/05/07 10:50:55.903684 [INF] Listening for gateways connections on localhost:7222
[85803] 2019/05/07 10:50:55.903696 [INF] Address for gateway "A" is localhost:7222
[85803] 2019/05/07 10:50:55.903909 [INF] Listening for client connections on 0.0.0.0:4222
[85803] 2019/05/07 10:50:55.903914 [INF] Server id is NBHUDBF3TVJSWCDPG2HSKI4I2SBSPDTNYEXEMOFAZUZYXVA2IYRUGPZU
[85803] 2019/05/07 10:50:55.903917 [INF] Server is ready
[85803] 2019/05/07 10:50:56.830669 [INF] 127.0.0.1:50892 - gid:2 - Processing inbound gateway connection
[85803] 2019/05/07 10:50:56.830673 [INF] 127.0.0.1:50891 - gid:1 - Processing inbound gateway connection
[85803] 2019/05/07 10:50:56.831079 [INF] 127.0.0.1:50892 - gid:2 - Inbound gateway connection from "C" (NBHWDFO3KHANNI6UCEUL27VNWL7NWD2MC4BI4L2C7VVLFBSMZ3CRD7HE) registered
[85803] 2019/05/07 10:50:56.831211 [INF] 127.0.0.1:50891 - gid:1 - Inbound gateway connection from "B" (ND2UJB3GFUHXOQ2UUMZQGOCL4QVR2LRJODPZH7MIPGLWCQRARJBU27C3) registered
[85803] 2019/05/07 10:50:56.906103 [INF] Connecting to explicit gateway "B" (localhost:7333) at 127.0.0.1:7333
[85803] 2019/05/07 10:50:56.906104 [INF] Connecting to explicit gateway "C" (localhost:7444) at 127.0.0.1:7444
[85803] 2019/05/07 10:50:56.906404 [INF] 127.0.0.1:7333 - gid:3 - Creating outbound gateway connection to "B"
[85803] 2019/05/07 10:50:56.906444 [INF] 127.0.0.1:7444 - gid:4 - Creating outbound gateway connection to "C"
[85803] 2019/05/07 10:50:56.906647 [INF] 127.0.0.1:7444 - gid:4 - Outbound gateway connection to "C" (NBHWDFO3KHANNI6UCEUL27VNWL7NWD2MC4BI4L2C7VVLFBSMZ3CRD7HE) registered
[85803] 2019/05/07 10:50:56.906772 [INF] 127.0.0.1:7333 - gid:3 - Outbound gateway connection to "B" (ND2UJB3GFUHXOQ2UUMZQGOCL4QVR2LRJODPZH7MIPGLWCQRARJBU27C3) registered
```
Once all the gateways are up, these clusters of one will forward messages as expected:
```text
> nats-pub -s localhost:4444 foo bar
Published [foo] : 'bar'
# On a different session...
> nats-sub -s localhost:4333 ">"
Listening on [>]
[#1] Received on [foo]: 'bar'
```
### `Gateway` Configuration Block
| Property | Description |
| :------ | :---- |
| `advertise` | Hostport `<host>:<port>` to advertise to other gateways. |
| `authorization` | Authorization block (same as other nats-server `authorization` configuration). |
| `connect_retries` | Number of times the server will try to connect to a discovered gateway. |
| `gateways` | List of Gateway entries - see below. |
| `host` | Interface where the gateway will listen for incomming gateway connections. |
| `listen` | Combines `host` and `port` as `<host>:<port>` |
| `name` | Name for this cluster, all gateways belonging to the same cluster, should specify the same name. |
| `port` | Port where the gateway will listen for incomming gateway connections. |
| `reject_unknown` | If `true`, gateway will reject connections from gateways that are not configured in `gateways`. |
| `tls` | TLS configuration block (same as other nats-server `tls` configuration). |
#### `Gateway` Entry
The `gateways` configuration block is a list of gateway entries with the following properties:
| Property | Description |
| :------ | :---- |
| `name` | Gateway name. |
| `url` | Hostport `<host>:<port>` describing where the remote gateway can be reached. If multiple IPs are returned, one is randomly selected. |
| `urls` | A list of `url` |
### `TLS` Configuration Block
| Property | Description |
| :------ | :---- |
| `ca_file` | TLS certificate authority file. |
| `cert_file` | TLS certificate file. |
| `cipher_suites` | When set, only the specified TLS cipher suites will be allowed. Values must match golang version used to build the server. |
| `curve_preferences` | List of TLS cypher curves to use in order. |
| `insecure` | Skip certificate verfication. |
| `key_file` | TLS certificate key file. |
| `timeout` | TLS handshake timeout in fractional seconds. |
| `verify_and_map` | If `true`, require and verify client certificates and use values map certificate values for authentication purposes. |
| `verify` | If `true`, require and verify client certificates. |

76
gateways/simple.svg Normal file
View File

@@ -0,0 +1,76 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns="http://www.w3.org/2000/svg" xmlns:xl="http://www.w3.org/1999/xlink" version="1.1" viewBox="152 192 358 280" width="358" height="280">
<defs>
<font-face font-family="Helvetica Neue" font-size="16" panose-1="2 0 5 3 0 0 0 2 0 4" units-per-em="1000" underline-position="-100" underline-thickness="50" slope="0" x-height="517" cap-height="714" ascent="951.9958" descent="-212.99744" font-weight="400">
<font-face-src>
<font-face-name name="HelveticaNeue"/>
</font-face-src>
</font-face>
<marker orient="auto" overflow="visible" markerUnits="strokeWidth" id="FilledArrow_Marker" stroke-linejoin="miter" stroke-miterlimit="10" viewBox="-1 -4 10 8" markerWidth="10" markerHeight="8" color="black">
<g>
<path d="M 8 0 L 0 -3 L 0 3 Z" fill="currentColor" stroke="currentColor" stroke-width="1"/>
</g>
</marker>
</defs>
<metadata> Produced by OmniGraffle 7.10.2
<dc:date>2019-05-07 16:42:18 +0000</dc:date>
</metadata>
<g id="Canvas_1" fill-opacity="1" stroke="none" stroke-dasharray="none" fill="none" stroke-opacity="1">
<title>Canvas 1</title>
<rect fill="white" x="152" y="192" width="358" height="280"/>
<g id="Canvas_1: Layer 1">
<title>Layer 1</title>
<g id="Graphic_5">
<circle cx="185.75" cy="225.75" r="33.250053130238" fill="white"/>
<circle cx="185.75" cy="225.75" r="33.250053130238" stroke="gray" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<text transform="translate(164.15 215.75)" fill="black">
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="11.968" y="15">A1</tspan>
</text>
</g>
<g id="Graphic_6">
<circle cx="185.75" cy="332.25" r="33.250053130238" fill="white"/>
<circle cx="185.75" cy="332.25" r="33.250053130238" stroke="gray" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<text transform="translate(164.15 322.25)" fill="black">
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="11.968" y="15">A2</tspan>
</text>
</g>
<g id="Graphic_7">
<circle cx="185.75" cy="438.25" r="33.2500531302381" fill="white"/>
<circle cx="185.75" cy="438.25" r="33.2500531302381" stroke="gray" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<text transform="translate(164.15 428.25)" fill="black">
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="11.968" y="15">A3</tspan>
</text>
</g>
<g id="Graphic_9">
<circle cx="476.25" cy="225.75" r="33.250053130238" fill="white"/>
<circle cx="476.25" cy="225.75" r="33.250053130238" stroke="gray" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<text transform="translate(454.65 215.75)" fill="black">
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="11.672" y="15">B1</tspan>
</text>
</g>
<g id="Graphic_8">
<circle cx="476.25" cy="332.25" r="33.250053130238" fill="white"/>
<circle cx="476.25" cy="332.25" r="33.250053130238" stroke="gray" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<text transform="translate(454.65 322.25)" fill="black">
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="11.672" y="15">B2</tspan>
</text>
</g>
<g id="Line_10">
<line x1="219.00002" y1="225.75" x2="433.1" y2="225.75" marker-end="url(#FilledArrow_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
</g>
<g id="Line_11">
<line x1="216.9757" y1="320.80236" x2="435.72924" y2="240.6053" marker-end="url(#FilledArrow_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
</g>
<g id="Line_12">
<line x1="216.99302" y1="426.8498" x2="435.70677" y2="347.04374" marker-end="url(#FilledArrow_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
</g>
<g id="Line_14">
<line x1="449.4098" y1="245.38355" x2="220.58062" y2="412.7715" marker-end="url(#FilledArrow_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="1.0,5.0" stroke-width="1"/>
</g>
<g id="Line_15">
<line x1="443" y1="332.25" x2="228.90002" y2="332.25" marker-end="url(#FilledArrow_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="1.0,5.0" stroke-width="1"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.8 KiB

109
gateways/three_gw.svg Normal file
View File

@@ -0,0 +1,109 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns="http://www.w3.org/2000/svg" xmlns:xl="http://www.w3.org/1999/xlink" version="1.1" viewBox="152 140.7461 648.5 356.93095" width="648.5" height="356.93095">
<defs>
<font-face font-family="Helvetica Neue" font-size="16" panose-1="2 0 5 3 0 0 0 2 0 4" units-per-em="1000" underline-position="-100" underline-thickness="50" slope="0" x-height="517" cap-height="714" ascent="951.9958" descent="-212.99744" font-weight="400">
<font-face-src>
<font-face-name name="HelveticaNeue"/>
</font-face-src>
</font-face>
<marker orient="auto" overflow="visible" markerUnits="strokeWidth" id="FilledArrow_Marker" stroke-linejoin="miter" stroke-miterlimit="10" viewBox="-1 -4 10 8" markerWidth="10" markerHeight="8" color="black">
<g>
<path d="M 8 0 L 0 -3 L 0 3 Z" fill="currentColor" stroke="currentColor" stroke-width="1"/>
</g>
</marker>
<marker orient="auto" overflow="visible" markerUnits="strokeWidth" id="FilledArrow_Marker_2" stroke-linejoin="miter" stroke-miterlimit="10" viewBox="-1 -4 10 8" markerWidth="10" markerHeight="8" color="black">
<g>
<path d="M 8 0 L 0 -3 L 0 3 Z" fill="currentColor" stroke="currentColor" stroke-width="1"/>
</g>
</marker>
</defs>
<metadata> Produced by OmniGraffle 7.10.2
<dc:date>2019-05-07 16:43:34 +0000</dc:date>
</metadata>
<g id="Canvas_1" fill-opacity="1" stroke="none" stroke-dasharray="none" fill="none" stroke-opacity="1">
<title>Canvas 1</title>
<rect fill="white" x="152" y="140.7461" width="648.5" height="356.93095"/>
<g id="Canvas_1: Layer 1">
<title>Layer 1</title>
<g id="Graphic_5">
<circle cx="185.75" cy="225.75" r="33.250053130238" fill="white"/>
<circle cx="185.75" cy="225.75" r="33.250053130238" stroke="gray" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<text transform="translate(164.15 215.75)" fill="black">
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="11.968" y="15">A1</tspan>
</text>
</g>
<g id="Graphic_6">
<circle cx="185.75" cy="332.25" r="33.250053130238" fill="white"/>
<circle cx="185.75" cy="332.25" r="33.250053130238" stroke="gray" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<text transform="translate(164.15 322.25)" fill="black">
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="11.968" y="15">A2</tspan>
</text>
</g>
<g id="Graphic_7">
<circle cx="185.75" cy="438.25" r="33.2500531302381" fill="white"/>
<circle cx="185.75" cy="438.25" r="33.2500531302381" stroke="gray" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<text transform="translate(164.15 428.25)" fill="black">
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="11.968" y="15">A3</tspan>
</text>
</g>
<g id="Graphic_9">
<circle cx="476.25" cy="225.75" r="33.250053130238" fill="white"/>
<circle cx="476.25" cy="225.75" r="33.250053130238" stroke="gray" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<text transform="translate(454.65 215.75)" fill="black">
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="11.672" y="15">B1</tspan>
</text>
</g>
<g id="Graphic_8">
<circle cx="476.25" cy="332.25" r="33.250053130238" fill="white"/>
<circle cx="476.25" cy="332.25" r="33.250053130238" stroke="gray" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<text transform="translate(454.65 322.25)" fill="black">
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="11.672" y="15">B2</tspan>
</text>
</g>
<g id="Line_10">
<line x1="219.00002" y1="225.75" x2="433.1" y2="225.75" marker-end="url(#FilledArrow_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
</g>
<g id="Line_11">
<line x1="216.9757" y1="320.80236" x2="435.72924" y2="240.6053" marker-end="url(#FilledArrow_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
</g>
<g id="Line_12">
<line x1="216.99302" y1="426.8498" x2="435.70677" y2="347.04374" marker-end="url(#FilledArrow_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
</g>
<g id="Line_14">
<line x1="449.4098" y1="245.38355" x2="220.58062" y2="412.7715" marker-end="url(#FilledArrow_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="1.0,5.0" stroke-width="1"/>
</g>
<g id="Line_15">
<line x1="443" y1="332.25" x2="228.90002" y2="332.25" marker-end="url(#FilledArrow_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="1.0,5.0" stroke-width="1"/>
</g>
<g id="Graphic_18">
<circle cx="766.75" cy="225.75" r="33.250053130238" fill="white"/>
<circle cx="766.75" cy="225.75" r="33.250053130238" stroke="gray" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<text transform="translate(745.15 215.75)" fill="black">
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="11.376" y="15">C1</tspan>
</text>
</g>
<g id="Line_19">
<line x1="509.5" y1="225.75" x2="723.6" y2="225.75" marker-end="url(#FilledArrow_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
</g>
<g id="Line_24">
<path d="M 737.9152 209.17695 C 691.19614 184.57088 592.20205 141.2461 474.34375 141.2461 C 363.9938 141.2461 271.8946 179.22656 223.17214 204.26238" marker-end="url(#FilledArrow_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="1.0,4.0" stroke-width="1"/>
</g>
<g id="Line_25">
<path d="M 215.77826 452.5492 C 273.97692 477.73306 407.3362 523.2367 531.3711 477.8711 C 649.03855 434.8344 718.7598 324.48573 748.9529 265.0568" marker-end="url(#FilledArrow_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="1.0,4.0" stroke-width="1"/>
</g>
<g id="Line_26">
<path d="M 206.45157 358.2732 C 244.25963 400.88577 331.892 478.7072 453.0625 456.4961 C 575.3629 434.07786 689.0991 317.5855 739.4792 259.19245" marker-end="url(#FilledArrow_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="1.0,4.0" stroke-width="1"/>
</g>
<g id="Line_27">
<path d="M 199.5272 256.02128 C 228.04436 312.15083 301.47194 426.71484 425.75 426.71484 C 549.392 426.71484 679.6919 313.32052 736.8824 256.88767" marker-end="url(#FilledArrow_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="1.0,4.0" stroke-width="1"/>
</g>
<g id="Line_28">
<line x1="507.4757" y1="320.80236" x2="726.2292" y2="240.6053" marker-end="url(#FilledArrow_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
</g>
<g id="Line_30">
<path d="M 736.5963 211.71723 C 709.7235 200.97972 667.2586 188 619 188 C 577.0519 188 540.7803 197.8071 515.29633 207.424" marker-end="url(#FilledArrow_Marker_2)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="1.0,4.0" stroke-width="1"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.6 KiB