[CDH] Redis: Remote Dictionary Server

基本概念

一、安装

Redis: Remote Dictionary Server 远程字典服务

使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。

其他接口支持:https://redis.io/clients

原代码下载:https://github.com/antirez/redis

 

二、启动服务

[root@node01 bin]# ls
dump.rdb  mkreleasehdr.sh  redis-benchmark  redis-check-aof  redis-check-rdb  redis-cli  redis-server
[root@node01 bin]# .
/redis-server ../etc/redis.conf 8952:C 08 Dec 17:22:00.641 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo 8952:C 08 Dec 17:22:00.641 # Redis version=4.0.11, bits=64, commit=00000000, modified=0, pid=8952, just started 8952:C 08 Dec 17:22:00.641 # Configuration loaded

 

三、图形界面

Ref: How to start using RDM 

 

 

 

Redis 教程

一,"client命令行"模式

[菜鸟教程]:https://www.runoob.com/redis/redis-tutorial.html

复制代码
[root@node01 bin]# ls
dump.rdb  mkreleasehdr.sh  redis-benchmark  redis-check-aof  redis-check-rdb  redis-cli  redis-server

# 客户端命令行模式
[root@node01 bin]# .
/redis-cli 127.0.0.1:6379>

# 远程登录
$redis-cli -h 127.0.0.1 -p 6379 -a "mypass"
redis 127.0.0.1:6379>
redis 127.0.0.1:6379> PING

PONG
复制代码

 

 

二,菜鸟Redis 教程

  • 数据类型

Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及 zset (sorted set:有序集合)。Redis 在 2.8.9 版本添加了 HyperLogLog 结构

 

(1) 字符串

redis 127.0.0.1:6379> SET runoobkey redis
OK
redis
127.0.0.1:6379> GET runoobkey "redis"

  

(2) 哈希

复制代码
127.0.0.1:6379>  HMSET runoobkey name "redis tutorial" description "redis basic commands for caching" likes 20 visitors 23000
OK
127.0.0.1:6379> HGETALL runoobkey 1) "name" 2) "redis tutorial" 3) "description" 4) "redis basic commands for caching" 5) "likes" 6) "20" 7) "visitors" 8) "23000"
复制代码

  

(3) 列表

复制代码
redis 127.0.0.1:6379> LPUSH runoobkey redis
(integer) 1
redis 127.0.0.1:6379> LPUSH runoobkey mongodb (integer) 2
redis 127.0.0.1:6379> LPUSH runoobkey mysql (integer) 3
redis 127.0.0.1:6379> LRANGE runoobkey 0 10 1) "mysql" 2) "mongodb" 3) "redis"
复制代码

  

(4) 集合

复制代码
redis 127.0.0.1:6379> SADD runoobkey redis
(integer) 1
redis 127.0.0.1:6379> SADD runoobkey mongodb (integer) 1
redis 127.0.0.1:6379> SADD runoobkey mysql (integer) 1
redis 127.0.0.1:6379> SADD runoobkey mysql (integer) 0
redis 127.0.0.1:6379> SMEMBERS runoobkey 1) "mysql" 2) "mongodb" 3) "redis"
复制代码

  

(5) 有序集合

复制代码
redis 127.0.0.1:6379> ZADD runoobkey 1 redis
(integer) 1
redis 127.0.0.1:6379> ZADD runoobkey 2 mongodb (integer) 1
redis 127.0.0.1:6379> ZADD runoobkey 3 mysql (integer) 1
redis 127.0.0.1:6379> ZADD runoobkey 3 mysql (integer) 0
redis 127.0.0.1:6379> ZADD runoobkey 4 mysql  # 把之前的两个覆盖了呢 (integer) 0
redis 127.0.0.1:6379> ZRANGE runoobkey 0 10 WITHSCORES 1) "redis" 2) "1" 3) "mongodb" 4) "2" 5) "mysql" 6) "4"
复制代码

  

(6) HyperLogLog

复制代码
redis 127.0.0.1:6379> PFADD runoobkey "redis"
1) (integer) 1

redis 127.0.0.1:6379> PFADD runoobkey "mongodb"
1) (integer) 1

redis 127.0.0.1:6379> PFADD runoobkey "mysql"
1) (integer) 1

redis 127.0.0.1:6379> PFCOUNT runoobkey
(integer) 3
复制代码

 

  • 发布订阅

有例子,内容也不错.goto: Redis的发布/订阅工作模式详解

如下的Java代码示范不错. 

