Redis在docker下的的安装和基本使用

1,590 阅读26分钟

Redis的安装和使用

1redis的安装

(1)下载镜像文件

在linux虚拟机中启动docker,从docker中将redis从网络上pull下来

docker pull redis

(2)创建redis实例并启动

创建redis配置文件目录

mkdir -p /usr/local/docker/redis/conf

在配置文件录下创建redis.conf配置文件(因为redis镜像中这个redis.conf是一个目录所以要先创建一个这个配置文件,否在我们本地挂载点也会变成一个目录)

touch /usr/local/docker/redis/conf/redis.conf

在linux上挂载redis并启动

sudo docker run -p 6379:6379 --name redis \
-v /usr/local/docker/redis/data:/data \
-v /usr/local/docker/redis/conf/redis.conf:/etc/redis/redis.conf \
-d redis redis-server /etc/redis/redis.conf 

(3)查看正在运行的进程

docker ps

(4)控制台直接连接redis测试

docker exec -it redis bash

(5)检测redisb版本

Redis-server –v

或者

redis-cli -v

或者直接将上面的两个步骤合为一个步骤也可以,指令如下

docker exec -it redis redis-cli

(6)停止redis服务

docker stop redis

(7)启动redis服务

在docker中

docker start redis

传统方式启动(非docker环境)

redis-server 					#默认找redis.conf配置文件
redis-server &					#上面ctrl+c中断reis会退出,这个不会
redis-server redis6380.conf	#指定配置文件,这样可以启动多个实例

(8)重启redis服务

在docker中

docker restart redis

(9)查看redis服务版本

docker exec -it redis redis-server -v

(10)关闭防火墙

systemctl stop firewalld

(11)设置访问密码

默认没有密码,可以随意访问。redis速度相当快,在一个较好的服务器下,外部用户每秒可以进行15w次的密码尝试,这意味着必须指定非常强大的密码来防止暴力破解。如果要使用密码,打开redis.conf配置文件,

requirepass 123456    #480行,设置请求密码,这样访问时都需要先登录

修改完配置文件以后,要重启redis服务?(docker start redis)

127.0.0.1:6379> auth 123456		#客户端访问方式
jedis.auth(“123456”);				#jedis访问方式(学了以后用)

(12)Redis.conf配置文件

Redis 支持很多的参数,但都有默认值。

daemonize

默认情况下, redis 不是在后台运行的,如果需要在后台运行,把该项的值更改为 yes。

pidfile

当 Redis 在后台运行的时候, Redis 默认会把 pid 文件放在/var/run/redis.pid,你可以配置到其他地址。当运行多个 redis 服务时,需要指定不同的 pid 文件和端口

bind

指定 Redis 只接收来自于该 IP 地址的请求,如果不进行设置,那么将处理所有请求,在生产环境中最好设置该项

port

监听端口,默认为 6379

timeout

设置客户端连接时的超时时间,单位为秒。当客户端在这段时间内没有发出任何指令,那么关闭该连接

loglevel

log 等级分为 4 级, debug, verbose, notice, 和 warning。生产环境下一般开启 notice

logfile

配置 log 文件地址,默认使用标准输出,即打印在命令行终端的窗口上

databases

设置数据库的个数,可以使用 SELECT 命令来切换数据库。默认使用的数据库是 0

save

设置 Redis 进行数据库镜像的频率。

if(在 60 秒之内有 10000 个 keys 发生变化时){

进行镜像备份 (redis/data/)

}else if(在 300 秒之内有 10 个 keys 发生了变化){

进行镜像备份

}else if(在 900 秒之内有 1 个 keys 发生了变化){

进行镜像备份

}

rdbcompression

在进行镜像备份时,是否进行压缩

dbfilename

镜像备份文件的文件名

dir

数据库镜像备份的文件放置的路径。

这里的路径跟文件名要分开配置是因为 Redis 在进行备份时,先会将当前数据库的状态写入到一个临时文件中

等备份完成时,再把该临时文件替换为上面所指定的文件,而这里的临时文件和上面所配置的备份文件都会放在这个指定的路径当中

slaveof

设置该数据库为其他数据库的从数据库

masterauth

当主数据库连接需要密码验证时,在这里指定

requirepass

设置客户端连接后进行任何其他指定前需要使用的密码。

警告:因为 redis 速度相当快,所以在一台比较好的服务器下,一个外部的用户可以在一秒钟进行 150K 次的密码尝试,这意味着你需要指定非常非常强大的密码来防止暴力破解。

maxclients

限制同时连接的客户数量。当连接数超过这个值时, redis 将不再接收其他连接请求,

客户端尝试连接时将收到 error 信息。

maxmemory

设置 redis 能够使用的最大内存。

appendonly

默认情况下, redis 会在后台异步的把数据库镜像备份到磁盘,但是该备份是非常耗时的,而且备份也不能很频繁,如果发生诸如拉闸限电、拔插头等状况,那么将造成比较大范围的数据丢失。

所以 redis 提供了另外一种更加高效的数据库备份及灾难恢复方式。开启 append only 模式之后, redis 会把所接收到的每一次写操作请求都追加到appendonly.aof 文件中,当 redis 重新启动时,会从该文件恢复出之前的状态。

但是这样会造成 appendonly.aof 文件过大,所以 redis 还支持了 BGREWRITEAOF 指令,对appendonly.aof 进行重新整理。

