Java连接redis

redis支持哪些语言可以操作

 

 

使用jedis

(1)添加jedis依赖

<dependency>
  <groupId>redis.clients</groupId>
  <artifactId>jedis</artifactId>
  <version>3.6.0</version>
</dependency>

(2)代码测试

public class TestJedis {
    @Test
    //测试redis连接
    public void test01(){
        //String host, int port redis服务器的ip和端口号
        Jedis jedis = new Jedis("192.168.100.130",6379);
        //字符串操作
        System.out.println("=============字符串操作==================");
        String set = jedis.set("k1", "v1");
        System.out.println(set);
        String k1 = jedis.get("k1");
        System.out.println(k1);
        Long k11 = jedis.expire("k1", 30l);
        System.out.println(k11);
        Boolean k12 = jedis.exists("k1");
        System.out.println(k12);
        String mset = jedis.mset("key1", "value1", "key2", "value2");
        System.out.println(mset);
        Set<String> keys = jedis.keys("*");
        System.out.println(keys);
 
 
        System.out.println("=============list操作==============");
        Long k2 = jedis.lpush("k2", "1", "2", "3");
        System.out.println(k2);
        List<String> k21 = jedis.lrange("k2", 0, -1);
        System.out.println(k21);
    }
}

 

使用连接池连接redis

@Test
public void testPool(){
    //this(poolConfig, (String)host, 6379);
    //创建连接池配置类
    JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
    jedisPoolConfig.setMaxIdle(10);
    jedisPoolConfig.setMinIdle(3);
    jedisPoolConfig.setMaxWaitMillis(3000);
    JedisPool jedisPool = new JedisPool(jedisPoolConfig,"192.168.100.130",6379);
    Jedis resource = jedisPool.getResource();
    List<String> k2 = resource.lrange("k2", 1, -1);
    System.out.println(k2);
}

比较jedis和jedisPool的效率

@Test
public void compare(){
    Jedis jedis = new Jedis("192.168.100.130",6379);
    long start = System.currentTimeMillis();
    for (int i = 0; i < 1000; i++) {
        jedis.ping();
        jedis.close();
    }
    long end = System.currentTimeMillis();
    System.out.println("不使用连接池:"+(end-start));
 
 
    JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
    jedisPoolConfig.setMaxIdle(10);
    jedisPoolConfig.setMinIdle(3);
    jedisPoolConfig.setMaxWaitMillis(3000);
    JedisPool jedisPool = new JedisPool(jedisPoolConfig,"192.168.100.130",6379);
    long start1 = System.currentTimeMillis();
    for (int i = 0; i < 1000; i++) {
        //从连接池获取资源
        Jedis resource = jedisPool.getResource();
        resource.ping();
        //释放资源到连接池
        resource.close();
    }
    long end1 = System.currentTimeMillis();
    System.out.println("使用连接池:"+(end1-start1));
}

 使用连接池比不使用连接池的效率高三倍。

Java连接redis集群模式

开启redis集群

 

连接Windows可视化工具redis plus

注意一定要放行集群中所有的端口或者关闭防火墙

firewall-cmd --add-port=6001/tcp --zone=public --permanent

systemctl restart firewalld

 测试

@Test
public void testCluster(){
    Set<HostAndPort> nodes = new HashSet<>();
    nodes.add(new HostAndPort("192.168.100.130",6001));
    nodes.add(new HostAndPort("192.168.100.130",6002));
    nodes.add(new HostAndPort("192.168.100.130",6003));
    nodes.add(new HostAndPort("192.168.100.130",6004));
    nodes.add(new HostAndPort("192.168.100.130",6005));
    nodes.add(new HostAndPort("192.168.100.130",6006));
    JedisCluster jedisCluster = new JedisCluster(nodes);
    Long hset = jedisCluster.hset("dumpling", "name", "dumpling");
    System.out.println(hset);
}

 

 

springboot整合redis

springboot对redis的操作封装了两个StringRedisTemplate和RedisTemplate类,StringRedisTemplate是RedisTemplate的子类,StringRedisTemplate它只能存储字符串类型,无法存储对象类型。要想用StringRedisTemplate存储对象必须把对象转为json字符串。
 

