spring boot 整合redis
Redis是什么
Redis是一种开放源代码(BSD许可)的内存中数据结构存储,用作数据库,缓存和消息代理。 Redis提供数据结构有字符串,哈希,列表,集合,带范围查询的排序集合,位图,超日志,地理空间索引和流。 Redis具有内置的复制,Lua脚本,LRU过期策略,事务和不同级别的磁盘持久性,并通过Redis Sentinel和Redis Cluster自动分区提供高可用性。
为什么使用Redis
- 速度非常快
- 支持的数据结构比其他缓存更多
- 大多数语言都支持redis
- 它是开源且稳定的
springboot中使用Redis
引入依赖
compile group: 'org.springframework.boot', name: 'spring-boot-starter-data-redis', version: '2.4.2'
配置缓存属性
spring.redis.host=127.0.0.1
spring.redis.port=6379
#spring.redis.password=
spring.redis.jedis.pool.max-active=8
spring.redis.jedis.pool.max-wait=-1
spring.redis.jedis.pool.max-idle=10
spring.redis.jedis.pool.min-idle=2
spring.redis.timeout=6000
下面以对用户的增删改查说明redis的使用
用户实体对象
@Entity
@Table(name = "user")
@Data
@Accessors(chain = true)
public class User implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false)
private Integer id;
@Column(name = "name")
private String name;
@Column(name = "age")
private Integer age;
}
在调用的方法上加上缓存注解以便可以缓存@CachePut,@Cacheable,@CacheEvict
@Service
@CacheConfig(cacheNames = "users")
public class UserServiceImpl implements UserService {
Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
UserRepository userRepository;
@CachePut(key ="#result.id")
@Override
public User addUser(User user) {
logger.debug("添加用户 ==>{}",user.toString());
return userRepository.save(user);
}
@Override
@CachePut(key ="#result.id")
public User updateUser(User user) {
logger.debug("更新用户 ==>{}",user.toString());
return userRepository.save(user);
}
@Override
public List<User> findAll() {
logger.debug("查询所有用户");
return userRepository.findAll();
}
@Override
@Cacheable(key="#id")
public User findById(Integer id) {
logger.debug("查询所有用户");
return userRepository.findById(id).get();
}
@Override
@CacheEvict(key = "#id")
public boolean deleteUser(Integer id) {
logger.debug("删除用户==>{}",id);
userRepository.deleteById(id);
return true;
}
}
rest api 调用 测试缓存
@RestController
@RequestMapping("/api/user")
public class UserController {
@Autowired
UserService userService;
@GetMapping("/findById/{id}")
private User findById(@PathVariable("id") Integer id){
return userService.findById(id);
}
@GetMapping("/findAll")
private List<User> findAllUser(Model model){
return userService.findAll();
}
@PostMapping(value="/save")
private ResponseEntity<User> save(@RequestBody User user){
User add= userService.addUser(user);
return new ResponseEntity<User>(add, HttpStatus.CREATED);
}
@PutMapping("/update")
private ResponseEntity<User> update(@RequestBody User user){
User update= userService.updateUser(user);
return new ResponseEntity<User>(update, HttpStatus.OK);
}
@DeleteMapping("/delete/{id}")
private ResponseEntity<HttpStatus> delete(@PathVariable("id") Integer id){
userService.deleteUser(id);
return new ResponseEntity<HttpStatus>(HttpStatus.ACCEPTED);
}
启动redis,启动类将@EnableCaching注解,启动spring boot 应用。当我们调用增删改查时,将会有缓存存入到redis中,或者从redis中删除。通过控制台的sql输出语句,和redis-cli和验证缓存的情况。
使用RedisTemplate
@Configuration
@EnableCaching
@EnableRedisRepositories
public class RedisConfig extends CachingConfigurerSupport
{
@Bean
@Primary
public RedisProperties redisProperties() {
return new RedisProperties();
}
@Bean
JedisConnectionFactory jedisConnectionFactory()
{
RedisProperties properties = redisProperties();
RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration();
configuration.setHostName(properties.getHost());
configuration.setPort(properties.getPort());
configuration.setPassword(properties.getPassword());
configuration.setDatabase(properties.getDatabase());
return new JedisConnectionFactory(configuration);
}
@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(jedisConnectionFactory());
RedisSerializer stringSerializer = new StringRedisSerializer();//序列化为String
Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
redisTemplate.setKeySerializer(stringSerializer);
redisTemplate.setValueSerializer(serializer);
redisTemplate.setHashKeySerializer(stringSerializer);
redisTemplate.setHashValueSerializer(serializer);
return redisTemplate;
}
}
配置redis哨兵模式
配置哨兵模式网上介绍的文章很多,这里不做过多的解释. redis 配置好主从模式,哨兵之后在spring boot中配置也比较简单。
spring boot 配置文件
spring:
redis:
host: localhost
port: 6379
jedis:
pool:
max-active: 10
max-idle: 5
max-wait: 8
min-idle: 2
timeout: 6000
password: admin
sentinel:
master: mymaster
nodes: 127.0.0.1:26379,127.0.0.1:26380,127.0.0.1:26381
连接工厂配置
@Bean
@Primary
public RedisProperties redisProperties() {
return new RedisProperties();
}
@Bean
JedisConnectionFactory jedisConnectionFactory(RedisSentinelConfiguration sentinelConfig)
{
return new JedisConnectionFactory(sentinelConfig);
}
@Bean
public RedisSentinelConfiguration sentinelConfiguration(){
RedisSentinelConfiguration redisSentinelConfiguration = new RedisSentinelConfiguration();
RedisProperties properties = redisProperties();
List<String> nodes=properties.getSentinel().getNodes();
redisSentinelConfiguration.master(properties.getSentinel().getMaster());
//配置redis的哨兵sentinel
Set<RedisNode> redisNodeSet = new HashSet<>();
nodes.forEach(x->{
redisNodeSet.add(new RedisNode(x.split(":")[0],Integer.parseInt(x.split(":")[1])));
});
redisSentinelConfiguration.setSentinels(redisNodeSet);
properties.getSentinel();
redisSentinelConfiguration.setPassword(RedisPassword.of(properties.getPassword()));
return redisSentinelConfiguration;
}
配置redis集群
window redis集群推荐将多个redis注册为window服务,方便管理。
spring boot 配置文件
spring:
cluster:
max-redirects: 3
nodes: 127.0.0.1:6379,127.0.0.1:6380,127.0.0.1:6381
连接工厂配置及redistemplate配置
@Bean
JedisConnectionFactory jedisConnectionFactory(JedisPoolConfig jedisPool,
RedisClusterConfiguration jedisClusterConfig)
{
return new JedisConnectionFactory(jedisClusterConfig,jedisPool);
}
@Bean
public JedisPoolConfig jedisPool(RedisProperties properties) {
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxIdle(properties.getJedis().getPool().getMaxIdle());
jedisPoolConfig.setMaxWaitMillis(properties.getJedis().getPool().getMaxWait().toMillis());
jedisPoolConfig.setMaxTotal(properties.getJedis().getPool().getMaxActive());
jedisPoolConfig.setMinIdle(properties.getJedis().getPool().getMinIdle());
return jedisPoolConfig;
}
@Bean
public RedisClusterConfiguration jedisConfig() {
RedisClusterConfiguration config = new RedisClusterConfiguration();
RedisProperties properties = redisProperties();
List<String> nodes=properties.getCluster().getNodes();
Set<RedisNode> redisNodeSet = new HashSet<>();
nodes.forEach(x->{
redisNodeSet.add(new RedisNode(x.split(":")[0],Integer.parseInt(x.split(":")[1])));
});
config.setClusterNodes(redisNodeSet);
config.setMaxRedirects(properties.getCluster().getMaxRedirects());
config.setPassword(RedisPassword.of(properties.getPassword()));
return config;
}
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(connectionFactory);
RedisSerializer stringSerializer = new StringRedisSerializer();//序列化为String
Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
redisTemplate.setKeySerializer(stringSerializer);
//redisTemplate.setValueSerializer(serializer);
redisTemplate.setHashKeySerializer(stringSerializer);
// redisTemplate.setHashValueSerializer(new JdkSerializationRedisSerializer());
return redisTemplate;
}
如果配置的集群正常,就可以使用redistemplate操作数据了。
spring boot Redis 消息订阅与发部
配置文件
spring
reids
channel:
site: site-visit
使用redis 消息监听容器
@Bean
public RedisMessageListenerContainer listenerContainer(MessageListenerAdapter listenerAdapter,
RedisConnectionFactory connectionFactory) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
container.addMessageListener(listenerAdapter, ChannelTopic.of(siteVisitChannel));
return container;
}
@Bean
public MessageListenerAdapter listenerAdapter(SiteVisitConsumer consumer) {
MessageListenerAdapter messageListenerAdapter = new MessageListenerAdapter(consumer);
messageListenerAdapter.setSerializer(new JdkSerializationRedisSerializer());
return messageListenerAdapter;
}
设置消息生产者
@Component
public class SiteVisitProducer {
Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Value("${spring.redis.channel.site}")
private String siteVisitChannel;
public void sendMessage(SiteVisitInfo pv) {
logger.info("新的访问信息==>>{}",pv);
redisTemplate.convertAndSend(siteVisitChannel, pv);
}
}
消费者端
public interface MessageDelegate {
void handleMessage(SiteVisitInfo message);
void handleMessage(String message);
void handleMessage(Map message);
void handleMessage(byte[] message);
void handleMessage(Serializable message);
// pass the channel/pattern as well
void handleMessage(Serializable message, String channel);
}
@Component
public class SiteVisitConsumer implements MessageDelegate{
@Autowired
SiteVisitInfoService siteVisitInfoService;
Logger logger = LoggerFactory.getLogger(getClass());
@Override
public void handleMessage(SiteVisitInfo message) {
logger.info("接受站点访问信息==>>>{}",message);
siteVisitInfoService.saveOrUpdate(message);
}
@Override
public void handleMessage(String message) {
logger.info("接受站点访问信息==>>>{}",message);
}
@Override
public void handleMessage(Map message) {
logger.info("接受站点访问信息==>>>{}",message);
}
@Override
public void handleMessage(byte[] message) {
logger.info("接受站点访问信息==>>>{}",message);
}
@Override
public void handleMessage(Serializable message) {
logger.info("接受站点访问信息==>>>{}",message);
}
@Override
public void handleMessage(Serializable message, String channel) {
logger.info("接受站点访问信息==>>>{}",message);
}
接下来,当我们在程序中发消息时siteVisitProducer.sendMessage(); 消费端将会接受到消息,处理接受到的消息即可。
使用Redis分布式锁
为什么要使用分布式锁
在单机情况下我们通常使用Java的内置锁来实现高并发情况,但是在分布式的情况下,如果有多个服务对外提供服务,即使使用java的锁也会造成数据不一致的情况,因为进入单个服务的请求是同步访问的,但该服务中的锁不能对其他服务起作用,最终导致数据出现错误。
springboot使用分布式锁
redis分布式锁实现的集中方式
-
setnx+expire ,又有这两个命令不是原子的,可能会引发并发问题
- lua脚本 或set key value [EX seconds][PX milliseconds][NX|XX] 命令 ,具备原子性
@Autowired RedisTemplate redisTemplate; @Autowired JedisPool jedisPool; private static final String REDIS_LOCK="covid"; public void saveDailyData() { Jedis jedis=jedisPool.getResource(); String value = UUID.randomUUID().toString()+Thread.currentThread().getName(); Boolean exist=redisTemplate.opsForValue().setIfAbsent(REDIS_LOCK,value); redisTemplate.expire(REDIS_LOCK,10, TimeUnit.SECONDS); /* String lua_scripts_lock = "if redis.call('setnx',KEYS[1],ARGV[1]) == 1 then" + "redis.call('expire',KEYS[1],ARGV[2]) return 1 else return 0 end"; Object result = jedis.eval(lua_scripts_lock, Collections.singletonList(REDIS_LOCK), Arrays.asList(value,"10"));*/ try { if(exist) { Covid[] dailyList = restTemplate.getForObject(covidDaliyDataApi, Covid[].class); logger.info("COVID daily data size ===>>>>>[{}]", dailyList.length); covidRepository.saveAll(Arrays.asList(dailyList)); } } catch (RestClientException e) { e.printStackTrace(); } finally { String lua_script_unlock = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; Object obj=jedis.eval(lua_script_unlock , Collections.singletonList(REDIS_LOCK), Collections.singletonList(value)); if(obj.toString().equals(1)){ logger.info("分布式锁{}删除成功",REDIS_LOCK); } jedisPool.close(); /* String currLockValue= (String) redisTemplate.opsForValue().get(REDIS_LOCK); if(currLockValue.equals(value)) redisTemplate.delete(REDIS_LOCK);*/ } }
- redis官方推荐 Redisson
//引入依赖 implementation group: 'org.redisson', name: 'redisson', version: '3.15.0' //在集群环境下Redisson @Bean public Redisson redisson() { RedisProperties properties = redisProperties(); List<String> clusterNodes = new ArrayList<>(); for (int i = 0; i < properties.getCluster().getNodes().size(); i++) { clusterNodes.add("redis://" + properties.getCluster().getNodes().get(i)); } Config config = new Config(); ClusterServersConfig clusterServersConfig = config.useClusterServers() .addNodeAddress(clusterNodes.toArray(new String[clusterNodes.size()])); clusterServersConfig.setPassword(properties.getPassword());//设置密码 return (Redisson) Redisson.create(config); } //RLock使用 RLock rLock = redisson.getLock(REDIS_LOCK); rLock.lock(); try { //业务代码 } catch (Exception e) { e.printStackTrace(); } finally { if(rLock.isLocked()&&rLock.isHeldByCurrentThread()){ rLock.unlock(); } }
分享到: