当前位置:   article > 正文

java连接redis及redis应用场景_java 连接redis

java 连接redis

        1.连接redis的三种方式

        (1)jedis  ----------传统项目  ssm

        (2)lettuce  ------- 被springboot整合

        (3)spingboot连接redis

        1.1 Jedis 

        1.1.1 引入jedis依赖

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

        1.1.2 测试

        注意:每次使用jedis对象时 都需要自己创建,当使用完后,需要关闭该对象。===>jedis中也存在连接池.

  1. @Test
  2. public void test01(){
  3. Jedis jedis = new Jedis("192.168.61.129",6379);
  4. //string类型
  5. String set = jedis.set("k4", "hello java");
  6. System.out.println(set);
  7. jedis.get("k4");
  8. Set<String> keys = jedis.keys("*");
  9. System.out.println(keys);
  10. //hash类型
  11. Map<String,String> map = new HashMap<>();
  12. map.put("name","lht");
  13. map.put("age","22");
  14. map.put("sex","男");
  15. long k5 = jedis.hset("k5", map);
  16. System.out.println(k5);
  17. String hget = jedis.hget("k5", "name");
  18. System.out.println(hget);
  19. Map<String, String> k51 = jedis.hgetAll("k5");
  20. System.out.println(k51);
  21. //list 队列
  22. long lpush = jedis.lpush("k6", "aaa", "sss", "ddd");
  23. System.out.println(lpush);
  24. List<String> k6 = jedis.lpop("k6", 1);
  25. System.out.println(k6);
  26. jedis.close();
  27. }

        1.1.3 jedis连接池的使用

  1. @Test
  2. public void test02(){
  3. //连接池的配置
  4. JedisPoolConfig config = new JedisPoolConfig();
  5. config.setMaxTotal(10);//最多的连接个数
  6. config.setMaxIdle(10);//最多空闲个数
  7. config.setMinIdle(2);//最小空闲个数
  8. config.setTestOnBorrow(true);//在获取连接是验证连接的连通性
  9. //创建连接池对象
  10. JedisPool jedisPool = new JedisPool(config,"192.168.61.129",6379);
  11. Jedis jedis = jedisPool.getResource();
  12. String set = jedis.set("k7", "v7");
  13. String k7 = jedis.get("k7");
  14. System.out.println(k7);
  15. jedis.close();
  16. }

        1.1.4 测试jedis使用和不使用连接池的区别

        下面是使用jedis连接池

  1. @Test
  2. public void test03(){
  3. //连接池的配置信息
  4. JedisPoolConfig config=new JedisPoolConfig();
  5. config.setMaxTotal(100);//最多的连接个数
  6. config.setMaxIdle(10); //最多空闲的连接个数
  7. config.setMinIdle(2); //最小的空闲个数
  8. config.setTestOnBorrow(true);//在获取连接对象时是否验证该连接对象的连通性
  9. //创建连接池对象
  10. JedisPool jedisPool=new JedisPool(config,"192.168.61.129",6379);
  11. long start = System.currentTimeMillis();
  12. for(int i=0;i<10000;i++){
  13. Jedis jedis = jedisPool.getResource();
  14. String ping = jedis.ping();
  15. jedis.close();
  16. }
  17. long end = System.currentTimeMillis();
  18. System.out.println("耗时:"+(end-start));//耗时:6053
  19. }

        不使用jedis连接池

  1. @Test
  2. public void test04(){
  3. long start = System.currentTimeMillis();
  4. //Jedis(String host, int port)
  5. for(int i=0;i<10000;i++){
  6. Jedis jedis=new Jedis("192.168.61.129",6379);
  7. String ping = jedis.ping();
  8. jedis.close();
  9. }
  10. long end = System.currentTimeMillis();
  11. System.out.println("耗时:"+(end-start));//耗时:10581
  12. }

        可以看出,使用连接池比不使用连接池的耗时较少。 

        1.2 springboot整合redis

        springboot在整合redis时提高两个模板类,StringRedisTemplate和RedisTemplate.以后对redis的操作都在该模板类中。StringRedisTemplate是RedisTemplate的子类

        1.2.1 StringRedisTemplate

        1.2.1.1引入redis相关依赖

<!--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.2.1.2 修改配置文件