所以我认为推荐生产环境下的做法为关闭镜像,开启appendonly.aof,同时可以选择在访问较少的时间每天对 appendonly.aof 进行重写一次。

appendfsync

设置对 appendonly.aof 文件进行同步的频率。 always 表示每次有写操作都进行同步,

everysec 表示对写操作进行累积,每秒同步一次。这个需要根据实际业务场景进行配置

vm-enabled

是否开启虚拟内存支持。因为 redis 是一个内存数据库,而且当内存满的时候,无法接收新的写请求,所以在 redis 2.0 中,提供了虚拟内存的支持。

但是需要注意的是, redis中,所有的 key 都会放在内存中,在内存不够时,只会把 value 值放入交换区。这样保证了虽然使用虚拟内存,但性能基本不受影响

同时,你需要注意的是你要把vm-max-memory 设置到足够来放下你的所有的 key

vm-swap-file

设置虚拟内存的交换文件路径

vm-max-memory

这里设置开启虚拟内存之后, redis 将使用的最大物理内存的大小。默认为 0, redis 将把他所有的能放到交换文件的都放到交换文件中,以尽量少的使用物理内存。

在生产环境下,需要根据实际情况设置该值,最好不要使用默认的 0

vm-page-size

设置虚拟内存的页大小,如果你的 value 值比较大,比如说你要在 value 中放置博客、新闻之类的所有文章内容,就设大一点,如果要放置的都是很小的内容,那就设小一点。

vm-pages

设置交换文件的总的 page 数量, 需要注意的是, page table 信息会放在物理内存中,每8 个 page 就会占据 RAM 中的 1 个 byte。总的虚拟内存大小 = vm-page-size * vm-pages

vm-max-threads

设置 VM IO 同时使用的线程数量。因为在进行内存交换时,对数据有编码和解码的过程,所以尽管 IO 设备在硬件上本上不能支持很多的并发读写,但是还是如果你所保存的 vlaue 值比较大,将该值设大一些,还是能够提升性能的

glueoutputbuf

把小的输出缓存放在一起,以便能够在一个 TCP packet 中为客户端发送多个响应,具体原理和真实效果我不是很清楚。所以根据注释,你不是很确定的时候就设置成 yes

hash-max-zipmap-entries

在 redis 2.0 中引入了 hash 数据结构。当 hash 中包含超过指定元素个数并且最大的元素没有超过临界时, hash 将以一种特殊的编码方式(大大减少内存使用)来存储,这里可以设置这两个临界值

activerehashing

开启之后, redis 将在每 100 毫秒时使用 1 毫秒的 CPU 时间来对 redis 的 hash 表进行重新 hash,可以降低内存的使用。

当你的使用场景中,有非常严格的实时性需要,不能够接受 Redis 时不时的对请求有 2 毫秒的延迟的话,把这项配置为 no。

如果没有这么严格的实时性要求,可以设置为 yes,以便能够尽可能快的释放内存

2Redis应用基础

2.1基础命令

(1)redis-cli

默认连接:IP 127.0.0.1 端口 6379

redis-cli

指定IP端口:

redis-cli –h 127.0.0.1p 6379  

Redis提供了PING-PONG机制,测试与客户端和服务器链接是否正常

redis-cli ping

redis-cli
redis 127.0.0.1:6379>ping
PONG

正常回复

127.0.0.1:6379>SET test 123
OK

错误回复(以error开头,后面跟着错误信息)

127.0.0.1:6379>TEST
(error) ERR unknown command 'TEST'

整数回复

127.0.0.1:6379>INCR test_incr
(integer) 1

字符串回复(最长久的一种回复,双引号包裹)

127.0.0.1:6379>get test
“123

多行字符串回复

127.0.0.1:6379>KEYS *
1) "test_incr"
2) "test"

退出

127.0.0.1:6379> exit

关闭

127.0.0.1:6379> shutdown

(2)keys

字符串类型是redis中最基本的数据类型,它能存储任何形式的字符串,包括二进制数据。可以存储JSON化的对象、字节数组等。一个字符串类型键允许存储的数据最大容量是512MB。

赋值与取值:

SET key value

GET key

127.0.0.1:6379> keys *
(empty list or set)
127.0.0.1:6379> set test1 123
OK
127.0.0.1:6379> set test2 ab
OK
127.0.0.1:6379> keys *
1) "test1"
2) "test2"
127.0.0.1:6379> get test1
"123"
127.0.0.1:6379> get test2
"ab"
127.0.0.1:6379> get test3
(nil)
127.0.0.1:6379>

(3)keys通配符

获取符合规则的建名列表。

KEYS *
keys test[_]* 
keys t[a-d]

说明:

? 匹配一个字符,例如 keys ?est1

* 匹配任意个(包括0个)字符

[] 匹配括号间的任一字符,例如 keys test[12]。还可以使用“-“表示范围。

例如test[1-3]匹配test1/test2/test3

\x 匹配字符x,用于转义符合,如果要匹配“?“就需要使用?

(4)select

redis默认支持16个数据库,对外都是以一个从0开始的递增数字命名,可以通过参数database来修改默认数据库个数。客户端连接redis服务后会自动选择0号数据库,可以通过select命令更换数据库,例如选择1号数据库:

127.0.0.1:6379>SELECT 1
OK
127.0.0.1:6379>GET test
(nil)