1、StringRedisTemplate

(1)引入相关依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

(2)编写配置文件

# redis配置
spring.redis.host=192.168.100.130
spring.redis.port=6380

(3)注入StringRedisTemplate

@Autowired
private StringRedisTemplate stringRedisTemplate;

(4)使用StringRedisTemplate

该类把对每种数据类型的操作,单独封装了相应的内部类。 

存储字符串类型数据

@SpringBootTest
class Jedis02ApplicationTests {
 
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
 
    @Test
    public void testString(){
        //获取所有的key
        stringRedisTemplate.keys("*");
        //是否存在指定的key
        stringRedisTemplate.hasKey("key");
        //删除指定的key
        stringRedisTemplate.delete("key");
 
        //对字符串进行操作
        ValueOperations<String, String> forValue = stringRedisTemplate.opsForValue();
        //存入字符串类型的value并设置过期时间
        forValue.set("姓名","张三",24, TimeUnit.HOURS);
        //相当于setnx(),存入成功则返回true,存入失败则返回false
        Boolean aBoolean = forValue.setIfAbsent("姓名", "李四");
        System.out.println(aBoolean);
        //在指定key的value值追加指定内容,返回值为value值的大小
        Integer append = forValue.append("姓名", "大好人");
        System.out.println(append);
 
    }
 
}

 

 

存储hash类型的数据 

在StringRedisTemplate类中所有的属性都必须为String类型

@SpringBootTest
class Jedis02ApplicationTests {
 
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
 
    @Test
    public void testHash(){
        //对hash类型操作
        HashOperations<String, Object, Object> forHash = stringRedisTemplate.opsForHash();
        forHash.put("hash1","name","dumpling");
        forHash.put("hash1","age","18");
 
        Map<String,String> map = new HashMap<>();
        map.put("name","skin");
        map.put("address","China");
        forHash.putAll("hash2",map);
 
        //获取指定key的所有内容
        Map<Object, Object> hash2 = forHash.entries("hash2");
        System.out.println(hash2);
 
        //获取指定key的指定field的value值
        Object o = forHash.get("hash1", "name");
        System.out.println(o);
 
        //获取指定key的所有field值,返回类型为set,因为field值没有重复的
        Set<Object> hash1 = forHash.keys("hash1");
        System.out.println(hash1);
 
        //获取指定key的所有value值,返回类型为list,因为value值的内容有重复的
        List<Object> hash11 = forHash.values("hash1");
        System.out.println(hash11);
    }
}

 

 

 

2、RedisTemplate

RedisTemplate类默认的采用jdk自带的序列化方式,我们在使用的时候会发现在redis数据库中会有乱码现象,我们需要手动指定序列化方式。

 

 

@SpringBootTest
class Jedis02ApplicationTests02 {
 
    @Autowired
    private RedisTemplate redisTemplate;
 
    @Test
    public void test(){
        //为field设置序列化方式
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        //为value设置序列化方式
        redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<Object>(Object.class));
        ValueOperations valueOperations = redisTemplate.opsForValue();
 
        valueOperations.set("String1","dumpling");
        Object o = valueOperations.get("String1");
        System.out.println(o);
 
        valueOperations.set("user",new User("dumpling",23));
    }
}

 

上面的RedisTemplate需要每次都指定key value以及field的序列化方式,我们可以写一个配置类,为RedisTemplate指定好序列化。以后再用就无需指定。

redis的使用场景

1、作为缓存

数据存储在内存中,数据查询速度快。可以分摊数据库压力。

 

适合放入缓存的数据:

查询频率比较高,修改的频率比较低

安全系数低的数据

使用redis作为缓存

这里使用mybatis-plus来做演示

(1)引入相关依赖

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.2</version>
</dependency>

(2)编写配置文件

# redis配置
spring.redis.host=192.168.100.130
spring.redis.port=63780
 
# mysql配置
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=990412
spring.datasource.url=jdbc:mysql://localhost:3306/stu?serverTimezone=Asia/Shanghai
 
# 日志设置
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

(3)测试

@Service
public class StuService {
 
    @Autowired
    private StuMapper stuMapper;
    @Autowired
    RedisTemplate redisTemplate;
 