复制代码
package org.xninja.ghoulich.JedisTest;
import redis.clients.jedis.Jedis;
public class App {
@SuppressWarnings(
"resource") public static void main(String[] args) {
final Jedis jedis = new Jedis("192.168.1.109", 6379); final Jedis pjedis = new Jedis("192.168.1.109", 6379);
final MyListener listener = new MyListener(); final MyListener plistener = new MyListener();

// new 一个构造函数,并定义类中的方法 Thread thread
= new Thread(new Runnable() { public void run() { jedis.subscribe(listener, "mychannel"); } });
Thread pthread
= new Thread(new Runnable() { public void run() { pjedis.psubscribe(plistener, "mychannel.*"); } });
thread.start(); pthread.start(); } }
复制代码

如下监听器,会对频道和模式的订阅、接收消息和退订等事件进行监听,然后进行相应的处理。

复制代码
package org.xninja.ghoulich.JedisTest;
import redis.clients.jedis.JedisPubSub;

public class MyListener extends JedisPubSub {

        // 取得订阅的消息后的处理
        public void onMessage(String channel, String message) {
                System.out.println("onMessage: " + channel + "=" + message);
                if (message.equals("quit"))
                        this.unsubscribe(channel);
        }
// 初始化订阅时候的处理 public void onSubscribe(String channel, int subscribedChannels) { System.out.println("onSubscribe: " + channel + "=" + subscribedChannels); }
// 取消订阅时候的处理 public void onUnsubscribe(String channel, int subscribedChannels) { System.out.println("onUnsubscribe: " + channel + "=" + subscribedChannels); }
// 初始化按模式的方式订阅时候的处理 public void onPSubscribe(String pattern, int subscribedChannels) { System.out.println("onPSubscribe: " + pattern + "=" + subscribedChannels); }
// 取消按模式的方式订阅时候的处理 public void onPUnsubscribe(String pattern, int subscribedChannels) { System.out.println("onPUnsubscribe: " + pattern + "=" + subscribedChannels); }
// 取得按模式的方式订阅的消息后的处理 public void onPMessage(String pattern, String channel, String message) { System.out.println("onPMessage: " + pattern + "=" + channel + "=" + message); if (message.equals("quit")) this.punsubscribe(pattern); } }
复制代码

 

  • 事务

先以 MULTI 开始一个事务, 然后将多个命令入队到事务中, 最后由 EXEC 命令触发事务, 一并执行事务中的所有命令。

单个 Redis 命令的执行是原子性的,但 Redis 没有在事务上增加任何维持原子性的机制,所以 Redis 事务的执行并不是原子性的。

事务可以理解为一个打包的批量执行脚本,但批量指令并非原子化的操作,中间某条指令的失败不会导致前面已做指令的回滚,也不会造成后续的指令不做。

复制代码
redis 127.0.0.1:7000> multi
OK
redis
127.0.0.1:7000> set a aaa QUEUED
redis
127.0.0.1:7000> set b bbb  # 如果在 set b bbb 处失败,set a 已成功不会回滚,set c 还会继续执行。 QUEUED
redis
127.0.0.1:7000> set c ccc QUEUED
redis
127.0.0.1:7000> exec 1) OK 2) OK 3) OK
复制代码

 

 

 

Redis 实战

  • Kafka --> Redis

Kafka输出数据到Redis,以及Hbase。

Hbase api另外单独讲解,此处只涉及到redis部分.