说明:

Redis不支持自定义数据库名称。

Redis不支持为每个数据库设置访问密码。

Redis的多个数据库之间不是安全隔离的,FLUSHALL命令会清空所有数据库的数据。

清除屏幕内容

clear

(5)exists

判断一个键是否存在。

如果键存在则返回整数类型1,否则返回0。

127.0.0.1:6379> keys *
1) "test_incr"
2) "test1"
127.0.0.1:6379> exists test1
(integer) 1
127.0.0.1:6379> exists test3
(integer) 0
127.0.0.1:6379>

(6)del

删除键,可以删除一个或者多个键,多个键用空格隔开,返回值是删除的键的个数。

127.0.0.1:6379> del test1
(integer) 1
127.0.0.1:6379> del test1
(integer) 0
127.0.0.1:6379> del test1 test_incr
(integer) 1
127.0.0.1:6379>

(7)type

获得键值的数据类型,返回值可能是string(字符串)、hash(散列类型)、list(列表类型)、set(集合类型)、zset(有序集合类型)。

127.0.0.1:6379> keys *
1) "test1"
2) "test2"
127.0.0.1:6379> type test1
string
127.0.0.1:6379> type test2
string

(8)help

127.0.0.1:6379> help
redis-cli 2.8.19
Type: "help @<group>" to get a list of commands in <group>
      "help <command>" for help on <command>
      "help <tab>" to get a list of possible help topics
      "quit" to exit
127.0.0.1:6379> help type

  TYPE key
  summary: Determine the type stored at key
  since: 1.0.0
  group: generic

官网:www.redis.io帮助

(9)flushall

清空所有数据库。

127.0.0.1:6379> FLUSHALL
OK

(10)flushdb

127.0.0.1:6379> FLUSHDB
OK

2.2Redis数据类型之字符串(重点)

存放的字符串为二进制是安全的。字符串长度支持到512M。

(1)incry/incyby

递增数字INCR key当存储的字符串是整数时,redis提供了一个实用的命令INCR,其作用是让当前键值递增,并返回递增后的值。

127.0.0.1:6379> keys *
1) "test1"
2) "test2"
127.0.0.1:6379> get test1
"123"
127.0.0.1:6379> get test1
"abc"
127.0.0.1:6379> get test2
(nil)
127.0.0.1:6379> incr num
(integer) 1
127.0.0.1:6379> keys *
1) "num"	
2) "test1"
3) "test"
127.0.0.1:6379> incr num
(integer) 2
127.0.0.1:6379> incr num
(integer) 3
127.0.0.1:6379>

从上面例子可以看出,如果num不存在,则自动会创建,如果存在自动+1。

指定增长系数

语法:INCRBY key increment

127.0.0.1:6379> incr num
(integer) 2
127.0.0.1:6379> incr num
(integer) 3
127.0.0.1:6379> incrby num 2
(integer) 5
127.0.0.1:6379> incrby num 2
(integer) 7
127.0.0.1:6379> incrby num 2
(integer) 9
127.0.0.1:6379> incr num
(integer) 10
127.0.0.1:6379>

(2)decr/decrby

减少指定的整数

DECR key 按照默认步长(默认为1)进行递减

DECRBY key decrement 按照指定步长进行递减

127.0.0.1:6379> incr num
(integer) 10
127.0.0.1:6379> decr num
(integer) 9
127.0.0.1:6379> decrby num 3

(3)incrbyfloat

整数时,第一次加可以得到正确结果,浮点数后再加浮点就会出现精度问题。

原来下面的例子2.8.7注意在新版本中已经修正了这个浮点精度问题。3.0.7

INCRBYFLOAT key decrement

127.0.0.1:6379> set num 131
(integer) 131
127.0.0.1:6379> incrbyfloat num 0.7131.7127.0.0.1:6379> incrbyfloat num 0.7132.3999999999999999

(4)append

向尾部追加值。如果键不存在则创建该键,其值为写的value,即相当于SET key value。返回值是追加后字符串的总长度。

语法:APPEND key value

127.0.0.1:6379> keys *
1) "num"
2) "test1"
3) "test"
127.0.0.1:6379> get test
"123"
127.0.0.1:6379> append test "abc"
(integer) 6
127.0.0.1:6379> get test
"123abc"
127.0.0.1:6379>

(5)strlen

字符串长度,返回数据的长度,如果键不存在则返回0。注意,如果键值为空串,返回也是0。

语法:STRLEN key

127.0.0.1:6379> get test
"123abc"
127.0.0.1:6379> strlen test
(integer) 6
127.0.0.1:6379> strlen tnt
(integer) 0
127.0.0.1:6379> set tnt ""
OK
127.0.0.1:6379> strlen tnt
(integer) 0
127.0.0.1:6379> exists tnt
(integer) 1
127.0.0.1:6379>

(6)mset/mget

同时设置/获取多个键值

语法:MSET key value [key value …]

MGET key [key …]

127.0.0.1:6379> flushall
OK
127.0.0.1:6379> keys *
(empty list or set)
127.0.0.1:6379> mset a 1 b 2 c 3
OK
127.0.0.1:6379> mget a b c
1) "1"
2) "2"
3) "3"
127.0.0.1:6379>

2.3Redis有效时间(重点)

(1)Expire (设置生效时长-单位秒)