#redis的配置信息
spring.redis.host=192.168.61.129
spring.redis.port=6379
#数量信息
spring.redis.lettuce.pool.max-active=8
spring.redis.lettuce.pool.max-wait=-1ms
spring.redis.lettuce.pool.max-idle=8
spring.redis.lettuce.pool.min-idle=0

         1.2.1.3 测试

  1. @SpringBootTest
  2. class SpringbootRedisApplicationTests {
  3. //springboot整合redis时会把springredistemplate创建并交于spring容器管理
  4. @Autowired
  5. private StringRedisTemplate redisTemplate;
  6. @Test
  7. void contextLoads() {
  8. //关乎key的操作
  9. Set<String> keys = redisTemplate.keys("*");
  10. System.out.println("数据库中当前keys========"+keys);
  11. Boolean k1 = redisTemplate.hasKey("k1");//判断指定key是否存在
  12. System.out.println("指定key是否存在:"+k1);
  13. Boolean k11 = redisTemplate.delete("k1");//删除指定key
  14. System.out.println("删除指定key是否成功:"+k11);
  15. //操作字符串 StringRedisTemplate会把对每一种数据的操作单独封装为一个类
  16. ValueOperations<String, String> stringValue = redisTemplate.opsForValue();//value
  17. //set key value
  18. stringValue.set("k1","流浪地球2");
  19. //获取指定key get key
  20. String k12 = stringValue.get("k1");
  21. System.out.println("k1======"+k12);
  22. //setnx key value
  23. Boolean aBoolean = stringValue.setIfAbsent("k2", "战狼2");//key不存在就添加
  24. System.out.println("是否添加成功:" + aBoolean);
  25. //递增 k3 22 incr key
  26. //Long k3 = stringValue.increment("k3");
  27. // System.out.println(k3);//每次递增1
  28. Long k31 = stringValue.increment("k3", 4);//每次递增4
  29. System.out.println("k3========"+k31);
  30. //递减 decr key
  31. stringValue.decrement("k3",1);
  32. //mset key value key value
  33. Map<String,String> map = new HashMap<>();
  34. map.put("a1","v1");
  35. map.put("a2","v2");
  36. map.put("a3","v3");
  37. stringValue.multiSet(map);
  38. //mget key key key
  39. List<String> list = new ArrayList<>();
  40. list.add("a1");
  41. list.add("a2");
  42. list.add("a3");
  43. stringValue.multiGet(list);
  44. //hash操作
  45. HashOperations<String, Object, Object> hash = redisTemplate.opsForHash();
  46. //hset key filed value field value
  47. hash.put("k4","name","卢海腾");
  48. hash.put("k4","age","22");
  49. //hmset key filed value filed value
  50. Map<String,String> map2 = new HashMap<>();
  51. map2.put("name","张亚祥");
  52. map2.put("age","22");
  53. hash.putAll("k5",map2);
  54. //hgetall key 拿到key 所有的字段
  55. Map<Object, Object> k4 = hash.entries("k4");
  56. System.out.println(k4);
  57. //hkeys key 拿到指定key所有的filed属性
  58. Set<Object> k41 = hash.keys("k4");
  59. System.out.println(k41);
  60. //hvals key 拿到指定key所有的values
  61. System.out.println(hash.values("k4"));
  62. //list操作
  63. ListOperations<String, String> list2 = redisTemplate.opsForList();
  64. //lpush key value value
  65. list2.leftPushAll("l1","la","lb","lc");
  66. //lrange key start end
  67. list2.range("l1",0,-1);
  68. //lpop key 获取并移除第一个元素
  69. list2.leftPop("l1", Duration.ofMinutes(1));
  70. //set操作
  71. SetOperations<String, String> set = redisTemplate.opsForSet();
  72. //sadd key value value ....向集合添加一个或多个成员
  73. set.add("s1","ss","sss","ssss");
  74. set.add("s2","ss","sss","aas","aaa");
  75. //smemebers key 返回集合中的所有成员
  76. set.members("s1");
  77. // SRANDMEMBER KEY 随机获取一个或多个元素
  78. set.randomMember("s1");
  79. set.randomMembers("s1",2);
  80. //sinter key key: 返回给定所有集合的交集
  81. set.intersect("s1","s2");
  82. //zset有序集合 操作
  83. ZSetOperations<String, String> zset = redisTemplate.opsForZSet();
  84. //zadd key score value score value :向有序集合添加一个或多个成员,或者更新已存在成员的分数
  85. //添加一个
  86. zset.add("z1","math",90);
  87. //添加多个
  88. Set <ZSetOperations.TypedTuple<String>> set2 = new HashSet();
  89. DefaultTypedTuple<String> typedTuple = new DefaultTypedTuple<String>("math", 90.0);
  90. DefaultTypedTuple<String> typedTuple2 = new DefaultTypedTuple<String>("chinese", 80.0);
  91. DefaultTypedTuple<String> typedTuple3 = new DefaultTypedTuple<String>("english", 70.0);
  92. set2.add(typedTuple);
  93. set2.add(typedTuple2);
  94. set2.add(typedTuple3);
  95. zset.add("z2",set2);
  96. // zrange key 通过索引区间返回有序集合成指定区间内的成员
  97. zset.range("z2",0,-1);
  98. //ZREVRANK key start end 返回有序集合中指定成员的排名,有序集成员按分数值递减(从大到小)排序
  99. zset.rangeWithScores("z2",0,100);
  100. }
  101. }

        1.2.2 RedisTemplate

        它是StringRedisTemplate的父类,它类可以存储任意数据类型,但是任意类型必须序列化,默认采用的是jdk的序列化方式。jdk序列化方式阅读能力差,而且占用空间大. 我们在使用是一般需要人为指定序列化方式

  1. @SpringBootTest
  2. class SpringbootRedisApplicationTests2 {
  3. //springboot整合redis时会把springredistemplate创建并交于spring容器管理
  4. @Autowired
  5. private RedisTemplate redisTemplate;
  6. @Test
  7. void contextLoads() {
  8. redisTemplate.setKeySerializer(new StringRedisSerializer());
  9. redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
  10. ValueOperations valueOperations = redisTemplate.opsForValue();
  11. valueOperations.set("user2",new User(2,"张三丰","44"));
  12. Object user2 = valueOperations.get("user2");
  13. System.out.println(user2);
  14. }
  15. }

        如果每次使用都自己指定序列化方式会比较麻烦,可以通过配置进行统一序列化。

  1. @Configuration
  2. public class RedisConfig {
  3. //比如验证码
  4. @Bean //该方法的返回对象交于spring容器管理
  5. public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
  6. RedisTemplate<String, Object> template = new RedisTemplate<>();
  7. RedisSerializer<String> redisSerializer = new StringRedisSerializer();
  8. Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
  9. ObjectMapper om = new ObjectMapper();
  10. om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
  11. om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
  12. jackson2JsonRedisSerializer.setObjectMapper(om);
  13. template.setConnectionFactory(factory);
  14. //key序列化方式
  15. template.setKeySerializer(redisSerializer);
  16. //value序列化
  17. template.setValueSerializer(jackson2JsonRedisSerializer);
  18. //value hashmap序列化
  19. template.setHashValueSerializer(jackson2JsonRedisSerializer);
  20. //field序列化 key field value
  21. template.setHashKeySerializer(redisSerializer);
  22. return template;
  23. }
  24. }

         1.3 springboot连接集群

