192.168.50.130
7000
7001
哨兵
26379,26780,26381
关闭服务方式1
./redis-cli -h 127.0.0.1 -p 6379 shutdown
方式2
ps -ef | grep ‘redis’
连接服务查看信息
info replication
RDB
RDB是整体快照备份一样,就像我们系统进行镜像的备份这种快照处理,当然看到这个大家应该会有一个问题,这样备份效率相对比较慢,而且一次备份数据比较大,所以官方也不推荐使用此方案进行数据持久化,但我们还得结合实际情况使用,像
redis主从复制的原理底层数据就是通过RDB。
触发条件 save 同步保存
bgsave 异步保存
AOF
日志追加数据持久化,即在原先的数据基础上进行追加,而不是完全覆写,这样效率高
appendfsync属性 always everys 进行指令存储 save delete io操作要求较大 write
配置说明:
protected-mode no #关闭保护模式
daemonize yes #是否启用后台运行
port 6666 #设置端口
requirepass 123456 # 设置使用密码
logfile ./redislog_louie.log # 设置日志文件
dir ./ #设置数据文件存储位置
# bind 192.168.217.130 127.0.0.1 # 0.0.0.0 任何ip都可以连接
save 20 1 # 自动保存策略,20秒内有一个key发生变化就自动保存
dbfilename rdb_louie.rdb # rdb文件名
stop-writes-on-bgsave-error yes # 发生错误中断写入,建议开启
rdbcompression yes # 数据文件压缩,建议开启
rdbchecksum yes # 开启crc64错误校验,建议开启
appendonly yes # 开启aof
appendfilename aof_louie.aof # aof 日志文件名
appendfsync everysec # 每秒记录一次日志,建议everysec
no-appendfsync-on-rewrite yes # 重写过程中是否向日志文件写入,yes 代表rewrite过程中,不向aof文件中追加信息,rewrite结束后再写入,no 代表rewrite执行的同时,也向aof追加信息
auto-aof-rewrite-percentage 100 # 触发重写文件增长百分比 默认100%
auto-aof-rewrite-min-size 64mb # 触发重写最小aof文件尺寸
主从复制 两台redis服务
主服务器
daemonize yes
port 7000
logfile master7000.log
dir /usr/local/redis/data
requirepass 123456
masterauth 123456 # 132服务器配置masterauth作用主要是为了后期sentinel引入后重新选举master并且7000端口redis重新加入主从复制时必备的,否则会出现权限不足
#bind 192.168.217.130 127.0.0.1
# AOF 数据持久化
appendonly yes
appendfilename aof-master-7000.aof
appendfsync everysec
no-appendfsync-on-rewrite yes
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
从服务器
port 7001
daemonize yes
logfile slaver7001.log
dir /usr/local/redis/data/
requirepass 123456
# 低版本的redis这里会是slaveof,意思是一样的,因为slave是比较敏感的词汇,所以在redis后面的版本中不在使用slave的概念,取而代之的是replicaof
slaveof 192.168.50.130 7000
masterauth 123456
#bind 192.168.217.133 127.0.0.1
# AOF 数据持久化
appendonly yes
appendfilename aof-slaver-7001.aof
appendfsync everysec
no-appendfsync-on-rewrite yes
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
启动
cd /usr/local/redis/etc/master-slaver
## 查看树状结构
tree
./usr/local/redis/bin/redis-service redis-master-7000.config
./usr/local/redis/bin/redis-service redis-slaver-7001.config
测试在这里插入图片描述
./redis-cli -p 7000 -h 127.0.0.1
## 验证密码
auth 123456
# 测试 客户端 连接主节点
shutdown save # 手动下线
# 客户端 登录 从节点服务器
info replication #查看信息
全量同步
Redis全量复制一般发生在Slave初始化阶段,这时Slave需要将Master上的所有数据都复制一份。具体步骤如下:
1)从服务器连接主服务器,发送SYNC命令;
2)主服务器接收到SYNC命名后,开始执行BGSAVE命令生成RDB文件并使用缓冲区记录此后执行的所有写命令;
3)主服务器BGSAVE执行完后,向所有从服务器发送快照文件,并在发送期间继续记录被执行的写命令;
4)从服务器收到快照文件后丢弃所有旧数据,载入收到的快照;
5)主服务器快照发送完毕后开始向从服务器发送缓冲区中的写命令;
6)从服务器完成对快照的载入,开始接收命令请求,并执行来自主服务器缓冲区的写命令;
增量同步
Redis增量复制是指Slave初始化后开始正常工作时主服务器发生的写操作同步到从服务器的过程。
增量复制的过程主要是主服务器每执行一个写命令就会向从服务器发送相同的写命令,从服务器接收并执行收到的写命令。
Redis主从同步策略
主从刚刚连接的时候,进行全量同步;全同步结束后,进行增量同步。当然,如果有需要,slave 在任何时候都可以发起全量同步。redis 策略是,无论如何,首先会尝试进行增量同步,如不成功,要求从机进行全量同步。
提高负载
主从哨兵是全量存储,集群是分布式存储,只使用集群的话,如果其中一台机器挂了,就会导致部分数据丢失,所以集群一般是多套主从结合使用
配置文件内容为如下:其他的配置文件修改一下端口以及log文件、日志文件即可。其中中间部分cluster代表集群设置
配置文件
mkdir /usr/local/redis/etc/cluster
redis-7000.config
daemonize yes
port 7000
logfile 7000.log
dir ./redis/data/
#bind 192.168.217.130 127.0.0.1
pidfile /redis/data/redis_6379.pid
cluster-enabled yes
cluster-config-file nodes_7000.conf
cluster-node-timeout 15000
appendonly yes
appendfilename aof-7000.aof
appendfsync everysec
no-appendfsync-on-rewrite yes
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
redis-7001.config
daemonize yes
port 7001
logfile 7001.log
dir ./redis/data/
#bind 192.168.217.130 127.0.0.1
pidfile /redis/data/redis_6379.pid
cluster-enabled yes
cluster-config-file nodes_7001.conf
cluster-node-timeout 15000
appendonly yes
appendfilename aof-7001.aof
appendfsync everysec
no-appendfsync-on-rewrite yes
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
启动
cd /usr/local/redis/etc/cluster/
./bin/redis-server cluster-6379.conf
./bin/redis-server cluster-6380.conf
./bin/redis-server cluster-6381.conf
./bin/redis-server cluster-6382.conf
./bin/redis-server cluster-6383.conf
./src/redis-server cluster-6384.conf
槽位的分配和主从关系的设定有两种方式进行,
127.0.0.1:6379>cluster meet 127.0.0.1 6380
127.0.0.1:6379>cluster meet 127.0.0.1 6381
127.0.0.1:6379>cluster meet 127.0.0.1 6382
127.0.0.1:6379>cluster meet 127.0.0.1 6383
127.0.0.1:6379>cluster meet 127.0.0.1 6384 #连接到其他节点
cluster nodes #查看集群状态
分别连接到 节点 分配hash槽
./bin/redis-cli -p 6379
#连接到各节点服务器 分配hash槽
#因为还没有将16384个槽分配到集群节点中。虚拟槽的分配可以使用redis-cli分别连接到6379,6380和6381端口的节点中,然后分别执行如下命令:
127.0.0.1:6379>cluster addslots {0...5461}
127.0.0.1:6380>cluster addslots {5462...10922}
127.0.0.1:6381>cluster addslots {10923...16383}
./bin/redis-cli --cluster create 192.168.50.130:7000 192.168.50.130:7001 192.168.50.133:7002 192.168.50.133:7003 --cluster-replicas 1
#登录redis客户端 ./src/redis-cli -c -p 7000, -c 参数代表连接到集群中
提高高可用性
解决这个问题我们就需要有一种方案让redis宕机后可以自动进行故障转移,还好redis给我们提供一种高可用解决方案 Redis-Sentinel。Redis-sentinel本身也是一个独立运行的进程,它能监控多个master-slave集群,发现master宕机后能进行自动切换。Sentinel可以监视任意多个主服务器
以及主服务器属下的从服务器,并在被监视的主服务器下线时,自动执行故障转移操作。
主机说明 主机IP 端口 sentinel端口
master 192.168.50.130 7000 26379
slave 192.168. 50.133 7001 26380
slave 192.168. 50.133 7001 26381
sentinel-26379.conf
port 26379
daemonize yes
logfile "sentinel26379.log"
dir "/usr/local/redis/data/"
sentinel monitor mymaster 192.168.50.130 7000 2
sentinel failover-timeout mymaster 15000
sentinel auth-pass mymaster 123456
#bind 192.168.50.130
sentinel-26380.conf
port 26380
daemonize yes
logfile "sentinel26380.log"
dir "/usr/local/redis/data/"
sentinel monitor mymaster 192.168.50.130 7000 2
sentinel failover-timeout mymaster 15000
sentinel auth-pass mymaster 123456
#bind 192.168.50.130
sentinel-26381.conf
port 26381
daemonize yes
logfile "sentinel26381.log"
dir "/usr/local/redis/data/"
sentinel monitor mymaster 192.168.50.130 7000 2
sentinel failover-timeout mymaster 15000
sentinel auth-pass mymaster 123456
#bind 192.168.50.130
cd /usr/local/redis/etc/sentinel
./usr/local/redis/bin/redis-sentinel sentinel-26379.conf
./usr/local/redis/bin/redis-sentinel sentinel-26380.conf
./usr/local/redis/bin/redis-sentinel sentinel-26381.conf
检测
由于sentinel节点也是一个redis实例,因而我们可以通过如下命令使用redis-cli连接sentinel节点:
./usr/local/redis/bin/redis-cli -p 26379
127.0.0.1:26379> info sentinel
参数 说明
sentinel monitor
告诉sentinel去监听地址为ip:port的一个master,这里的master-name可以自定义,quorum是一个数字,指明当有多少个sentinel认为一个master失效时,master才算真正失效
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所配置的规则来了。
mymaster:监控主数据的名称,自定义即可,可以使用大小写字母和“.-_”符号
192.168.1.9:监控的主数据库的IP
6379:监控的主数据库的端口
2:最低通过票数
先拿setnx来争抢锁,抢到之后,再用expire给锁加一个过期时间防止锁忘记了释放。
设置字符串锁
设置锁字符串健 若存在则设置失败
SETNX(“keycode::11”,1) == 1 //设置成功
SETNX(“keycode::11”,1) == 0 //设置失败
业务处理完释放锁
DEL(“keycode::11”,1)
设置锁字符串健的失效时间
PEXPIRE(“keycode::11”,10000)
设置全局锁
Boolean getlock =”ok”.equals(jedis.set(“keycode::11”,”1”,”NX”,“EX”,10))
释放全局锁
jdedis.del(“keycode::11”)
//设置时是否 被占用
Boolean b = this.getRedisTemplate().opsForValue().setIfAbsent(key, value);
//释放
this.getRedisTemplate().delete(key)
public void del(String key, String[] fields) {
RedisSerializer keySerializer = getRedisTemplate().getKeySerializer();
RedisSerializer hashKeySerializer = getRedisTemplate().getHashKeySerializer();
getRedisTemplate().executePipelined((RedisCallback<Object>) connection -> {
byte[][] fieldByte = new byte[fields.length][];
for (int i =0; i< fields.length; i++) {
fieldByte[i] = hashKeySerializer.serialize(fields[i]);
}
connection.hDel(keySerializer.serialize(key), fieldByte);
return null;
});
}
// 批量 key 失效
public int batchExpire(List<ObjectExpire> expires) {
return getRedisTemplate().execute(new RedisCallback<Integer>() {
@Override
public Integer doInRedis(RedisConnection redisConnection) throws DataAccessException {
RedisSerializer<String> serializer = new StringRedisSerializer();
redisConnection.openPipeline();
for (ObjectExpire expire : expires) {
redisConnection.expire(serializer.serialize(expire.getKey()), expire.getExpire());
}
List<Object> list = redisConnection.closePipeline();
return 1;
}
});
}
//hash 删除
public void del(String key, String[] fields) {
RedisSerializer keySerializer = getRedisTemplate().getKeySerializer();
RedisSerializer hashKeySerializer = getRedisTemplate().getHashKeySerializer();
getRedisTemplate().executePipelined((RedisCallback<Object>) connection -> {
byte[][] fieldByte = new byte[fields.length][];
for (int i =0; i< fields.length; i++) {
fieldByte[i] = hashKeySerializer.serialize(fields[i]);
}
connection.hDel(keySerializer.serialize(key), fieldByte);
return null;
});
}
案例 贴的访问数量
INCR key
GET key
EXPIRE key seconds(不止字符串健)设置一个key的过期时间 (秒)
PEXPIRE key millseconds 设置一个key 的过期时间 (毫秒)
针对数字操作
INCRBY key increment 对数字key进行{increment} 的增加
DECRBY key increment 对数字key进行{increment} 的减少
INCR key 对数字key进行+1
DECR key 对数字key进行 -1
Redis事务相关的命令
MULTI、EXEC、DISCARD、WATCH
包含5个命令 MULTI、EXEC、DISCARD、WATCH、UNWATCH。
DISCARD 取消事务,放弃执行事务块内的所有命令。
EXEC 执行所有事务块内的命令。
MULTI 标记一个事务块的开始。
UNWATCH 取消 WATCH 命令对所有 key 的监视。
WATCH key [key ...] 监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。
Redis 监控 锁机制 首先设置乐观锁
WATCH key [key ...]
开启事务
MULTI
命令入列
....
执行事务
EXEC
eq:
redisTemplate.multi();
redisTemplate.opsForValue().increment("xxx",1);
redisTemplate.opsForValue().increment("ttt",1);
redisTemplate.exec();
Hash结构数据 散列表
hset key field value
hget key field
#设置获取多个字典元素键值
hmset key field value [field value...]
hmget key field [field...]
#获取字典中所有键值
hgetall key
#判断是否存在,不存在赋值
hexists key field value
#增加数字的值
hincrby key key field increment
List结构数据 有序可重复
lpush key value [value...]
rpush key value [value...]
#从列表两端弹出元素:
lpop key
rpop key
Set结构数据 无须不可重复
#增加删除元素(如果键不存在直接创建):
SAAD key member [member ...]
SREM key member [member ...]
Zset结构数据 有序不可重复的 set集合 设置分数 进行排序
ZADD key score member [score member]
#获取元素分数:
ZSCORE key [member]
## Redis key的过期时间和永久有效
EXPIRE和PERSIST命令 ttl 命令
PERSIST 设置永久 ttl 查看过期时间 永久为 -1
EXPIRE <KEY> <TTL> : 将键的生存时间设为 ttl 秒
PEXPIRE <KEY> <TTL> :将键的生存时间设为 ttl 毫秒
EXPIREAT <KEY> <timestamp> :将键的过期时间设为 timestamp 所指定的秒数时间戳
PEXPIREAT <KEY> <timestamp>: 将键的过期时间设为 timestamp 所指定的毫秒数时间戳.
定时删除:在设置键的过期时间的同时,创建一个定时器timer). 让定时器在键的过期时间来临时,立即执行对键的删除操作。
惰性删除:放任键过期不管,但是每次从键空间中获取键时,都检查取得的键是否过期
定期删除:每隔一段时间程序就对数据库进行一次检查,删除里面的过期键
需要人为 操作
volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
no-enviction(驱逐):禁止驱逐数据
注意这里的6种机制,volatile和allkeys规定了是对已设置过期时间的数据集淘汰数据还是从全部数据集淘汰数据,后面的lru、ttl以及random是三种不同的淘汰策略,再加上一种no-enviction永不回收的策略。
使用策略规则:
1、如果数据呈现幂律分布,也就是一部分数据访问频率高,一部分数据访问频率低,则使用allkeys-lru
2、如果数据呈现平等分布,也就是所有的数据访问频率都相同,则使用allkeys-random
一个字符串类型的值能存储最大容量是多少 512M
maxmemory 占用物理内存的比列 默认时0 不限制 只有达到阈值才会触发 数据删除策略
maxmemory-samples 随机获取部分数量的数据
maxmemory-policy allkeys-lru
(1)客户端执行一条新命令,导致数据库需要增加数据(比如set key value)
(2)Redis会检查内存使用,如果内存使用超过maxmemory,就会按照置换策略删除一些key
(3)新的命令执行成功
sscan 支持三个参数cursor=0, match=None, count=None
#先添加两个数字
127.0.0.1:6379> sadd myset1 998, 123
# 用正则匹配大于100的值
127.0.0.1:6379> sscan myset1 0 match "[1-9][0-9][0-9]*"
1) "0"
2) 1) "123"
2) "998,"
#指定count 为1,返回一个
127.0.0.1:6379> sscan myset1 0 match "[1-9][0-9][0-9]*" count 1
1) "2"
2) 1) "123"
127.0.0.1:6379> sadd myset1 189
(integer) 1
127.0.0.1:6379> sadd myset1 89
127.0.0.1:6379> sscan myset1 0 match "[1-9][0-9][1-9]*"
1) "3"
2) 1) "123"
2) "998,"
3) "189"
#指定cursor
127.0.0.1:6379> sscan myset1 1 match "[1-9][0-9][1-9]*"
1) "0"
2) 1) "189"
# 匹配更复杂的
127.0.0.1:6379> sscan myset1 0 match "{'i': '[1-9][0-9][0-9]*'}"
1) "3"
2) 1) "{'i': '990', 'i2': '991'}"
yum install psmisc
#!/bin/sh
# chkconfig: 2345 18 85
# redis
#$1为从服务器端传入的第一个参数
. /etc/rc.d/init.d/functions
PIDFILE=/var/run/redis.pid
start(){
/usr/local/redis/bin/redis-server /usr/local/redis/etc/master-slaver/redis-master-7000.config &
/usr/local/redis/bin/redis-server /usr/local/redis/etc/master-slaver/redis-slaver-7001.config &
/usr/local/redis/bin/redis-sentinel /usr/local/redis/etc/sentinel/sentinel-26379.conf &
/usr/local/redis/bin/redis-sentinel /usr/local/redis/etc/sentinel/sentinel-26380.conf &
/usr/local/redis/bin/redis-sentinel /usr/local/redis/etc/sentinel/sentinel-26381.conf
}
stop(){
/usr/local/redis/bin/redis-cli -p 7000 -h 127.0.0.1 -a 123456 shutdown > /dev/null 2>&1
/usr/local/redis/bin/redis-cli -p 7001 -h 127.0.0.1 -a 123456 shutdown > /dev/null 2>&1
/usr/local/redis/bin/redis-cli -p 26379 -h 127.0.0.1 -a 123456 shutdown > /dev/null 2>&1
/usr/local/redis/bin/redis-cli -p 26380 -h 127.0.0.1 -a 123456 shutdown > /dev/null 2>&1
/usr/local/redis/bin/redis-cli -p 26381 -h 127.0.0.1 -a 123456 shutdown > /dev/null 2>&1
}
case $1 in
start)
if [ -f $PIDFILE ]
then
echo "$PIDFILE exists, process is already running or crashed"
else
start
fi
;;
stop)
if [ ! -f $PIDFILE ]
then
echo "$PIDFILE does not exist, process is not running"
else
stop
fi
;;
restart)
stop
sleep 3
start
;;
status)
status=`pstree | grep redis |wc -l`
if [ $status == 0 ]; then
echo 'redis close'
else
echo -e "\033[31;47m redis running \033[0m"
fi
;;
redis-cli)
#启动redis客户端
/usr/local/redis/bin/redis-cli -p 7000 -h 127.0.0.1 -a 123456
;;
*)
echo "Please input [status]、[start]、[stop]、[redis-cli]"
echo "For example:service redis start"
;;
esac
设置开机启动
等级0表示:表示关机
等级1表示:单用户模式
等级2表示:无网络连接的多用户命令行模式
等级3表示:有网络连接的多用户命令行模式
等级4表示:不可用
等级5表示:带图形界面的多用户模式
等级6表示:重新启动
10是启动优先级,90是停止优先级,优先级范围是0-100,数字越大,优先级越低。
4、其它相关命令
添加开机自启动服务:chkconfig --add redis
查看开机自启动服务:chkconfig --list redis
#/etc/init.d 存放 redis.sh 脚本 ,脚本头设置 chkconfig 开机 和 关机的 优先级,开机执行start 关机执行 stop
##/var/lock/subsys/ 目录下建立 同名空文件 关机是用
touch /var/lock/subsys/redis
chmod +x /etc/init.d/redis
chkconfig --add redis
chkconfig --list redis
#支持服务操作
service redis start
service redis stop
依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
<dependency>
<groupId>com.fasterxml.jackson.coregroupId>
<artifactId>jackson-databindartifactId>
<version>2.6.7version>
dependency>
<dependency>
<groupId>org.apache.commonsgroupId>
<artifactId>commons-pool2artifactId>
<version>2.5.0version>
dependency>
配置哨兵 主从
spring:
redis:
open: true # 是否开启redis缓存 true开启 false关闭
database: 0
host: 127.0.0.1
port: 6379
password: # 密码(默认为空)
timeout: 6000ms # 连接超时时长(毫秒)
lettuce:
pool:
max-active: 1000 # 连接池最大连接数(使用负值表示没有限制)
max-wait: -1ms # 连接池最大阻塞等待时间(使用负值表示没有限制)
max-idle: 10 # 连接池中的最大空闲连接
min-idle: 5 # 连接池中的最小空闲连接
sentinel:
master: mymaster
nodes: 192.168.50.130:26379,192.168.50.130:26380,192.168.50.130:26381
配置类
public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
{
@SuppressWarnings("unused")
private ObjectMapper objectMapper = new ObjectMapper();
public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
private Class<T> clazz;
static
{
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
}
public FastJson2JsonRedisSerializer(Class<T> clazz)
{
super();
this.clazz = clazz;
}
@Override
public byte[] serialize(T t) throws SerializationException
{
if (t == null)
{
return new byte[0];
}
return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
}
@Override
public T deserialize(byte[] bytes) throws SerializationException
{
if (bytes == null || bytes.length <= 0)
{
return null;
}
String str = new String(bytes, DEFAULT_CHARSET);
return JSON.parseObject(str, clazz);
}
public void setObjectMapper(ObjectMapper objectMapper)
{
Assert.notNull(objectMapper, "'objectMapper' must not be null");
this.objectMapper = objectMapper;
}
protected JavaType getJavaType(Class<?> clazz)
{
return TypeFactory.defaultInstance().constructType(clazz);
}
}
@Configuration
@EnableCaching //加上这个注解是的支持缓存注解
public class RedisConfig extends CachingConfigurerSupport
{
@Bean
@SuppressWarnings(value = { "unchecked", "rawtypes", "deprecation" })
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory)
{
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
//设置开启事务
template.setEnableTransactionSupport(true);
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
serializer.setObjectMapper(mapper);
template.setValueSerializer(serializer);
// 使用StringRedisSerializer来序列化和反序列化redis的key值
template.setKeySerializer(new StringRedisSerializer());
template.afterPropertiesSet();
return template;
}
}
// 服务 Api
private RedisTemplate redisTemplate
redisTemplate.opsForValue()
redisTemplate.expire(key, timeout, unit)
ValueOperations<String, T> operation = redisTemplate.opsForValue();
operation.get(key);
redisTemplate.delete(collection)
redisTemplate.opsForList()
redisTemplate.boundSetOps(key
redisTemplate.opsForSet()
redisTemplate.opsForHash().put(key, hKey, value)
//锁 和 配置失效 释放锁
boolean redisTemplate.opsForValue().setIfAbsent(key, value);
redisTemplate.expire(key, timeout, unit)
redisTemplate.execute(new SessionCallback<Object>() {
@Override
@SuppressWarnings({ "unchecked", "rawtypes" })
public Object execute(RedisOperations operations) throws DataAccessException {
List<Object> result = null;
do {
int count = 0;
operations.watch(key); // watch某个key,当该key被其它客户端改变时,则会中断当前的操作
String value = (String) operations.opsForValue().get(key);
if (!StringUtils.isEmpty(value)) {
count = Integer.parseInt(value);
}
count = count + 1;
operations.multi(); //开始事务
operations.opsForValue().set(key, String.valueOf(count));
try {
result = operations.exec(); //提交事务
} catch (Exception e) { //如果key被改变,提交事务时这里会报异常
}
} while (result == null); //如果失败则重试
return null;
}
});
//executePipelined 方法会执行 RedisCallback or SessionCallback 的回调方法以返回结果.
List<Object> results = stringRedisTemplate.executePipelined(new RedisCallback<Object>() {
public Object doInRedis(RedisConnection connection) throws DataAccessException {
StringRedisConnection stringRedisConn = (StringRedisConnection)connection;
for(int i=0; i< batchSize; i++) {
stringRedisConn.rPop("myqueue");
}
return null;
}
});
粘性会话
如果某台服务器宕机,那么会话信息就没有了。
复制会话
每台机器都复制会话,如果量太大的话,不现实
集中会话
使用 mongo 、redis 等统一保持会话
当请求进来的时候,SessionRepositoryFilter 会先拦截到请求,将 request 和 response 对象转换成 SessionRepositoryRequestWrapper 和 SessionRepositoryResponseWrapper 。后续当第一次调用 request 的getSession方法时,会调用到 SessionRepositoryRequestWrapper 的getSession方法。这个方法是被从写过的,逻辑是先从 request 的属性中查找,如果找不到;再查找一个key值是"SESSION"的 Cookie,通过这个 Cookie 拿到 SessionId 去 Redis 中查找,如果查不到,就直接创建一个RedisSession 对象,同步到 Redis 中。
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
springBoot 配置
@EnableRedisHttpSession-存放在缓存redis
@EnableMongoHttpSession-存放在Nosql的MongoDB
@EnableJdbcHttpSession-存放数据库
//启动
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 86400*30)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session</artifactId>
</dependency>
//业务
request.getSession().setAttribute("url", request.getRequestURL());
request.getSession().getId()
request.getSession().getAttribute("url")
spring:
session:
storeType: redis
MongoDB 官方
下载
mongodb community server
tar -zxvf mongodb-linux-x86_64-rhel70-4.4.6.tgz
mv /home/admin/mongodb/mongodb-linux-x86_64-rhel70-4.4.6 mongodb4
export PATH=/usr/local/mongodb4/bin:$PATH
source /etc/profile
##默认情况下 MongoDB 启动后会初始化以下两个目录:
##数据存储目录:/var/lib/mongodb
##日志文件目录:/var/log/mongodb
##我们在启动前可以先创建这两个目录并设置当前用户有读写权限:
sudo mkdir -p /var/lib/mongo
sudo mkdir -p /var/log/mongodb
##暂时废弃
sudo chown `admin` /var/lib/mongo # 设置权限
sudo chown `admin` /var/log/mongodb # 设置权限
##启动 方式1
mongod --dbpath /var/lib/mongo --logpath /var/log/mongodb/mongod.log --fork
#停止 方式1
mongod --dbpath /var/lib/mongo --logpath /var/log/mongodb/mongod.log --shutdown
##配置文件启动
##创建 mongodb.conf 配置文件
dbpath = /usr/local/mongodb4/data/db #数据文件存放目录
logpath = /usr/local/mongodb4/logs/mongodb.log #日志文件存放目录
bind_ip = 0.0.0.0 #远程访问
port = 27017 #端口
fork = true #以守护程序的方式启用,即在后台运行
#nohttpinterface = true
#启动 方式2
mongod --config /usr/local/mongodb4/etc/mongodb.conf
#启动 方式2
mongod --shutdown --config /usr/local/mongodb4/etc/mongodb.conf
## 客户端
cd /usr/local/mongodb4/bin
./mongo
##由于它是一个JavaScript shell,您可以运行一些简单的算术运算:
## 客户端停止服务
use admin ## 切换某db
db.shutdownServer()
在 /etc/init.d/ 文件夹编写脚本 mongodb
#!/bin/sh
#chkconfig: 2345 80 90
#description: mongodb shell
if test -f /sys/kernel/mm/transparent_hugepage/enabled; then
echo never > /sys/kernel/mm/transparent_hugepage/enabled
fi
if test -f /sys/kernel/mm/transparent_hugepage/defrag; then
echo never > /sys/kernel/mm/transparent_hugepage/defrag
fi
start() {
/usr/local/mongodb4/bin/mongod --config /usr/local/mongodb4/etc/mongodb.conf
}
stop() {
/usr/local/mongodb4/bin/mongod --config /usr/local/mongodb4/etc/mongodb.conf --shutdown
}
case $1 in
start)
start
RETVAL=$?
;;
stop)
stop
;;
restart)
stop
start
RETVAL=$?
;;
*)
echo $"Usage: -bash {start|stop|restart}"
exit $RETVAL
esac
在 /var/lock/subsys/ 下间 mongodb 同名空文件夹
touch /var/lock/subsys/mongodb
chmod +x /etc/init.d/mongodb
# 添加服务
chkconfig --add mongodb
# 查看服务
chkconfig --list
# 删除服务
chkconfig --del mongodb
mongodb://127.0.0.1:27017/?compressors=disabled&gssapiServiceName=mongodb
spring:
data:
mongodb:
uri: mongodb://192.168.50.130:27017/testDb
##如果要配置多个数据库,则中间用","分割
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-mongodbartifactId>
dependency>
MongoTestDao
@Component
public class MongoTestDao {
@Autowired
private MongoTemplate mongoTemplate;
/**
* 创建对象
*/
public void saveTest(TUser test) {
mongoTemplate.save(test);
}
/**
* 根据用户名查询对象
* @return
*/
public TUser findTestByName(String name) {
Query query=new Query(Criteria.where("name").is(name));
TUser mgt = mongoTemplate.findOne(query , TUser.class);
return mgt;
}
/**
* 更新对象
*/
public void updateTest(TUser test) {
Query query=new Query(Criteria.where("id").is(test.getId()));
Update update= new Update().set("age", test.getAge()).set("name", test.getName());
//更新查询返回结果集的第一条
mongoTemplate.updateFirst(query,update,TUser.class);
//更新查询返回结果集的所有
// mongoTemplate.updateMulti(query,update,TestEntity.class);
}
/**
* 删除对象
* @param id
*/
public void deleteTestById(Integer id) {
Query query=new Query(Criteria.where("id").is(id));
mongoTemplate.remove(query,TUser.class);
}
//大于gt 小于lt,大于等于gte,小于等于lte , 非空ne
// Criteria.where("name").is("").and("age").gt(12)
public List<TUser> queryLikName(String name){
Pattern pattern = Pattern.compile("^.*"+ name +".*$", Pattern.CASE_INSENSITIVE);
return mongoTemplate.find(Query.query(Criteria.where("name").regex(pattern))
.with(Sort.by(Sort.Direction.ASC, "age")), TUser.class);
}
//分页查询
public Map<String, Object> findByPage(String name, int page , int size) {
Map<String, Object> result = new HashMap<>();
Pageable pageable = PageRequest.of(page, size, Sort.Direction.DESC, name);
//正则
Pattern pattern = Pattern.compile("^.*"+name+".*$", Pattern.CASE_INSENSITIVE);
//创建查询对象
Query query = new Query();
//设置模糊查询
query.addCriteria(Criteria.where("name").regex(pattern));
//排序
query.with(Sort.by(Sort.Direction.ASC, "age"));
//设置分页
query.with(pageable);
//查询当前页数据集合
List<TUser> userList = mongoTemplate.find(query, TUser.class);
//查询总记录数
int count=(int) mongoTemplate.count(query,TUser.class);
//创建分页实体对象
result.put("list",userList);
result.put("page",pageable.getPageNumber());
result.put("size",pageable.getPageSize());
result.put("total",count);
return result;
}
}
基于 MongoRepository spring-data-mongo 进行基础的 增删改查操作
基于 GridFsTemplate 进行的 进行存读取文件
@Autowired
GridFsTemplate gridFsTemplate;
//save
gridFsTemplate.store(file.getInputStream(), file.getFilename(), "yml");
gridFsTemplate().store(file.getInputStream(), fileName, file.getContentType(), metaData);
//get
GridFsResource[] txtFiles = gridFsTemplate.getResources("*");
for (GridFsResource txtFile : txtFiles) {
System.out.println(txtFile.getFilename());
}
//删除
Query query = Query.query(Criteria.where("_id").is("objectId"));
gridFsTemplate.delete(query);
//下载 获取 文件流
GridFsResource gridFsResource = gridFsTemplate.getResource(gridFSFile);
InputStream inputStream = gridFsResource.getInputStream();
@Configuration
public class MongodbCfg {
@Bean(name = "excelGridFs")
@Primary
GridFsTemplate gridFsTemplate(MongoDatabaseFactory factory, MongoTemplate mongoTemplate) {
return new GridFsTemplate(factory, mongoTemplate.getConverter(),"excel");
}
@Bean(name = "imgGridFs")
GridFsTemplate gridFsTemplateFs2(MongoDatabaseFactory factory, MongoTemplate mongoTemplate) {
return new GridFsTemplate(factory, mongoTemplate.getConverter(),"img");
}
}
public abstract class BaseFileService {
protected abstract GridFsTemplate getGridFsTemp();// 针对性的 传入 GridFsTemplate
/**
*文件流:inputStream
* fileName:文件的唯一标识id
* file.getContentType():内容类型
* metaData:元数据
* @param file 文件
* @param fileName
* @return
*/
public String saveFile(MultipartFile file, String fileName) {
DBObject metaData = new BasicDBObject();
metaData.put("createdDate", new Date());
InputStream inputStream = null;
try {
inputStream = file.getInputStream();
getGridFsTemp().store(inputStream, fileName, file.getContentType(), metaData);
} catch (IOException e) {
}
return fileName;
}
/**
* 根据文件名称检索对应的文件
* @param fileId
* @return
* @throws IOException
*/
public GridFSFile getFileById(String fileId) {
GridFSFile gridFSFile =
getGridFsTemp().findOne(Query.query(Criteria.where("_id").is(fileId)));
if (gridFSFile == null) {
return null;
}
return gridFSFile;
}
/**
* 根据文件名称检索对应的文件
* @param fileName
* @return
* @throws IOException
*/
public GridFSFile getFileByName(String fileName) throws IOException {
GridFSFile gridFSFile = getGridFsTemp()
.findOne(new Query(Criteria.where("filename").is(fileName)));
if (gridFSFile == null) {
return null;
}
return gridFSFile;
}
/**
*
* @param out
* @param gridFSFile
*/
public void writTo(OutputStream out,GridFSFile gridFSFile){
//创建gridFsResource,用于获取流对象
GridFsResource gridFsResource = getGridFsTemp().getResource(gridFSFile);
//获取流中的数据
InputStream inputStream = null;
try {
inputStream = gridFsResource.getInputStream();
int len = 0;
byte[] b = new byte[1024];
while ((len = inputStream.read(b)) != -1) {
out.write(b, 0, len);
}
out.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (inputStream != null) {
inputStream.close();
}
if (out != null) {
out.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 根据文件的唯一标识删除对应的文件
* @param fileName
*/
public void removeFile(String fileName) {
getGridFsTemp().delete(new Query().addCriteria(Criteria.where("filename").is(fileName)));
}
/**
* 字节流 文件名称
* @param body
* @param filename
* @param appends
*/
public String saveFile(byte[] body, String filename, Map<String, Serializable> appends) {
Document metadata = new Document();
if (appends != null && !appends.isEmpty()) {
for (Map.Entry<String, Serializable> entry : appends.entrySet()) {
metadata.put((String)entry.getKey(), (Object)entry.getValue());
}
}
ObjectId store = getGridFsTemp().store(new ByteArrayInputStream(body), filename,metadata);
return store.toString();
}
}
mongo
## 创建库
use testDb
## 查看当前db
db
## 插入数据
db.runootb.insert({"name":"菜鸟教程"})
## 查看所有的db
show dbs
##删除当前库
db.dropDatabase()
## 查看当前库的 集合
show tables
## 删除当前库的 runoob集合
db.runoob.drop()
对象存储: 分段读取 分段存储, 基于 bucket 桶的概念,实现对象数据的分布式存储,最大5G 最小 5MB, 断点续传需要自己实现。
Minio在分布式和单机模式下,所有读写操作都严格遵守read-after-write一致性模型。
minio
wget https://dl.min.io/server/minio/release/linux-amd64/minio
chmod +x minio
可以启动前配置密码 默认是 minioadmin
export MINIO_ROOT_USER=minio
export MINIO_ROOT_PASSWORD=minio13
nohup ./minio server /usr/local/minio/data/ &
wget https://dl.min.io/client/mc/release/linux-amd64/mc
chmod +x mc
./mc --help
mc alias set --api --path
mc alias set minio http://127.0.0.1 minioadmin minioadmin
./mc tree --files
./mc du /usr/local/minio/data/
minio 的 putObject 方法 传 上传对象大小 就,走的分段上传 最大 5g
JAVA SDK
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>7.0.2</version>
</dependency>
列出存储桶中被部分上传的对象。
Iterable
删除一个未完整上传的对象。
removeIncompleteUpload(String bucketName, String objectName)
presignedPutObject(String bucketName, String objectName, Integer expires)
生成一个给HTTP PUT请求用的presigned URL。浏览器/移动端的客户端可以用这个URL进行上传,即使其所在的存储桶是私有的。这个presigned URL可以设置一个失效时间,默认值是7天。
presignedGetObject(String bucketName, String objectName, Integer expires)
生成一个给HTTP GET请求用的presigned URL。浏览器/移动端的客户端可以用这个URL进行下载,即使其所在的存储桶是私有的。这个presigned URL可以设置一个失效时间,默认值是7天。不得大于7天
minio 实现 断点续传,
1 业务上 将上传数据进行拆分, 上传成多个 objct对象, 记录上传状态。 如果失败的就可以重写上传那一部分的
2. 然后 listObject 列出拆分好的文件,然后 调用 composeObject 进行合并成最终的 object