Redis在实际使用过程中更多的用作缓存,然而缓存的数据一般都是需要设置有效时间的(缓存内存是有限的,不可能无限制增加),即到期后数据自动销毁。

语法:EXPIRE key seconds

127.0.0.1:6379> flushall
OK
127.0.0.1:6379> set bomb tnt
OK
127.0.0.1:6379> expire bomb 10
(integer) 1
127.0.0.1:6379> ttl bomb
(integer) 5
127.0.0.1:6379> ttl bomb
(integer) 3
127.0.0.1:6379> ttl bomb
(integer) 3
127.0.0.1:6379> ttl bomb
(integer) 2
127.0.0.1:6379> ttl bomb
(integer) 1
127.0.0.1:6379> ttl bomb
(integer) -2
127.0.0.1:6379> ttl bomb
(integer) -2
127.0.0.1:6379>

TTL查看key的剩余时间,当返回值为-2时,表示键被删除。

当 key 不存在时,返回 -2 。 当 key 存在但没有设置剩余生存时间时,返回 -1 。 否则,以毫秒为单位,返回 key 的剩余生存时间。

注意:在 Redis 2.8 以前,当 key 不存在,或者 key 没有设置剩余生存时间时,命令都返回 -1 。

(2)Persist(取消时长设置)

通过persist让对特定key设置的生效时长失效。

语法:PERSIST key

127.0.0.1:6379> set bomb tnt
OK
127.0.0.1:6379> expire bomb 60
(integer) 1
127.0.0.1:6379> ttl bomb
(integer) 49
127.0.0.1:6379> persist bomb
(integer) 1
127.0.0.1:6379> ttl bomb
(integer) -1
127.0.0.1:6379>

设置新的数据时需要重新设置该key的生存时间,重新设置值也会清除生存时间。

(3)pexpire(单位毫秒)

pexpire 让key的生效时长以毫秒作为计量单位,可应用于秒杀场景。

语法:PEXPIRE key milliseconds

127.0.0.1:6379> set bomb tnt
OK
127.0.0.1:6379> pexpire bomb 10000
(integer) 1
127.0.0.1:6379> ttl bomb
(integer) 6
127.0.0.1:6379> ttl bomb
(integer) 3
127.0.0.1:6379> ttl bomb
(integer) -2
127.0.0.1:6379>

设置生存时间为毫秒,可以做到更精确的控制。

2.4Redis高级中的hash结构(重点)

在redis中用的最多的就是hash和string类型。

(1)问题

假设有User对象以JSON序列化的形式存储到redis中,User对象有id、username、password、age、name等属性,存储的过程如下:

保存、更新:

User对象->json(string)->redis

如果在业务上只是更新age属性,其他的属性并不做更新应该怎么做呢?

Redis数据类型之散列类型hash

散列类型存储了字段(field)和字段值的映射,但字段值只能是字符串,不支持其他类型,也就是说,散列类型不能嵌套其他的数据类型。一个散列类型可以包含最多232-1个字段。

(2)hset/hget

相关命令

HSET key field value
HGET key field
HMSET key field value [field value…]
HMGET key field [field]
HGETALL key

HSET和HGET赋值和取值

127.0.0.1:6379> hset user username chenchen
(integer) 1
127.0.0.1:6379> hget user username
"chenchen"
127.0.0.1:6379> hset user username chen
(integer) 0
127.0.0.1:6379> keys user
1) "user"
127.0.0.1:6379> hgetall user
1) "username"
2) "chen"
127.0.0.1:6379> 
127.0.0.1:6379> hset user age 18
(integer) 1
127.0.0.1:6379> hset user address "xi'an"
(integer) 1
127.0.0.1:6379> hgetall user
1) "username"
2) "chen"
3) "age"
4) "18"
3) "address"
4) "xi'an"
127.0.0.1:6379>

HSET命令不区分插入和更新操作,当执行插入操作时HSET命令返回1,当执行更新操作时返回0。

(3)hincrby

127.0.0.1:6379> hdecrby article total 1		#执行会出错
127.0.0.1:6379> hincrby article total -1		#没有hdecrby自减命令
(integer) 1
127.0.0.1:6379> hget article total			#获取值

(4)hmset/hmget

HMSET和HMGET设置和获取对象属性

127.0.0.1:6379> hmset person username tony age 18
OK
127.0.0.1:6379> hmget person age username
1) "18"
2) "tony"
127.0.0.1:6379> hgetall person
1) "username"
2) "tony"
3) "age"
4) "18"
127.0.0.1:6379>

注意:上面HMGET字段顺序可以自行定义

(5)hexists

属性是否存在

127.0.0.1:6379> hexists killer
(error) ERR wrong number of arguments for 'hexists' command
127.0.0.1:6379> hexists killer a
(integer) 0
127.0.0.1:6379> hexists user username
(integer) 1
127.0.0.1:6379> hexists person age
(integer) 1
127.0.0.1:6379>

(6) hdel

删除属性

127.0.0.1:6379> hdel user age
(integer) 1
127.0.0.1:6379> hgetall user
1) "username"
2) "chen"
127.0.0.1:6379> hgetall person
1) "username"
2) "tony"
3) "age"
4) "18"
127.0.0.1:6379>

(7)hkeys/hvals

只获取字段名HKEYS或字段值HVALS

127.0.0.1:6379> hkeys person
1) "username"
2) "age"
127.0.0.1:6379> hvals person
1) "tony"
2) "18"