spring.redis.jedis.pool.max-active=8
spring.redis.jedis.pool.max-wait=-1ms
spring.redis.jedis.pool.max-idle=8
spring.redis.jedis.pool.min-idle=0
# 设置redis重定向的次数---根据主节点的个数
spring.redis.cluster.max-redirects=3
#集群spring.redis.cluster.nodes=192.168.61.129:7001,192.168.61.129:7002,192.168.61.129:7003,192.168.61.129:7004,192.168.61.129:7005,192.168.61.129:7006

        2. redis应用场景

        2.1 redis可以作为缓存

        2.1.1 缓存的原理

        2.1.2 缓存的作用

        减少访问数据库的频率,提高系统的性能

        2.1.3 什么样的数据适合放入缓存 

        (1)查询频率高的

        (2)修改频率低的

        (3)数据安全性要求高的

        2.1.4 如何使用缓存

        (1)添加相应的依赖

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  4. <modelVersion>4.0.0</modelVersion>
  5. <parent>
  6. <groupId>org.springframework.boot</groupId>
  7. <artifactId>spring-boot-starter-parent</artifactId>
  8. <version>2.3.12.RELEASE</version>
  9. <relativePath/> <!-- lookup parent from repository -->
  10. </parent>
  11. <groupId>com.ykq</groupId>
  12. <artifactId>qy163-springboot-redis02</artifactId>
  13. <version>0.0.1-SNAPSHOT</version>
  14. <name>qy163-springboot-redis02</name>
  15. <description>Demo project for Spring Boot</description>
  16. <properties>
  17. <java.version>8</java.version>
  18. </properties>
  19. <dependencies>
  20. <dependency>
  21. <groupId>org.springframework.boot</groupId>
  22. <artifactId>spring-boot-starter-data-redis</artifactId>
  23. </dependency>
  24. <dependency>
  25. <groupId>org.apache.commons</groupId>
  26. <artifactId>commons-pool2</artifactId>
  27. </dependency>
  28. <dependency>
  29. <groupId>com.baomidou</groupId>
  30. <artifactId>mybatis-plus-boot-starter</artifactId>
  31. <version>3.5.1</version>
  32. </dependency>
  33. <dependency>
  34. <groupId>org.springframework.boot</groupId>
  35. <artifactId>spring-boot-starter-web</artifactId>
  36. </dependency>
  37. <dependency>
  38. <groupId>mysql</groupId>
  39. <artifactId>mysql-connector-java</artifactId>
  40. </dependency>
  41. <dependency>
  42. <groupId>org.projectlombok</groupId>
  43. <artifactId>lombok</artifactId>
  44. <optional>true</optional>
  45. </dependency>
  46. <dependency>
  47. <groupId>org.springframework.boot</groupId>
  48. <artifactId>spring-boot-starter-test</artifactId>
  49. <scope>test</scope>
  50. </dependency>
  51. </dependencies>
  52. <build>
  53. <plugins>
  54. <plugin>
  55. <groupId>org.springframework.boot</groupId>
  56. <artifactId>spring-boot-maven-plugin</artifactId>
  57. <configuration>
  58. <excludes>
  59. <exclude>
  60. <groupId>org.projectlombok</groupId>
  61. <artifactId>lombok</artifactId>
  62. </exclude>
  63. </excludes>
  64. </configuration>
  65. </plugin>
  66. </plugins>
  67. </build>
  68. </project>

        (2)配置文件 

