Jedis — это Java-клиент для Redis, и этот код — пример приложения Jedis.
Мой технический блог синхронизирован:Temple Network.GitHub.IO/2019/12/07/…
Общий режим Redis
Раскол Redisрежим ведущий-ведомыйа такжекластерный режим.
режим ведущий-ведомый
Режим Master-Slave использует один экземпляр Redis в качестве Master (Master) и другие экземпляры в качестве резервного компьютера (раба). Мастер поддерживает различные операции, такие как запись и чтение, а подсовесные опоры читают операции и синхронизируют данные с мастером Отказ Основная идея режима Master-Slave - разделение чтения и записи, избыточного хранения данных и HA, если есть проблема с главным узлом, Master-Slave переключение может быть достигнуто через Redis Sentinel.
Система Sentinel используется для управления несколькими серверами Redis (экземплярами), система выполняет следующие три задачи:
- Мониторинг: Sentinel постоянно проверяет правильность работы ваших главных и подчиненных серверов.
- Уведомления: Sentinel может отправлять уведомления администраторам или другим приложениям через API, когда возникает проблема с одним из отслеживаемых серверов Redis.
- Автоматический переход на другой ресурс: когда главный сервер не работает должным образом, Sentinel запускает операцию автоматического перехода на другой ресурс, которая обновляет один из подчиненных серверов вышедшего из строя главного сервера до нового главного сервера и позволяет отказавшему главному серверу работать с другими подчиненными серверами. изменен для репликации нового главного сервера; когда клиент пытается подключиться к вышедшему из строя главному серверу, кластер также возвращает клиенту адрес нового главного сервера, чтобы кластер мог использовать новый главный сервер вместо отказавшего сервер.
кластерный режим
Хотя режим Redis master-slave очень мощный, его архитектура с одним ведущим устройством при столкновении сАвтономная память,параллелизм,потокКогда вы будете ждать узкого места, вы будете беспомощны.Появление кластера Redis должно решить проблемы, возникающие в режиме master-slave. До появления Redis Cluster для решения проблемы Redis в отрасли появилось несколько отличных кластерных решений Redis, таких какTwemproxyа такжеCodis, если интересно, можете идти учиться, в этой статье больше не будет сравниваться достоинства и недостатки каждого.
Распределение данных в режиме кластера
Теория распределения данных
Выдержки из справочного документа 3, автор этой статьи уже сделал хорошее резюме.
Распределенная база данныхСначала решитьВесь набор данныхсогласно сПравила разбиениясопоставить снесколько узловпроблема, каждый узел отвечает заОбщие данныеПодмножество.
Распределение данных Обычнохэш-раздела такжепоследовательный разделЭти два метода сравниваются следующим образом:
Разделение | Функции | сопутствующие товары |
---|---|---|
хэш-раздел | Степень дисперсии хорошая, распределение данных не имеет никакого отношения к бизнесу и не может быть доступно последовательно. | Кластер Redis, Cassandra, Dynamo |
последовательный раздел | Степень дискретности легко изменить, распределение данных связано с бизнесом, и к ним можно обращаться последовательно. | БигТабл, HBase, Гипертаблица |
из-заRedis ClusterиспользоватьПравила разбиения хэша, обсуждалось здесьхэш-раздел. Существует несколько общих правил разбиения хеша:
-
Раздел остатка узла: используйте определенные данные, такие как ключ Redis или идентификатор пользователя, а затем используйте формулу в соответствии с количеством узлов N:
hash(key)% N
Вычислите хеш-значение, чтобы определить, на какой узел сопоставляются данные. Этот метод прост и практичен, общеязыковая база данныхПодбиблиотека и подтаблица, обычно используется метод предварительного разделения, и количество разделов планируется заранее в соответствии с предполагаемым объемом данных. Недостаток также очевиден, когда количество узлов меняется, напримерРасширениеилисокращать, Узлы данныхСопоставление отношенийТребуется пересчет, который приведет к повторной миграции данных. -
Согласованное разбиение хэша:Согласованное хешированиеЭто может очень хорошо решить проблему стабильности и поставить всеузел храненияустроен впритыкХэш кольцо, каждый ключ после вычисления Hashпо часовой стрелкеНайдите соседний узел хранения для хранения. когда есть узлыПрисоединяйсяилипокидать, затрагиваются только последующие узлы, смежные с этим узлом по часовой стрелке на хеш-кольце. Добавление и удаление узлов влияет только на соседние узлы в направлении по часовой стрелке в хэш-кольце и не влияет на другие узлы, но это все равно приведет к тому, что некоторые данные в хэш-кольце не попадут. При использовании небольшого количества узлов изменения узлов сильно повлияют на отображение данных в хеш-кольце.Распределенная схема не подходит для небольшого количества узлов данных.Обычный согласованный хеш-раздел должен удваивать или вычитать половину узлов при добавлении или удалении узлов, чтобы обеспечить баланс данных и нагрузки..
-
раздел виртуального слота: Раздел виртуального слота разумно использует хеш-пространство и использует хорошо распределенную хэш-функцию для сопоставления всех данных с фиксированным диапазоном целых чисел.Целое число определяется какслот.Этот диапазон, как правило, намного больше, чем количество узлов, например, диапазон слотов Redis Cluster — от 0 до 16383. Слот — это базовая единица управления данными и миграции в кластере. Основная цель использования крупномасштабных слотов — облегчить разделение данных и расширение кластера. Каждый узел отвечает за определенное количество слотов.Поскольку перемещение хеш-слотов с одного узла на другой не останавливает службу, добавление, удаление или изменение количества хэш-слотов на узле не приведет к тому, что кластер станет недоступным..
Особенности раздела виртуального слота Redis:
- Связь между разъединением данных и узлов упрощает сложность расширения и сокращения узлов.
- Узел сам поддерживает отношения сопоставления слотов и не требует, чтобы клиент или прокси-служба поддерживали метаданные раздела слотов.
- Поддерживает сопоставление запросов между узлами, слотами и ключами для таких сценариев, как маршрутизация данных и онлайн-масштабирование.
Функциональные ограничения Redis Cluster
Redis
относительный кластеравтономныйСуществуют некоторые ограничения в функциональности, которые требуютРазработчикЗнайте заранее и избегайте его, когда вы его используете.
-
key
Массовые операцииПоддержка ограничена.
аналогичныйmset
,mget
операции, в настоящее время поддерживаются только пары с одинаковымиslot
стоило тогоkey
воплощать в жизньМассовые операции. длякарта на разные slot
стоило тогоkey
из-за исполненияmget
,mget
Такие операции могут выполняться на нескольких узлах и поэтому не поддерживаются.
-
key
транзакционная операцияПоддержка ограничена.
только поддержкамного key
существуетНа том же узлеизтранзакционная операция, когда несколькоkey
распространяется вразныена узленевозможноИспользуйте функцию транзакции.
-
key
так какраздел данныхминимальная степень детализации
не может разместитьбольшое значение ключаобъекты, такие какhash
,list
и т.д. сопоставитьразные узлы.
- не поддерживаетсяНесколько пространств баз данных
автономныйвнизRedis
может поддерживать16
база данных (db0 ~ db15
),кластерный режимможно использовать толькоОдинпространство базы данных, т.е.db0
.
- копировать структуруподдерживает только один слой
подчиненный узелтолько копироватьглавный узел,не поддерживаетсяВложенный дерево копиейструктура.
Классическая реализация джедаев
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.*;
import redis.clients.util.JedisClusterCRC16;
import java.util.*;
public class RedisCacheDelegate extends AbstractCache implements CacheManager, Cache {
private static Logger logger = LoggerFactory.getLogger(RedisCacheDelegate.class);
/**
* 集群节点
*/
private String clusterNodes;
/**
* 重试次数
*/
private int maxAttempts;
/**
* 超时时间,单位是秒
*/
private int timeout;
private JedisCluster jedisCluster;
private JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
private static RedisCacheDelegate redisCacheDelegate = null;
private JedisClusterInfoCache cache;
private final static String WARM_KEY = "warm_key";
private final static String WARM_VALUE = "value";
public static RedisCacheDelegate getInstant(CacheProperties cacheProperties) {
if (redisCacheDelegate == null) {
synchronized (RedisCacheDelegate.class) {
if (redisCacheDelegate == null) {
redisCacheDelegate = new RedisCacheDelegate(cacheProperties.getNodes(), cacheProperties.getTimeout(), cacheProperties.getMaxAttempts());
}
}
}
return redisCacheDelegate;
}
private RedisCacheDelegate(String clusterNodes, int timeout, int maxAttempts) {
this.clusterNodes = clusterNodes;
this.timeout = timeout;
this.maxAttempts = maxAttempts;
init();
}
private JedisPoolConfig getJedisPoolConfig() {
//连接最长等待时间,默认是-1
jedisPoolConfig.setMaxWaitMillis(200);
//连接池最大数量
jedisPoolConfig.setMaxTotal(50);
//最小闲置个数 闲置超过最小闲置个数但不超过最大闲置个数,则逐步清理闲置直到最小闲置个数
jedisPoolConfig.setMinIdle(10);
//最大闲置个数 闲置超过最大闲置个数则直接杀死超过部分
jedisPoolConfig.setMaxIdle(30);
//连接耗尽等待,等待最长{MaxWaitMillis}毫秒
jedisPoolConfig.setBlockWhenExhausted(true);
//是否开启jmx监控
jedisPoolConfig.setJmxEnabled(true);
//是否开启空闲资源监测
jedisPoolConfig.setTestWhileIdle(true);
//空闲资源的检测周期(单位为毫秒)
jedisPoolConfig.setMinEvictableIdleTimeMillis(60000);
//资源池中资源最小空闲时间(单位为毫秒),达到此值后空闲资源将被移除
jedisPoolConfig.setTimeBetweenEvictionRunsMillis(30000);
//做空闲资源检测时,每次的采样数,如果设置为-1,就是对所有连接做空闲监测
jedisPoolConfig.setNumTestsPerEvictionRun(-1);
return jedisPoolConfig;
}
@Override
public void init() {
String[] serverArray = clusterNodes.split(",");
Set<HostAndPort> nodes = new HashSet<>();
for (String ipPort : serverArray) {
String[] ipPortPair = ipPort.split(":");
nodes.add(new HostAndPort(ipPortPair[0].trim(), Integer.valueOf(ipPortPair[1].trim())));
}
jedisCluster = new JedisCluster(nodes, timeout * 1000, maxAttempts, getJedisPoolConfig());
MetaObject metaObject = SystemMetaObject.forObject(jedisCluster);
cache = (JedisClusterInfoCache) metaObject.getValue("connectionHandler.cache");
warm();
}
/**
* warm the jedis pool
*/
@Override
public void warm() {
set(WARM_KEY, WARM_VALUE, 60);
for (int i = 0; i < jedisPoolConfig.getMinIdle(); i++) {
ttl(WARM_KEY);
}
}
@Override
public void set(String key, String value) {
jedisCluster.set(key, value);
}
@Override
public void set(String key, String value, int expiredTime) {
jedisCluster.setex(key, expiredTime, value);
}
@Override
public void mSet(Map<String, String> data) {
if (data != null && data.size() > 0) {
data.forEach((key, value) -> jedisCluster.set(key, value));
}
}
@Override
public void mSetPipLine(Map<String, String> data) {
setPipLine(data, 0);
}
private void setPipLine(Map<String, String> data, int expiredTime) {
if (data.size() < 1) {
return;
}
//保存地址+端口和命令的映射
Map<JedisPool, Map<String, String>> jedisPoolMap = new HashMap<>();
JedisPool currentJedisPool = null;
for (String key : data.keySet()) {
//计算哈希槽
int crc = JedisClusterCRC16.getSlot(key);
//通过哈希槽获取节点的连接
currentJedisPool = cache.getSlotPool(crc);
if (jedisPoolMap.containsKey(currentJedisPool)) {
jedisPoolMap.get(currentJedisPool).put(key, data.get(key));
} else {
Map<String, String> inner = new HashMap<>();
inner.put(key, data.get(key));
jedisPoolMap.put(currentJedisPool, inner);
}
}
//保存结果
Map<String, String> map = null;
//执行
for (Map.Entry<JedisPool, Map<String, String>> entry : jedisPoolMap.entrySet()) {
try {
currentJedisPool = entry.getKey();
map = entry.getValue();
Jedis jedis = currentJedisPool.getResource();
//获取pipeline
Pipeline currentPipeline = jedis.pipelined();
// NX是不存在时才set, XX是存在时才set, EX是秒,PX是毫秒
if (expiredTime > 0) {
map.forEach((k, v) -> currentPipeline.setex(k, expiredTime, v));
} else {
map.forEach((k, v) -> currentPipeline.set(k, v));
}
//从pipeline中获取结果
currentPipeline.sync();
currentPipeline.close();
jedis.close();
} catch (Exception e) {
logger.error("setPipline error.", e);
}
}
}
@Override
public void mSet(Map<String, String> data, int expiredTime) {
if (data != null && data.size() > 0) {
data.forEach((key, value) -> jedisCluster.setex(key, expiredTime, value));
}
}
@Override
public void mSetPipLine(Map<String, String> data, int expiredTime) {
setPipLine(data, expiredTime);
}
@Override
public String get(String key) {
return jedisCluster.get(key);
}
@Override
public List<String> mGet(List<String> keys) {
if (keys.size() < 1) {
return null;
}
List<String> result = new ArrayList<>(keys.size());
for (String key : keys) {
result.add(jedisCluster.get(key));
}
return result;
}
@Override
public List<String> mGetPipLine(List<String> key) {
return getPipLine(key);
}
@Override
public long ttl(String key) {
return jedisCluster.ttl(key);
}
private List<String> getPipLine(List<String> keys) {
if (keys.size() < 1) {
return null;
}
List<String> result = new ArrayList<>(keys.size());
Map<String, String> resultMap = new HashMap<>(keys.size());
if (keys.size() == 1) {
result.add(jedisCluster.get(keys.get(0)));
return result;
}
//保存地址+端口和命令的映射
Map<JedisPool, List<String>> jedisPoolMap = new HashMap<>();
List<String> keyList = null;
JedisPool currentJedisPool = null;
Pipeline currentPipeline = null;
for (String key : keys) {
//cuteculate hash
int crc = JedisClusterCRC16.getSlot(key);
//通过哈希槽获取节点的连接
currentJedisPool = cache.getSlotPool(crc);
if (jedisPoolMap.containsKey(currentJedisPool)) {
jedisPoolMap.get(currentJedisPool).add(key);
} else {
keyList = new ArrayList<>();
keyList.add(key);
jedisPoolMap.put(currentJedisPool, keyList);
}
}
//保存结果
List<Object> res;
//执行
for (Map.Entry<JedisPool, List<String>> entry : jedisPoolMap.entrySet()) {
try {
currentJedisPool = entry.getKey();
keyList = entry.getValue();
//获取pipeline
Jedis jedis = currentJedisPool.getResource();
currentPipeline = jedis.pipelined();
for (String key : keyList) {
currentPipeline.get(key);
}
//从pipeline中获取结果
res = currentPipeline.syncAndReturnAll();
currentPipeline.close();
jedis.close();
for (int i = 0; i < keyList.size(); i++) {
resultMap.put(keyList.get(i), res.get(i) == null ? null : res.get(i).toString());
}
} catch (Exception e) {
logger.error("getPipLine error.", e);
}
}
//sort
for (String key : keys) {
result.add(resultMap.containsKey(key) ? resultMap.get(key) : null);
}
return result;
}
@Override
public void destroy() {
try {
jedisCluster.close();
} catch (Exception e) {
}
}
}
Справочная документация
1,Играйте с кластером Redis Cluster
2,Режим Redis Sentinel реализует переключение ведущий-ведомый