(8)hlen

元素个数

127.0.0.1:6379> hlen user
(integer) 1
127.0.0.1:6379> hlen person
(integer) 2
127.0.0.1:6379>

2.5Redis高级中的list结构

(1)问题

Redis高级中的list结构

Redis的list类型其实就是一个每个子元素都是string类型的双向链表。可以通过push,pop操作从链表的头部或者尾部添加删除元素。这使得list既可以用作栈,也可以用作队列。

有意思的是list的pop操作还有阻塞版本的,当我们[lr]pop一个list对象时,如果list是空,或者不存在,会立即返回nil。但是阻塞版本的b[lr]pop可以则可以阻塞,当然可以加超时时间,超时后也会返回nil。为什么要阻塞版本的pop呢,主要是为了避免轮询。举个简单的例子如果我们用list来实现一个工作队列。执行任务的thread可以调用阻塞版本的pop去获取任务这样就可以避免轮询去检查是否有任务存在。当任务来时候工作线程可以立即返回,也可以避免轮询带来的延迟。

(2)lpush

在key对应list的头部添加字符串元素

redis 127.0.0.1:6379> lpush mylist "world"
(integer) 1
redis 127.0.0.1:6379> lpush mylist "hello"
(integer) 2
redis 127.0.0.1:6379> lrange mylist 0 -1
1) "hello"
2) "world"
redis 127.0.0.1:6379>

其中,Redis Lrange 返回列表中指定区间内的元素,区间以偏移量 START 和 END 指定。 其中 0 表示列表的第一个元素, 1 表示列表的第二个元素,以此类推。 你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。

(3)rpush

在key对应list的尾部添加字符串元素

redis 127.0.0.1:6379> rpush mylist2 "hello"
(integer) 1
redis 127.0.0.1:6379> rpush mylist2 "world"
(integer) 2
redis 127.0.0.1:6379> lrange mylist2 0 -1
1) "hello"
2) "world"
redis 127.0.0.1:6379>

(4)查看list

redis 127.0.0.1:6379> lrange mylist3 0 -1

(5)del

redis 127.0.0.1:6379> del mylist

(6)linsert

在key对应list的特定位置之前或之后添加字符串元素

redis 127.0.0.1:6379> rpush mylist3 "hello"
(integer) 1
redis 127.0.0.1:6379> rpush mylist3 "world"
(integer) 2
redis 127.0.0.1:6379> linsert mylist3 before "world" "there"
(integer) 3
redis 127.0.0.1:6379> lrange mylist3 0 -1
1) "hello"
2) "there"
3) "world"
redis 127.0.0.1:6379>

(7)lset

设置list中指定下标的元素值(一般用于修改操作)

redis 127.0.0.1:6379> rpush mylist4 "one"
(integer) 1
redis 127.0.0.1:6379> rpush mylist4 "two"
(integer) 2
redis 127.0.0.1:6379> rpush mylist4 "three"
(integer) 3
redis 127.0.0.1:6379> lset mylist4 0 "four"
OK
redis 127.0.0.1:6379> lset mylist4 -2 "five"
OK
redis 127.0.0.1:6379> lrange mylist4 0 -1
1) "four"
2) "five"
3) "three"
redis 127.0.0.1:6379>

(8)lrem

从key对应list中删除count个和value相同的元素,count>0时,按从头到尾的顺序删除。

redis 127.0.0.1:6379> rpush mylist5 "hello"
(integer) 1
redis 127.0.0.1:6379> rpush mylist5 "hello"
(integer) 2
redis 127.0.0.1:6379> rpush mylist5 "foo"
(integer) 3
redis 127.0.0.1:6379> rpush mylist5 "hello"
(integer) 4
redis 127.0.0.1:6379> lrem mylist5 2 "hello"
(integer) 2
redis 127.0.0.1:6379> lrange mylist5 0 -1
1) "foo"
2) "hello"
redis 127.0.0.1:6379>

count<0时,按从尾到头的顺序删除

redis 127.0.0.1:6379> rpush mylist6 "hello"
(integer) 1
redis 127.0.0.1:6379> rpush mylist6 "hello"
(integer) 2
redis 127.0.0.1:6379> rpush mylist6 "foo"
(integer) 3
redis 127.0.0.1:6379> rpush mylist6 "hello"
(integer) 4
redis 127.0.0.1:6379> lrem mylist6 -2 "hello"
(integer) 2
redis 127.0.0.1:6379> lrange mylist6 0 -1
1) "hello"
2) "foo"
redis 127.0.0.1:6379>

count=0时,删除全部

redis 127.0.0.1:6379> rpush mylist7 "hello"
(integer) 1
redis 127.0.0.1:6379> rpush mylist7 "hello"
(integer) 2
redis 127.0.0.1:6379> rpush mylist7 "foo"
(integer) 3
redis 127.0.0.1:6379> rpush mylist7 "hello"
(integer) 4
redis 127.0.0.1:6379> lrem mylist7 0 "hello"
(integer) 3
redis 127.0.0.1:6379> lrange mylist7 0 -1
1) "foo"
redis 127.0.0.1:6379>

(9)ltrim

保留指定key 的值范围内的数据

