Redis基础
Redis安装
# 下载
cd /tmp
wget http://download.redis.io/releases/redis-3.2.11.tar.gz
# 解压
tar -zxvf redis-3.2.11.tar.gz
# 建立软连接
ln -s redis-3.2.11 redis
# 编译安装
cd redis
make
make install
# 查看redis-开头的可执行文件
cd src
ll | grep redis-
# 显示如下
-rwxr-xr-x 1 root root 2432576 Nov 25 18:00 redis-benchmark
-rw-rw-r-- 1 root root 29559 Sep 21 22:20 redis-benchmark.c
-rw-r--r-- 1 root root 108448 Nov 25 18:00 redis-benchmark.o
-rwxr-xr-x 1 root root 25168 Nov 25 18:00 redis-check-aof
-rw-rw-r-- 1 root root 6328 Sep 21 22:20 redis-check-aof.c
-rw-r--r-- 1 root root 33688 Nov 25 18:00 redis-check-aof.o
-rwxr-xr-x 1 root root 5191328 Nov 25 18:00 redis-check-rdb
-rw-rw-r-- 1 root root 12789 Sep 21 22:20 redis-check-rdb.c
-rw-r--r-- 1 root root 54496 Nov 25 18:00 redis-check-rdb.o
-rwxr-xr-x 1 root root 2585448 Nov 25 18:00 redis-cli
-rw-rw-r-- 1 root root 90678 Sep 21 22:20 redis-cli.c
-rw-r--r-- 1 root root 366344 Nov 25 18:00 redis-cli.o
-rwxr-xr-x 1 root root 5191328 Nov 25 18:00 redis-sentinel
-rwxr-xr-x 1 root root 5191328 Nov 25 18:00 redis-server
-rwxrwxr-x 1 root root 60852 Sep 21 22:20 redis-trib.rb
# 回到redis目录
cd ..
# 启动redis
redis-server
# 可以用下面三种方式验证redis服务器是否正常
# 查询redis进程
ps -ef | grep redis
root 30253 27126 0 18:10 pts/0 00:00:00 redis-server *:6379
root 30279 30259 0 18:19 pts/1 00:00:00 grep --color=auto redis
# 查询redis端口占用
netstat -antpl | grep redis
tcp 0 0 0.0.0.0:6379 0.0.0.0:* LISTEN 30253/redis-server
tcp6 0 0 :::6379 :::* LISTEN 30253/redis-server
# 用redis客户端进行连接,然后执行ping,返回PONG
redis-cli -h 127.0.0.1 -p 6379
127.0.0.1:6379> ping
PONG
Redis启动方式
- 直接启动:redis-server
- 动态参数启动:redis-server --port 6380
- 指定配置文件启动:redis-server configPath
生产环境建议指定配置文件启动,单机多实例配置文件可以用端口区分开,比如像下面这样:
mv redis.conf redis-6380.conf
vim redis-6380.conf
暂时我们仅指定以下几项基础配置:
# 是否以守护进程方式启动
daemonize yes
# redis对外端口
port 6380
# 工作目录
dir ./
# 日志文件
logfile "6380.log"
用指定配置文件方式重新启动Redis
redis-server redis-6380.conf
ps -ef | grep redis
root 30339 1 0 18:55 ? 00:00:00 redis-server 127.0.0.1:6380
root 30344 30259 0 18:56 pts/1 00:00:00 grep --color=auto redis
Redis客户端
redis-cli连接
# 我们在任意目录下启动redis客户端
redis-cli -h 127.0.0.1 -p 6379
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> incr number
(integer) 1
127.0.0.1:6379> set hello world
OK
127.0.0.1:6379> set foo bar
OK
127.0.0.1:6379> get hello
"world"
127.0.0.1:6379>
redis-cli返回值
- 状态回复
127.0.0.1:6379> ping
PONG
- 错误回复
127.0.0.1:6379> hget hello
(error) ERR wrong number of arguments for 'hget' command
- 整数回复
127.0.0.1:6379> incr number
(integer) 1
- 字符串回复
127.0.0.1:6379> set hello world
OK
127.0.0.1:6379> get hello
"world"
- 多行字符串回复
127.0.0.1:6379> set foo bar
OK
127.0.0.1:6379> mget hello foo
1) "world"
2) "bar"
Redis基础配置
- daemonize:是否守护进程(no|yes)
- port:Redis对外端口
- logfile:Redis系统日志
- dir:Redis工作目录
除了上面几个最基本的配置,redis还有下面这些高级配置:
- RDB config
- AOF config
- show Log config
- maxMemory等等
后续穿插讲解
可以通过以下命令列出配置:
# 列出配置文件的配置项,去除注释行和空行
cat redis.conf | grep -v '#' | grep -v '^$'
bind 127.0.0.1
protected-mode yes
port 6379
tcp-backlog 511
timeout 0
tcp-keepalive 300
daemonize yes
supervised no
pidfile /var/run/redis_6379.pid
loglevel notice
logfile ""
databases 16
save 900 1
save 300 10
save 60 10000
stop-writes-on-bgsave-error yes
rdbcompression yes
rdbchecksum yes
dbfilename dump.rdb
dir ./
slave-serve-stale-data yes
slave-read-only yes
repl-diskless-sync no
repl-diskless-sync-delay 5
repl-disable-tcp-nodelay no
slave-priority 100
appendonly no
appendfilename "appendonly.aof"
appendfsync everysec
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
aof-load-truncated yes
lua-time-limit 5000
slowlog-log-slower-than 10000
slowlog-max-len 128
latency-monitor-threshold 0
notify-keyspace-events ""
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
list-max-ziplist-size -2
list-compress-depth 0
set-max-intset-entries 512
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
hll-sparse-max-bytes 3000
activerehashing yes
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit slave 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60
hz 10
aof-rewrite-incremental-fsync yes
Redis API
通用命令
- keys [pattern]:显示通配符匹配的key
- dbsize:查看key总数
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3 k4 v4
OK
127.0.0.1:6379> dbsize
(integer) 4
127.0.0.1:6379> sadd myset a b c d e
(integer) 5
127.0.0.1:6379> dbsize
(integer) 5
- exist key:判断key是否存在
127.0.0.1:6379> set a b
OK
127.0.0.1:6379> exist a
(integer) 1
127.0.0.1:6379> del a
(integer) 1
127.0.0.1:6379> exists a
(integer) 0
- del key [key...] 删除指定key-value
127.0.0.1:6379> set a b
OK
127.0.0.1:6379> get a
"b"
127.0.0.1:6379> del a
(integer) 1
127.0.0.1:6379> get a
(nil)
- expire key seconds 设置key过期时间
- ttl key 查看key的剩余过期时间
- persist key 删除key的过期设置
127.0.0.1:6379> set hello world
OK
127.0.0.1:6379> expire hello 20
(integer) 1
127.0.0.1:6379> ttl hello
(integer) 16
127.0.0.1:6379> get hello
"world"
127.0.0.1:6379> ttl hello
(integer) 7
127.0.0.1:6379> ttl hello
(integer) -2 (-2代表key已经不存在了)
127.0.0.1:6379> get hello
(nil)
127.0.0.1:6379> set hello world
OK
127.0.0.1:6379> expire hello 20
(integer) 1
127.0.0.1:6379> ttl hello
(integer) 16 (还有16秒过期)
127.0.0.1:6379> persist hello
(integer) 1
127.0.0.1:6379> ttl hello
(integer) -1 (-1代表key存在,并且没有设置过期时间)
127.0.0.1:6379> get hello
"wordl"
- type key 查看数据类型
redis数据类型:string hash list set zset
127.0.0.1:6379> set a b
OK
127.0.0.1:6379> type a
string
127.0.0.1:6379> sadd myset 1 2 3
(integer) 3
127.0.0.1:6379> type myset
set
- 时间复杂度
命令 | 时间复杂度 |
---|---|
keys | O(n) |
dbsize | O(1) |
del | O(1) |
exists | O(1) |
expire | O(1) |
type | O(1) |
数据结构和内部编码
单线程
Redis单线程设计,因此使用时要注意几点:
- 一次只运行一条命令
- 不要执行长(慢)命令
长(慢)命令:keys, flushall, flushdb, slow lua script, mutil/exec, operate big value(collection)
字符串
使用场景
- 缓存
- 计数器
- 分布式锁
- 等等
命令
- get、set、del
127.0.0.1:6379> set hello "world"
OK
127.0.0.1:6379> get hello
"world"
127.0.0.1:6379> del hello
1
127.0.0.1:6379> get hello
(nil)
- incr、decr、incrby、decrby
127.0.0.1:6379> get counter
(nil)
127.0.0.1:6379> incr counter
(integer) 1
127.0.0.1:6379> get counter
"1"
127.0.0.1:6379> incrby counter 99
(integer) 100
127.0.0.1:6379> decr counter
(integer) 99
127.0.0.1:6379> get counter
"99"
- set、setnx、setxx、setex
127.0.0.1:6379> exists php
(integer) 0 (0代表不存在)
127.0.0.1:6379> set php good
OK
127.0.0.1:6379> sexnx php bad
(integer) 0
127.0.0.1:6379> set php best xx
OK
127.0.0.1:6379> get php
"best"
127.0.0.1:6379> exists java
(integer) 0
127.0.0.1:6379> set java best
(integer) 1
127.0.0.1:6379> set java easy xx
OK
127.0.0.1:6379> get java
"easy"
127.0.0.1:6379> exists lua
(integer) 0
127.0.0.1:6379> set lua hehe xx
(nil)
- mset、mget
127.0.0.1:6379> mset hello world java best php good
OK
127.0.0.1:6379> mget hello java php
"world"
"best"
"good"
- getset、append、strlen
127.0.0.1:6379> set hello world
OK
127.0.0.1:6379> getset hello php
"world"
127.0.0.1:6379> get hello
"php"
127.0.0.1:6379> append hello ",java"
(integer) 8
127.0.0.1:6379> get hello
"pho,java"
127.0.0.1:6379> strlen hello
(integer) 8
127.0.0.1:6379> set hello "足球"
OK
127.0.0.1:6379> strlen hello
(integer) 4 (一个中文占2个字节)
- incrbyfloat、getrange、setrange
127.0.0.1:6379> incr counter
(integer) 1
127.0.0.1:6379> incrbyfloat counter 1.1
"2.1"
127.0.0.1:6379> get counter
"2.1"
127.0.0.1:6379> set hello javabest
OK
127.0.0.1:6379> getrange hello 0 2
"jav"
127.0.0.1:6379> setrange hello 4 p
(integer) 8
127.0.0.1:6379> get hello
"javapest"
- 时间复杂度
命令 | 含义 | 时间复杂度 |
---|---|---|
set key value | 设置key-value | O(1) |
get key | 获取key-value | O(1) |
del key | 删除key-value | O(1) |
setnx setxx | 根据key是否存在设置key-value | O(1) |
incr decr | 计数 | O(1) |
mget mset | 批量操作key-value | O(n) |
实战
- 记录网站每个用户个人主页的访问量
incr userid:pageview(单线程:无竞争)
-
缓存视频的基本信息(数据源在MySQL中)
伪代码如下:
public VideoInfo get(long id) { String redisKey = redisPrefix + id; VideoInfo videoInfo = redis.get(redisKey); if (videoInfo == null) { videoInfo = mysql.get(id); if (videoInfo != null) { // 序列化 redis.set(redisKey, serialize(videoInfo)); } } return videoInfo; }
- 分布式id生成器
利用了redis的单线程,即使是多个服务同时来获取ID,也得一个个来。
哈希
命令
- hget、hset、hdel
# hset 设置field-value对
127.0.0.1:6379> hset user:1:info age 23
(integer) 1
# hget 获取field的value
127.0.0.1:6379> hget user:1:info age
"23"
127.0.0.1:6379> hset user:1:info name ronaldo
(integer) 1
# hgetall 获取所有key-value对
127.0.0.1:6379> hgetall user:1:info
1) "age"
2) "23"
3) "name"
4) "ronaldo"
# hdel 删除key-value对
127.0.0.1:6379> hdel user:1:info age
(integer) 1
127.0.0.1:6379> hgetall user:1:info
1) "name"
2) "ronaldo"
- hexists、hlen
127.0.0.1:6379> hgetall user:1:info
1) "age"
2) "23"
3) "name"
4) "ronaldo"
# hexists 判断filed是否存在
127.0.0.1:6379> hexists user:1:info name
(integer) 1
# 判断field长度
127.0.0.1:6379> hlen user:1:info
(integer) 2
- hmget、hmset
# hmset 设置多个filed-value对
127.0.0.1:6379> hmset user:2:info age 30 name kaka page 50
OK
127.0.0.1:6379> hlen user:2:info
(integer) 3
# hmget 获取多个field的value
127.0.0.1:6379> hmget user:2:info age name
1) "30"
2) "kaka"
- hgetall、hvals、hkeys
# hgetall key:返回hash key对应所有的field和value
127.0.0.1:6379> hgetall user:2:info
1) "age"
2) "30"
3) "name"
4) "kaka"
5) "page"
6) "50"
# hvals key:返回hash key对应所有filed的value
127.0.0.1:6379> hvals user:2:info
1) "30"
2) "kaka"
3) "50"
# hkeys key:返回hash key对应所有field
127.0.0.1:6379> hkeys user:2:info
1) "age"
2) "name"
3) "page"
- 对比string的api
string | hash |
---|---|
get | hget |
set setnx | hset hsetnx |
del | hdel |
incr incrby decr decrby | hincrby |
mset | hmset |
mget | hmget |
- 时间复杂度
命令 | 时间复杂度 |
---|---|
hget hset hdel | O(1) |
hexists | O(1) |
hincrby | O(1) |
hgetall hvals hkeys | O(n) |
hmget hmset | O(n) |
实战
- 记录网站每个用户个人主页的访问量
hincrby user:1:info pageview count
-
缓存视频的基本信息(数据源在MySQL中)
伪代码如下:
public VideoInfo get(long id) { String redisKey = redisPrefix + id; Map<String, String> hashMap = redis.hgetAll(redisKey); VideoInfo videoInfo = transferMapToVideo(hashMap); if (videoInfo == null) { videoInfo = mysql.get(id); if (videoInfo != null) { redis.hmset(redisKey, transferVideoToMap(videoInfo)); } } return videoInfo; }
列表
有序可重复列表
数据结构
命令
- rpush、lpush
- linsert
- lpop、rpop
- lrem
- ltrim
- lrange
- lindex
- llen
- lset
- blpop、brpop
实战规则
- 栈:LPUSH + LPOP
- 队列: LPUSH + RPOP
- 定长集合:LPUSH + LTRIM
- 消息队列:LPUSH + BRPOP
集合
集合元素无序不可重复
API介绍
- sadd srem
- scard sismember srandmember smembers
- sdiff sinter sunion
- API演示
127.0.0.1:6379> sadd user:1:follow it news his sports
(integer) 4
127.0.0.1:6379> smembers user:1:follow
1) "news"
2) "his"
3) "it"
4) "sports"
127.0.0.1:6379> spop user:1:follow
"news"
127.0.0.1:6379> smembers user:1:follow
1) "his"
2) "it"
3) "sports"
127.0.0.1:6379> scard user:1:follow
(integer) 3
127.0.0.1:6379> sismember user:1:follow entertainment
(integer) 0
有序集合
有序不可重复集合
API介绍
- zadd
- zrem
- zscore
- zincrby
- zcard
- zrange
- zrangebyscore
- zcount
- zremrangebyrank
- zremrangebyscore
API使用演示
# zadd 添加集合元素
127.0.0.1:6379> zadd player:rank 1000 ronaldo 900 messi 800 c-ronaldo 600 kaka
(integer) 4
# zscore 获取分值
127.0.0.1:6379> zscore player:rank kaka
"600"
# zcard 获取元素个数
127.0.0.1:6379> zcard player:rank
(integer) 4
# zrank 获取排名,从0开始算
127.0.0.1:6379> zrank player:rank ronaldo
(integer) 3
# zrem 删除元素
127.0.0.1:6379> zrem player:rank messi
(integer) 1
# zrange 获取指定索引范围内的升序元素
127.0.0.1:6379> zrange player:rank 0 -1 withscores
1) "kaka"
2) "600"
3) "c-ronaldo"
4) "800"
5) "ronaldo"
6) "1000"
127.0.0.1:6379> zadd player:rank 1000 ronaldo 900 messi 800 c-ronaldo 600 kaka
(integer) 4
127.0.0.1:6379> zrange player:rank 0 -1
1) "kaka"
2) "c-ronaldo"
3) "messi"
4) "ronaldo"
127.0.0.1:6379> zcount player:rank 700 901
(integer) 2
127.0.0.1:6379> zrangebyscore player:rank 700 901
1) "c-ronaldo"
2) "messi"
127.0.0.1:6379> zremrangebyrank player:rank 0 1
(integer) 2
127.0.0.1:6379> zrange player:rank 0 -1
1) "messi"
2) "ronaldo"
127.0.0.1:6379> zrange player:rank 0 -1 withscores
1) "messi"
2) "900"
3) "ronaldo"
4) "1000"
API查漏补缺
- zrevrank:zrank的反排
- zrevrange zrange的反排
- zrevrangebyscore zrangebyscore的反排
- zinterstore 集合交集运算并存储
- zunionstore 集合并集运算并存储
zset总结
操作类型 | 命令 |
---|---|
基本操作 | zadd zrem zcard zincrby zscore |
范围操作 | zrange zrangebyscore zcount zremrangebyrank |
集合操作 | zunionstore zinterstore |
Redis开源客户端
Redis开源客户端涵盖各种语言:
Java客户端Jedis
这里主要详细介绍下Redis的Java开源客户端工具Jedis的使用
获取jedis
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
jedis直连
Jedis jedis = new Jedis("127.0.0.1", 6379);
jedis.set("hello", "world");
String value = jedis.get("hello");
Jedis构造函数
Jedis(String host, int port, int connectionTimeout, int soTimeout)
- host: Redis节点机器IP
- post: Redis端口
- connectionTimeout: 连接超时时间
- soTimeout: 客户端读写超时时间
这里只列了常用参数,其他像密码,或基于安全的SSL,用到的时候自行查阅即可。
简单使用
// 1. string
jedis.set("hello", world); // OK
jedis.get("hello"); // "world"
jedis.incr("counter"); // 1
// 2. hash
jedis.hset("myhash", "f1", "v1");
jedis.hset("myhash", "f2", "v2");
jedis.hgetAll("myhash"); // {f1=v1, f2=v2}
// 3. list
jedis.rpush("mylist", 1);
jedis.rpush("mylist", 2);
jedis.rpush("mylist", 3);
jedis.lrange("mylist", 0, -1); // [1, 2, 3]
// 4. set
jedis.sadd("myset", "a");
jedis.sadd("myset", "b");
jedis.sadd("myset", "a");
jedis.smembers("myset"); // [b, a]
// 5. zset
jedis.zadd("myzset", 99, "tom");
jedis.zadd("myzset", 66, "peter");
jedis.zadd("myzset", 33, "james");
jedis.zrangeWithScores("myzset", 0, -1); // [[["james"], 33.0], [["peter"], 66.0], [["tom"], 99.0]]
Jedis连接池
GenericObjectPoolConfig poolConfig = new GenericObjectPollConfig();
JedisPool jedisPool = new JedisPool(poolConfig, "127.0.0.1", 6379);
Jedis jedis = null;
try {
// 1. 从连接池获取Jedis对象
jedisPool.getResource();
// 2. 执行操作
jedis.set("hello", "world");
} catch(Exception e) {
e.printStackTrace();
} finally {
if (jedis != null) {
// 3. 如果使用JedisPool,close操作不是关闭连接,而是归还连接池
jedis.close();
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。