    public Stu findById(Integer id){
        ValueOperations valueOperations = redisTemplate.opsForValue();
        Object o = valueOperations.get("stu::" + id);
        if (o!=null){
            return (Stu) o;
        }
        Stu byId = stuMapper.selectById(id);
        if (byId!=null){
            valueOperations.set("stu::"+id,byId,24, TimeUnit.HOURS);
        }
        System.out.println(byId);
        return byId;
    }
 
    public Integer insert(Stu stu){
        int insert = stuMapper.insert(stu);
        return insert;
    }
 
    public Integer delete(Integer id){
        redisTemplate.delete("stu::"+id);
        int i = stuMapper.deleteById(id);
        return i;
    }
 
    public Stu update(Stu stu){
        redisTemplate.delete("stu::"+stu.getId());
        stuMapper.updateById(stu);
        return stu;
    }
}

 

我们在查询时从缓存中查找和从数据库查找之后存入缓存的业务: 前部分代码等同于@before通知,后部分代码等同于后置通知。 我们可以AOP完成缓存代码和业务代码分离。

spring框架帮我们提供了相应的注解。解析该注解就可以达到我们的目的。

(1)加入缓存的配置类

@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
    RedisSerializer<String> redisSerializer = new StringRedisSerializer();
    Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
    //解决查询缓存转换异常的问题
    ObjectMapper om = new ObjectMapper();
    om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
    om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
    jackson2JsonRedisSerializer.setObjectMapper(om);
    // 配置序列化(解决乱码的问题),过期时间600秒
    RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
            .entryTtl(Duration.ofSeconds(600)) //缓存过期10分钟 ---- 业务需求。
            .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))//设置key的序列化方式
            .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer)) //设置value的序列化
            .disableCachingNullValues();
    RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
            .cacheDefaults(config)
            .build();
    return cacheManager;
}

(2)开启注解

 (3) 使用注解

@Service
public class StuService {
 
    @Autowired
    private StuMapper stuMapper;
 
    //查询注解 cacheNames表示缓存的名称 key:唯一标志---stu::key
    // 先从缓存中查看key为(cacheNames::key)的是否存在
    // 如果存在则不会执行方法体,如果不存在则执行方法体并把方法的返回值存入缓存中
    @Cacheable(cacheNames = {"stu"},key = "#id")
    public Stu findById(Integer id){
        Stu byId = stuMapper.selectById(id);
        return byId;
    }
 
    public Integer insert(Stu stu){
        int insert = stuMapper.insert(stu);
        return insert;
    }
 
    //先删除缓存,在执行方法体
    @CacheEvict(cacheNames = "stu",key = "#id")
    public Integer delete(Integer id){
        int i = stuMapper.deleteById(id);
        return i;
    }
 
    //这个注解可以确保方法被执行,同时方法的返回值也被记录到缓存中,实现缓存和数据库的同步更新
    @CachePut(cacheNames = "stu",key = "#stu.id")
    public Stu update(Stu stu){
        stuMapper.updateById(stu);
        return stu;
    }
}

 

 2、分布式锁

使用压测工具测试高并发下带来线程安全问题

 

我们可以发现同一个库存被使用了n次,数据库中也出现了负数,这是线程安全导致的。

解决方案:

1、使用synchronized或lock锁

@Service
public class ProductStockServiceImpl2 implements ProductStockService {
    @Autowired
    private ProductStockDao productStockDao;
 
    @Override
    public  String decreaseStock(Integer productId) {
        synchronized (this){
            //查看该商品的库存数量
            Integer stock = productStockDao.findStockByProductId(productId);
            if (stock > 0) {
                //修改库存每次-1
                productStockDao.updateStockByProductId(productId);
                System.out.println("扣减成功!剩余库存数:" + (stock - 1));
                return "success";
            } else {
                System.out.println("扣减失败!库存不足!");
                return "fail";
            }
        }
    }
}

 

 

使用synchronized 或者lock锁 如果我们搭建了项目集群,那么该锁无效。

测试:使用idea开启集群项目

 

 

 

我们发现运行结果中仍然有重复的,数据库中的值依然变成负数了