复制代码
public class GpsConsumer implements Runnable {
private static Logger log = Logger.getLogger(GpsConsumer.class); private final KafkaConsumer<String, String> consumer; private final String topic;
//计数消费到的消息条数 private static int count = 0; private FileOutputStream file = null; private BufferedOutputStream out = null; private PrintWriter printWriter = null; private String lineSeparator = null; private int batchNum = 0; JedisUtil instance = null; Jedis jedis = null; private String cityCode = ""; private Map<String, String> gpsMap = new HashMap<String, String>(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public GpsConsumer(String topic, String groupId) {
if (topic.equalsIgnoreCase(TopicName.CHENG_DU_GPS_TOPIC.getTopicName())) { cityCode = Constants.CITY_CODE_CHENG_DU; } else if (topic.equalsIgnoreCase(TopicName.XI_AN_GPS_TOPIC.getTopicName())) { cityCode = Constants.CITY_CODE_XI_AN; } else if (topic.equalsIgnoreCase(TopicName.HAI_KOU_ORDER_TOPIC.getTopicName())) { cityCode = Constants.CITY_CODE_HAI_KOU; }else{ throw new IllegalArgumentException(topic+",主题名称不合法!"); }
Properties props
= new Properties();//pro-cdh props.put("bootstrap.servers", Constants.KAFKA_BOOTSTRAP_SERVERS);  // 设置好kafka集群 props.put("group.id", groupId); props.put("enable.auto.commit", "true"); props.put("auto.offset.reset", "earliest"); props.put("session.timeout.ms", "30000"); props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer"); props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer"); consumer = new KafkaConsumer<String,String>(props);    # 第一步,构造好了kafka consumer this.topic = topic; }
  //----------------------------------------------------------------------------------------------
@Override
public void run() { while (true) { try { doWork(); } catch (Exception e) { e.printStackTrace(); } } } public void doWork() throws Exception { batchNum++; consumer.subscribe(Collections.singletonList(this.topic)); ConsumerRecords<String, String> records = consumer.poll(1000);
System.out.println(
"第" + batchNum + "批次," + records.count());
//司机ID String driverId = ""; //订单ID String orderId = ""; //经度 String lng = ""; //维度 String lat = ""; //时间戳 String timestamp = "";
Order order
= null; Order startEndTimeOrder = null; Object tmpOrderObj = null;

     /**
      * Jeff: 如果有数据,则记录下来
      */
if (records.count() > 0) {

// (1) hbase就绪
Table table
= HBaseUtil.getTable(Constants.HTAB_GPS);
// (2) Redis就绪 JedisUtil instance
= JedisUtil.getInstance(); jedis = instance.getJedis();

       /////////////////////////////////////////////////////
List
<Put> puts = new ArrayList<>(); String rowkey = ""; if (gpsMap.size() > 0) { gpsMap.clear(); } //表不存在时创建表 if (!HBaseUtil.tableExists(Constants.HTAB_GPS)) { HBaseUtil.createTable(HBaseUtil.getConnection(), Constants.HTAB_GPS, Constants.DEFAULT_FAMILY); } for (ConsumerRecord<String, String> record : records) { count++; log.warn("Received message: (" + record.key() + ", " + record.value() + ") at offset " + record.offset() + ",count:" + count);
String value
= record.value(); if (value.contains(",")) { order = new Order(); String[] split = value.split(","); driverId = split[0]; orderId = split[1]; timestamp = split[2]; lng = split[3]; lat = split[4]; rowkey = orderId + "_" + timestamp; gpsMap.put("CITYCODE", cityCode); gpsMap.put("DRIVERID", driverId); gpsMap.put("ORDERID", orderId); gpsMap.put("TIMESTAMP", timestamp + ""); gpsMap.put("TIME", sdf.format(new Date(Long.parseLong(timestamp+"000")))); gpsMap.put("LNG", lng); gpsMap.put("LAT", lat); order.setOrderId(orderId); puts.add(HBaseUtil.createPut(rowkey, Constants.DEFAULT_FAMILY.getBytes(), gpsMap));
////////////
// hbase //
            //////////////////////////////////////////////////////////////////////////////////////////////
// redis //
///////////
//1.存入实时订单单号 jedis.sadd(Constants.REALTIME_ORDERS, cityCode + "_" + orderId);
//2.存入实时订单的经纬度信息 jedis.lpush(cityCode + "_" + orderId, lng + "," + lat);
//3.存入订单的开始结束时间信息 byte[] orderBytes = jedis.hget(Constants.ORDER_START_ENT_TIME.getBytes(), orderId.getBytes()); if (orderBytes != null) { tmpOrderObj = ObjUtil.deserialize(orderBytes); } if (null != tmpOrderObj) { startEndTimeOrder = (Order) tmpOrderObj; startEndTimeOrder.setEndTime(Long.parseLong(timestamp+"000")); jedis.hset(Constants.ORDER_START_ENT_TIME.getBytes(), orderId.getBytes(), ObjUtil.serialize(startEndTimeOrder)); } else { //第一次写入订单的开始时间,开始时间和结束时间一样 order.setStartTime(Long.parseLong(timestamp)); order.setEndTime(Long.parseLong(timestamp)); jedis.hset(Constants.ORDER_START_ENT_TIME.getBytes(), orderId.getBytes(), ObjUtil.serialize(order)); }
hourOrderInfoGather(jedis, gpsMap);
}
else if (value.contains("end")) { jedis.lpush(cityCode + "_" + orderId, value); } }
table.put(puts); instance.returnJedis(jedis); } log.warn(
"正常结束..."); }
/** * 统计城市的每小时的订单信息和订单数 * @throws Exception */ public void hourOrderInfoGather(Jedis jedis, Map<String, String> gpsMap) throws Exception{
String time
= gpsMap.get("TIME"); String orderId = gpsMap.get("ORDERID"); String day = time.substring(0,time.indexOf(" ")); String hour = time.split(" ")[1].substring(0,2);
//redis表名,小时订单统计 String hourOrderCountTab = cityCode + "_" + day + "_hour_order_count"; //redis表名,小时订单ID String hourOrderField = cityCode + "_" + day + "_" + hour; String hourOrder = cityCode + "_order"; int hourOrderCount = 0;
//redis set集合中存放每小时内的所有订单id if(!jedis.sismember(hourOrder,orderId)){ //使用set存储小时订单id jedis.sadd(hourOrder,orderId); String hourOrdernum = jedis.hget(hourOrderCountTab, hourOrderField); if(StringUtils.isEmpty(hourOrdernum)){ hourOrderCount = 1; }else{ hourOrderCount = Integer.parseInt(hourOrdernum) + 1; } //HashMap 存储每个小时的订单总数 jedis.hset(hourOrderCountTab, hourOrderField, hourOrderCount+""); } } public static void main(String[] args) { Logger.getLogger("org.apache.kafka").setLevel(Level.INFO); //kafka主题 String topic = "cheng_du_gps_topic"; //消费组id String groupId = "cheng_du_gps_consumer_01"; GpsConsumer gpsConsumer = new GpsConsumer(topic, groupId); Thread start = new Thread(gpsConsumer); start.start(); } }
复制代码

 

 

Redis与业务逻辑

以上代码涉及到如下几个操作,侧面也反映了redis的若干数据结构如何使用的问题,以切合业务逻辑.

jedis.sadd
jedis.lpush
jedis.hget
jedis.hset

腾讯课堂,Redis使用精髓-微博与微信如何用Redis巧妙构建

 

  • String

(1) 减库存方法,利用分布式锁:

线程1 SETNX product:1001 true

1. 查询商品1001的库存

2. 减库存

3. 写回数据库

del product:1001

 

(2) 博客浏览量的实现,原子加减:

INCR article:readcount:1000

 

  • Hash

(1) 购物车界面:

添加商品:hset cart:1001 10088 1

增加数量:hincrby cart:1001 10088 1

商品总数:hlen cart:1001

删除商品:hdel cart:1001 20088

获取购物车所有商品:hgetall cart:1001

 

  • List

(1) 常见数据结构

Stack = LPUSH + LPOP

Queue = LPUSH + RPOP

Blocking MQ = LPUSH + BRPOP

 

(2) 微博消息流:

1. MacTalk发微博,消息ID为10018 --> LPUSH msg:18888 10018

2. 博猪二发微博,消息ID为10018 --> LPUSH msg:18888 10086

3. 查看最新微博消息: --> LRANGE msg:18888 0 5

 

  • Set

(1) 微信抽奖小程序

1. 点击参与抽奖加入集合:SADD key {userID}

2. 查看参与抽奖所有用户:SMEMBERS key

3. 抽取count名中奖者:SRANDMEMBER key [count] / SPOP key [count]

 

(2) 微信朋友圈操作

1. 点赞:SADD like:{msg id} {user id}

2. 取消点赞:SREM like:{msg id} {user id}

3. 检查用户是否点过赞:SISMEMBER like:{msg id} {user id}

4. 获取点赞的用户列表:SMEMBERS like:{msg id}

5. 获取点赞用户数:SCARD like:{msg id}

 

(3) 关注关系查询

1. 共同关注:SINTER zhugeSet yangguoSet

2. 我关注的人也关注他 (yangguo):SISMEMBER simaSet yangguo;SISMEMBER lubanSet yangguo

3. 我可能认识的人:SDIFF yangguoSet zhugeSet

 

  • Zset

(1) 排行榜实现

1. 点击新闻:ZINCRBY hotNews:20190919 1 守护地球

2. 展示当日排行前十:ZREVRANGE hotNews:20190819 0 10 WITHSCORES

3. 展示七日排行前十:ZREVRANGE hotNews:20190813-20190819 0 10 WITHSCORES

 

 

 

高级知识点 

你是否知道Redis为什么有16 个数据库?

听说Redis都会遇到并发、雪崩等难题?我用10分钟就解决了

2019BATJ面试题汇总详解:MyBatis+MySQL+Spring+Redis+多线程

「面试」我是如何在面试别人Redis相关知识时“软怼”他的

Redis源码分析(一)--Redis结构解析

 

End.

posted @   郝壹贰叁  阅读(404)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示