redis 127.0.0.1:6379> rpush mylist8 "one"
(integer) 1
redis 127.0.0.1:6379> rpush mylist8 "two"
(integer) 2
redis 127.0.0.1:6379> rpush mylist8 "three"
(integer) 3
redis 127.0.0.1:6379> rpush mylist8 "four"
(integer) 4
redis 127.0.0.1:6379> ltrim mylist8 1 -1
OK
redis 127.0.0.1:6379> lrange mylist8 0 -1
1) "two"
2) "three"
3) "four"
redis 127.0.0.1:6379>

(10)lpop

从list的头部删除元素,并返回删除元素

redis 127.0.0.1:6379> lrange mylist 0 -1
1) "hello"
2) "world"
redis 127.0.0.1:6379> lpop mylist
"hello"
redis 127.0.0.1:6379> lrange mylist 0 -1
1) "world"
redis 127.0.0.1:6379>

(11)rpop

从list的尾部删除元素,并返回删除元素:

redis 127.0.0.1:6379> lrange mylist2 0 -1
1) "hello"
2) "world"
redis 127.0.0.1:6379> rpop mylist2
"world"
redis 127.0.0.1:6379> lrange mylist2 0 -1
1) "hello"
redis 127.0.0.1:6379>

(12)llen

返回key对应list的长度:

redis 127.0.0.1:6379> llen mylist5
(integer) 2
redis 127.0.0.1:6379>

(13)index

返回名称为key的list中index位置的元素:

redis 127.0.0.1:6379> lrange mylist5 0 -1
1) "three"
2) "foo"
redis 127.0.0.1:6379> lindex mylist5 0
"three"
redis 127.0.0.1:6379> lindex mylist5 1
"foo"
redis 127.0.0.1:6379>

(14)rpoplpush

从第一个list的尾部移除元素并添加到第二个list的头部,最后返回被移除的元素值,整个操作是原子的.如果第一个list是空或者不存在返回nil:

rpoplpush lst1 lst1
rpoplpush lst1 lst2

2.6Redis高机中的set结构

Redis的Set是string类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。Redis中Set集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。集合中最大的成员数为232 - 1 (4294967295每个集合可存储40多亿个成员)。

(1)sadd

添加元素,重复元素添加失败,返回0

127.0.0.1:6379> sadd name tony
(integer) 1
127.0.0.1:6379> sadd name hellen
(integer) 1
127.0.0.1:6379> sadd name rose
(integer) 1
127.0.0.1:6379> sadd name rose
(integer) 0

(2)smembers

获取内容

127.0.0.1:6379> smembers name
1) "hellen"
2) "rose"
3) "tony"

(3)spop

移除并返回集合中的一个随机元素

127.0.0.1:6379> smembers internet
1) "amoeba"
2) "redis"
3) "rabbitmq"
4) "nginx"
127.0.0.1:6379> spop internet
"rabbitmq"
127.0.0.1:6379> spop internet
"nginx"
127.0.0.1:6379> smembers internet
1) "amoeba"
2) "redis"

(4)scard

获取成员个数

127.0.0.1:6379> scard name
(integer) 3

(5)smove

移动一个元素到另外一个集合

127.0.0.1:6379> sadd internet amoeba nginx redis
(integer) 3
127.0.0.1:6379> sadd bigdata hadopp spark rabbitmq
(integer) 3
127.0.0.1:6379> smembers internet
1) "amoeba"
2) "redis"
3) "nginx"
127.0.0.1:6379> smembers bigdata
1) "hadopp"
2) "spark"
3) "rabbitmq"
127.0.0.1:6379> smove bigdata internet rabbitmq
(integer) 1
127.0.0.1:6379> smembers internet
1) "amoeba"
2) "redis"
3) "rabbitmq"
4) "nginx"
127.0.0.1:6379> smembers bigdata
1) "hadopp"
2) "spark"
127.0.0.1:6379>

(6)sunion

并集

127.0.0.1:6379> sunion internet bigdata
1) "redis"
2) "nginx"
3) "rabbitmq"
4) "amoeba"
5) "hadopp"
6) "spark"

2.7Redis数据持久化的两种模式(重点)

(1)简介

Redis中为了保证在系统宕机(类似进程被杀死)情况下,能更快的进行故障恢复,设计了两种数据持久化方案,分别为rdb和aof。

Rdb方式是通过手动(save-阻塞式或bgsave-异步)或周期性方式保存redis中key/value的一种机制,Rdb方式一般为redis的默认数据持久化方式.

Aof方式是通过记录写操作日志的方式,记录redis数据的一种持久化机制,这个机制默认是没有开启的.

(2)rdb和aof比较

rdbaof
fork一个进程,遍历hash table,利用copy on write,把整个db dump保存下来。 save,bgsave,shutdown, slave 命令会触发这个操作。粒度比较大,如果save, shutdown, slave 之前crash了,则中间的操作没办法恢复。把写操作指令,持续的写到一个类似日志文件里。(类似于从postgresql等数据库导出sql一样,只记录写操作) 粒度较小,crash(宕机)之后,只有crash之前没有来得及做日志的操作,这些数据是没办法恢复。

两种区别就是,一个是持续的用日志记录写操作,crash(崩溃)后利用日志恢复;一个是平时写操作的时候不触发写,只有手动提交save命令,或者是shutdown关闭命令时,才触发备份操作。

选择的标准,就是看系统是愿意牺牲一些性能,换取更高的缓存一致性(aof),还是愿意写操作频繁的时候,不启用备份来换取更高的性能,待手动运行save的时候,再做备份(rdb)。rdb这个就更有些 最终一致性(eventually consistent)的意思了。

