一.spring-data-redis整合
1.1 spring-data-redis简介
Spring Boot 提供了对 Redis 集成的组件包:spring-boot-starter-data-redis,它依赖于 spring-data-redis 和 lettuce。Spring Boot 1.0 默认使用的是 Jedis 客户端,2.0 替换成了 Lettuce,但如果你从 Spring Boot 1.5.X 切换过来,几乎感受不到差异,这是因为 spring-boot-starter-data-redis 为我们隔离了其中的差异性。
- Lettuce:是一个可伸缩线程安全的 Redis 客户端,多个线程可以共享同一个 RedisConnection,它利用优秀 Netty NIO 框架来高效地管理多个连接。
- Spring Data:是 Spring 框架中的一个主要项目,目的是为了简化构建基于 Spring 框架应用的数据访问,包括非关系数据库、Map-Reduce 框架、云数据服务等,另外也包含对关系数据库的访问支持。
- Spring Data Redis:是 Spring Data 项目中的一个主要模块,实现了对 Redis 客户端 API 的高度封装,使对 Redis 的操作更加便捷。
1.2 整合spring data redis
引入依赖包,引入 commons-pool 2 是因为 Lettuce 需要使用 commons-pool 2 创建 Redis 连接池。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
1.3 redis连接配置
1.redis单例模式连接配置
redis的单节点实例,可以通过下面的配置连接redis单节点实例数据库:
spring:
redis:
database: 0 # Redis 数据库索引(默认为 0)
host: 192.168.161.3 # Redis 服务器地址
port: 6379 # Redis 服务器连接端口
password: 123456 # Redis 服务器连接密码(默认为空)
timeout: 5000 # 连接超时,单位ms
lettuce:
pool:
max-active: 8 # 连接池最大连接数(使用负值表示没有限制) 默认 8
max-wait: -1 # 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1
max-idle: 8 # 连接池中的最大空闲连接 默认 8
min-idle: 0 # 连接池中的最小空闲连接 默认 0
2.redis哨兵模式连接配置
redis另外一种非常常用的部署模式是哨兵模式,如果你的公司使用的是这种部署模式,它相对于单实例模式更加的高可用。
- redis哨兵模式实际上是两种模式的组合,即主从模式和哨兵模式。当
Master
节点离线后,哨兵sentinel监控节点会把Slave
节点切换为Master
节点,保证服务的高可用 - 哨兵模式是在主从模式的基础上增加了哨兵sentinel监控节点。最简单的哨兵模式需要一个redis的
Master
节点、一个redis的Slave
、另外三个哨兵监控节点。
需要注意的是,当我们使用spring boot连接哨兵模式的redis集群,连接的是sentinel节点,而不是redis服务实例节点。注意上图的连接顺序。 Application Client是我们的应用程序,sentinel node是哨兵节点。
spring:
redis:
password: 123456
timeout: 5000
sentinel: # 哨兵模式连接配置
master: mymaster #master节点名称,redis sentinel模式安装的时候会配置
nodes: 192.168.1.201:26379,192.168.1.202:26379,192.168.1.203:26379 # 哨兵的IP:Port列表
lettuce
pool:
max-active: 8
max-wait: -1
max-idle: 8
min-idle: 0
3.redis集群模式连接配置
Redis Cluster是Redis的分布式解决方案,在Redis 3.0版本正式推出的,有效解决了Redis分布式方面的需求。当遇到单机内存、并发、流量等瓶颈时,可以采用Cluster架构达到负载均衡的目的。分布式集群首要解决问题是:把整个数据集按照分区规则映射到多个节点上,即把数据集按照一定的规则划分到多个节点上,每个节点只保存整个数据集的一个子集。
无论是单节点还是master-slave,其redis服务都保存了数据集的完整副本。cluster模式不是,其redis实例节点只包含完整数据集的子集。
- 当程序客户端随意访问一个redis node节点时,可能会发现其操作的数据或者应该写入的数据位置,并不在当前node节点上。
- 此时,当前被访问的redis node节点会告知客户端,你应该去哪个节点访问数据或写入数据
- 然后客户端获取目标node节点的地址,重定向到该节点的地址,去访问或写入数据。
下面的配置,是针对redis集群模式连接访问的配置。
spring:
redis:
password: 123456
timeout: 5000
database: 0
cluster: #集群模式配置
nodes: 192.168.1.11:6379,192.168.1.12:6379,192.168.1.13:6379,192.168.1.14:6379,192.168.1.15:6379,192.168.1.16:6379
max-redirects: 3 # 重定向的最大次数
lettuce:
pool:
max-active: 8
max-wait: -1
max-idle: 8
min-idle: 0
二.使用redisTemplate操作数据
2.1 redis模板封装类
RedisTemplate 的封装使我们能够更方便的进行redis数据操作,比直接使用Jedis或者Lettuce的java SDK要方便很多。RedisTemplate作为java 操作redis数据库的API模板更通用,可以操作所有的redis数据类型。
// 注入RedisTemplate,更通用
@Resource
private RedisTemplate<String, Object> redisTemplate;
ValueOperations<String,Object> ValueOperations = redisTemplate.opsForValue();//操作字符串
HashOperations<String, String, Object> hashOperations = redisTemplate.opsForHash();//操作 hash
ListOperations<String, Object> listOperations = redisTemplate.opsForList();//操作 list
SetOperations<String, Object> setOperations = redisTemplate.opsForSet();//操作 set
ZSetOperations<String, Object> zSetOperations = redisTemplate.opsForZSet();//操作有序 set
ListOperations、ValueOperations、HashOperations、SetOperations、ZSetOperations等都是针对专有数据类型进行操作,使用起来更简洁。
除了RedisTemplate模板类,还有另一个模板类叫做StringRedisTemplate 。二者都提供了用来操作redis数据库的API。
二者的区别在于
- 操作的数据类型不同,以List类型为例:RedisTemplate操作
List<Object>;
,StringRedisTemplate操作List<String>
- 序列化数据的方式不同,RedisTemplate使用的是JdkSerializationRedisSerializer 存入数据会将数据先序列化成字节数组然后在存入Redis数据库。 StringRedisTemplate使用的是StringRedisSerializer
@Resource
private RedisTemplate redisTemplate;
@Resource
private StringRedisTemplate stringRedisTemplate;
@GetMapping("/save")
public void saveArticle(){
Article article = Article.builder().author("William")
.content("Content").createTime(new Date()).title("SpringBoot实战").build();
redisTemplate.opsForValue().set("article:jdk", article);
stringRedisTemplate.opsForValue().set("article:string", "SpringBoot实战");
}
2.2 解决Redis客户端查看缓存结果人工无法阅读问题
因为RedisTemplate默认使用JDK的二进制序列化方式(JdkSerializationRedisSerializer),所以Redis序列化存储后人看不懂,但是程序是能看懂的。为了使人可以阅读,可以使用下面两种序列化器:
- 采用StringRedisSerializer对key进行序列化(字符串格式)
- 采用Jackson2JsonRedisSerializer对value将进行序列化(JSON格式)
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate redisTemplate = new RedisTemplate();
redisTemplate.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
//重点在这四行代码
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
序列化方式对比:
- JdkSerializationRedisSerializer: 使用JDK提供的序列化功能。 优点是反序列化时不需要提供类型信息(class),但缺点是需要实现Serializable接口,还有序列化后的结果非常庞大,是JSON格式的5倍左右,这样就会消耗redis服务器的大量内存。而且是以二进制形式保存,自然人无法理解。
- Jackson2JsonRedisSerializer: 使用Jackson库将对象序列化为JSON字符串。优点是速度快,序列化后的字符串短小精悍,不需要实现Serializable接口。似乎没啥缺点。
- StringRedisSerializer序列化之后的结果,自然人也是可以理解,但是value只能是String类型,不能是Object。。
三. 集群多节点应用session共享
3.1 spring session 共享的实现原理
集群应用的Session共享
- 同一IP(域名),不同端口,在同一个浏览器cookies是共享的。不同IP(域名)的Cookies,在同一个浏览器Cookies肯定不共享的。对于这种情况需要在集群应用的前面加上负载均衡器逆向代理,如:nginx,haproxy。让客户端看上去访问的是同一个IP(代理IP),从而浏览器认为基于这个IP的Cookies是共享的。
- SESSION正常是由Servlet容器来维护的(内存里面,每个服务器内存是不共享的),这样SESSION就无法共享。如果希望Session共享,就需要把sessionID的存储放到一个统一的地方,如:redis。SessionID的维护交给Spring session则更加方便。
- 除了Cookies可以维持Sessionid,Spring Session还提供了了另外一种方式,就是使用header传递SESSIONID。目的是为了方便类似于手机这种没有cookies的客户端进行session共享。
3.2 集成Spring session
1.引入spring-session-redis的maven依赖
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2.配置启用Redis的httpSession
在启动类上方加上注解,启动SpringSession管理应用的session,并设置session数据的有效期30分钟
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 30 * 60 * 1000)
3.配置redis链接信息(application.yml)
参考第一节配置
4.测试
@Controller
public class SessionController {
@RequestMapping(value="/uid",method = RequestMethod.GET)
public @ResponseBody
String uid(HttpSession session) {
return "sessionId:" + session.getId();
}
}
通过不同端口启动两个服务:
-Dserver.port=8080 -Dserver.httpPort=80 -Dspring.profiles.active=dev -Ddebug
-Dserver.port=8081 -Dserver.httpPort=81 -Dspring.profiles.active=dev -Ddebug
依次访问,看看效果.通过返回值session.getId()
即:sessionid来判断,如果sessionid一致,则证明session共享成功了。
四. RedisLockRegistry分布式锁
4.1 集成spring-integration-redis
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-integration</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-redis</artifactId>
</dependency>
4.1 注册RedisLockRegistry
@Configuration
public class RedisLockConfig {
@Bean
public RedisLockRegistry redisLockRegistry(RedisConnectionFactory redisConnectionFactory) {
//第一个参数redisConnectionFactory
//第二个参数registryKey,分布式锁前缀,设置为项目名称会好些
//该构造方法对应的分布式锁,默认有效期是60秒.可以自定义
return new RedisLockRegistry(redisConnectionFactory, "boot-launch");
//return new RedisLockRegistry(redisConnectionFactory, "boot-launch",60);
}
}
4.3 使用RedisLockRegistry
@DeleteMapping(value = "/delete/{id}")
public void updateUser(@PathVariable("id") Long id) {
String lockKey = "lock:" + id;
//获取锁资源
Lock lock = redisLockRegistry.obtain(lockKey);
try {
//加锁
lock.lock();
//这里写需要处理业务的业务代码
articleService.deleteArticle(id);
} finally {
lock.unlock(); //释放锁
}
}
- RedisLockRegistry实现分布式锁,registryKey是锁key的前缀。
- 默认的锁过期时间是60秒,提供了自定义RedisLockRegistry(redisConnectionFactory, registryKey,expiredAfter)的构造函数可以使用
- 当尝试去unlock已经过期的锁的时候,会抛出异常IllegalStateException,即RedisLockRegistry不支持锁的续期。
- RedisLockRegistry实现的分布式锁是“可重入”的,可重入就是说某个线程已经获得某个锁,该线程可以再次获取锁而不会出现死锁。基于java.util.concurrent.locks.ReentrantLock实现可重入锁