2

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还有下面这些高级配置:

  1. RDB config
  2. AOF config
  3. show Log config
  4. 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)

数据结构和内部编码

clipboard.png

单线程

Redis单线程设计,因此使用时要注意几点:

  1. 一次只运行一条命令
  2. 不要执行长(慢)命令

长(慢)命令: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)

实战

  1. 记录网站每个用户个人主页的访问量

    incr userid:pageview(单线程:无竞争)

  2. 缓存视频的基本信息(数据源在MySQL中)

    clipboard.png

    伪代码如下:

    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;
      }
  3. 分布式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)

实战

  1. 记录网站每个用户个人主页的访问量

    hincrby user:1:info pageview count

  2. 缓存视频的基本信息(数据源在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;
    }

列表

有序可重复列表

数据结构
clipboard.png

clipboard.png

命令

  • rpush、lpush

clipboard.png

clipboard.png

  • linsert

clipboard.png

  • lpop、rpop

clipboard.png

clipboard.png

  • lrem

clipboard.png

clipboard.png

  • ltrim

clipboard.png

clipboard.png

  • lrange

clipboard.png

  • lindex

clipboard.png

  • llen

clipboard.png

  • lset

clipboard.png

  • blpop、brpop

clipboard.png

实战规则

  • 栈:LPUSH + LPOP
  • 队列: LPUSH + RPOP
  • 定长集合:LPUSH + LTRIM
  • 消息队列:LPUSH + BRPOP

集合

集合元素无序不可重复

API介绍

  • sadd srem

clipboard.png

  • scard sismember srandmember smembers

clipboard.png

  • sdiff sinter sunion

clipboard.png

  • 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

clipboard.png

  • zrem

clipboard.png

  • zscore

clipboard.png

  • zincrby

clipboard.png

  • zcard

clipboard.png

  • zrange

clipboard.png

  • zrangebyscore

clipboard.png

  • zcount

clipboard.png

  • zremrangebyrank

clipboard.png

  • zremrangebyscore

clipboard.png

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开源客户端涵盖各种语言:

clipboard.png

参考:https://redis.io/clients

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();
    }
}

zhutianxiang
1.5k 声望328 粉丝