目录
Redis基础知识
数据一般都在内存中,虽然Redis支持持久化,但是主要用作备份恢复,一般是作为缓存数据库辅助持久化的数据库
Redis数据类型除了支持简单的key-value模式,还支持多种数据结构的存储,比如list,set,hash,zset等
安装官网http://redis.io。安装在linux系统中,要配置C语言环境
Redis使用的是单线程+多路IO复用,默认的端口号是6379。
总结:什么是redis?redis是nosql数据库,用作恢复备份,缓存数据,辅助持久化。
配置
修改配置文件 : vi/etc/redis.conf
NETWORK中
注释 bind 127.0.0.1
protected-mode no 开启远程访问
deamonize yes redis的后台启动
常用操作指令
#解压redisLinux压缩包
tar zxvf redis-5.0.5.tar.gz
#编译
cd redis-5.0.5
make
#可能会遇到没有安装C语言环境的错误
yum -y install gcc-c++ automake antoconf
make
#如果遇到致命错误:jemalloc/jemalloc.h:没有那个文件或者目录,需要指定一下
make MALLOC=libc
#安装到指定路径/usr/local/redis下
make /usr/local/redis install
#进入路径
cd /usr/local/redis
ll
cd bin/
#前台启动
./redis-server
#后台启动将redis-5.0.5路径下的redis.conf拷贝到/usr/local/redis/bin路径下
cd redis-5.0.5
cp redis.conf /usr/local/redis/bin
cd /usr/local/redis/bin
#修改配置
vim redis.conf
#启动
./redis-serer redis.conf
#查看服务进程
ps -ef|grep redis
#连接./redis -cli -p 指定IP地址,./redis -cli -h 指定端口号,./redis -cli -a 指定密码
./redis -cli
#输入ping返回pong表示连接成功
#停止服务
shutdown
#操作String类型,set key value
set name zhangsan
#获取value get key
get zhangsan
#操作多个值
mset age 18 addr shanghai
mget age addr
#操作Hash类型键值对类型,hest redisKey HashKey HashValue
hset user name zhangsan
hget user name
#操作多个值
hmset user age 18 addr beijing
gmset user name age addr
#全部获取
hgetall user
#删除
hdel user age
#del可以删除所有类型
del name
del user
#清空数据库
flushall
#操作list类型,从左边添加,结果 wangwu zhangsan
lpush students zhangsan wangwu
#从右边添加
rpush students lisi zhaosi,结果 lisi zhaosi
#获取后面数字是索引下标
lrange students 0 3
#加起来结果 wangwu zhangsan lisi zhaosi
#查看长度
llen students
#删除,由于list中可以存相同的值所以删除时可以指定数量
lrem students 1 lisi
#操作set类型,添加
sadd letters aaa bbb ccc ddd eee
#查看
smember letters
#长度
scard letters
#删除
srem letters bbb
#操做zset类型,可以根据分数排序
zadd score 1 zhangsan 4 zhangsi 3 wangwu 2 zhousan
#查看
zrange score 0 3
#结果是 zhangsan zhousan wangwu zhangsi
#长度
zcard score
#删除
zrem score zhangsan
#设置失效时间10s
set code test ex 10
#查看还剩多少时间,-2表示失效,-1表示永久不失效
ttl test
#nx表示key不存在才能成功,xx表示之前存在才能设置成功
set code 12 xx
什么是Redis的发布和订阅?
Redis发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)
接收消息。Redis客户端可以订阅任意数量的频道。
测试导包‘redis.clients’
//创建Jedis对象
Jedis jedis = new Jedis("192.168.44.168",6379);
//测试
String value = jedis.ping();
System.Out.Println(value);
可以关闭虚拟机的防火墙
查看防火墙:systemctl status firewalld
关闭:systemctl stop firewalld
这样做安全性不高,我们可以只开放6379端口
bind 127.0.0.1 #指定只有本机才能访问redis服务器
bind 0.0.0.0 #所有的
bind 192.168.1.253 #指定
案例:
1.输入手机号点击发送随机生成6位数字码,2分钟内有效,
2.输入验证码点击验证,返回成功或失败
3.每个手机号每天只能输入3次
思路:
1.生成随机6位数字验证码,(Random)
2.两分钟内有效 (把验证码放到redis里面,设置过期时间120s)
3.判断验证码是否一致 (从redis获取验证码和输入的验证码进行比较)
4.每个手机每天只能发送3次验证码(用incr每天发送之后+1,当大于2时提交就不能发送,使用
jedis.setex(key,24*60*60,"1")24小时有效)
Redis和Spring boot 整合
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--spring2.x集成redis所需要common-pool2-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.6.0</version>
</dependency>
2.application.properties配置配置redis配置
#Redis服务器地址
Spring.redis.host = 192.168.140.136
#Redis服务器连接端口
Spring.redis.port = 6379
#Redis数据库索引(默认为0)
Spring.redis.database = 0
#连接超时时间(毫秒)
Spring.redis.timeout = 1800000
#连接池最大连接数(使用负值表示没有限制)
Spring.redis.lettuce.pool.max-active = 20
#最大阻塞等待时间(负数表示没限制)
Spring.redis.lettuce.pool.max-wait = -1
#连接池中的最大空闲连接
Spring.redis.lettuce.pool.max-idle = 5
#连接池中的最小空闲连接
Spring.redis.lettuce.pool.min-idle = 0
3.添加redis配置类(固定的)
4.在启动类上添加注解@EnableRedisHttpSession解决分布式session问题
@SpringBootApplication
@EnableRedisHttpSession
@MapperScan("com.xxxx.seckill.mapper")
public class SeckillApplication {
public static void main(String[] args) {
SpringApplication.run(SeckillApplication.class, args);
}
}
Redis事务
Redis事务是一个单独的隔离操作,事务中的所有命令都会序列化,按顺序地执行,事务在执行的过程中不会被其他客户端发送的命令请求所打断。
Redis事务的主要作用就是串联多个命令防止别的命令插队。
Multi、Exec、discard
从输入Multi命令开始,进入队列中,不会立即执行,输入Exec后执行。组队的过程中可以通过discard来放弃组队(组队中有错误都不执行,组队中没有错误,执行时谁有错误谁不执行)
事务的冲突问题,redis中锁来解决
Redis事务三特性
1.单独的隔离操作
事务中的所有命令都会序列化,按顺序地执行,事务在执行的过程中不会被其他客户端发送的命令请求打断
2.没有隔离级别的概念
队列中的命令没有提交前都不会实际被执行,因为事务提交前任何指令都不会被实际执行
3.不保证原子性
事务中如果有一条命令执行失败,其后的命令仍然执行,没有回滚
jedis.sismember(userkey,uid);//判断set集合中有无uid
ab -n 1000 -c 100 -p ~/postfile -T
-n:请求数量。-c:并发数量。-p 提交文件。 -T提交类型
超卖和超时间的问题:
通过连接池解决超时问题。通过事务解决超卖问题
jedis.watch(kckey);//监视
Transaction multi = jedis.multi();
multi.decr(kckey); //组队操作,库存-1
multi.sadd(userkey,uid);//把成功秒杀的用户加到清单里面
List<Object> result = multi.exec();//执行
乐观锁造成库存遗留问题,通过lua脚本解决
Redis持久化方式
RDB(Redis DataBase)
采用写时复制技术,数据通过一个临时区域再储存到dump.rdb。保证数据的一致性和完整性
优势:
1.适合大规模的数据恢复
2.对数据完整性和一致性要求不高更适合使用
3.节省磁盘空间
4.恢复速度快
劣势:
1.Fork的时候,内存中的数据被克隆了一份,大致2倍的膨胀性需要考虑
2.虽然Redis再fork时使用了写时复制技术,如果数据庞大时还是比较销毁性能
3.有备份周期在一定间隔时间做一次备份,所以如果Redis意外down掉的话就会丢失最后一次快照后所有修改
AOF(Append Only File)
追加在文档
以日志的形式来记录每个写操作,将Redis执行过的所有写指令记录下(读操作不记录),只许追加文件但不可以改写文件
AOF和RDB同时开启,系统默认取AOF的数据
修复文件指令
redis-check-aof --fix appendonly.aof
AOF持久化流程
1、客户端的请求写命令会被append到AOF缓冲区内
2、AOF缓冲区根据AOF持久化策略【always,everysec,no】将操作sync同步到磁盘的AOF文件中
3、AOF文件大小超过重写策略或手动重写时会对AOF文件rewrite重写,压缩AOF文件容量
4、Redis服务重启时,会重启load加载AOF文件中的写操作达到数据恢复的目的
优势
1、备份机制更稳健,丢失数据概率更低
2、可读的日志文本,通过操作AOF稳定可以处理错误操作
劣势
1、比起RDB占用更多的磁盘空间
2、恢复备份速度要慢
3、每次读写都同步的话,有一定的性能压力
4、存在个别Bug造成不能恢复
用哪个好?
官方推荐两个都启用,如果对数据不敏感,可以选单独使用RDB;不建议单独使用AOF,因为可能出现Bug,如果只是做纯内存可以不用
主从复制
能干什么?
1.读写分离,性能扩展,写的操作在主服务器中,读的操作在从服务器中
2.容灾快速恢复
步骤
1、创建/myredis文件夹
2、复制redis.conf配置文件到文件夹中
3、配置一主两从,创建三个配置文件:redis6379.conf、redis6380.conf、redis6381.conf
4、编辑配置文件
include/myredis/redis.conf
pidfile/var/run/redis-6379.pid
port 6379
dbfilename dump6379.rdb
5、启动三个redis服务(redis-server redis6379.conf)查看当前主机运行状况(
redis-cli -p -6379 连接 info replication 查看)在从机上执行slave of 主机ip 端口号
从服务器下面可以有从服务器
用slave of no one 将从机变成主机
哨兵模式
目的:当主服务机宕机了可以从下线的主服务的所有从服务里面挑选一个从服务,将其转成主服务
在自定义的myRedis目录下面创建sentinel.conf文件(名字绝不能错)
配置哨兵:
sentinel monitor mymaster 127.0.0.1 6379 1
mymaster:监控对象起的服务器名称
127.0.0.1:主机ip
6379:主机端口号
1:至少有一个哨兵同意迁移数量
从下线的主服务的所有从服务里面挑选一个从服务,将其转成主服务,选择条件依次为:
1.选择优先级靠前的
2.选择偏移量最大的
3.选择runid最小的从服务
Redis集群
解决的问题:
1.容量不够
2.并发写操作
3.主从模式,主机宕机导致IP地址改变应用程序
redis cluster 配置修改
cluster-enable yes 打开集群模式
cluster-config-file node-6379.conf 设定节点配置文件名
cluster-node-timeout 15000 设定节点失联时间超过该时间(ms)集群自动进行主从切换
(小技巧:%s/6379/6380 全部替换)
开启服务,进行合体,在(/opt/redis-6.2.1/src 下有无redis-trib.rb 新版本中有配置此环境)在路径下执行
redis-cli --cluster create --cluster -replisca 1 所以服务的ip:端口
-c 采用集群策略连接,设置数据会自动切换到相应的写主机
redis -clis -c-p 6379
通过cluster nodes 命令查看集群信息分配原则尽量保证每个主数据库运行在不同的ip地址上,每个从库和主库不在一个ip地址上
不在一个slot下的键值是不能使用mget,mset等多键操作;可以通过{ }来定义组的概念,从而使key中的{ }内容相同的键值对放在一个slot中去
当主机宕机后从机会变成主机,主机重启后会成为从机
如果某一段插槽的主从都挂掉,而redis.conf的配置cluster-require-full-coverage为yes,那么整个集群都挂掉,如果为no该插槽数据全部不能使用也无法储存。
spring继承redis
public class JedisClusterTest{
public static void main (String[] args){
HostAndPort hostAndPort = new HostAndPort("192.168.44.168",6379);
JedisCluster jedisCluster = new JedisCluster(hostAndPort);
jedisCluster.set("b1","value1");
String value = jedisCluster.get("b1");
System.out.println("value:" + value);
jedisCluster.close();
}
}
Redis集群提供了以下好处
1.实现扩容;
2.分摊压力;
3.无中心化配置相对简单
不足:
1.多键操作是不被支持
2.多键的redis事务是不被支持,lua脚本不被支持
3.由于集群方案出现较晚,很多公司已采用了其他的集群方案,而代理或者客户端分片的方案想要迁移到redis cluster需要整体迁移,过渡复杂度较大。
缓存穿透
原因:1.应用服务器压力变大
2.redis 命中率降低
3.一直查询数据库
解决方案:1、对空值缓存
2、设置可访问的名单(白名单)
3、采用布隆过滤器
4、进行实时监控
缓存击穿
原因:redis某一个key过期了,大量访问使用这个key
解决方案:1、预先设置热门数据
2、实时调整:现场监控哪些数据热门,实时调整key的过期时间
3、使用锁
缓存雪崩
原因:在极少时间段,查询的key大量集中过期
解决方案:1、构建多级缓存架构,Nginx缓存,redis缓存
2、使用锁式队列
3、设置过期标志更新缓存
4、将缓存失效时间分散开
分布式锁
对集群其他机器都有效
实现方法
1.基于数据库
2.基于redis(性能最高)
3.基于zookeeper(可靠性最高)
基于redis实现分布式锁
测试:
@RestController
@RequestMapping("/redisTest")
public class RedisTest{
@Autowired
private RedisTemplate redisTemplate;
@GetMapping("testLock")
public void testLock(){
String uuid = UUID.randomUUID().toString();
//1.获取锁
Boolean lock = redisTemplate.opsForValue()
.setIfAbsent("lock", uuid,3, TimeUnit.SECONDS);
//2.获取锁成功,查询num的值
if(lock){
Object value = redisTemplate.opsForValue().get("num");
//2.1、num判空
if(StringUtils.isEmpty(value)){
return;
}
//2.2、将值转成int类型
int num = Integer.parseInt(value+"");
//2.3把redis的num加1
redisTemplate.opsForValue().set("num", ++num);
//2.4 释放锁
String lockuuid = (String) redisTemplate.opsForValue().get("lock");
if (lockuuid.equals(uuid)){
redisTemplate.delete("lock");
}
}else {
//3.获取锁失败,每隔0.1秒再获取一次
try {
Thread.sleep(1000);
testLock();
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}