2.8Redis事务管理(重点)

(1)背景

大多数数据库的事务控制,假如是乐观锁的方式,一般都是基于数据版本(version)的记录机制实现的。即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个”version”字段来实现读取出数据时,将此版本号一同读出,之后更新时,对此版本号加1。此时,将提交数据的版本号与数据库表对应记录的当前版本号进行比对,如果提交的数据版本号大于数据库当前版本号,则予以更新,否则认为是过期数据。

Redis也采用类似的机制,使用watch命令会监视给定的key,当exec时候如果监视的key从调用watch后发生过变化,则整个事务会失败。也可以调用watch多次监视多个key。这样就可以对指定的key加乐观锁了。注意watch的key是对整个连接有效的,事务也一样。如果连接断开,监视和事务都会被自动清除。当然exec,discard,unwatch命令都会清除连接中的所有监视。

(2)基本概念

redis是单线程(但是在6.0中真正引用多线程的应用),提交命令时,其它命令无法插入其中,轻松利用单线程实现了事务的原子性。那如果执行多个redis命令呢?自然就没有事务保证,于是redis有下列相关的redis命令来实现事务管理。

multi 开启事务

exec 提交事务

discard 取消事务

watch 监控,如果监控的值发生变化,则提交事务时会失败

unwatch 去掉监控

Redis保证一个事务中的所有命令要么都执行,要么都不执行(原子性)。如果在发送EXEC命令前客户端断线了,则Redis会清空事务队列,事务中的所有命令都不会执行。而一旦客户端发送了EXEC命令,所有的命令就都会被执行,即使此后客户端断线也没关系,因为Redis中已经记录了所有要执行的命令。

(3)exec提交事务

例如:模拟转账,王有200,张有700,张给王转100。过程如下:

127.0.0.1:6379> set w 200
OK
127.0.0.1:6379> set z 700
OK
127.0.0.1:6379> mget w z
1) "200"
2) "700"
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby z 100
QUEUED			#注意此命令根本没有执行,而是把其放在一个队列中
127.0.0.1:6379> incrby w 100
QUEUED
127.0.0.1:6379> mget w z
QUEUED
127.0.0.1:6379> get w			#同时,这些相关的变量也不能再读取
QUEUED
127.0.0.1:6379> get z
QUEUED
127.0.0.1:6379> exec
1) (integer) 600
2) (integer) 300
3) 1) "300"
   2) "600"
4) "300"
5) "600"
127.0.0.1:6379> mget w z
1) "300"
2) "600"
127.0.0.1:6379>

(4)如果有错误指令,自动取消

127.0.0.1:6379> mget w z
1) "300"
2) "600"
127.0.0.1:6379> multi
OK
127.0.0.1:6379> get w
QUEUED
127.0.0.1:6379> set w 100
QUEUED
127.0.0.1:6379> abc
(error) ERR unknown command 'abc'
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> mget w z				#可以看出数据并未变化
1) "300"
2) "600"
127.0.0.1:6379>

(5)discard取消事务

注意redis事务太简单,没有回滚,而只有取消。

127.0.0.1:6379> mget z w
1) "600"
2) "300"
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incrby z 100
QUEUED
127.0.0.1:6379> discard
OK
127.0.0.1:6379> get z
"600"
127.0.0.1:6379> exec
(error) ERR EXEC without MULTI

(6)秒杀抢票事务处理

客户端1:

127.0.0.1:6379> set ticket 1
OK
127.0.0.1:6379> set money 0
OK
127.0.0.1:6379> watch ticket		#乐观锁,对值进行观察,改变则事务失败
OK
127.0.0.1:6379> multi				#开启事务
OK
127.0.0.1:6379> decr ticket
QUEUED
127.0.0.1:6379> incrby money 100
QUEUED

客户端2:还没等客户端1提交事务,此时客户端2把票买到了。

127.0.0.1:6379> get ticket
"1"
127.0.0.1:6379> decr ticket
(integer) 0

客户端1:

127.0.0.1:6379> exec
(nil)										#执行事务,失败
127.0.0.1:6379> get ticket
"0"
127.0.0.1:6379> unwatch					#取消监控

3java中操作redis数据库

(1)jedis的应用

添加依赖

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.5.2</version>
</dependency>

快速入门

@Test
void testRedisStringOper(){
    Jedis jedis=new Jedis("192.168.174.130", 6379);
    //jedis.auth("123456");假如你的redis设置了密码
    jedis.set("id", "101");
    jedis.set("name", "tony");
    System.out.println("set ok");
    String id=jedis.get("id");
    String name=jedis.get("name");
    System.out.println("id="+id+";name="+name);
    jedis.incr("id");
    jedis.incrBy("id", 2);
    System.out.println(jedis.strlen("name"));
    //......
}

说明:当在测试之前,我们需要将redis.conf配置文件中的bind 127.0.0.1元素注释掉,

并且将其保护模式(protected-mode)设置为no(redis3.0之后默认开启了这个策略) ,当修改了配置以后,一定要记得重启redis,然后再进行访问.

(2)连接池JedisPool应用

