-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add "Production usage" section to Jedis quick start (#2636)
- Loading branch information
Showing
1 changed file
with
92 additions
and
72 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -187,104 +187,124 @@ public class Main { | |
} | ||
``` | ||
|
||
### Example: Indexing and querying JSON documents | ||
### Production usage | ||
|
||
Make sure that you have Redis Stack and `Jedis` installed. | ||
### Configuring Connection pool | ||
As mentioned in the previous section, use `JedisPool` or `JedisPooled` to create a connection pool. | ||
`JedisPooled`, added in Jedis version 4.0.0, provides capabilities similar to `JedisPool` but with a more straightforward API. | ||
A connection pool holds a specified number of connections, creates more connections when necessary, and terminates them when they are no longer needed. | ||
|
||
Import dependencies and add a sample `User` class: | ||
Here is a simplified connection lifecycle in a pool: | ||
|
||
```java | ||
import redis.clients.jedis.JedisPooled; | ||
import redis.clients.jedis.search.*; | ||
import redis.clients.jedis.search.aggr.*; | ||
import redis.clients.jedis.search.schemafields.*; | ||
|
||
class User { | ||
private String name; | ||
private String email; | ||
private int age; | ||
private String city; | ||
|
||
public User(String name, String email, int age, String city) { | ||
this.name = name; | ||
this.email = email; | ||
this.age = age; | ||
this.city = city; | ||
} | ||
|
||
//... | ||
} | ||
``` | ||
1. A connection is requested from the pool. | ||
2. A connection is served: | ||
- An idle connection is served when non-active connections are available, or | ||
- A new connection is created when the number of connections is under `maxTotal`. | ||
3. The connection becomes active. | ||
4. The connection is released back to the pool. | ||
5. The connection is marked as stale. | ||
6. The connection is kept idle for `minEvictableIdleTime`. | ||
7. The connection becomes evictable if the number of connections is greater than `minIdle`. | ||
8. The connection is ready to be closed. | ||
|
||
Connect to your Redis database with `JedisPooled`. | ||
It's important to configure the connection pool correctly. | ||
Use `GenericObjectPoolConfig` from [Apache Commons Pool2](https://commons.apache.org/proper/commons-pool/apidocs/org/apache/commons/pool2/impl/GenericObjectPoolConfig.html). | ||
|
||
```java | ||
JedisPooled jedis = new JedisPooled("localhost", 6379); | ||
ConnectionPoolConfig poolConfig = new ConnectionPoolConfig(); | ||
// maximum active connections in the pool, | ||
// tune this according to your needs and application type | ||
// default is 8 | ||
poolConfig.setMaxTotal(8); | ||
|
||
// maximum idle connections in the pool, default is 8 | ||
poolConfig.setMaxIdle(8); | ||
// minimum idle connections in the pool, default 0 | ||
poolConfig.setMinIdle(0); | ||
|
||
// Enables waiting for a connection to become available. | ||
poolConfig.setBlockWhenExhausted(true); | ||
// The maximum number of seconds to wait for a connection to become available | ||
poolConfig.setMaxWait(Duration.ofSeconds(1)); | ||
|
||
// Enables sending a PING command periodically while the connection is idle. | ||
poolConfig.setTestWhileIdle(true); | ||
// controls the period between checks for idle connections in the pool | ||
poolConfig.setTimeBetweenEvictionRuns(Duration.ofSeconds(1)); | ||
|
||
// JedisPooled does all hard work on fetching and releasing connection to the pool | ||
// to prevent connection starvation | ||
JedisPooled jedis = new JedisPooled(poolConfig, "localhost", 6379); | ||
``` | ||
|
||
Let's create some test data to add to your database. | ||
|
||
```java | ||
User user1 = new User("Paul John", "[email protected]", 42, "London"); | ||
User user2 = new User("Eden Zamir", "[email protected]", 29, "Tel Aviv"); | ||
User user3 = new User("Paul Zamir", "[email protected]", 35, "Tel Aviv"); | ||
``` | ||
### Timeout | ||
|
||
Create an index. In this example, all JSON documents with the key prefix `user:` are indexed. For more information, see [Query syntax](/docs/interact/search-and-query/query/). | ||
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: | ||
|
||
```java | ||
jedis.ftCreate("idx:users", | ||
FTCreateParams.createParams() | ||
.on(IndexDataType.JSON) | ||
.addPrefix("user:"), | ||
TextField.of("$.name").as("name"), | ||
TagField.of("$.city").as("city"), | ||
NumericField.of("$.age").as("age") | ||
HostAndPort hostAndPort = new HostAndPort("localhost", 6379); | ||
|
||
JedisPooled jedisWithTimeout = new JedisPooled(hostAndPort, | ||
DefaultJedisClientConfig.builder() | ||
.socketTimeoutMillis(5000) // set timeout to 5 seconds | ||
.connectionTimeoutMillis(5000) // set connection timeout to 5 seconds | ||
.build(), | ||
poolConfig | ||
); | ||
``` | ||
|
||
Use `JSON.SET` to set each user value at the specified path. | ||
### Exception handling | ||
The Jedis Exception Hierarchy is rooted on `JedisException`, which implements `RuntimeException`, and are therefore all unchecked exceptions. | ||
|
||
```java | ||
jedis.jsonSetWithEscape("user:1", user1); | ||
jedis.jsonSetWithEscape("user:2", user2); | ||
jedis.jsonSetWithEscape("user:3", user3); | ||
``` | ||
JedisException | ||
├── JedisDataException | ||
│ ├── JedisRedirectionException | ||
│ │ ├── JedisMovedDataException | ||
│ │ └── JedisAskDataException | ||
│ ├── AbortedTransactionException | ||
│ ├── JedisAccessControlException | ||
│ └── JedisNoScriptException | ||
├── JedisClusterException | ||
│ ├── JedisClusterOperationException | ||
│ ├── JedisConnectionException | ||
│ └── JedisValidationException | ||
└── InvalidURIException | ||
``` | ||
|
||
Let's find user `Paul` and filter the results by age. | ||
#### General Exceptions | ||
In general, Jedis can throw the following exceptions while executing commands: | ||
|
||
```java | ||
var query = new Query("Paul @age:[30 40]"); | ||
var result = jedis.ftSearch("idx:users", query).getDocuments(); | ||
System.out.println(result); | ||
// Prints: [id:user:3, score: 1.0, payload:null, properties:[$={"name":"Paul Zamir","email":"[email protected]","age":35,"city":"Tel Aviv"}]] | ||
``` | ||
- `JedisConnectionException` - when the connection to Redis is lost or closed unexpectedly. Configure failover to handle this exception automatically with Resilience4J and the built-in Jedis failover mechanism. | ||
- `JedisAccessControlException` - when the user does not have the permission to execute the command or the user ID and/or password are incorrect. | ||
- `JedisDataException` - when there is a problem with the data being sent to or received from the Redis server. Usually, the error message will contain more information about the failed command. | ||
- `JedisException` - this exception is a catch-all exception that can be thrown for any other unexpected errors. | ||
|
||
Return only the `city` field. | ||
Conditions when `JedisException` can be thrown: | ||
- Bad return from a health check with the `PING` command | ||
- Failure during SHUTDOWN | ||
- Pub/Sub failure when issuing commands (disconnect) | ||
- Any unknown server messages | ||
- Sentinel: can connect to sentinel but master is not monitored or all Sentinels are down. | ||
- MULTI or DISCARD command failed | ||
- Shard commands key hash check failed or no Reachable Shards | ||
- Retry deadline exceeded/number of attempts (Retry Command Executor) | ||
- POOL - pool exhausted, error adding idle objects, returning broken resources to the pool | ||
|
||
```java | ||
var city_query = new Query("Paul @age:[30 40]"); | ||
var city_result = jedis.ftSearch("idx:users", city_query.returnFields("city")).getDocuments(); | ||
System.out.println(city_result); | ||
// Prints: [id:user:3, score: 1.0, payload:null, properties:[city=Tel Aviv]] | ||
``` | ||
All the Jedis exceptions are runtime exceptions and in most cases irrecoverable, so in general bubble up to the API capturing the error message. | ||
|
||
Count all users in the same city. | ||
## DNS cache and Redis | ||
|
||
```java | ||
AggregationBuilder ab = new AggregationBuilder("*") | ||
.groupBy("@city", Reducers.count().as("count")); | ||
AggregationResult ar = jedis.ftAggregate("idx:users", ab); | ||
When you connect to a Redis with multiple endpoints, such as [Redis Enterprise Active-Active](https://redis.com/redis-enterprise/technology/active-active-geo-distribution/), it's recommended to disable the JVM's DNS cache to load-balance requests across multiple endpoints. | ||
|
||
for (int idx=0; idx < ar.getTotalResults(); idx++) { | ||
System.out.println(ar.getRow(idx).getString("city") + " - " + ar.getRow(idx).getString("count")); | ||
} | ||
// Prints: | ||
// London - 1 | ||
// Tel Aviv - 2 | ||
You can do this in your application's code with the following snippet: | ||
```java | ||
java.security.Security.setProperty("networkaddress.cache.ttl","0"); | ||
java.security.Security.setProperty("networkaddress.cache.negative.ttl", "0"); | ||
``` | ||
|
||
### Learn more | ||
|
||
* [Jedis API reference](https://www.javadoc.io/doc/redis.clients/jedis/latest/index.html) | ||
* [Failover with Jedis](https://github.com/redis/jedis/blob/master/docs/failover.md) | ||
* [GitHub](https://github.com/redis/jedis) |