Redis下载安装
Redis下载
1、登录Linux
2、下载安装gcc编译器:yum install gcc,测试gcc版本:gcc --version
3、下载redis-6.2.6.tar.gz上传到 /opt 目录
4、进入到 /opt 目录,执行解压命令:tar -zxvf redis-6.2.6.tar.gz
5、解压完成后,进入目录:cd redis-6.2.6,执行make命令(编译指令)
6、执行:make install,进行安装 (也可以直接执行make && make install)
7、安装OK,安装目录在 /usr/local/bin
Redis后台启动&使用
默认在6379端口
修改配置文件:vi /etc/redis.conf
daemonize no => daemonize yes
修改端口
启动【绝对路径】:/usr/local/bin/redis-server /etc/redis.conf
Path已经存了路径变量/usr/local/bin,可以直接redis-server /etc/redis.conf
查看是否启动:
ps -aux | grep redis
netstat -anp | more
在/usr/local/bin安装目录下进行访问
用客户端访问:redis-cli (端口默认在6379)
指定端口方式:redis-cli -p 6379
退出访问:quit
关闭
单实例关闭:redis-cli shutdown(端口默认在6379)
多实例关闭,指定端口关闭:redis-cli -p 6379 shutdown
访问redis再关闭:shutdown
Redis指令
基础操作
如果key已经保存了一个值,那么set操作会覆盖原来的值
对key(键)操作
keys *1:筛选keys末尾是1
TTL key
对DB(数据库)操作
Redis五大数据类型/结构
string
list
保存多个数据,底层使用双向链表存储结构实现,允许重复
set
不允许重复,无序
hash
hash适合用于存储对象,类似Java里面的Map<String, Object>
zset(sorted set)
有序集合
Redis配置
Redis配置文件
常规配置
设置密码
daemonize:是否以守护进程方式启动
loglevel:日志级别
logfile:日志文件位置
设定库的数量
Units单位
、
include
network
bind
protected_mode
port
timeout
tcp-keepalive
gengeral
daemonize
pidfile
loglevel
logfile
databases 16
security
redis.conf中设置密码,永久设置
用户名默认是default,可以不写
limits
maxclients
maxmemory
maxmemory-policy
maxmemory-samples
发布和订阅
客户端订阅频道示意图
当给这个频道发布消息后,消息就会发送给订阅的客户端
命令行实现发布订阅操作
先订阅再发布
发布的消息没有持久化
订阅的客户端,只能收到订阅后发布的消息
1、PUBLISH channel msg
将信息 message 发送到指定的频道 channel
2、SUBSCRIBE channel [channel ...]
订阅频道,可以同时订阅多个频道
3、UNSUBSCRIBE [channel ...]
取消订阅指定的频道, 如果不指定频道,则会取消订阅所有频道
4、PSUBSCRIBE pattern [pattern ...]
订阅一个或多个符合给定模式的频道,每个模式以 * 作为匹配符,比如 it* 匹配所有
以 it 开头的频道( it.news 、 it.blog 、 it.tweets 等等), news.* 匹配所有 以 news. 开
头的频道( news.it 、 news.global.today 等等),诸如此类
5、PUNSUBSCRIBE [pattern [pattern ...]]
退订指定的规则, 如果没有参数则会退订所有规则
IDEA-Jedis操作Redis数据
创建Maven项目,引入依赖
需要防火墙打开Redis的端口
将bind 127.0.0.1注释掉,支持远程连接
protected_mode保护模式设为no,支持远程连接
如果Redis配置了密码,则需要进行身份校验
jedis.auth("foobared");
IDEA-SpringBoot2整合Redis
通过RedisTemplate完成对Redis的操作
引入依赖
application.properties
#Redis 服务器地址
spring.redis.host=192.168.102.130
#Redis 服务器连接端口
spring.redis.port=6379
#Redis 如果有密码,需要配置, 没有密码就不要写
#spring.redis.password=hspedu
#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
redis配置类
默认配置存在的问题:
redisTemplate 模糊查找 key 数据为空
使用Java程序读取客户端写入的数据,转换异常,是因为没有使用配置类进行序列化,除非都是数据都是通过Java程序读和写
@EnableCaching
@Configuration
public class RedisConfig extends CachingConfigurerSupport {@Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {RedisTemplate<String, Object> template =new RedisTemplate<>();System.out.println("template=>" + template);RedisSerializer<String> redisSerializer =new StringRedisSerializer();Jackson2JsonRedisSerializer jackson2JsonRedisSerializer =new Jackson2JsonRedisSerializer(Object.class);ObjectMapper om = new ObjectMapper();om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.WRAPPER_ARRAY);jackson2JsonRedisSerializer.setObjectMapper(om);template.setConnectionFactory(factory);
//key 序列化方式template.setKeySerializer(redisSerializer);
//value 序列化template.setValueSerializer(jackson2JsonRedisSerializer);
//value hashmap 序列化template.setHashValueSerializer(jackson2JsonRedisSerializer);return template;}@Beanpublic CacheManager cacheManager(RedisConnectionFactory factory) {RedisSerializer<String> redisSerializer =new StringRedisSerializer();Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = newJackson2JsonRedisSerializer(Object.class);
//解决查询缓存转换异常的问题ObjectMapper om = new ObjectMapper();om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.WRAPPER_ARRAY);jackson2JsonRedisSerializer.setObjectMapper(om);
// 配置序列化(解决乱码的问题),过期时间 600 秒RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofSeconds(600)).serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer)).serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer)).disableCachingNullValues();RedisCacheManager cacheManager = RedisCacheManager.builder(factory).cacheDefaults(config).build();return cacheManager;}
}
Controller
其他类型的操作方法差不多
Redis持久化-RDB
RDB:在指定的时间间隔内将内存中的数据集快照写入磁盘, 也就 Snapshot 快照,恢复时
将快照文件读到内存
RDB持久化流程
Fork&Copy-On-Write
RDB配置
dump.rdb文件
在哪个目录启动,一旦shutdown(默认save保存),目录就会生成dump.rdb文件
在不同的目录启动,redis就是全新的,数据是由当前目录的dump.rdb恢复的
直接指定统一路径更加安全
相关配置&参数&操作
默认快照配置
save VS bgsave
flushall
把内存的数据清空,再持久化到dump.rdb
Save
stop-writes-on-bgsave-error
rdbcompression
rdbchecksum
动态停止RDB
临时生效,重启恢复
RDB备份&恢复
定时备份dump.rdp:Shell脚本 或 使用cp命令,把dump.rdb.bak文件名修改成dump.rdb即可
优势&劣势
Redis持久化-AOF
AOF持久化流程
AOF开启
AOF启动/修复/恢复
同步频率设置
Rewrite压缩
优势&劣势
RDB or AOF
事务
Redis不具备原子性,比如三条指令完成一个操作,第二条失败,其他成功,不会进行回滚
Mysql会进行回滚
Redis事务三特性
1、单独的隔离操作
2、没有隔离级别的概念
3、不保证原子性
事务相关命令Multi、Exec、discard
需求:依次向Rdis添加三组数据,使用事务来完成
注意事项和细节
事务冲突及解决方案
抢票问题
悲观锁
乐观锁
watch & unwatch
连接池技术
使用连接池来获取Redis连接
volatile的作用:
1、线程的可见性:当一个线程去修改一个共享变量时,另一个线程可以读取修改的值
2、顺序的一致性:禁止指令重排
保证每次调用返回的 jedisPool 是单例,构造器私有化
使用双重校验,保证 jedisPool 只创建一次,可以解决超卖问题
public class JedisPoolUtil {private static volatile JedisPool jedisPool = null;private JedisPoolUtil() {}public static JedisPool getJedisPoolInstance() {if (null == jedisPool) {synchronized (JedisPoolUtil.class) {if (null == jedisPool) {JedisPoolConfig poolConfig = new JedisPoolConfig();//对连接池进行配置poolConfig.setMaxTotal(200);poolConfig.setMaxIdle(32);poolConfig.setMaxWaitMillis(100 * 1000);poolConfig.setBlockWhenExhausted(true);poolConfig.setTestOnBorrow(true);jedisPool = new JedisPool(poolConfig, "192.168.102.130", 6379, 60000);}}}return jedisPool;}//释放回连接池public static void release(RedisProperties.Jedis jedis) {if (null != jedis) {jedis.close();}}
}
Lua脚本
使用Lua脚本可以解决超卖和库存遗留问题
可以直接代替连接池的代码,现在只需要从连接池获取连接
public class SecKillRedisByLua {static String secKillScript = "local userid=KEYS[1];\r\n" +"local ticketno=KEYS[2];\r\n" +"local stockKey='sk:'..ticketno..\":ticket\";\r\n" +"local usersKey='sk:'..ticketno..\":user\";\r\n" +"local userExists=redis.call(\"sismember\",usersKey,userid);\r\n" +"if tonumber(userExists)==1 then \r\n" +" return 2;\r\n" +"end\r\n" +"local num= redis.call(\"get\" ,stockKey);\r\n" +"if tonumber(num)<=0 then \r\n" +" return 0;\r\n" +"else \r\n" +" redis.call(\"decr\",stockKey);\r\n" +" redis.call(\"sadd\",usersKey,userid);\r\n" +"end\r\n" +"return 1";//使用lua脚本完成秒杀的核心方法public static boolean doSecKill(String uid,String ticketNo) {//先从redis连接池,获取连接JedisPool jedisPoolInstance = JedisPoolUtil.getJedisPoolInstance();Jedis jedis = jedisPoolInstance.getResource();//就是将lua脚本进行加载String sha1 = jedis.scriptLoad(secKillScript);//evalsha是根据指定的 sha1校验码, 执行缓存在服务器的脚本Object result = jedis.evalsha(sha1, 2, uid, ticketNo);String resString = String.valueOf(result);//根据lua脚本执行返回的结果,做相应的处理if("0".equals(resString)) {System.out.println("票已经卖光了..");jedis.close();return false;}if("2".equals(resString)) {System.out.println("不能重复购买..");jedis.close();return false;}if("1".equals(resString)) {System.out.println("抢购成功");jedis.close();return true;} else {System.out.println("购票失败..");jedis.close();return false;}}
}
主从复制
搭建一主多从
在hspredis目录创建三个文件(一主二从,在同一个机器上通过端口来区分)
编辑配置文件 redis6379(6380、6381).conf
从 /hspredis 目录下启动redis
主从复制-原理
如果从服务器 down 了, 重新启动, 仍然可以获取 Master 的最新数据
如果主服务器 down 了, 从服务器并不会抢占为主服务器, 当主服务器恢复后, 从服务器仍然指向原来的主服务器
薪火相传
将6381的主机设置为6380,数据从6380获取
6380仍然是从服务器,有一个从服务器,从服务器是127.0.0.1 6381
6379仍然是master,现在只有一个从服务器127.0.0.1 6380
反客为主
哨兵模式
调整为一主二从模式,根据前面配置重启即可
哨兵的端口是 26379
当主机挂掉,从机选举中产生新的主机
如果原来的主机重启, 会自动成为从机
Redis集群
集群搭建
1、将rdb、aof文件都删掉
rm -rf dump63*.rdb
2、新增redis6379.conf配置文件信息,删除不必要的内容
cluster-enabled yes 打开集群模式
cluster-config-file nodes-6379.conf 设定节点配置文件名
cluster-node-timeout 15000 设定节点失联时间,超过该时间(毫秒),集群自动进行主从切换
3、删除redis6380和redis6381.conf,复制五份6379的conf(一共六份)
修改配置文件里的端口信息:vi配置文件 :%s/6379/6380直接替换字符串
4、启动6个Redis服务
5、查看是否有node文件
6、将六个节点合成一个集群
注意事项及细节
集群使用
集群方式登录
集群故障恢复
集群的Jedis开发
引入依赖
1. 这里的set可以加入多个入口
2. 因为我们没有做日志配置,输出时,有些提示,但是不影响使用
3. 如果我们使用的是集群,需要把相关的端口都打开,否则会报错
4. JedisCluster的构造器有多个,也可以直接传入HostAndPort
public class JedisCluster_ {public static void main(String[] args) {Set<HostAndPort> set = new HashSet<HostAndPort>();set.add(new HostAndPort("192.168.198.135",6379));JedisCluster jedisCluster = new JedisCluster(set);jedisCluster.set("address","bj~北京");String address = jedisCluster.get("address");System.out.println("address-->" + address);jedisCluster.close();}
}
优点&缺点
Redis应用问题&解决方案
缓存穿透
缓存击穿
缓存雪崩
分布式锁
Redis实现分布式锁
基本实现
1、setnx key value,理解为上锁,在key没有删除前,不能执行相同key的上锁命令
2、del key,理解为释放锁
3、expire key seconds,给锁设置过期时间,防止死锁
4、ttl key,查看某个锁过期时间,没有过期执行相同ikey会失败,-2是已过期,-1是永不过期
5、set key value nx ex seconds,设置锁的同时,指定过期时间,防止死锁
代码实现
Lua脚本保证删除原子性
Redis新功能
ACL
给jack增加set权限
删除用户