概念:
Redis是用C语言开发的一个开源的、高性能、键值对(key-value)数据库
官方提供的数据:
50个并发执行100000个请求时读的速度是:110000 次每秒,写的速度是:81000 次每秒
官网
中文网
下载后解压就可以直接使用
redis.windows.conf
:配置文件
redis.cli.exe
:Redis客户端
redis.server.exe
:Redis服务端
Redis存储的是 key-value 格式的数据
key 都是i字符串类型,value 有5 种不同的数据结构
数据类型 | 对应的JAVA类型 | 备注 |
---|---|---|
string(字符串) | String | |
list(列表) | LinkedList | 可重复 |
set(集合) | Set | 不可重复,无序 |
hash(哈希) | Map | |
sortedset(有序集合) | TreeSet | 不可重复,有序 |
存储
set [key] [value];
# 例如
set username lisi;
获取
get [key];
# 例如
get username; # lisi
删除
del [key];
# 例如
del username;
存储
hset [key] [field] [value];
#例如
hset user username lisi;
hset user password 123;
获取
hget [key] [filed]; # 获取 key 中 field 字段对应的值
hgetall [key] # 获取key中所有的字段及只
# 例如
hget user username; # lisi
hgetall user; # username=lisi password=123
删除
hdel [key] [field]; # 删除 key 中的 field 字段及其值
# 例如
hdel user username; # 删除 user 中的 username 字段及其值
可以添加一个元素到列表的头部(左)或尾部(右)
可以模拟队列与堆栈
添加
lpush [key] [value]; # 将元素添加到列表头部(左)
rpush [key] [value]; # 将元素添加到列表尾部(右)
# 例如
lpush name zhangsan; # 在name列表的头部(左)添加zhangsan
rpush name lisi; # 在name列表的尾部(右)添加lisi
获取
lrange [key] [start] [end] # 范围获取(start从0开始)
# 例如
lrange name 1 2; #
lrange name 0 -1; # 获取name列表中的所有元素
删除
lpop [key]; # 删除列表头部(最左边)的元素,并返回该元素
rpop [key]; # 删除列表头部(最左边)的元素,并返回该元素
# 例如
lpop user; # zhangsan
rpop user; # lisi
不可重复
存储
sadd [key] [value];
# 例如
sadd name lisi;
获取
smembers [key]; # 获取集合中的所有元素
# 例如
smembers name;
删除
srem [key] [value]; # 删除集合中的某个元素
# 例如
srem name lisi;
不可重复,且元素有顺序
每个元素都会关联一个double类型的分数(score)
Redis正是通过分数来为集合中的元素进行排序的。
存储
zadd [key] [score] [value];
# 例如
zadd name 60 lisi;
zadd name 79 zhangsan;
获取
zrange [key] [start] [end] {[whthscore]}
# 例如
zrange name 0 -1;
# 1)"lisi"
# 2)"zhangsan"
zrange name 0 -1 withscore;
# 1)"lisi"
# 2)"60"
# 3)"zhangsan"
# 4)"79"
删除
zrem [key] [value];
# 例如
zrem name lisi;
keys *(pattern); # 查询所有的键(正则表达式)
type [key]; # 查询键key 对应的value 的数据类型
del key: # 删除指定的键key 及其值 value
Redis 事务可以一次执行多个命令, 并且带有以下三个重要的保证:
一个事务从开始到执行会经历以下三个阶段:
以下是一个事务的例子, 它先以 MULTI 开始一个事务, 然后将多个命令入队到事务中, 最后由 EXEC 命令触发事务, 一并执行事务中的所有命令:
redis 127.0.0.1:6379> MULTI #开启事务
OK
redis 127.0.0.1:6379> SET book-name "Mastering C++ in 21 days"
QUEUED
redis 127.0.0.1:6379> GET book-name
QUEUED
redis 127.0.0.1:6379> SADD tag "C++" "Programming" "Mastering Series"
QUEUED
redis 127.0.0.1:6379> SMEMBERS tag
QUEUED
redis 127.0.0.1:6379> EXEC
1) OK
2) "Mastering C++ in 21 days"
3) (integer) 3
4) 1) "Mastering Series"
2) "C++"
3) "Programming"
单个 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
QUEUED
redis 127.0.0.1:7000> set c ccc
QUEUED
redis 127.0.0.1:7000> exec
1) OK
2) OK
3) OK
如果在 set b bbb 处失败,set a 已成功不会回滚,set c 还会继续执行。
下表列出了 redis 事务的相关命令:
序号 | 命令及描述 | 描述 |
---|---|---|
1 | DISCARD | 取消事务,放弃执行事务块内的所有命令 |
2 | EXEC | 执行所有事务块内的命令 |
3 | MULTI | 标记一个事务块的开始 |
4 | UNWATCH | 取消 WATCH 命令对所有 key 的监视 |
5 | [WATCH key key …] | 监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。 |
redis没有实现访问控制这个功能,但是它提供了一个轻量级的认证方式,可以编辑redis.conf配置来启用认证。
初始化Redis密码
在配置文件中有个参数:requirepass
,这个就是配置Redis访问密码的参数
比如:requirepass test123;
(PS:需要重启Redis才能生效)
Redis的查询速度是非常快的,外部用户一秒内可以尝试多达150K个密码;所以密码尽量要长(对于DBA没有必要必须记住密码)
不重启Redis设置密码
requirepass
的密码(当Redis重启时依然有效)redis 127.0.0.1:6379> config set requirepass test123
redis 127.0.0.1:6379> config get requirepass
(error) ERR operation not permitted
redis 127.0.0.1:6379> auth test123
redis 127.0.0.1:6379> config get requirepass
# 查询结果
1)"requirepass"
2)"test123"
PS:如果配置文件中没添加密码,那么Redis重启后,密码失效
登录有密码的Redis
redis-cli -p 6379 -a test123
redis-cli -p 6379
redis 127.0.0.1:6379> auth test123
AUTH命令跟其他redis命令一样,是没有加密的;阻止不了攻击者在网络上窃取你的密码;
认证层的目标是提供多一层的保护。如果防火墙或者用来保护redis的系统防御外部攻击失败的话,外部用户如果没有通过密码认证还是无法访问redis的。
主从复制,就是主机数据更新后根据配置和策略,自动同步到备机的master/slaver机制。
Master以写为主,Slave以读为主
Redis集群中的配置
由于redis的高性能,在应用中对其依赖很高,有时候一台redis服务器性能不够,需要配置redis集群。最简单的就是一台用来读,一台用来写。一般对读的需求比较大,所以可以配置一主(写)从(读)。
本次是在本地搭建两台虚拟机分别做一主一从。
ip为192.168.2.100为主服务器
ip为192.168.2.101为从服务器
首先给两台服务器分别安装redis
配置主服务器
进入主服务器,打开Redis配置文件
将bind 127.0.0.1
这行注释或者指定ip。(注释:即所有ip都能连接)
开启守护进程:daemonize yes
设置访问密码:requirepass 123456
PS:由于Redis性能非常高,撞库风险极大,建议线上把密码设置非常复杂,最好能在第2步中指定ip
注意:
当然,既然用到主从了,那说明对redis依赖非常高,还有几个参数需要根据服务器配置来设置
第一个就是客户端最大连接数(maxclients),默认是10000,可根据需求更改
第二个就是最大内存(maxmemory,默认不受限制,但如果有多个从服务器,建议还是设置个低于服务器内存的值)
第三个是内存策略(maxmemory-policy,默认为noeviction),如果内存足够用则不用管,如果内存不够用,建议设置最近最少使用策略(LRU),默认是内存不够则报错
# volatile-lru -> remove the key with an expire set using an LRU algorithm
# allkeys-lru -> remove any key according to the LRU algorithm
# volatile-random -> remove a random key with an expire set
# allkeys-random -> remove a random key, any key
# volatile-ttl -> remove the key with the nearest expire time (minor TTL)
# noeviction -> don't expire at all, just return an error on write operations
#
# The default is:
#
# maxmemory-policy noeviction
至此主服务器配置完毕!
启动redis服务
[root@localhost redis-4.0.10]# service redis start
配置从服务器
前四步与主服务器配置基本一致
配置所属主服务器ip和端口
slaveof 192.128.2.100 6379
配置所属主服务器的密码(再次强调,要将密码设置非常复杂,这里只是演示)
masterauth 123456
需要注意的是,从服务器通常是只读,所以要配置只读(默认是只读,不要更改即可)
配置只读
slave-read-only yes
配置完成,启动服务
[root@localhost redis-4.0.10]# service redis start
测试
使用redis客户端或者telnet都可以
本次使用redis客户端
进入主服务器
进入redis客户端
[root@localhost redis-4.0.10]# /usr/local/redis/bin/redis-cli
由于设置了密码,所以需要鉴权
127.0.0.1:6379> auth 123456
设置一个值
127.0.0.1:6379>set name zhangsan
进入从服务器(192.168.2.101)
使用get命令获取name的值
[root@localhost redis-4.0.10]# /usr/local/redis/bin/redis-cli
127.0.0.1:6379> auth 123456
127.0.0.1:6379> get name
如果获取到了zhangsan代表配置成功
如果在从服务器上写,则会报错,如下;
127.0.0.1:6379> set age 11
(error) READONLY You Can't write against a read only slave
至此,redis主从复制配置完成,如果需要配置多台从服务器,可以重复第三步
同一台服务器上配置主从
redis.conf
daemonize yes
appendonly no
创建一个ms目录,存放主从关系
拷贝redis.conf到ms目录下
cp redis.conf ms/redis6379.conf
cp redis.conf ms/redis6380.conf
cp redis.conf ms/redis6381.conf
修改每个Redis的配置文件
# 全部删掉在配置
include [redis.conf的路径]
port 6380
pidfile /var/run/redis_6380.pid
dbfilename dump6380.rdb
slaveof 127.0.0.1 6379
启动
redis-server ms/redis_6379.conf
redis-server ms/redis_6380.conf
redis-server ms/redis_6381.conf
客户端连接
redis-cli -p 6379
redis-cli -p 6380
redis-cli -p 6381
查看Redis的主从复制信息(是Master还是slave)
info replication
命令:打印主从复制关系
建立主从关系
在从机上加一行命令:
slaveof [ip] [port]
:成为某个实例的从服务器
建议在配置文件中配置:slaveof 127.0.0.1 6379
127.0.0.1:6380> slaveof localhost 6379
127.0.0.1:6381> slaveof localhost 6379
切入点问题:
slave1、slave2是从头开始复制还是从切入点开始复制? —— 从头开始复制
比如从机k4进来了,那之前的123是否也可以复制? —— 可以
master——slaver1——slaver2
虽然slaver1对于slaver2来说是其主机,但slaver1实质上还只是一个从机,不能执行写操作
**作用:**可以有效减轻Master的写压力,去中心化降低风险,提升服务器性能
**风险:**风险是一旦某个Slaver宕机,后面的Slave都没法备份
当一个Master宕机后,后面的slave可以立刻升为Master,其后面的slave不用做任何修改
用slaveof no one
:将从机变为主机
反客为主的自动版,能够在后台监控主机是否故障,如果故障了根据投票数自动将从机转换为主机
配置哨兵
以上图的一仆二主模式为例
进入redis文件夹下,新建sentinel.conf文件
# sentinel monitor
# 告诉sentinel去监听地址为ip:port的一个master,这里的master-name可以自定义,quorum是一个数字,指明当有多少个sentinel认为一个master失效时,master才算真正失效
sentinel monitor mymaster 127.0.0.1 6379 1
# sentinel auth-pass
# 设置连接master和slave时的密码,注意的是sentinel不能分别为master和slave设置不同的密码,因此master和slave的密码应该设置相同。
# sentinel down-after-milliseconds
# 这个配置项指定了需要多少失效时间,一个master才会被这个sentinel主观地认为是不可用的。 单位是毫秒,默认为30秒
# sentinel parallel-syncs
# 这个配置项指定了在发生failover主备切换时最多可以有多少个slave同时对新的master进行 同步,这个数字越小,完成failover所需的时间就越长,但是如果这个数字越大,就意味着越 多的slave因为replication而不可用。可以通过将这个值设为 1 来保证每次只有一个slave 处于不能处理命令请求的状态。
# sentinel failover-timeout
# failover-timeout 可以用在以下这些方面:
# 1. 同一个sentinel对同一个master两次failover之间的间隔时间。
# 2. 当一个slave从一个错误的master那里同步数据开始计算时间。直到slave被纠正为向正确的master那里同步数据时。
# 3.当想要取消一个正在进行的failover所需要的时间。
# 4.当进行failover时,配置所有slaves指向新的master所需的最大时间。不过,即使过了这个超时,slaves依然会被正确配置为指向master,但是就不按parallel-syncs所配置的规则来了。
启动哨兵
执行
redis-sentinel /redis/sentinel-26379.conf
slave-priority 100
:数字越低,优先级越高
slave-priority 0
:永远不让该从机做主机(适用于性能极差的服务器上的Redis)
Redis集群实现了对Redis的水平扩容,即启动N个Redis节点,将整个数据库分布存储在这N个节点中,每个节点存储总数据的1/N
Redis集群通过分区(partition)来提供一定程度的可用性(availability):
即使集群中有一部分节点失效或者无法进行通讯,集群也可以继续处理命令请求
redis集群采用P2P模式,是完全去中心化的,不存在中心节点或者代理节点;
redis集群是没有统一的入口的,客户端(client)连接集群的时候连接集群中的任意节点(node)即可,集群内部的节点是相互通信的(PING-PONG机制),每个节点都是一个redis实例;
为了实现集群的高可用,即判断节点是否健康(能否正常使用),redis-cluster有这么一个投票容错机制:如果集群中超过半数的节点投票认为某个节点挂了,那么这个节点就挂了(fail)。这是判断节点是否挂了的方法;
那么如何判断集群是否挂了呢? -> 如果集群中任意一个节点挂了,而且该节点没有从节点(备份节点),那么这个集群就挂了。这是判断集群是否挂了的方法;
那么为什么任意一个节点挂了(没有从节点)这个集群就挂了呢? -> 因为集群内置了16384个slot(哈希槽),并且把所有的物理节点映射到了这16384[0-16383]个slot上,或者说把这些slot均等的分配给了各个节点。当需要在Redis集群存放一个数据(key-value)时,redis会先对这个key进行crc16算法,然后得到一个结果。再把这个结果对16384进行求余,这个余数会对应[0-16383]其中一个槽,进而决定key-value存储到哪个节点中。所以一旦某个节点挂了,该节点对应的slot就无法使用,那么就会导致集群无法正常工作。
综上所述,每个Redis集群理论上最多可以有16384个节点。
在usr/local目录下新建redis-cluster目录,用于存放集群节点
把redis目录下的bin目录下的所有文件复制到/usr/local/redis-cluster/redis01目录下,不用担心这里没有redis01目录,会自动创建的。操作命令如下(注意当前所在路径):
cp -r redis/bin/ redis-cluster/redis01
删除redis01目录下的快照文件dump.rdb,rm -rf dump.rdb
并且修改该目录下的redis.cnf文件,具体修改两处地方:
一是端口号修改为7001,
二是开启集群创建模式,将cluster-enabled yes
的注释打开
将redis-cluster/redis01文件复制5份到redis-cluster目录下(redis02-redis06),创建6个redis实例,模拟Redis集群的6个节点。然后将其余5个文件下的redis.conf里面的端口号分别修改为7002-7006。
接着启动所有redis节点,由于一个一个启动太麻烦了,所以在这里创建一个批量启动redis节点的脚本文件,命令为start-all.sh,文件内容如下:
cd redis01
./redis-server redis.conf
cd ..
cd redis02
./redis-server redis.conf
cd ..
cd redis03
./redis-server redis.conf
cd ..
cd redis04
./redis-server redis.conf
cd ..
cd redis05
./redis-server redis.conf
cd ..
cd redis06
./redis-server redis.conf
cd ..
创建好启动脚本文件之后,需要修改该脚本的权限,使之能够执行,指令如下:
chmod +x starth-all.sh
执行start-all.sh脚本,启动6个redis节点
ok,至此6个redis节点启动成功,接下来正式开启搭建集群,以上都是准备条件。大家不要觉得图片多看起来冗长所以觉得麻烦,其实以上步骤也就一句话的事情:创建6个redis实例(6个节点)并启动。
要搭建集群的话,需要使用一个工具(脚本文件),这个工具在redis解压文件的源代码里。因为这个工具是一个ruby脚本文件,所以这个工具的运行需要ruby的运行环境,就相当于java语言的运行需要在jvm上。所以需要安装ruby,指令如下:
yum install ruby
yum install rubygems
然后需要把ruby相关的包安装到服务器(/opt),我这里用的是redis-3.0.0.gem,大家需要注意的是:redis的版本和ruby包的版本最好保持一致。
将Ruby包安装到服务器:需要先下载再安装
gem install redis-3.0.0.gem
# gem install --local redis-3.0.0.gem
上一步中已经把ruby工具所需要的运行环境和ruby包安装好了,接下来需要把这个ruby脚本工具复制到usr/local/redis-cluster目录下。那么这个ruby脚本工具在哪里呢?之前提到过,在redis解压文件的源代码里,即redis/src目录下的redis-trib.rb文件。
将该ruby工具(redis-trib.rb)复制到redis-cluster目录下,指令如下:
cp redis-trib.rb /usr/local/redis-cluster
然后使用该脚本文件搭建集群,指令如下:
./redis-trib.rb create --replicas 1 47.106.219.251:7001 47.106.219.251:7002 47.106.219.251:7003 47.106.219.251:7004 47.106.219.251:7005 47.106.219.251:7006
注意:此处大家应该根据自己的服务器ip输入对应的ip地址!
中途有个地方需要手动输入yes即可
至此,Redi集群搭建成功!大家注意最后一段文字,显示了每个节点所分配的slots(哈希槽),这里总共6个节点,其中3个是从节点,所以3个主节点分别映射了0-5460、5461-10922、10933-16383solts。
最后连接集群节点,连接任意一个即可:
redis01/redis-cli -p 7001 -c
注意:一定要加上-c,不然节点之间是无法自动跳转的!如下图可以看到,存储的数据(key-value)是均匀分配到不同的节点的:
加上两条redis集群基本命令:
1.查看当前集群信息
cluster info
2.查看集群里有多少个节点
cluster nodes
Redis是一个内存数据库,当Redis服务端重启获取服务器重启,数据会丢失,我们可以将Redis内存中的数据持久化保存到硬盘中。
Redis持久化机制:
默认方式,不需要配置
在一定的时间间隔内,检测key的value的变化情况,然后持久化。
编辑redis.windows.conf
文件
save 900 1 # 900秒内,如果有 1 个key的value发生了变化,就持久化
save 300 10 # 300秒内,如果有 10 个key的value发生了变化,就持久化
save 60 10000 # 60秒内,如果有 10000 个key的value发生了变化,就持久化
重启Redis服务器,并指定配置文件
$ redis.server.exe redis.widows.conf
持久化后会生成一个 xxx.rdb 文件
日志记录的方式,可以记录每一条命令的操作
不建议使用,影响性能
编辑redis.windows.conf
文件
appendonly no # no:关闭AOF,yes:开启AOF
# 开启AOF后配置持久化策略
# appendfsync always # 每一次操作都进行一次持久化
# appendfsync everysec # 每个一秒进行一次持久化
# appendfsync no # 不持久化
重启Redis服务器,并指定配置文件
$ redis.server.exe redis.widows.conf
持久化后会生成一个 xxx.aof 文件
Jedis:一款Java操作Redis数据库的工具
jedis-2.8.1.jar
<dependency>
<groupId>redis.clientsgroupId>
<artifactId>jedisartifactId>
<version>2.8.1version>
dependency>
Jedis jedis = new Jedis("localhost",6379);
jedis.set("username","list");
jedis.close();
string(字符串类型)
jedis.set("key", "value");
jedis.get("key");
jedis.setex(String key, int seconds, String value); // 存储key,value 并指定过期时间seconds(秒)
hash(哈希类型)
jedis.hset(String key, String field, String value);
jedis.hget(String key, String field);
jedis.hgetAll(String key) // 返回值 Map
list(列表类型)
jedis.lpush(String key, String ... values); // 添加到列表头部(最左边)
jedis.rpush(String key, String ... values); // 添加到列表尾部(最右边)
jedis.lrange(String key, long start, long end); // 范围获取,返回值 List
jedis.lpop(String key) // 获取并删除列表头部(最左边)的元素
jedis.rpop(String key) // 获取并删除列表尾部(最右边)的元素
set(集合类型)
jedis.sadd(String key, String ... value);
jedis.smembers(String key); // 获取key中的全部元素,返回值 Set
sortedset(有续集合类型)
jedis.zadd(String key, double score, String values);
jedis.zadd(String key, Map<String, Double>);
jedis.zrange(String key, long start, long end);
JedisPool jar包: commons-pool2-2.3.jar
JedisPool jedisPool = new JedisPool(config,"localhost",6379);
Jedis jedis = JedisPool.getResource();
jedis.close();
//0.创建一个配置对象
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(50); // 最大活动对象个数
config.setMaxIdle(10); // 最大保持连接状态的对象个数
//1.创建Jedis连接池对象
JedisPool jedisPool = new JedisPool(config,"localhost",6379);
//2.获取连接
Jedis jedis = jedisPool.getResource();
//3. 使用
jedis.set("hehe","heihei");
//4. 关闭 归还到连接池中
jedis.close();
package com.dahua0318.util;
import java.io.IOException;
import java.util.Properties;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
/**
* Jedis连接池工具类
* Copyright: Copyright (c) 2019 LanRu-Caifu
*
* @ClassName: JedisUtils.java
* @Description: Jedis连接池工具类
*
* @version: v1.0.0
* @author: Administrator
*/
public class JedisUtils {
// @Fields pool : 声明Jedis连接池对象
private static JedisPool pool;
static {
//创建Properties属性集对象
Properties prop = new Properties();
try {
//将配置文件数据加载到属性集中
prop.load(JedisUtils.class.getClassLoader().getResourceAsStream("jedis.properties"));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//创建一个Jedis连接池配置对象
JedisPoolConfig config = new JedisPoolConfig();
//配置连接池的最大活动对象数 maxTotal
config.setMaxTotal(Integer.parseInt(prop.getProperty("maxTotal")));
//配置连接池的最大能够保持idel状态的对象数 maxIdle
config.setMaxIdle(Integer.parseInt(prop.getProperty("maxIdle")));
//创建连接池对象(连接池配置对象,主机名,端口号)
pool = new JedisPool(config,prop.getProperty("host"),Integer.parseInt(prop.getProperty("port")));
}
/**
* 获取Jedis连接池连接对象Jedis
* @return jedis Jedis
*/
public static Jedis getJedis() {
return pool.getResource();
}
}
使用Redis缓存一些关系型数据库(MySQL,Oracle等)中不经常发生变化的数据。
如果数据一旦发生变化,则需要更新缓存。
例如:
MySQL数据库的表执行增删改的操作,需要将Redis中的缓存数据删除,重新存入。
在service层对应的增删改业务操作中,将Redis缓存删除,在查询的业务操作中重新存入。