server.port=8888

spring.datasource.url=jdbc:mysql:///homework
spring.datasource.password=123456
spring.datasource.username=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

#redis的配置信息
spring.redis.host=192.168.61.129
spring.redis.port=6379
#最多获取数
spring.redis.lettuce.pool.max-active=8
spring.redis.lettuce.pool.max-wait=-1ms
spring.redis.lettuce.pool.max-idle=8
spring.redis.lettuce.pool.min-idle=0

         (3)service

        当执行增删改操纵时必须保证缓存和数据库数据一致性。---删除缓存

  1. @Service
  2. public class StudentServiceImpl implements StudentService {
  3. @Autowired
  4. private StudentMapper studentMapper;
  5. @Autowired
  6. private RedisTemplate<String,Object> redisTemplate;
  7. @Override
  8. public Student findById(Integer id) {
  9. ValueOperations<String, Object> forValue = redisTemplate.opsForValue();
  10. //1.查询缓存
  11. Object o = forValue.get("student::" + id);
  12. if(o!=null){ //缓存命中
  13. return (Student) o;
  14. }
  15. Student student = studentMapper.selectById(id);
  16. //2.查询到应该放入缓存
  17. if(student!=null){
  18. forValue.set("student::"+id,student);
  19. }
  20. return student;
  21. }
  22. @Override
  23. public Integer insert(Student student) {
  24. int insert = studentMapper.insert(student);
  25. return insert;
  26. }
  27. @Override
  28. public Integer delete(Integer id) {
  29. //缓存与数据库数据保持一致
  30. redisTemplate.delete("student::"+ id );
  31. int i = studentMapper.deleteById(id);
  32. return i;
  33. }
  34. @Override
  35. public Integer update(Student student) {
  36. redisTemplate.delete("student::"+ student.getSid());
  37. int i = studentMapper.updateById(student);
  38. return i;
  39. }
  40. }

        2.2  redis使用分布式锁

        2.2.1 通过使用jmeter压测工具测试

        同一个库存数被多个线程卖,会出现线程安全问题。--------------->出现线程安全问题时如何解决--------------->可以使用锁解决:----synchronized和Lock锁

  1. @Service
  2. public class StockService_lock_syn {
  3. @Autowired
  4. private StockDao stockDao;
  5. public static Object o=new Object();
  6. Lock lock=new ReentrantLock();
  7. public String jianStock(Integer pid){
  8. //1.0版本
  9. try {
  10. lock.lock();//加锁
  11. //1. 查询指定的商品库存
  12. Stock stock = stockDao.selectById(pid);
  13. if (stock.getNum() > 0) {
  14. //2.库存减1
  15. stock.setNum(stock.getNum() - 1);
  16. stockDao.updateById(stock);
  17. System.out.println("库存剩余数量:" + stock.getNum());
  18. return "减库存成功";
  19. } else {
  20. System.out.println("库存不足");
  21. return "库存减失败";
  22. }
  23. }
  24. finally {
  25. lock.unlock(); //释放锁
  26. }
  27. }
  28. }

        2.2.2 集群模式

        上面的synchronized或Lock锁是否适合集群模式|分布式系统。不适合、因为synchronized都是基于JVM的本地锁。

        (1) 在项目中跑集群

         (2) jmeter 压测

 

         经过压测可以发现两台集群出现了重复现象

        2.2.3 redis来解决分布式锁

        通过redis中setnx命令进行占锁,del来释放锁

  1. @Service
  2. public class StockService_lock_syn {
  3. @Autowired
  4. private StockDao stockDao;
  5. public static Object o=new Object();
  6. Lock lock=new ReentrantLock();
  7. @Autowired
  8. private StringRedisTemplate redisTemplate;
  9. public String jianStock(Integer pid){
  10. //2.0
  11. //占锁
  12. ValueOperations<String, String> forValue = redisTemplate.opsForValue();
  13. // Boolean aBoolean = forValue.setIfAbsent("product::" + pid, "", 30, TimeUnit.SECONDS);
  14. //占锁失败
  15. while (!forValue.setIfAbsent("product::" + pid, "", 30, TimeUnit.SECONDS)){
  16. try {
  17. Thread.sleep(20);
  18. } catch (InterruptedException e) {
  19. e.printStackTrace();
  20. }
  21. }
  22. //占锁成功
  23. try {
  24. //1. 查询指定的商品库存
  25. Stock stock = stockDao.selectById(pid);
  26. if (stock.getNum() > 0) {
  27. //2.库存减1
  28. stock.setNum(stock.getNum() - 1);
  29. stockDao.updateById(stock);
  30. System.out.println("库存剩余数量:" + stock.getNum());
  31. return "减库存成功";
  32. } else {
  33. System.out.println("库存不足");
  34. return "库存减失败";
  35. }
  36. }finally {
  37. //释放锁资源
  38. redisTemplate.delete("product::"+pid);
  39. }
  40. }
  41. }

        如果你的业务代码的执行时间超过30s,当前线程删除的是其他线程的锁资源。 --watchDog机制---------->每个10s检测当前线程是否还持有所资源,如果持有则为当前线程延迟。---可以自己设置watchDog机制------------>第三方Redission完美的解决分布式锁。

         2.2.4 redisson解决redis超时问题

         (1)引入相关依赖

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

        (2)主函数加载

  1. @SpringBootApplication
  2. public class RedisLockQy145Application {
  3. public static void main(String[] args) {
  4. SpringApplication.run(RedisLockQy145Application.class, args);
  5. }
  6. @Bean //创建redisson交于spring容器来管理
  7. public RedissonClient redisson() {
  8. Config config = new Config();
  9. config.setLockWatchdogTimeout(300);
  10. config.useSingleServer().setAddress("redis://192.168.61.129:6379");
  11. RedissonClient redisson = Redisson.create(config);
  12. return redisson;
  13. }
  14. }

         (3)测试

  1. @Service
  2. public class StockService_lock_syn {
  3. @Autowired
  4. private StockDao stockDao;
  5. public static Object o=new Object();
  6. Lock lock=new ReentrantLock();
  7. @Autowired
  8. private StringRedisTemplate redisTemplate;
  9. @Autowired
  10. private RedissonClient redisson;
  11. public String jianStock(Integer pid){
  12. //3.0 防止业务代码执行时间超过锁时间
  13. RLock lock = redisson.getLock("product::" + pid);
  14. try {
  15. lock.lock(30,TimeUnit.SECONDS);//加锁: 如果程序执行是出现一次
  16. //1. 查询指定的商品库存
  17. Stock stock = stockDao.selectById(pid);
  18. if (stock.getNum() > 0) {
  19. //2.库存减1
  20. stock.setNum(stock.getNum() - 1);
  21. stockDao.updateById(stock);
  22. System.out.println("库存剩余数量:" + stock.getNum());
  23. return "减库存成功";
  24. } else {
  25. System.out.println("库存不足");
  26. return "库存减失败";
  27. }
  28. }finally {
  29. lock.unlock();
  30. }
  31. }
  32. }

      

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小舞很执着/article/detail/812709
推荐阅读
相关标签
  

闽ICP备14008679号