@Test
void testJedisPool(){
    // 构建连接池配置信息
    JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
    // 设置最大连接数
    jedisPoolConfig.setMaxTotal(200);
    // 构建连接池
    JedisPool jedisPool = new JedisPool(jedisPoolConfig, "192.168.174.130", 6379);
    // 从连接池中获取连接
    Jedis jedis = jedisPool.getResource();
    // 读取数据
    System.out.println(jedis.get("name"));
    // 释放连接池
    jedisPool.close();
}

(3)RedisTemplate应用

第一步:创建spring boot项目

第二步:添加redis依赖 (spring-boot-starter-data-redis)

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

(4)快速入门

编写一段代码,通过StringRedisTemplate对象操作远端redis中的字符串数据。

第一步:配置连接redis的url和端口(application.yml)

spring:
  redis:
    host: 192.168.64.129
    port: 6379

第二步:基于StringRedisTemplate实现对象远端redis中字符串操作

@SpringBootTest
public class StringRedisTemplateTests {
    /**此对象为spring提供的一个用于操作redis数据库中的字符串的一个对象*/
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Test
    void testOpsForValueSet(){
        stringRedisTemplate.opsForValue().set("age", "18");
    }
}

第三步:基于RedisTemplate实现对远端复杂redis中数据的操作。

@SpringBootTest
public class RedisTemplateTests {
    /**
     * 通过此对象操作redis中复杂数据类型的数据,例如hash结构
     */
    @Autowired
    private RedisTemplate redisTemplate;

    @Test
    void testSetData(){
        SetOperations setOperations=redisTemplate.opsForSet();
        setOperations.add("setKey1", "A","B","C","C");
        Object members=setOperations.members("setKey1");
        System.out.println("setKeys="+members);
        //........
    }

    @Test
    void testListData(){
      //向list集合放数据
        ListOperations listOperations = redisTemplate.opsForList();
        listOperations.leftPush("lstKey1", "100"); //lpush
        listOperations.leftPushAll("lstKey1", "200","300");
        listOperations.leftPush("lstKey1", "100", "105");
        listOperations.rightPush("lstKey1", "700");
        Object value= listOperations.range("lstKey1", 0, -1);
        System.out.println(value);
      //从list集合取数据
        Object v1=listOperations.leftPop("lstKey1");//lpop
        System.out.println("left.pop.0="+v1);
        value= listOperations.range("lstKey1", 0, -1);
        System.out.println(value);
    }

    /**通过此方法操作redis中的hash数据*/
    @Test
    void testHashData(){
        HashOperations hashOperations = redisTemplate.opsForHash();//hash
        Map<String,String> blog=new HashMap<>();
        blog.put("id", "1");
        blog.put("title", "hello redis");
        hashOperations.putAll("blog", blog);
        hashOperations.put("blog", "content", "redis is very good");
        Object hv=hashOperations.get("blog","id");
        System.out.println(hv);
        Object entries=hashOperations.entries("blog");
        System.out.println("entries="+entries);
    }

}

4 Redis高级特性应用

1redis分片

何为分片

Redis中的分片思想就是把鸡蛋放到不同的篮子中进行存储。因为一个redis服务的存储能力是有限。分片就是实现redis扩容的一种有效方案。

2启动多个服务

传统方式(了解)

参数:port端口,daemonize后台运行,protected-mode保护模式

redis-server --port 6379 --daemonize yes --protected-mode no

redis-server --port 6380 --daemonize yes --protected-mode no

查看进程

[root@localhost redis-5.0.0]# ps -ef|grep redis

root 6405 1 0 13:11 ? 00:00:44 redis-server *:6379

root 8858 1 2 15:20 ? 00:00:00 redis-server *:6380

root 8863 8715 0 15:21 pts/1 00:00:00 grep --color=auto redis

docker方式:

sudo docker run -p 6380:6379 --name redis01 \
-v /usr/local/docker/redis/data:/data \
-v /usr/local/docker/redis/conf/redis.conf:/etc/redis/redis.conf \
-d redis redis-server /etc/redis/redis.conf 
sudo docker run -p 6381:6379 --name redis02 \
-v /usr/local/docker/redis/data:/data \
-v /usr/local/docker/redis/conf/redis.conf:/etc/redis/redis.conf \
-d redis redis-server /etc/redis/redis.conf 
docker start redis01
docker start redis02

3Redis客户端分片存储实现

实现分布式缓存,Redis多个节点的透明访问

@Test	//分片
	public void shard(){
		
		
		//构造各个节点链接信息,host和port
		List<JedisShardInfo> infoList = 
new ArrayList<JedisShardInfo>();
		JedisShardInfo info1 = 
new JedisShardInfo("192.168.163.200",6379);
		//info1.setPassword("123456");
		infoList.add(info1);
		JedisShardInfo info2 = new JedisShardInfo("192.168.163.200",6380);
		infoList.add(info2);
		JedisShardInfo info3 = new JedisShardInfo("192.168.163.200",6381);
		infoList.add(info3);
		
		//分片jedis
		
		JedisPoolConfig config = new JedisPoolConfig();
		config.setMaxTotal(500);	//最大链接数
		
		ShardedJedisPool pool = new ShardedJedisPool(config, infoList);
		//ShardedJedis jedis = new ShardedJedis(infoList);
		ShardedJedis jedis = pool.getResource();	//从pool中获取
		for(int i=0;i<10;i++){
			jedis.set("n"+i, "t"+i);
		}
		System.out.println(jedis.get("n9"));
		jedis.close();
	}