我们使用redis中存储key来作为锁。

@Service
public class ProductStockServiceImpl2 implements ProductStockService {
    @Autowired
    private ProductStockDao productStockDao;
 
    @Autowired
    private RedisTemplate redisTemplate;
 
    @Override
    public  String decreaseStock(Integer productId) {
        ValueOperations valueOperations = redisTemplate.opsForValue();
        Boolean aBoolean = valueOperations.setIfAbsent("productId::" + productId, "成功");
        if (aBoolean){
            try {
                //查看该商品的库存数量
                Integer stock = productStockDao.findStockByProductId(productId);
                if (stock > 0) {
                    //修改库存每次-1
                    productStockDao.updateStockByProductId(productId);
                    System.out.println("扣减成功!剩余库存数:" + (stock - 1));
                    return "success";
                } else {
                    System.out.println("扣减失败!库存不足!");
                    return "fail";
                }
            } finally {
                redisTemplate.delete("productId::" + productId);
            }
        }
        return "系统正忙~~~~~~~~~~";
    }
}

 

Redis分布式锁的缺陷


Redis分布式锁主要依靠Redis服务来完成,我们的应用程序其实是Redis节点的客户端,一旦客户端没有释放锁,服务端就会一直持有这个锁,其他进程中的线程就无法获取到这把锁,于是就会发生锁死的情况。所以我们在使用Redis分布式锁的时候,务必要设置锁的过期时间。

 

主要基于下面两点:
网络抖动

客户端A中的一个线程获取到了锁,然后执行finally中的释放锁的代码时,这时候网络出问题了,导致客户端A没有成功释放锁。此时对于redis服务端来说,它会一直把锁给客户端A,这样的话其他客户端自然也就不能获取到这个锁。    如果是设置了过期时间的话,即使客户端和服务端的网络不通了,服务端依然在进行时间的计算,时间到了直接把锁释放掉,等网络通了,不影响其他客户端获取锁。

Redis宕机

客户端A获取到了锁,Redis服务器突然宕机,锁没有释放。等到Redis再次恢复的时候,Redis服务端还会把锁给到客户端A,这样也会发生锁死的情况。

    如果是设置了过期时间的话,服务器恢复后就会继续倒计时,时间到了服务器自动把锁释放,其他客户端也就可以尝试去获取锁了

Redis分布式锁不能解决超时的问题,分布式锁有一个超时时间,程序的执行如果超出了锁的超时时间就会出现问题。

解决方法:可以使用redission依赖解决在这个问题。

解决超时问题的原理:

为持有锁的线程开启一个守护线程,守护线程会每隔10秒检查当前线程是否还持有锁,如果持有则延迟生存时间。

 (1)引入依赖

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.13.4</version>
</dependency>

(2)将Redission交与spring容器管理

@SpringBootApplication
public class DistrinctLockApplication {
 
    public static void main(String[] args) {
        SpringApplication.run(DistrinctLockApplication.class, args);
    }
    //获取redisson对象并交于spring容器管理
    @Bean //setnx()
    public Redisson redisson(){
        Config config =new Config();
        config.useSingleServer().
                setAddress("redis://192.168.100.130:6379").
                //redis默认有16个数据库
                setDatabase(0);
        return (Redisson) Redisson.create(config);
    }
 
}

使用redission对象

@Service
public class ProductStockServiceImpl2 implements ProductStockService {
    @Autowired
    private ProductStockDao productStockDao;
 
    @Autowired
    private Redisson redisson;
 
    @Override
    public  String decreaseStock(Integer productId) {
        //获取锁
        RLock lock = redisson.getLock("product::" + productId);
        try {
            //加锁
            lock.lock(10,TimeUnit.SECONDS);
            //查看该商品的库存数量
            Integer stock = productStockDao.findStockByProductId(productId);
            if (stock > 0) {
                //修改库存每次-1
                productStockDao.updateStockByProductId(productId);
                System.out.println("扣减成功!剩余库存数:" + (stock - 1));
                return "success";
            } else {
                System.out.println("扣减失败!库存不足!");
                return "fail";
            }
        } finally {
            //释放锁
            lock.unlock();
        }
    }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值