基本概念

一、安装

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:> SET runoobkey redis
OK
redis 127.0.0.1:> GET runoobkey
"redis"

(2) 哈希

127.0.0.1:>  HMSET runoobkey name "redis tutorial" description "redis basic commands for caching" likes  visitors
OK
127.0.0.1:> HGETALL runoobkey
) "name"
) "redis tutorial"
) "description"
) "redis basic commands for caching"
) "likes"
) ""
) "visitors"
) ""

(3) 列表

redis 127.0.0.1:> LPUSH runoobkey redis
(integer) 1
redis 127.0.0.1:> LPUSH runoobkey mongodb
(integer) 2
redis 127.0.0.1:> LPUSH runoobkey mysql
(integer) 3
redis 127.0.0.1:> LRANGE runoobkey ) "mysql"
) "mongodb"
) "redis"

(4) 集合

redis 127.0.0.1:> SADD runoobkey redis
(integer) 1
redis 127.0.0.1:> SADD runoobkey mongodb
(integer) 1
redis 127.0.0.1:> SADD runoobkey mysql
(integer) 1
redis 127.0.0.1:> SADD runoobkey mysql
(integer) 0
redis 127.0.0.1:> SMEMBERS runoobkey ) "mysql"
) "mongodb"
) "redis"

(5) 有序集合

redis 127.0.0.1:> ZADD runoobkey  redis
(integer) 1
redis 127.0.0.1:> ZADD runoobkey mongodb
(integer) 1
redis 127.0.0.1:> ZADD runoobkey mysql
(integer) 1
redis 127.0.0.1:> ZADD runoobkey mysql
(integer) 0
redis 127.0.0.1:> ZADD runoobkey mysql  # 把之前的两个覆盖了呢
(integer) 0
redis 127.0.0.1:> ZRANGE runoobkey WITHSCORES ) "redis"
) ""
) "mongodb"
) ""
) "mysql"
) ""

(6) HyperLogLog

redis 127.0.0.1:> PFADD runoobkey "redis"
) (integer) redis 127.0.0.1:> PFADD runoobkey "mongodb"
) (integer) redis 127.0.0.1:> PFADD runoobkey "mysql"
) (integer) redis 127.0.0.1:> PFCOUNT runoobkey
(integer)
  • 发布订阅

有例子,内容也不错.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.

[CDH] Redis: Remote Dictionary Server的更多相关文章

  1. Redis : REmote DIctionary Server

    REmote DIctionary Server(Redis) 是一个由Salvatore Sanfilippo写的key-value存储系统. Redis是一个开源的使用ANSI C语言编写.遵守B ...

  2. Redis教程(REmote DIctionary Server)——一个高性能的key-value数据库

    redis(REmote DIctionary Server)是什么? Redis是一个开源的使用ANSI C语言编写.支持网络.可基于内存亦可持久化的日志型.Key-Value数据库,并提供多种语言 ...

  3. redis(Remote Dictionary Server)

    redis的简介和使用   简介 redis(Remote Dictionary Server)是一种Nosql技术,它是一个开源的高级kv存储和数据结构存储系统,它经常被拿来和Memcached相比 ...

  4. Redis(REmote DIctionary Server)基础

    Redis(REmote DIctionary Server)基础 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. Redis是一个开放源代码(BSD许可)的内存数据结构存储,用作数 ...

  5. Redis(Remote Dictionary Server)入门

    说说特性 存储结构:键值对支持多种数据类型,包括字符串类型,散列类型,列表类型,集合类型,有序集合类型. 内存存储与持久化:支持将内存中的数据异步写入磁盘中. 丰富的功能:支持为每个键值对设置生存时间 ...

  6. 二:Redis:(REmote DIctionary Server)远程字典服务器

    Redis是完全开源免费的,用C语言编写的,遵循BSD协议,是一个高性能的(key-value)分布式内存数据库,基于内存运行,并支持持久化的NOSQL数据库,是当前最热门的NOSQL数据库之一,也被 ...

  7. redis基础-Remote Dictionary Server

    Redis支持多个数据库,并且每个数据库的数据是隔离的不能共享,并且基于单机才有,如果是集群就没有数据库的概念. Redis默认支持16个数据库(可以通过配置文件支持更多,无上限),可以通过配置dat ...

  8. Step by step Install a Local Report Server and Remote Report Server Database

    原创地址:http://www.cnblogs.com/jfzhu/p/4012097.html 转载请注明出处 前面的文章<Step by step SQL Server 2012的安装 &g ...

  9. The remote SSH server rejected X11 forwarding request

    两台相同的虚拟机,一台没有错误,一个经常出现警告,内容如下所示: The remote SSH server rejected X11 forwarding request 找了很多方法,最后发现是安 ...

随机推荐

  1. DA_01_linux_物理机局域网工作机制

    一:物理机局域网工作机制: 二:域名服务的工作流程: 首先通过域名映射到IP地址,如果没有找到域名,向外部服务器DNS查询,然后通过IP地址访问服务器: 三:Vmware--NAT虚拟网络配置:

  2. 车钥匙开关上找不到+24V的问题 - 岱峰 - DGY90

    背景: 本人外行,用万用表,在车身电路上查找电瓶正极. 机种:吊管机:机型:岱峰-DGY90 过程: 经过测试,车钥匙开关各连接点电压: 标记B - OFF时电压0,ON时电压+25V 标记BR - ...

  3. CentOS7 - Package does not match intended download 问题解决

    yum 安装软件,有时会出现 Error: Package does not match intended download,这时需要彻底清除已有的下载,然后重新安装即可. $ sudo yum cl ...

  4. es的相关知识二(检索文档)

    一.es的使用 1.检索文档: 想要从Elasticsearch中获取文档,我们使用同样的 _index  . _type  . _id  ,但是HTTP方法改为 GET  : GET /{index ...

  5. StringBuffer常用方法

    StringBuffer常用的方法 package com.mangosoft.java.string; /** * 字符串特点:字符串是常量,它们的值在创建之后不能更改. * * 字符串的内容一旦发 ...

  6. 【pip】使用

    错误及解决 install 1.pip install aip 报错[Windows,python3.6] ERROR: Could not find a version that satisfies ...

  7. node监听80端口权限问题

    报了这个错误: Error: listen EACCES: permission denied 127.0.0.1:80 at Server.setupListenHandle [as _listen ...

  8. Hbuilder 开发微信小程序的代码高亮

    一.点击“工具”-“选项”-搜索“文件关联” 二.点击“添加”文件类型,点击确定 三.在相关联的编辑器中点击“添加”按钮,选择CSS Editor,点击确定,重新打开 *.wxss 类型的文件即可 其 ...

  9. sysbench库文件路径不对

    #sysbench --versionsysbench: error while loading shared libraries: libmysqlclient.so .20: cannot ope ...

  10. Java xml和map,list格式的转换-摘抄

    import java.io.ByteArrayOutputStream; import java.util.ArrayList; import java.util.HashMap; import j ...