本文已参与「新人创作礼」活动,一起开启掘金创作之路
一、SpringBoot引入Redis
1、引入Redis
<!--redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--aop-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--fastjson-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.75</version>
</dependency>
2、配置yml
spring:
redis:
open: true
host: localhost
port: 6379
password: 123456
timeout: 6000ms # 连接超时时长(毫秒)
jedis:
pool:
max-active: 1000 # 连接池最大连接数(使用负值表示没有限制)
max-wait: -1ms # 连接池最大阻塞等待时间(使用负值表示没有限制)
min-idle: 0 # 连接池中的最小空闲连接
max-idle: 8 # 连接池中的最大空闲连接
database: 0
3、配置redis
/**
* Redis配置
*
* @author Mark sunlightcs@gmail.com
*/
@Configuration
public class RedisConfig {
@Autowired
private RedisConnectionFactory factory;
@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new StringRedisSerializer());
redisTemplate.setConnectionFactory(factory);
return redisTemplate;
}
@Bean
public HashOperations<String, String, Object> hashOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForHash();
}
@Bean
public ValueOperations<String, String> valueOperations(RedisTemplate<String, String> redisTemplate) {
return redisTemplate.opsForValue();
}
@Bean
public ListOperations<String, Object> listOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForList();
}
@Bean
public SetOperations<String, Object> setOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForSet();
}
@Bean
public ZSetOperations<String, Object> zSetOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForZSet();
}
}
4、RedisUtil类
@Component
public class RedisUtils {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private ValueOperations<String, String> valueOperations;
@Autowired
private HashOperations<String, String, Object> hashOperations;
@Autowired
private ListOperations<String, Object> listOperations;
@Autowired
private SetOperations<String, Object> setOperations;
@Autowired
private ZSetOperations<String, Object> zSetOperations;
/** 默认过期时长,单位:秒 */
public final static long DEFAULT_EXPIRE = 60 * 60 * 24;
/** 不设置过期时长 */
public final static long NOT_EXPIRE = -1;
public void set(String key, Object value, long expire){
valueOperations.set(key, toJson(value));
if(expire != NOT_EXPIRE){
redisTemplate.expire(key, expire, TimeUnit.SECONDS);
}
}
public void set(String key, Object value){
set(key, value, DEFAULT_EXPIRE);
}
public <T> T get(String key, Class<T> clazz, long expire) {
String value = valueOperations.get(key);
if(expire != NOT_EXPIRE){
redisTemplate.expire(key, expire, TimeUnit.SECONDS);
}
return value == null ? null : fromJson(value, clazz);
}
public <T> T get(String key, Class<T> clazz) {
return get(key, clazz, NOT_EXPIRE);
}
public String get(String key, long expire) {
String value = valueOperations.get(key);
if(expire != NOT_EXPIRE){
redisTemplate.expire(key, expire, TimeUnit.SECONDS);
}
return value;
}
public String get(String key) {
return get(key, NOT_EXPIRE);
}
public void delete(String key) {
redisTemplate.delete(key);
}
/**
* Object转成JSON数据
*/
private String toJson(Object object){
if(object instanceof Integer || object instanceof Long || object instanceof Float ||
object instanceof Double || object instanceof Boolean || object instanceof String){
return String.valueOf(object);
}
return JSON.toJSONString(object);
}
/**
* JSON数据,转成Object
*/
private <T> T fromJson(String json, Class<T> clazz){
return JSON.parseObject(json,clazz);
}
}
5、Redis切面处理类
/**
* Redis切面处理类
*
* @author Mark sunlightcs@gmail.com
*/
@Aspect
@Configuration
public class RedisAspect {
private Logger logger = LoggerFactory.getLogger(getClass());
//是否开启redis缓存 true开启 false关闭
@Value("${spring.redis.open: false}")
private boolean open;
@Around("execution(* cn.amoqi.springboot.redis.util.RedisUtils.*(..))")
public Object around(ProceedingJoinPoint point) throws Throwable {
Object result = null;
if(open){
try{
result = point.proceed();
}catch (Exception e){
logger.error("redis error", e);
throw new RuntimeException("Redis服务异常");
}
}
return result;
}
}
二、引入redisson
1、注解
<!-- redisson -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.10.4</version>
</dependency>
2、配置Redisson
2.1 简单配置
//简单使用
//@Bean
public RedissonClient redissonClient(@Value("${spring.redis.host}") String host,@Value("${spring.redis.port}") String port,
@Value("${spring.redis.password}") String password){
Config config = new Config();
SingleServerConfig singleServerConfig = config.useSingleServer();
singleServerConfig.setAddress("redis://"+host+":"+port);
if(StringUtils.hasText(password)){
singleServerConfig.setPassword(password);
}
return Redisson.create(config);
}
2.2 配置文件配置
在resource里放入redisson.yaml
singleServerConfig:
idleConnectionTimeout: 10000
connectTimeout: 10000
timeout: 3000
retryAttempts: 3
retryInterval: 1500
password:
subscriptionsPerConnection: 5
clientName: null
address: "redis://127.0.0.1:6379"
subscriptionConnectionMinimumIdleSize: 1
subscriptionConnectionPoolSize: 50
connectionMinimumIdleSize: 24
connectionPoolSize: 64
database: 0
dnsMonitoringInterval: 5000
threads: 16
nettyThreads: 32
codec: !<org.redisson.client.codec.StringCodec> {}
transportMode: "NIO"
在RedisConfig配置
@Value("${spring.profiles.active:}")
private String activeProfile;
private String getConfigPath() {
if(!StringUtils.hasText(activeProfile)){return "classpath:redisson.yaml";}
String[] profiles = activeProfile.split(",");
Set<String> profileSet = Arrays.stream(profiles).collect(Collectors.toSet());
if (profileSet.contains("test")) {
return "classpath:redisson-test.yaml";
}
if (profileSet.contains("prod")) {
return "classpath:redisson-prod.yaml";
}
return "classpath:redisson.yaml";
}
@Bean(destroyMethod = "shutdown")
@ConditionalOnMissingBean(RedissonClient.class)
public RedissonClient redissonClient() throws IOException {
String configPath = getConfigPath();
File configFile = ResourceUtils.getFile(configPath);
Config config = Config.fromYAML(configFile);
return Redisson.create(config);
}
2.3 集群配置
在resource里放入redisson.yaml,在RedisConfig配置和2.2一样即可
clusterServersConfig:
idleConnectionTimeout: 10000
connectTimeout: 10000
timeout: 3000
retryAttempts: 3
retryInterval: 1500
failedSlaveReconnectionInterval: 3000
failedSlaveCheckInterval: 60000
password: null
subscriptionsPerConnection: 5
clientName: null
loadBalancer: !<org.redisson.connection.balancer.RoundRobinLoadBalancer> {}
subscriptionConnectionMinimumIdleSize: 1
subscriptionConnectionPoolSize: 50
slaveConnectionMinimumIdleSize: 24
slaveConnectionPoolSize: 64
masterConnectionMinimumIdleSize: 24
masterConnectionPoolSize: 64
readMode: "MASTER"
subscriptionMode: "MASTER"
nodeAddresses:
- "redis://192.168.80.140:6379"
- "redis://192.168.80.141:6379"
- "redis://192.168.80.142:6379"
scanInterval: 1000
pingConnectionInterval: 0
keepAlive: false
tcpNoDelay: false
threads: 16
nettyThreads: 32
codec: !<org.redisson.client.codec.StringCodec> {}
transportMode: "NIO"
3、使用
3.1 加锁使用
/**
* 测试redission锁
* @return
*/
@RequestMapping("/redissionClient")
public String init(){
RLock lock = redissonClient.getLock("name");
System.out.println(Thread.currentThread().getId());
try {
int i = 0;
System.out.println(Thread.currentThread());
/*waitTime:尝试获取锁的时间,leaseTime:释放锁的时间 TimeUnit:时间的单位*/
while (!lock.tryLock(50,50002, TimeUnit.MILLISECONDS)){
i++;
lock = redissonClient.getLock("name"+i);
}
System.out.println("获取到lock"+lock.getName());
System.out.println("执行业务");
try {
boolean locked = lock.isLocked();
System.out.println("是否正在锁:"+locked);
TimeUnit.SECONDS.sleep(5);
locked = lock.isLocked();
System.out.println("是否正在锁:"+locked);
} catch (InterruptedException e) {
e.printStackTrace();
}
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println("释放锁");
if(lock.isHeldByCurrentThread()){
lock.unlock();
}
}
return "777777";
}
3.2 定时任务应用
工具类
@Service
public class RedisDelayedQueue {
/**
* 任务回调监听
*
* @param <T>
*/
public abstract static class TaskEventListener<T> {
/**
* 执行方法
*
* @param t
*/
public abstract void invoke(T t);
}
@Autowired
RedissonClient redissonClient;
/**
* 添加队列
*
* @param t DTO传输类
* @param delay 时间数量
* @param timeUnit 时间单位
* @param <T> 泛型
*/
public <T> void addQueue(T t, long delay, TimeUnit timeUnit) {
RBlockingQueue<T> blockingFairQueue = redissonClient.getBlockingQueue(t.getClass().getName());
RDelayedQueue<T> delayedQueue = redissonClient.getDelayedQueue(blockingFairQueue);
delayedQueue.offer(t, delay, timeUnit);
delayedQueue.destroy();
}
/**
* 获取队列
*
* @param zClass DTO泛型
* @param taskEventListener 任务回调监听
* @param <T> 泛型
* @return
*/
public <T> void run(Class zClass, TaskEventListener taskEventListener) {
RBlockingQueue<T> blockingFairQueue = redissonClient.getBlockingQueue(zClass.getName());
//由于此线程需要常驻,可以新建线程,不用交给线程池管理
((Runnable) () -> {
while (true) {
try {
T t = blockingFairQueue.poll(10,TimeUnit.SECONDS);
if(t!=null){
taskEventListener.invoke(t);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).run();
}
/**
* 获取队列,使用java8进行处理
*
* @param zClass DTO泛型
* @param consumer 消费者方法
* @param <T> 泛型
* @return
*/
public <T> void run(Class zClass, Consumer consumer) {
RBlockingQueue<T> blockingFairQueue = redissonClient.getBlockingQueue(zClass.getName());
//由于此线程需要常驻,可以新建线程,不用交给线程池管理
((Runnable) () -> {
while (true) {
try {
T t = blockingFairQueue.poll(10,TimeUnit.SECONDS);
if(t!=null){
consumer.accept(t);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).run();
}
}
放入线程池里或者启动一个线程进行消费
final RedissonClient redissonClient;
final TaskExecutor taskExecutor;
public IndexController(RedissonClient redissonClient,TaskExecutor taskExecutor) {
this.redissonClient = redissonClient;
this.taskExecutor = taskExecutor;
}
@PostConstruct
public void initTask(){
/*RedisDelayedQueue.TaskEventListener<Student> taskEventListener = new RedisDelayedQueue.TaskEventListener<Student>() {
@Override
public void invoke(Student taskBodyDTO) {
//这里调用你延迟之后的代码
log.info("执行...." + taskBodyDTO.getName() + "===" + taskBodyDTO.getAge());
}
};*/
/*实现线程池进行处理*/
//taskExecutor.execute(()->redisDelayedQueue.run(Student.class, taskEventListener));
/*新建一个线程进行处理*/
//new Thread(()->redisDelayedQueue.run(Student.class, taskEventListener)).start();
/*使用java8函数的方法*/
new Thread(()->redisDelayedQueue.run(Student.class,x-> System.out.println("执行...." + JSON.toJSONString(x) + "==="))).start();
}
使用
/**
* 测试Redis定时任务
* @return
*/
@RequestMapping("/task")
public String taskTest(){
Student student1 = new Student("zhangsan",20);
redisDelayedQueue.addQueue(student1,20L,TimeUnit.SECONDS);
Student student2 = new Student("zhangsan",21);
redisDelayedQueue.addQueue(student2,21L,TimeUnit.SECONDS);
Student student3 = new Student("zhangsan",22);
redisDelayedQueue.addQueue(student3,22L,TimeUnit.SECONDS);
Student student4 = new Student("zhangsan",23);
redisDelayedQueue.addQueue(student4,23L,TimeUnit.SECONDS);
return "task";
}
3.3 发布订阅的使用
配置订阅类:
@Configuration
public class MessageConfiguration {
private RedisConnectionFactory redisConnectionFactory;
private MessageSubscriber messageSubscriber;
@Autowired
public void setRedisConnectionFactory(RedisConnectionFactory redisConnectionFactory) {
this.redisConnectionFactory = redisConnectionFactory;
}
@Autowired
public void setMessageSubscriber(MessageSubscriber messageSubscriber) {
this.messageSubscriber = messageSubscriber;
}
@Bean
public RedisMessageListenerContainer redisContainer() {
RedisMessageListenerContainer container
= new RedisMessageListenerContainer();
container.setConnectionFactory(redisConnectionFactory);
container.addMessageListener(messageSubscriber, topic());
return container;
}
private Topic topic() {
return new ChannelTopic("topic");
}
}
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.Set;
@Slf4j
@Service
public class MessageSubscriber implements MessageListener {
private final Object lock = new Object();
private ObjectMapper objectMapper;
public MessageSubscriber() {
this.objectMapper = new ObjectMapper()
.registerModule(new JavaTimeModule())
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.disable(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS)
.disable(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS);
}
/**
* Callback for processing received objects through Redis.
*
* @param message message must not be {@literal null}.
* @param pattern pattern matching the channel (if specified) - can be {@literal null}.
*/
@Override
public void onMessage(Message message, byte[] pattern) {
try {
String msg = objectMapper.readValue(message.getBody(), String.class);
System.out.println("收到订阅消息:"+msg);
} catch (IOException e) {
e.printStackTrace();
}
}
}
发布类接口:
/**
* 消息发布
*
* @author jeff
* created on 2020/9/11 11:16
*/
public interface MessagePublisher {
/**
* 发送消息
*
* @param message
*/
void send(String message);
/**
* 发送消息
*
* @param channel
* @param message
*/
void send(String channel, String message);
}
发布类实现:
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
@Slf4j
@Service
public class MessagePublisherImpl implements MessagePublisher {
private ObjectMapper objectMapper;
private StringRedisTemplate redisTemplate;
public MessagePublisherImpl() {
this.objectMapper = new ObjectMapper()
.registerModule(new JavaTimeModule())
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.disable(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS)
.disable(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS);
}
@Autowired
public void setRedisTemplate(StringRedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
/**
* 发送消息
*
* @param message
*/
@Override
public void send(String message) {
send("topic", message);
}
/**
* 发送消息
*
* @param channel
* @param message
*/
@Override
public void send(String channel, String message) {
try {
String messageStr = objectMapper.writeValueAsString(message);
redisTemplate.convertAndSend(channel, messageStr);
} catch (JsonProcessingException e) {
log.error("cannot push message to channel " + channel + ", message: " + message, e);
}
}
}