《Redis实战》学习笔记
初识Redis
Redis是一个速度极快的非关系型数据库(NoSQL),他可以存储key和5种不同类型的value之间的映射。能够将内存中的键值数据持久化到硬盘当中。
redis与集中常用存储的比较
我们知道了Redis是基于内存的数据库,那么当服务器关闭的时候,我们内存中的的数据将何去何从呢?
Redis提供了两种不同形式的数据持久化的方式:
- 时间点转储(point-in-time dump)
- 转储命令(dump-to-disk)
第一种方式即在指定时间段内有指定数量的写操作执行,我们通过设置时间段和操作数来控制其自动转储。第二种方式下我们可以通过两条转储命令的任意一条来执行,它通过将所有修改了数据库的命令记录到一个只可追加的文件中,我们根据情况将只追加写入设置为从不同步、每秒同步一次或者每条命令同步一次。
同时,Redis实现了主从服务器的架构来进行拓展与故障转移。接收复制的从服务器可连接上主服务器,接收主服务器发送的整个服务器的初始副本,在后续主服务器新增写命令时,从服务器都会获取到并执行,从而实现从服务器数据的实时更新,使我们访问任意一台从服务器时都能够接受到最新数据。
Redis的数据结构
- String 可以是字符串、整数或浮点数
- List 链表,可从链表两端推入或弹出元素
- Set 无序集合
- Hash 包含键值对的无序散列表
- ZSet 有序集合,按照score来分布
下面我们将逐一介绍各个结构
- Redis中的字符串
在其他的编程语言中String也是常用的数据类型,在这里String拥有一些公共的操作方法如:get、set、del等。
这里以key为hello,值为world的字符串示例,首先启动redis服务端与客户端如下:
上面图中,我们使用redis-cli直接与服务器进行简单的交互,设置了hello world字符串,并进行删除,删除成功的话服务器返回一个整数1。
- 列表
Redis中操作列表有以下几种方法:
- rpush&&lpush 从列表的左端或者右端推入数据元素
- lpop&&rpop 从列表的左端或右端弹出元素
- lindex 获取列表指定位置的元素
- lrange 获取列表指定范围的一组元素
127.0.0.1:6379> rpush listkey item1
(integer) 1
127.0.0.1:6379> rpush listkey item1
(integer) 2
127.0.0.1:6379> rpush listkey item2
(integer) 3
127.0.0.1:6379> LRANGE listkey 0
(error) ERR wrong number of arguments for 'lrange' command
127.0.0.1:6379> LRANGE listkey 0 -1
1) "item1"
2) "item1"
3) "item2"
127.0.0.1:6379> LRANGE listkey 0 -2
1) "item1"
2) "item1"
127.0.0.1:6379> LRANGE listkey 0 -3
1) "item1"
127.0.0.1:6379> LRANGE listkey -3
(error) ERR wrong number of arguments for 'lrange' command
127.0.0.1:6379> LRANGE listkey 0 -1
1) "item1"
2) "item1"
3) "item2"
127.0.0.1:6379> lpop listkey
"item1"
127.0.0.1:6379> LRANGE listkey 0 -1
1) "item1"
2) "item2"
127.0.0.1:6379> LINDEX listkey 0
"item1"
127.0.0.1:6379> LINDEX listkey 1
"item2"
127.0.0.1:6379> LINDEX listkey 2
(nil)
127.0.0.1:6379>
上述示范了对列表的一些基本操作。
- Redis的集合
集合与列表类似,但集合通过散列表来控制其不能够存储值相同的元素,它提供了一下一些方法:
- sadd 将指定元素添加到集合
- smembers 以序列形式返回集合包含的所有元素
- sismembers 检查某个元素是否存在与集合中,返回0或1
- srem 若元素存在于集合中,则移除该元素
127.0.0.1:6379> sadd setkey item1
(integer) 1
127.0.0.1:6379> sadd setkey item2
(integer) 1
127.0.0.1:6379> sadd setkey item3
(integer) 1
127.0.0.1:6379> sadd setkey item3
(integer) 0
127.0.0.1:6379> SMEMBERS setkey
1) "item3"
2) "item1"
3) "item2"
127.0.0.1:6379> SISMEMBER item1
(error) ERR wrong number of arguments for 'sismember' command
127.0.0.1:6379> SISMEMBER setkey item1
(integer) 1
127.0.0.1:6379> SISMEMBER setkey item4
(integer) 0
127.0.0.1:6379> SREM setkey item1
(integer) 1
127.0.0.1:6379> SMEMBERS setkey
1) "item3"
2) "item2"
127.0.0.1:6379>
以上为对setkey进行的一些基本操作示例。
- Redis的散列
散列表用来存储键值对,这里提供了几种操作该数据类型的方法:
- HSet 在散列里面关联起给定的键值对
- HGet 获取指定散列键的值
- HGETALL 获取散列表中所有键值对
- HDEL 删除键
127.0.0.1:6379> hset hashkey key1 value1
(integer) 1
127.0.0.1:6379> hset hashkey key1 value2
(integer) 0
127.0.0.1:6379> hset hashkey key2 value2
(integer) 1
127.0.0.1:6379> hset hashkey key3 value2
(integer) 1
127.0.0.1:6379> HGET hashkey key1
"value2"
127.0.0.1:6379> HGET hashkey key2
"value2"
127.0.0.1:6379> HGET hashkey key3
"value2"
127.0.0.1:6379> HGETALL hashkey
1) "key1"
2) "value2"
3) "key2"
4) "value2"
5) "key3"
6) "value2"
127.0.0.1:6379> HDEL hashkey key1
(integer) 1
127.0.0.1:6379> HGETALL hashkey
1) "key2"
2) "value2"
3) "key3"
4) "value2"
127.0.0.1:6379>
以上为对hash数据结构的一些基本操作,注意,当重复设置同一key的不同value时,后设置的value会覆盖前面设置的value,并返回0。
- Redis的有序集合
有序集合和散列类似,都用键值对进行存储。在zset中,集合的键被称为member,值被称为score。每个成员都是不同的,分值也必须是浮点数,在zset中,可根据score来对成员元素进行排序。
它也提供了一些基本的操作命令:
- zadd 将一个带有给定分值的成员添加到集合中
- zrange 根据元素在有序排列中所给的位置,从有序集合中获取多个元素
- zrangebyscore 获取有序集合在给定分值范围中的元素
- zrem 若集合中存在该成员则删除
127.0.0.1:6379> ZADD zsetkey 666 mem1
(integer) 1
127.0.0.1:6379> ZADD zsetkey 667 mem2
(integer) 1
127.0.0.1:6379> ZADD zsetkey 666 mem1
(integer) 0
127.0.0.1:6379> ZADD zsetkey 668 mem1
(integer) 0
127.0.0.1:6379> ZRANGE zset 0 -1
(empty list or set)
127.0.0.1:6379> ZRANGE zsetkey 0 -1
1) "mem2"
2) "mem1"
127.0.0.1:6379> zadd zsetkey 669 mem3
(integer) 1
127.0.0.1:6379> ZRANGE zsetkey 0 -1
1) "mem2"
2) "mem1"
3) "mem3"
127.0.0.1:6379> ZRANGE zsetkey 0 -1 withscore
(error) ERR syntax error
127.0.0.1:6379> ZRANGE zsetkey 0 -1 withscores
1) "mem2"
2) "667"
3) "mem1"
4) "668"
5) "mem3"
6) "669"
127.0.0.1:6379> ZRANGEBYSCORE zsetkey 0 600 withscores
(empty list or set)
127.0.0.1:6379> ZRANGEBYSCORE zsetkey 0 900 withscores
1) "mem2"
2) "667"
3) "mem1"
4) "668"
5) "mem3"
6) "669"
127.0.0.1:6379> ZREM zsetkey mem2
(integer) 1
127.0.0.1:6379> ZRANGE zsetkey 0 -1 withscores
1) "mem1"
2) "668"
3) "mem3"
4) "669"
127.0.0.1:6379>
以上为操作zset的一些简单案例。通过zadd可修改同一member的分值,此时返回0。
使用Redis解决的实际问题
- 使用Redis进行登录和cookies缓存
- 使用Redis实现购物车
- 数据行缓存
- 网页分析
- 等等…
Redis中的命令
1. 字符串
在Redis中,字符串包括字节串、整数、浮点数。
首先介绍一下Redis中字符串的自增自减命令:
- INCR 自增1
- DECR 自减1
- INCRBY 根据给定数据自增
- DECRBY 根据给定数据自减
- INCRBYFLOAT 根据给定浮点数自增
示例如下:
127.0.0.1:6379> INCR Integer
(integer) 7
127.0.0.1:6379> DECR Integer
(integer) 6
127.0.0.1:6379> get Integet
(nil)
127.0.0.1:6379> get Integer
"6"
127.0.0.1:6379> INCRBY Integer 3
(integer) 9
127.0.0.1:6379> DECRBY Integer 3
(integer) 6
127.0.0.1:6379> INCRBYFLOAT Integet 3.3
"3.3"
127.0.0.1:6379> INCRBYFLOAT Integer 3.3
"9.3"
127.0.0.1:6379>
这里要注意的是,我们可以对一个不存在的键值进行以上操作,redis会将该键的值默认为0,如上的Integet,我们原先没有设置,但却可以直接进行INCRBYFLOAT 操作。
处理字符串子串命令
- APPEND 在字符串末尾追加字符串
- GETRANGE 获取字符串子串,由 start、end参数 获取子串范围
- SETRANGE 从offset参数开始,更新字符串值
- GETBIT 将字符串当做二进制串,并获取偏移量为offset参数的值
- SETBIT 将字符串当做二进制串,并设置偏移量为offset参数的值
- BITCOUNT 获取二进制串里值为1的二进制位数
127.0.0.1:6379> set string "strTest"
OK
127.0.0.1:6379> get string
"strTest"
127.0.0.1:6379> APPEND string one
(integer) 10
127.0.0.1:6379> get string
"strTestone"
127.0.0.1:6379> APPEND string Two
(integer) 13
127.0.0.1:6379> get string
"strTestoneTwo"
127.0.0.1:6379> GETRANGE string 0 3
"strT"
127.0.0.1:6379> get string
"strTestoneTwo"
127.0.0.1:6379> SETRANGE string 7 Three
(integer) 13
127.0.0.1:6379> get string
"strTestThreeo"
127.0.0.1:6379> GETBIT string 3
(integer) 1
127.0.0.1:6379> GETBIT string 4
(integer) 0
127.0.0.1:6379> GETBIT string 5
(integer) 0
127.0.0.1:6379> GETBIT string 2
(integer) 1
127.0.0.1:6379> SETBIT string 2 0
(integer) 1
127.0.0.1:6379> get string
"StrTestThreeo"
127.0.0.1:6379> BITCOUNT string
(integer) 52
127.0.0.1:6379>
以上为上述几个命令演示。
2. 列表操作命令
在前面我们讲过了一些关于列表操作的基本命令,这里对列表这些命令做更详细的介绍。
- LTRIM 可根据start、end参数,决定取留列表的某一部分,如:
127.0.0.1:6379> LRANGE list-key 0 -1
1) "first"
2) "last"
3) "new last"
127.0.0.1:6379> LTRIM list-key 1 -1
OK
127.0.0.1:6379> LRANGE list-key 0 -1
1) "last"
2) "new last"
127.0.0.1:6379>
阻塞式列表的弹出与列表元素转移
如上以B开头的命令,使其阻塞一段时间后等到条件成立时或时间到时结束程序。
测试如下:
127.0.0.1:6379> LRANGE list-key2 0 -1
1) "first2"
2) "last2"
127.0.0.1:6379> BLPOP list-key2 3
1) "list-key2"
2) "first2"
127.0.0.1:6379> LRANGE list-key2 0 -1
1) "last2"
127.0.0.1:6379> BLPOP list-key2 first2 3
1) "list-key2"
2) "last2"
127.0.0.1:6379> LRANGE list-key2 0 -1
(empty list or set)
127.0.0.1:6379> BLPOP list-key2 first2 3
(nil)
(3.10s)
127.0.0.1:6379> BLPOP list-key2 first2 20
(nil)
(20.10s)
127.0.0.1:6379> BLPOP list-key2 first2 10
1) "list-key2"
2) "first2"
(2.76s)
127.0.0.1:6379> LRANGE list-key2 0 -1
(empty list or set)
127.0.0.1:6379> LPUSH first2
(error) ERR wrong number of arguments for 'lpush' command
127.0.0.1:6379> LPUSH list-key2 first2
(integer) 1
127.0.0.1:6379> RPOPLPUSH list-key2 list-key
"first2"
127.0.0.1:6379> LRANGE list-key 0 -1
1) "first2"
2) "last"
3) "new last"
127.0.0.1:6379> LRANGE list-key2 0 -1
(empty list or set)
127.0.0.1:6379> BRPOPLPUSH list-key2 list-key 10
"first2"
(1.98s)
127.0.0.1:6379>
以上在阻塞过程中,我们使用另一个redis客户端对对应数据结构添加值,使其有元素可操作。
集合的命令操作
丢失…
使用Redis构建支持程序
使用Redis记录日志
记录当前日志,保留100条:
package redis;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Pipeline;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;
/**
* @Description TODO
* @Author zhenxing.dong
* @Date 2019/12/5 21:22
*/
public class Chacter05 {
public static final String DEBUG = "debug";
public static final String INFO = "info";
public static final String WARNING = "warning";
public static final String ERROR = "error";
public static final String CRITICAL = "critical";
public static final SimpleDateFormat TIMESTAMP =
new SimpleDateFormat("EEE MMM dd HH:00:00 yyyy");
private static final SimpleDateFormat ISO_FORMAT =
new SimpleDateFormat("yyyy-MM-dd'T'HH:00:00");
static{
ISO_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
}
public static void main(String args[]){
new Chacter05().run();
}
public void run(){
Jedis conn = new Jedis("localhost");
conn.select(15);
// conn.del("recent:test:info");
testLogRecent(conn);
// conn.lrange("recent:test:info",0,-1);
}
public void testLogRecent(Jedis conn) {
System.out.println("\n----- testLogRecent -----");
System.out.println("Let's write a few logs to the recent log");
for (int i = 200; i < 250; i++) {
logRecent(conn, "test", "this is message " + i);
}
List<String> recent = conn.lrange("recent:test:info", 0, -1);
System.out.println(
"The current recent message log has this many messages: " +
recent.size());
System.out.println("Those messages include:");
for (String message : recent){
System.out.println(message);
}
assert recent.size() >= 5;
}
public void logRecent(Jedis conn, String name, String message) {
logRecent(conn, name, message, INFO);
}
public void logRecent(Jedis conn, String name, String message, String severity) {
String destination = "recent:" + name + ':' + severity;
Pipeline pipe = conn.pipelined();
pipe.lpush(destination, TIMESTAMP.format(new Date()) + ' ' + message);
pipe.ltrim(destination, 0, 99);
pipe.sync();
}
}
如上述日志的函数logRecent()可见,我们可以通过设置一个list数据结构,将日志消息message推入list中,trim保留100条。
记录常用日志
通过zset有序集合,将日志信息出现的次数作为score,信息作为member存在zset中,可按序取出排列:
public void testLogCommon(Jedis conn) {
System.out.println("\n----- testLogCommon -----");
System.out.println("Let's write some items to the common log");
for (int count = 1; count < 7; count++) {
for (int i = 0; i < count; i++) {
logCommon(conn, "test", "message-" + count);
}
}
Set<Tuple> common = conn.zrevrangeWithScores("common:test:info", 0, -1);
System.out.println("The current number of common messages is: " + common.size());
System.out.println("Those common messages are:");
for (Tuple tuple : common) {
System.out.println(" " + tuple.getElement() + ", " + tuple.getScore());
}
assert common.size() >= 7;
}
public void logCommon(Jedis conn, String name, String message) {
logCommon(conn, name, message, INFO, 5000);
}
public void logCommon(
Jedis conn, String name, String message, String severity, int timeout) {
String commonDest = "common:" + name + ':' + severity;
String startKey = commonDest + ":start";
long end = System.currentTimeMillis() + timeout;
while (System.currentTimeMillis() < end) {
conn.watch(startKey);
String hourStart = ISO_FORMAT.format(new Date());
String existing = conn.get(startKey);
Transaction trans = conn.multi();
if (existing != null && COLLATOR.compare(existing, hourStart) < 0) {
trans.rename(commonDest, commonDest + ":last");
trans.rename(startKey, commonDest + ":pstart");
trans.set(startKey, hourStart);
}
trans.zincrby(commonDest, 1, message);
String recentDest = "recent:" + name + ':' + severity;
trans.lpush(recentDest, TIMESTAMP.format(new Date()) + ' ' + message);
trans.ltrim(recentDest, 0, 99);
List<Object> results = trans.exec();
// null response indicates that the transaction was aborted due to
// the watched key changing.
if (results == null) {
continue;
}
return;
}
}
Redis实现计数器并进行数据统计
查询IP地址所属城市与国家
服务的发现与配置