Сценарий применения общих команд Redis Применение структуры данных
инструкция
-
1, ссылка здесьРедис Разработка практических руководящих принциповГитбук и"Редис бой"собственные предыдущие заметки. Основная рама происходит отздесь.
-
2.Спасибо за старания.Здесь просто записываю и углубляю свое впечатление.
-
3. Эта статья будет синхронизирована для моего собственногоGuo_GitHub,удобно для собственного обзора.Если понравилось,можете нажать на звездочку.
-
4. Простите меня за то, что я не могу обобщить сам, поэтому я могу только копировать других, добавлять свое понимание и объединять свои предыдущие заметки.
-
5. Если есть орфографическая ошибка, пожалуйста, простите меня. Если у вас разные мнения, вы можете комментировать и работать вместе.
gogogo Давайте официально войдем в тему,
Введение в сценарии использования 5 структур данных в Redis
Redis – это хранилище структур данных в памяти с открытым исходным кодом (под лицензией BSD), используемое в качестве базы данных, кеша и брокера сообщений. Оно поддерживает такие структуры данных, как строки, хэши, списки, наборы, отсортированные наборы с запросами диапазона, растровые изображения, гиперлоглоги. Redis имеет встроенную репликацию, сценарии Lua, вытеснение LRU, транзакции и различные уровни сохраняемости на диске, а также обеспечивает высокую доступность с помощью Redis Sentinel и автоматического разделения с помощью Redis Cluster.
Очень и очень хорошо порекомендовать посмотреть «Redis в действии», особенно первые три главы. Ключевым моментом использования Redis является не проектирование базы данных, а выбор структуры данных для соответствующего сценария. Точно так же, как когда мы ранее манипулировали контейнером, разные варианты легко содержат разные объекты. У каждого есть свои особенности, и конкретный контент будет предоставлен вам, когда вы посмотрите на исходный код через два дня. Сегодняшняя тема — следующие пять структур данных.
Существует 5 основных структур данных:
- Строка ——> Строка
- Хэш --> словарь (хеш)
- Список ——> Список
- Set -> Коллекция
- Сортированный набор -> упорядоченный набор
1. Струна - Струна
Структура данных String представляет собой простой тип Key-Value, а Value может быть не только String, но и числом. Используя тип String, вы можете полностью реализовать текущийMemcached
(Существует только одна функция String), и она более эффективна. Вы также можете наслаждаться постоянством времени Redis (режим RDB или режим AOF) и предоставлять такие функции, как журнал и репликация. В дополнение к предоставлению get, set, incr, decr и других операций с Memcached, Redis также предоставляет следующие операции:
- LEN niushuai: O(1), чтобы получить длину строки
- APPEND niushuai redis: добавьте содержимое в строку и используйте интеллектуальное распределение памяти (каждый раз 2 раза)
- Установить и получить раздел строки
- Установить и получить битовую строку (bit)
- Пакетный набор содержимого серии строк
- атомный счетчик
- Магия команды GETSET: установите новое значение, очистив старое значение, и используйте его с атомарным счетчиком.
//1、计数器的使用
String articleId = String.valueOf(conn.incr("article:")); //String.valueOf(int i) : 将 int 变量 i 转换成字符串
Поместите некоторые общие команды, чтобы облегчить ваш обзор
set key value [ex 秒数] / [px 毫秒数] [nx] /[xx] //返回1表示成功,0失败
incr key //对key做加加操作,
decr key //对key做减减操作
setnx key value //仅当key不存在时才set,nx表示not exist。(重点)
mset key1 value1 key2 value2 .。。 //一次设置多个key,
--------------------------------------------------------------------------------
get key //如果key不存在返回null
mget key1 key2 ... keyN //一次获取多个key的值,如果对于的key不存在,则返回null
getset key value //设置新值返回旧值。
Идея распределенной блокировки:
- C3 Отправить Setnx Lock.foo Хотите получить замок, поскольку C0 также содержит замок, поэтому Redis возвращается на C3 0
- C3 отправляет GET lock.foo, чтобы проверить, не истек ли срок блокировки, и если нет, подождите или повторите попытку.
- И наоборот, если время истекло, C3 пытается получить блокировку, выполнив следующие действия:
- GETSET lock.foo
- Через GETSET, если временная метка, полученная C3, все еще истекла, это означает, что C3 получил блокировку, как он хотел.
- Если до C3 клиент с именем C4 выполняет указанную выше операцию на один шаг быстрее, чем C3, то отметка времени, полученная C3, является значением, для которого не истекло время ожидания.В это время C3 не получает блокировку по расписанию и должен ждать или повторите попытку. . Обратите внимание, что хотя C3 не получил блокировку, он перезаписал значение времени ожидания блокировки, установленное C4, но влияние этой очень небольшой ошибки незначительно.
Поддельный код:
# get lock
lock = 0
while lock != 1:
timestamp = current Unix time + lock timeout + 1 //时间戳
lock = SETNX lock.foo timestamp //尝试获取锁,返回0,则下面检查是否超时,GET。
if lock == 1 or (now() > (GET lock.foo) and now() > (GETSET lock.foo timestamp)):
break; //关键点上面这条代码
else:
sleep(10ms)
# do your job
do_job()
# release
if now() < GET lock.foo:
DEL lock.foo
gogogo
incr key //对key做加加操作,
decr key //对key做减减操作
incrby key interge //对key加指定的数
incrbyfloat key floatnumber //针对浮点数
append key value //返回新字符的长度
substr key start end //并不会修改字符串,返回截取的字符串
getrange key start end //返回子串
strlen key //取指定key的Value
2. Хэш — Словарь
在Memcached中,经常就爱那个一些结构化的信息打包成hashMap,在客户端序列化后存储为一个字符串的值,(通常为JSON格式),比如用户的昵称、年龄、性别、积分。 At this time, when one of the items needs to be modified, the general needs to take out the string (JSON), then deserialize it, modify the value of a certain item, and then serialize it into a string (JSON) and store его обратно.而Redis和Hash结构可以使你像在数据库中Update一个属性一样只修改某一项属性值。
В основе реализации лежит хеш-таблица, общая сложность операции составляет O (1), когда поле должно одновременно работать с множеством O (N), N - количество полей. Сценарий: индексация коренных народов. Например, объекты User, в дополнение к идентификатору, а иногда и по имени для запроса.
Общие команды:
hset key field value //设置hash field为指定值,如果key不存在,则先创建
hsetnx //同时,如果存在返回0,nx是not exist的意思
hmset key filed1 value1 ... filedN valueN //设置多个值
hget key field //获取指定的hash field
hmget key field1 field2 //获取全部指定的field
-------------------------------------------------------------------------------
hincrby key field integer //将指定的hash field加上给定值
hexists key field //测试指定field是否存在
hdel key field //删除指定的field
hlen key //返回会指定hash的field数量
hgetall //返回hash所有field和value
Сцены:
/**
* 使用Redis重新实现登录cookie,取代目前由关系型数据库实现的登录cookie功能
* 1、将使用一个散列来存储登录cookie令牌与与登录用户之间的映射。
* 2、需要根据给定的令牌来查找与之对应的用户,并在已经登录的情况下,返回该用户id。
*/
//1、尝试获取并返回令牌对应的用户 注意“login:” 一般使用冒号做分割符,这是不成文的规矩
conn.hget("login:", token);
//2、维持令牌与已登录用户之间的映射。
conn.hset("login:", token, user);
//6、移除被删除令牌对应的用户信息
conn.hdel("login:", tokens);
---------------------------注意中间还有其他步骤----------------------------------
/**
* 使用cookie实现购物车——就是将整个购物车都存储到cookie里面,
* 优点:无需对数据库进行写入就可以实现购物车功能,
* 缺点:怎是程序需要重新解析和验证cookie,确保cookie的格式正确。并且包含商品可以正常购买
* 还有一缺点:因为浏览器每次发送请求都会连cookie一起发送,所以如果购物车的体积较大,
* 那么请求发送和处理的速度可能降低。
* -----------------------------------------------------------------
* 1、每个用户的购物车都是一个散列,存储了商品ID与商品订单数量之间的映射。
* 2、如果用户订购某件商品的数量大于0,那么程序会将这件商品的ID以及用户订购该商品的数量添加到散列里。
* 3、如果用户购买的商品已经存在于散列里面,那么新的订单数量会覆盖已有的。
* 4、相反,如果某用户订购某件商品数量不大于0,那么程序将从散列里移除该条目
* 5、需要对之前的会话清理函数进行更新,让它在清理会话的同时,将旧会话对应的用户购物车也一并删除。
*/
//1、从购物车里面移除指定的商品 注意"cart:" 可以在数据迁移、转换 、和删除时轻松识别
conn.hdel("cart:" + session, item);
//2、将指定的商品添加到购物车
conn.hset("cart:" + session, item, String.valueOf(count));
//6、移除被删除令牌对应的用户信息
conn.hdel("login:", tokens);
--------------------------------------------------------------------------------
//5、将文章信息存储到一个散列里面。
//HMSET key field value [field value ...]
//同时将多个 field-value (域-值)对设置到哈希表 key 中。
//此命令会覆盖哈希表中已存在的域。
conn.hmset(article, articleData);
//为哈希表 key 中的域 field 的值加上增量 increment 。
//增量也可以为负数,相当于对给定域进行减法操作。
//HINCRBY counter page_view 200
conn.hincrBy(article, "votes", 1L);
//如果返回1表示还没有这个数据,注意-1后面的L
conn.hincrBy(article, "votes", -1L);
//3、根据文章ID获取文章的详细信息
Map<String,String> articleData = conn.hgetAll(id);
--------------------------测试-------------------------------------------------
//2、测试文章的投票过程
articleVote(conn, "other_user", "article:" + articleId);
String votes = conn.hget("article:" + articleId, "votes");
System.out.println("我们为该文章投票,目前该文章的票数 " + votes);
assert Integer.parseInt(votes) > 1;
На самом деле сценариев применения много. Вот только часть выдернутого фрагмента программы, вы можетекликните сюда. Следует отметить, что при совместном использовании берите эссенцию и удаляйте шлак.
3. Список - Список
Проще говоря, List — это связанный список (Redis использует двусторонний связанный список для реализации List). Используя структуру List, мы можем легко реализовать такие функции, как ранжирование последних новостей (например, TimeLine в Twitter). Применение списка - это очередь сообщений, которую можно использовать со списком.В операции PUSH задача сохраняется в списке, а затем операция POP рабочей строки извлекает задачу и выполняет ее. Redis также предоставляет API для управления определенным сегментом элементов в списке, который можно напрямую запросить для удаления определенного сегмента элементов в списке.
Заказ:
lpush key string //在key对应的list的头部添加元素
rpush key string //在list的尾部添加元素
lpushx key value //如果key不存在,什么都不做
rpushx key value //同上
linsert key BEFORE|AFTER pivot value //在list对应的位置之前或之后
---------------------------------------------------------------------------
llen key //查看列表对应的长度
lindex key index //指定索引的位置,0第一个
lrange key start end //查看一段列表
lrange key 0 -1 // -1表示返回所有数据
ltrim key start end //保留指定区间的元素
lset key index value //idnex表示指定索引的位置
ldel //删除元素
blpop key [key ...] timeout //阻塞队列
brpop key [key ...] timeout
Построение очереди сообщений на основе RedisНажмите здесь, очень хорошо написано. Чтобы проверить для себя, я использовал доктрину.
Вообще говоря, существует два сценария для очередей сообщений: один — режим издатель-подписчик, другой — режим производитель-потребитель. Очереди сообщений с использованием Redis для обоих сценариев могут быть реализованы.
- Модель производителя-потребителя: производители для производства сообщения в очереди, очередь монитор мониторинга нескольких потребителей одновременно, первым захватить новости, которые будут удалены из очереди; то есть может быть только на одном сообщении для каждого клиента только имеют. (Обычно используется при лечении высоких одновременных операций записи)
- Режим издатель-подписчик: издатель создает сообщения и помещает их в очередь, а несколько потребителей, прослушивающих очередь, получат одно и то же сообщение; то есть в нормальных условиях сообщения, полученные каждым потребителем, должны быть одинаковыми. (Он часто используется как часть необработанных данных в сборе журналов для нескольких сценариев приложений)
1. Redis как промежуточное ПО для сообщений:
- 1) Режим производителя/потребителя:
Этот метод реализован с помощью списковой структуры Redis. Производитель вызывает lpush Redis, чтобы вставить сообщение в определенный ключ, а Потребитель вызывает
brpop
(метод блокировки), чтобы продолжать прослушивание ключа.
// producer code
String key = "demo:mq:test";
String msg = "hello world";
redisDao.lpush(key, msg);
// consumer code
String key = "demo:mq:test";
while (true) {
// block invoke
List<String> msgs = redisDao.brpop(BLOCK_TIMEOUT, listKey); //注意brpop
if (msgs == null) continue;
String jobMsg = msgs.get(1);
processMsg(jobMsg);
}
1. Как использовать Redis для создания очереди сообщений
- 1. Во-первых, Redis по своей конструкции используется для кэширования, но из-за своих особенностей он может использовать его для создания очереди сообщений. Он имеет несколько API-интерфейсов блокировки (BrPOP, Sub, они заблокированы), которые можно использовать, что позволяет этим API-интерфейсам блокировки создавать очереди сообщений.
- 2. Затем другие характеристики сообщения, например, очередь FIFO также очень проста в реализации, требует только выборки данных объекта списка с самого начала, данные из хвостовой вилки могут быть реализованы.
2. Подписно-издательская система
Pub/Sub буквально означает «Публикация и подписка». В Redis вы можете настроить публикацию сообщений и подписку на сообщения для определенного значения ключа. Когда сообщение публикуется по значению ключа, все клиенты, подписавшиеся на него, получат соответствующее сообщение. Наиболее очевидным использованием этой функции является система обмена сообщениями в реальном времени, например, обычный мгновенный чат, групповой чат и другие функции.
КодНажмите здесь, и тот же человек, указанный выше, был запрошен:
Чтобы использовать функцию публикации/подписки Jedis, вы должны написать собственную реализацию JedisPubSub.
public class MyListener extends JedisPubSub {
// 取得订阅的消息后的处理
@Override
public void onMessage(String channel, String message) {
// TODO Auto-generated method stub
System.out.println(channel + "=" + message);
}
// 取得按表达式的方式订阅的消息后的处理
@Override
public void onPMessage(String pattern, String channel, String message) {
// TODO Auto-generated method stub
System.out.println(pattern + ":" + channel + "=" + message);
}
// 初始化订阅时候的处理
@Override
public void onSubscribe(String channel, int subscribedChannels) {
// TODO Auto-generated method stub
System.out.println("初始化 【频道订阅】 时候的处理 ");
}
// 取消订阅时候的处理
@Override
public void onUnsubscribe(String channel, int subscribedChannels) {
// TODO Auto-generated method stub
System.out.println("// 取消 【频道订阅】 时候的处理 ");
}
// 初始化按表达式的方式订阅时候的处理
@Override
public void onPSubscribe(String pattern, int subscribedChannels) {
// TODO Auto-generated method stub
System.out.println("初始化 【模式订阅】 时候的处理 ");
}
// 取消按表达式的方式订阅时候的处理
@Override
public void onPUnsubscribe(String pattern, int subscribedChannels) {
// TODO Auto-generated method stub
System.out.println("取消 【模式订阅】 时候的处理 ");
}
}
2, подразделение
public class Sub {
public static void main(String[] args) {
try {
Jedis jedis = getJedis();
MyListener ml = new MyListener();
//可以订阅多个频道
//jedis.subscribe(ml, "liuxiao","hello","hello_liuxiao","hello_redis");
//jedis.subscribe(ml, new String[]{"hello_foo","hello_test"});
//这里启动了订阅监听,线程将在这里被阻塞
//订阅得到信息在lister的onPMessage(...)方法中进行处理
//使用模式匹配的方式设置频道
jedis.psubscribe(ml, new String[]{"hello_*"});
} catch (Exception e) {
e.printStackTrace();
}
}
}
нужно знать, это:
При вызове subscribe() или psubscribe() текущий поток блокируется.
3. Паб
public class Pub {
public static void main(String[] args) {
try {
Jedis jedis = getJedis();
jedis.publish("hello_redis","hello_redis");
} catch (Exception e) {
e.printStackTrace();
}
}
}
4. Set - собрание (акцент)
Набор — это набор, а концепция набора — это комбинация неповторяющихся значений. Используя структуру данных Set, предоставляемую Redis, можно хранить некоторые агрегированные данные. Например, в приложении Twitter все подписчики пользователя могут храниться в наборе, и все подписчики пользователя могут храниться в наборе. Поскольку Redis предоставляет коллекции с такими операциями, как пересечение, объединение и различие, очень удобным для пользователя способом, очень удобно реализовывать такие функции, как общее внимание, общие предпочтения и друзья второй степени.Для всех вышеперечисленных операций над множествами вы также можете использовать различные команды, чтобы выбрать, следует ли вернуть результат клиенту или сохранить его в новую коллекцию.
- общий друг, второй друг
- Используя уникальность, вы можете подсчитать все независимые IP-адреса, заходящие на сайт.
- Когда друзья рекомендуют, найти пересечение по тегу, если оно больше определенного порога, его можно порекомендовать
Заказ:
sadd key member //添加元素,成功返回1,
srem key member //移除元素,成功返回1
spop key //删除并返回,如果set是空或者不存在则返回null
srandmember key //同spop,随机取set中一个元素,但是不删除
smove srckey dstkey member //集合间移动元素
scard key //查看集合的大小,如果set是空或者key不存在则返回0
sismember key member //判断member是否在Set中,存在返回1,0表示不存在或key不存在
smembers key //获取所有元素,返回key对应的所有元素,结果是无序的哦
--------------------------------------------------------------------------------
//集合交集
sinter key1 key2 //返回所有给定key的交集
sinterstore dstkey key1 key2 //同sinter,并同时保存并集到dstkey下
//集合并集
sunion key1 key2 //返回所有给定key的并集
sunionstore dstkey key1 key2 //同sunion,并同时保存并集到dstkey下
//集合差集
sdiff key1 key2 //返回给定key的差集
sdiffstore dstkey key1 key2 //同sdiff,并同时保存并集到dstkey下
Чтобы пользователи не могли голосовать за одну и ту же статью несколько раз, для каждой статьи необходимо записать список пользователей, которые проголосовали. Используйте коллекцию для хранения идентификаторов проголосовавших пользователей. Поскольку коллекция не может хранить несколько одинаковых элементов, не будет ситуации, когда один и тот же пользователь будет голосовать за одну и ту же статью несколько раз. Код:
2、程序需要使用SADD将文章发布者的ID添加到记录文章已投票用户名单的集合中
并使用EXPIRE命令为这个集合设置一个过期时间,让Redis在文章发布期满一周之后自动删除这个集合。
//2、添加到记录文章已投用户名单中,
conn.sadd(voted, user);
//3、设置一周为过期时间
conn.expire(voted, ONE_WEEK_IN_SECONDS);
--------------------------------------------------------------------------------
4、检查用户是否第一次为这篇文章投票,如果是第一次,则在增加这篇文章的投票数量和评分。
将一个或多个 member 元素加入到集合 key 当中,已经存在于集合的 member 元素将被忽略。
if (conn.sadd("voted:" + articleId, user) == 1) {
//ZINCRBY salary 2000 tom # tom 加薪啦!
conn.zincrby("score:", VOTE_SCORE, article);
//HINCRBY counter page_view 200
conn.hincrBy(article, "votes", 1L);
}
---------------------------------------------------------------------------------
/**
* 记录文章属于哪个群组
* 将所属一个群组的文章ID记录到那个集合中
* Redis不仅可以对多个集合执行操作,甚至在一些情况下,还可以在集合和有序集合之间执行操作
*/
//1、构建存储文章信息的键名
String article = "article:" + articleId;
for (String group : toAdd) {
//2、将文章添加到它所属的群组里面
conn.sadd("group:" + group, article);
}
5. Sorted Set - упорядоченный набор (акцент)
Реализация Sorted Set представляет собой смесь хэш-таблицы (элемент->оценка, используемая для реализации ZScore и определения того, находится ли элемент в наборе) и списка пропуска (оценка->элемент, отсортированный по оценке). Список пропуска чем-то напоминает сбалансированное бинарное дерево: баллы разных диапазонов разделены на слои, и каждый слой представляет собой связанный список, отсортированный по баллам. ZAdd/ZRem — это O(log(N)), ZRangeByScore/ZRemRangeByScore — это O(log(N)+M), N — размер набора, а M — количество элементов результата/операции. Видно, что N, которое могло быть очень большим, было критически логировано, а сложность Set размером в 10 миллионов всего меньше нескольких десятков. Конечно, если вы попали сразу в много элементов, М очень большое, то ничего не поделаешь.
Обычно используемые команды:
zadd key score member //添加元素到集合,元素在集合中存在则更新对应的score
zrem key member //1表示成功,如果元素不存在则返回0
zremrangebyrank min max //删除集合中排名在给定的区间
zincrvy key member //增加对于member的score的值。
zcard key //返回集合中元素的个数
//获取排名
zrank key member //返回指定元素在集合中的排名,
zrebrank key member //同时,但是集合中的元素是按score从大到小排序的
//获取排行榜
zrange key start end //类似lrange操作从集合中去指定区间的元素,返回时有序的。
//给给定分数区间的元素
zrangebyscore key min max
//评分的聚合
ZUNIONSTORE destination numkeys key [key ...] [WEIGHTS weight] [AGGREGATE SUM|MIN|MAX]
Код:
/**
* 1、每次用户浏览页面的时候,程序需都会对用户存储在登录散列里面的信息进行更新,
* 2、并将用户的令牌和当前时间戳添加到记录最近登录用户的集合里。
* 3、如果用户正在浏览的是一个商品,程序还会将商品添加到记录这个用户最近浏览过的商品有序集合里面,
* 4、如果记录商品的数量超过25个时,对这个有序集合进行修剪。
*/
//1、获取当前时间戳
long timestamp = System.currentTimeMillis() / 1000;
//2、维持令牌与已登录用户之间的映射。
conn.hset("login:", token, user);
//3、记录令牌最后一次出现的时间
conn.zadd("recent:", timestamp, token);
if (item != null) {
//4、记录用户浏览过的商品
conn.zadd("viewed:" + token, timestamp, item);
//5、移除旧记录,只保留用户最近浏览过的25个商品
conn.zremrangeByRank("viewed:" + token, 0, -26);
//6、为有序集key的成员member的score值加上增量increment。通过传递一个负数值increment 让 score 减去相应的值,
conn.zincrby("viewed:", -1, item);
}
---------------------------------------------------------------------------------
/**
*存储会话数据所需的内存会随着时间的推移而不断增加,所有我们需要定期清理旧的会话数据。
* 1、清理会话的程序由一个循环构成,这个循环每次执行的时候,都会检查存储在最近登录令牌的有序集合的大小。
* 2、如果有序集合的大小超过了限制,那么程序会从有序集合中移除最多100个最旧的令牌,
* 3、并从记录用户登录信息的散列里移除被删除令牌对应的用户信息,
* 4、并对存储了这些用户最近浏览商品记录的有序集合中进行清理。
* 5、于此相反,如果令牌的数量没有超过限制,那么程序会先休眠一秒,之后在重新进行检查。
*/
public void run() {
while (!quit) {
//1、找出目前已有令牌的数量。
long size = conn.zcard("recent:");
//2、令牌数量未超过限制,休眠1秒,并在之后重新检查
if (size <= limit) {
try {
sleep(1000);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
}
continue;
}
long endIndex = Math.min(size - limit, 100);
//3、获取需要移除的令牌ID
Set<String> tokenSet = conn.zrange("recent:", 0, endIndex - 1);
String[] tokens = tokenSet.toArray(new String[tokenSet.size()]);
ArrayList<String> sessionKeys = new ArrayList<String>();
for (String token : tokens) {
//4、为那些将要被删除的令牌构建键名
sessionKeys.add("viewed:" + token);
}
//5、移除最旧的令牌
conn.del(sessionKeys.toArray(new String[sessionKeys.size()]));
//6、移除被删除令牌对应的用户信息
conn.hdel("login:", tokens);
//7、移除用户最近浏览商品记录。
conn.zrem("recent:", tokens);
}
}
}
//7、移除用户最近浏览商品记录。
conn.zrem("recent:", tokens);
Другой случай:
/**
* 为了应对促销活动带来的大量负载,需要对数据行进行缓存,具体做法是:
* 1、编写一个持续运行的守护进程,让这个函数指定的数据行缓存到redis里面,并不定期的更新。
* 2、缓存函数会将数据行编码为JSON字典并存储在Redis字典里。其中数据列的名字会被映射为JSON的字典,
* 而数据行的值则被映射为JSON字典的值。
* -----------------------------------------------------------------------------------------
* 程序使用两个有序集合来记录应该在何时对缓存进行更新:
* 1、第一个为调用有序集合,他的成员为数据行的ID,而分支则是一个时间戳,
* 这个时间戳记录了应该在何时将指定的数据行缓存到Redis里面
* 2、第二个有序集合为延时有序集合,他的成员也是数据行的ID,
* 而分值则记录了指定数据行的缓存需要每隔多少秒更新一次。
* ----------------------------------------------------------------------------------------
* 为了让缓存函数定期的缓存数据行,程序首先需要将hangID和给定的延迟值添加到延迟有序集合里面,
* 然后再将行ID和当前指定的时间戳添加到调度有序集合里面。
*/
public void scheduleRowCache(Jedis conn, String rowId, int delay) {
//1、先设置数据行的延迟值
conn.zadd("delay:", delay, rowId);
//2、立即对需要行村的数据进行调度
conn.zadd("schedule:", System.currentTimeMillis() / 1000, rowId);
}
--------------------------------------------------------------------------------------
/**
* 1、通过组合使用调度函数和持续运行缓存函数,实现类一种重读进行调度的自动缓存机制,
* 并且可以随心所欲的控制数据行缓存的更新频率:
* 2、如果数据行记录的是特价促销商品的剩余数量,并且参与促销活动的用户特别多的话,那么最好每隔几秒更新一次数据行缓存:
* 另一方面,如果数据并不经常改变,或者商品缺货是可以接受的,那么可以每隔几分钟更新一次缓存。
*/
public class CacheRowsThread extends Thread {
private Jedis conn;
private boolean quit;
public CacheRowsThread() {
this.conn = new Jedis("localhost");
this.conn.select(14);
}
public void quit() {
quit = true;
}
public void run() {
Gson gson = new Gson();
while (!quit) {
//1、尝试获取下一个需要被缓存的数据行以及该行的调度时间戳,返回一个包含0个或一个元组列表
Set<Tuple> range = conn.zrangeWithScores("schedule:", 0, 0);
Tuple next = range.size() > 0 ? range.iterator().next() : null;
long now = System.currentTimeMillis() / 1000;
//2、暂时没有行需要被缓存,休眠50毫秒。
if (next == null || next.getScore() > now) {
try {
sleep(50);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
}
continue;
}
//3、提前获取下一次调度的延迟时间,
String rowId = next.getElement();
double delay = conn.zscore("delay:", rowId);
if (delay <= 0) {
//4、不必在缓存这个行,将它从缓存中移除
conn.zrem("delay:", rowId);
conn.zrem("schedule:", rowId);
conn.del("inv:" + rowId);
continue;
}
//5、继续读取数据行
Inventory row = Inventory.get(rowId);
//6、更新调度时间,并设置缓存值。
conn.zadd("schedule:", now + delay, rowId);
conn.set("inv:" + rowId, gson.toJson(row));
}
}
}
Подробнее см. в заметках, которые я написал ранее.Реализация веб-сайта покупок, связанная с Redis (Java)иСвязанная с Redis реализация веб-сайта голосования по статьям (Java).
Даже если эта тема Redis записана, gogogo.