-
Notifications
You must be signed in to change notification settings - Fork 280
DOC-5064 improvements to Jedis production usage advice #1400
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 4 commits
665c463
e0c14cb
b2f5c99
7490d77
55dbd02
14d1400
1a36cd4
0188a35
bdf109c
3636c56
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,12 +15,64 @@ title: Production usage | |
| weight: 6 | ||
| --- | ||
|
|
||
| The following sections explain how to handle situations that may occur | ||
| in your production environment. | ||
| This guide offers recommendations to get the best reliability and | ||
| performance in your production environment. | ||
|
|
||
| ## Checklist | ||
|
|
||
| Each item in the checklist below links to the section | ||
| for a recommendation. Use the checklist icons to record your | ||
| progress in implementing the recommendations. | ||
|
|
||
| [](#client-side-caching) | ||
| {{< checklist "prodlist" >}} | ||
| {{< checklist-item "#connection-pooling" >}}Connection pooling{{< /checklist-item >}} | ||
| {{< checklist-item "#client-side-caching" >}}Client-side caching{{< /checklist-item >}} | ||
| {{< checklist-item "#timeouts" >}}Timeouts{{< /checklist-item >}} | ||
| {{< checklist-item "#health-checks" >}}Health checks{{< /checklist-item >}} | ||
| {{< checklist-item "#tcp-keepalive" >}}TCP keepalive{{< /checklist-item >}} | ||
| {{< checklist-item "#exception-handling" >}}Exception handling{{< /checklist-item >}} | ||
| {{< checklist-item "#dns-cache-and-redis" >}}DNS cache and Redis{{< /checklist-item >}} | ||
| {{< /checklist >}} | ||
|
|
||
| ## Recommendations | ||
|
|
||
| The sections below offer recommendations for your production environment. Some | ||
| of them may not apply to your particular use case. | ||
|
|
||
| ### Connection pooling | ||
|
|
||
| Example code often opens a connection at the start, demonstrates a feature, | ||
| and then closes the connection at the end. However, production code | ||
| typically uses connections many times intermittently. Repeatedly opening | ||
| and closing connections has a performance overhead. | ||
|
|
||
| Use [connection pooling]({{< relref "/develop/clients/pools-and-muxing" >}}) | ||
| to avoid the overhead of opening and closing connections without having to | ||
| write your own code to cache and reuse open connections. See | ||
| [Connect with a connection pool]({{< relref "/develop/clients/jedis/connect#connect-with-a-connection-pool" >}}) | ||
| to learn how to use this technique with Jedis. | ||
|
|
||
| ### Client-side caching | ||
|
|
||
| [Client-side caching]({{< relref "/develop/clients/client-side-caching" >}}) | ||
| involves storing the results from read-only commands in a local cache. If the | ||
| same command is executed again later, the result can be obtained from the cache, | ||
| without contacting the server. This improves command execution time on the client, | ||
| while also reducing network traffic and server load. See | ||
| [Connect using client-side caching]({{< relref "/develop/clients/jedis/connect#connect-using-client-side-caching" >}}) | ||
| for more information and example code. | ||
|
|
||
| ### Timeouts | ||
|
|
||
| To set a timeout for a connection, use the `JedisPooled` or `JedisPool` constructor with the `timeout` parameter, or use `JedisClientConfig` with the `socketTimeout` and `connectionTimeout` parameters: | ||
| If a network or server error occurs while your code is opening a | ||
| connection or issuing a command, it can end up hanging indefinitely. | ||
| You can prevent this from happening by setting timeouts for socket | ||
| reads and writes and for opening connections. | ||
|
|
||
| To set a timeout for a connection, use the `JedisPooled` or `JedisPool` constructor with the `timeout` parameter, or use `JedisClientConfig` with the `socketTimeout` and `connectionTimeout` parameters. | ||
| (The socket timeout is the maximum time allowed for reading or writing data while executing a | ||
| command. The connection timeout is the maximum time allowed for establishing a new connection.) | ||
|
|
||
| ```java | ||
| HostAndPort hostAndPort = new HostAndPort("localhost", 6379); | ||
|
|
@@ -34,9 +86,54 @@ JedisPooled jedisWithTimeout = new JedisPooled(hostAndPort, | |
| ); | ||
| ``` | ||
|
|
||
| ### Health checks | ||
|
|
||
| If your code doesn't access the Redis server continuously then it | ||
| might be useful to make a "health check" periodically (perhaps once | ||
| every few seconds). You can do this using a simple | ||
| [`PING`]({{< relref "/commands/ping" >}}) command: | ||
|
|
||
| ```java | ||
| try (Jedis jedis = jedisPool.getResource()) { | ||
| if (! "PONG".equals(jedis.ping())) { | ||
| // Report problem. | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| Health checks help to detect problems as soon as possible without | ||
| waiting for a user to report them. | ||
|
|
||
| ### TCP keepalive | ||
|
|
||
| [TCP keepalive](https://en.wikipedia.org/wiki/Keepalive) is a technique | ||
| where TCP packets are periodically sent on an otherwise idle connection | ||
| to check that it is still working. You can enable TCP keepalive for a | ||
| connection using an option on the connection config builder: | ||
|
|
||
| ```java | ||
| JedisClientConfig clientConfig = DefaultJedisClientConfig.builder() | ||
| .connectionTimeoutMillis(2000) | ||
| .socketTimeoutMillis(2000) | ||
| .tcpKeepAlive(true) | ||
|
||
| .build(); | ||
|
|
||
| JedisPool pool = new JedisPool(poolConfig, "redis-host", clientConfig); | ||
| ``` | ||
|
|
||
| TCP keepalive can be especially useful to detect when unused connections | ||
| in a [connection pool](#connection-pooling) have been dropped due to | ||
| inactivity. | ||
|
|
||
| ### Exception handling | ||
|
|
||
| The Jedis Exception Hierarchy is rooted on `JedisException`, which implements `RuntimeException`, and are therefore all unchecked exceptions. | ||
| Redis handles many errors using return values from commands, but there | ||
| are also situations where exceptions can be thrown. In production code, | ||
| you should handle | ||
andy-stark-redis marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| The Jedis exception hierarchy is rooted on `JedisException`, which implements | ||
| `RuntimeException`. All exceptions in the hierarchy are therefore unchecked | ||
| exceptions. | ||
|
|
||
| ``` | ||
| JedisException | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| <li> | ||
| <select onchange="clChange('prodlist')"> | ||
| <option value="R">❌</option> | ||
| <option value="G">✅</option> | ||
| <option value="A">🔍</option> | ||
| <option value="X">∅</option> | ||
| </select> | ||
| {{- if index .Params 0 -}} | ||
| <a href="{{ index .Params 0 }}">{{ .Inner }}</a> | ||
| {{- else -}} | ||
| {{ .Inner }} | ||
| {{- end -}} | ||
| </li> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,112 @@ | ||
| {{ $formId := index .Params 0 }} | ||
| <form id="{{ $formId }}"> | ||
| <ul style="list-style-type: none;padding-left: 0px;"> | ||
| {{ .Inner }} | ||
| </ul> | ||
| <label for="gcount">✅ = </label> | ||
| <output name="gcount" id="gcount">0</output>/<output name="gtotal"></output>, | ||
| <label for="rcount">❌ = </label> | ||
| <output name="rcount" id="rcount">0</output>/<output name="rtotal"></output>, | ||
| <label for="acount">🔍 = </label> | ||
| <output name="acount" id="acount">0</output>/<output name="atotal"></output> | ||
| <br/> | ||
| (<label for="xcount">∅ = </label> | ||
| <output name="xcount" id="xcount">0</output>/<output name="xtotal"></output>) | ||
| </form> | ||
| <script> | ||
| document.addEventListener('DOMContentLoaded', () => { | ||
| let itemString = localStorage.getItem("{{ $formId }}"); | ||
|
|
||
| if (itemString !== "") { | ||
| setCLItemsFromString("{{ $formId }}", itemString); | ||
| } else { | ||
| clChange("{{ $formId }}"); | ||
| } | ||
| }); | ||
|
|
||
| /* | ||
| function clChange(formId) { | ||
| let form = document.getElementById(formId); | ||
| let counts = {R: 0, G: 0, A: 0, X:0}; | ||
|
|
||
| let listItems = form.getElementsByTagName("li"); | ||
|
|
||
| for (let elem of listItems) { | ||
| let menu = elem.getElementsByTagName("select")[0]; | ||
| let mvalue = menu.value; | ||
|
|
||
| counts[mvalue]++; | ||
| } | ||
|
|
||
| form.elements["rcount"].value = counts["R"]; | ||
| form.elements["gcount"].value = counts["G"]; | ||
| form.elements["acount"].value = counts["A"]; | ||
| form.elements["xcount"].value = counts["X"]; | ||
|
|
||
| let numClItems = listItems.length - counts["X"]; | ||
| form.elements["rtotal"].value = numClItems; | ||
| form.elements["gtotal"].value = numClItems; | ||
| form.elements["atotal"].value = numClItems; | ||
| form.elements["xtotal"].value = listItems.length; | ||
| } | ||
| */ | ||
|
|
||
|
|
||
| function getStringFromCLItems(formId) { | ||
| let result = ""; | ||
|
|
||
| let form = document.getElementById(formId); | ||
|
|
||
| let listItems = form.getElementsByTagName("li"); | ||
|
|
||
| for (let elem of listItems) { | ||
| let menu = elem.getElementsByTagName("select")[0]; | ||
| result += menu.value; | ||
| } | ||
|
|
||
| return result; | ||
| } | ||
|
|
||
|
|
||
| function setCLItemsFromString(formId, clString) { | ||
| let counts = {R: 0, G: 0, A: 0, X:0}; | ||
|
|
||
| let form = document.getElementById(formId); | ||
| let listItems = form.getElementsByTagName("li"); | ||
|
|
||
| if (clString.length < listItems.length) { | ||
| clString = clString.padEnd(listItems.length, "R"); | ||
| } else if (clString.length > listItems.length) { | ||
| clString = clString.substring(0, listItems.length); | ||
| } | ||
|
|
||
| for (let i = 0; i < clString.length; i++) { | ||
| let char = clString.charAt(i); | ||
| counts[char]++; | ||
| let menu = listItems[i].getElementsByTagName("select")[0]; | ||
| menu.value = char; | ||
| } | ||
|
|
||
| form.elements["rcount"].value = counts["R"]; | ||
| form.elements["gcount"].value = counts["G"]; | ||
| form.elements["acount"].value = counts["A"]; | ||
| form.elements["xcount"].value = counts["X"]; | ||
|
|
||
|
|
||
| let numClItems = listItems.length - counts["X"]; | ||
|
|
||
| form.elements["rtotal"].value = numClItems; | ||
| form.elements["gtotal"].value = numClItems; | ||
| form.elements["atotal"].value = numClItems; | ||
| form.elements["xtotal"].value = counts["X"]; | ||
|
|
||
| let itemChoices = getStringFromCLItems("{{ $formId }}"); | ||
| localStorage.setItem("{{ $formId }}", itemChoices); | ||
| } | ||
|
|
||
|
|
||
| function clChange(formId) { | ||
| let itemChoices = getStringFromCLItems(formId); | ||
| setCLItemsFromString(formId, itemChoices); | ||
| } | ||
| </script> |
Uh oh!
There was an error while loading. Please reload this page.