1. 连接redis
默认有三种方式连接redis.
第一种:jedis—传统的项目–ssm
第二种:lettuce:---->刚出现没有多久就被springboot整合进来。
第三种:springboot连接redis
1.1 jedis操作redis服务器
(1)引入jedis依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>4.3.1</version>
</dependency>
(2)编写相关的代码
@Testpublicvoidtest01(){// Jedis(String host, int port)Jedis jedis =newJedis("192.168.80.128",6379);// 该类包含很多对redis操作的方法,这些方法和原来我们使用的命令一样Set<String> keys = jedis.keys("*");System.out.println(keys);// 对string数据类型操作
jedis.set("k1","坤");String k1 = jedis.get("k1");System.out.println("k1: "+ k1);
jedis.setex("k2",16,"有很多");String k2 = jedis.get("k2");System.out.println("k2: "+ k2);// 对hash数据类型操作Map<String,String> map =newHashMap<>();
map.put("name","座右铭");
map.put("age","18");
map.put("hobby","优惠点");
jedis.hset("k3",map);Map<String,String> k3 = jedis.hgetAll("k3");System.out.println(k3);// 对list类型数据操作
jedis.lpush("k4","座右铭","优惠点","老油条","屏幕膜","万美元");List<String> k4 = jedis.lrange("k4",0,-1);System.out.println(k4);
jedis.close();}
每次使用jedis对象时 都需要自己创建,当使用完后,需要关闭该对象。===>jedis中也存在连接池.
1.2 jedis连接池的使用
@TestpublicvoidtestPool(){//连接池的配置信息JedisPoolConfig config=newJedisPoolConfig();
config.setMaxTotal(10);//最多的连接个数
config.setMaxIdle(10);//最多空闲的连接个数
config.setMinIdle(2);//最小的空闲个数
config.setTestOnBorrow(true);//在获取连接对象时是否验证该连接对象的连通性//创建连接池对象JedisPool jedisPool=newJedisPool(config,"192.168.80.128",6379);Jedis jedis = jedisPool.getResource();//清空当前库
jedis.flushDB();String set = jedis.set("k1","v1");String k1 = jedis.get("k1");System.out.println(k1);
jedis.close();//归还连接池}
1.3 测试jedis使用和不使用连接池的效率
@Testpublicvoidtest03(){//连接池的配置信息JedisPoolConfig config=newJedisPoolConfig();
config.setMaxTotal(100);//最多的连接个数
config.setMaxIdle(10);//最多空闲的连接个数
config.setMinIdle(2);//最小的空闲个数
config.setTestOnBorrow(true);//在获取连接对象时是否验证该连接对象的连通性//创建连接池对象JedisPool jedisPool=newJedisPool(config,"192.168.80.128",6379);long start =System.currentTimeMillis();for(int i=0;i<10000;i++){Jedis jedis = jedisPool.getResource();String ping = jedis.ping();
jedis.close();}long end =System.currentTimeMillis();System.out.println("耗时:"+(end-start));}@Testpublicvoidtest02(){long start =System.currentTimeMillis();//Jedis(String host, int port)for(int i=0;i<10000;i++){Jedis jedis=newJedis("192.168.80.128",6379);String ping = jedis.ping();
jedis.close();}long end =System.currentTimeMillis();System.out.println("耗时:"+(end-start));}
1.4 springboot整合redis
springboot在整合redis时提高两个模板类,StringRedisTemplate和RedisTemplate.以后对redis的操作都在该模板类中。StringRedisTemplate是RedisTemplate的子类。
<!--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>
修改配置文件
#redis的配置信息
spring.data.redis.host=192.168.80.128
spring.data.redis.port=6379
#最多获取数
spring.data.redis.lettuce.pool.max-active=8
spring.data.redis.lettuce.pool.max-wait=-1ms
spring.data.redis.lettuce.pool.max-idle=8
spring.data.redis.lettuce.pool.min-idle=0
测试:
packagecom.zym;importorg.junit.jupiter.api.Test;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.boot.test.context.SpringBootTest;importorg.springframework.data.redis.core.*;importjava.util.*;@SpringBootTestclassSpringbootRedisApplicationTests{//因为springboot整合redis时会把StringRedisTemplate创建并交于spring容器管理@AutowiredprivateStringRedisTemplate redisTemplate;@TestvoidcontextLoads(){//关于key的操作System.out.println(redisTemplate.keys("*"));//获取所有的key//是否存在指定的keyBoolean k1 = redisTemplate.hasKey("k1");System.out.println("是否存在指定的key:"+ k1);// 删除指定的keyBoolean k11 = redisTemplate.delete("k1");System.out.println("删除是否成功:"+ k11);}@Testvoidtest1(){//操作字符串===StringRedisTemplate会把对每一种数据的操作单独封装成一个类。//ValueOperations专门操作字符串ValueOperations<String,String> forValue = redisTemplate.opsForValue();//set 向指定key中设置value
forValue.set("k1","你好");//get 获取指定key的值String k12 = forValue.get("k1");System.out.println("k12=="+ k12);//mset 同时设置一个或多个 key-value 对Map<String,String> map =newHashMap<>();
map.put("k1","座右铭");
map.put("k2","有很多");
map.put("k3","另一条");
map.put("k4","屏幕膜");
forValue.multiSet(map);//mget 获取所有(一个或多个)给定 key 的值List<String> list =newArrayList<>();
list.add("k1");
list.add("k2");
list.add("k3");
list.add("k4");List<String> stringList = forValue.multiGet(list);System.out.println(stringList);//setnxBoolean aBoolean = forValue.setIfAbsent("k2","你也好");System.out.println("是否存入成功:"+ aBoolean);//incr decrLong k3 = forValue.increment("k3",20);System.out.println("k3:"+ k3);Long decrement = forValue.decrement("k3",20);System.out.println("k3:"+ decrement);}@Testvoidtest2(){//可以hash操作//hsetHashOperations<String,Object,Object> forHash = redisTemplate.opsForHash();
forHash.put("k1","name","平家祥");
forHash.put("k1","age","16");
forHash.put("k1","sex","男");Map<String,String> map =newHashMap<>();
map.put("name","胡萌");
map.put("age","17");
map.put("sex","女");
forHash.putAll("k2", map);//hgetSystem.out.println(forHash.get("k1","name"));//hgetallMap<Object,Object> k1 = forHash.entries("k1");System.out.println(k1);//hkeysSet<Object> k11 = forHash.keys("k1");System.out.println(k11);//hvalsSystem.out.println(forHash.values("k1"));//flushall
forHash.notifyAll();}@Testvoidtest3(){//可以list操作ListOperations<String,String> forList = redisTemplate.opsForList();//lpush
forList.leftPushAll("k1","name","age","sex");//lrangeList<String> k1 = forList.range("k1",0,-1);System.out.println(k1);//lpopString leftPop = forList.leftPop("k1");System.out.println(leftPop);}@Testvoidtest4(){//可以set操作SetOperations<String,String> forSet = redisTemplate.opsForSet();//sadd
forSet.add("k1","z","y","m","l");//smembersSet<String> k1 = forSet.members("k1");System.out.println(k1);//SRANDMEMBERString randomMember = forSet.randomMember("k1");System.out.println(randomMember);// sinterSet<String> intersect = forSet.intersect("k1","k2");System.out.println(intersect);}@Testvoidtest5(){//可以zset操作ZSetOperations<String,String> forZSet = redisTemplate.opsForZSet();//zadd
forZSet.add("k1","y",99);//zrangeSet<String> k1 = forZSet.range("k1",0,-1);System.out.println(k1);//ZREVRANKSet<String> k11 = forZSet.reverseRange("k1",0,-1);System.out.println(k11);}}
如果每次使用都人为指定序列化方式,统一设置redisTemplate的序列化
packagecom.zym.springbootredis.config;importcom.fasterxml.jackson.annotation.JsonAutoDetect;importcom.fasterxml.jackson.annotation.PropertyAccessor;importcom.fasterxml.jackson.databind.ObjectMapper;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.data.redis.connection.RedisConnectionFactory;importorg.springframework.data.redis.core.RedisTemplate;importorg.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;importorg.springframework.data.redis.serializer.RedisSerializer;importorg.springframework.data.redis.serializer.StringRedisSerializer;@ConfigurationpublicclassRedisConfig{@SuppressWarnings("all")@Bean//该方法的返回对象交于spring容器管理publicRedisTemplate<String,Object>redisTemplate(RedisConnectionFactory factory){RedisTemplate<String,Object> template =newRedisTemplate<>();RedisSerializer<String> redisSerializer =newStringRedisSerializer();Jackson2JsonRedisSerializer jackson2JsonRedisSerializer =newJackson2JsonRedisSerializer(Object.class);ObjectMapper om =newObjectMapper();
om.setVisibility(PropertyAccessor.ALL,JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
template.setConnectionFactory(factory);//key序列化方式
template.setKeySerializer(redisSerializer);//value序列化
template.setValueSerializer(jackson2JsonRedisSerializer);//value hashmap序列化
template.setHashValueSerializer(jackson2JsonRedisSerializer);//field序列化 key field value
template.setHashKeySerializer(redisSerializer);return template;}}
上面的连接都是连接的单机版的redis,真实项目它们的redis都是集群模式.
1.5 springboot连接集群
server.port=8888
spring.datasource.url=jdbc:mysql://192.168.1.51:3306/qy163
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=123456
#日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
#redis的配置信息
#spring.redis.host=192.168.80.128
#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
# 设置redis重定向的次数---根据主节点的个数
spring.redis.cluster.max-redirects=3
spring.redis.cluster.nodes=192.168.80.128:7001,192.168.80.128:7002,192.168.80.128:7003,192.168.80.128:7004,192.168.80.128:7005,192.168.80.128:7006
2. redis的应用场景
2.1 redis可以作为缓存
(1) 缓存的原理
(2)缓存的作用:
减少访问数据库的频率。–提高系统的性能。
(3)什么样的数据适合放入缓存
- 查询频率高的
- 修改频率低的
- 数据安全性要求低的。
(4)如何使用缓存
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.3.12.RELEASE</version><relativePath/><!-- lookup parent from repository --></parent><groupId>com.zym</groupId><artifactId>demo111</artifactId><version>0.0.1-SNAPSHOT</version><name>demo111</name><description>demo111</description><properties><java.version>8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>
配置文件
server.port=8888
spring.datasource.url=jdbc:mysql:///qy163
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.80.128
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
servie
@ServicepublicclassStudentServiceImplimplementsStudentService{@ResourceprivateStudentMapper studentMapper;@ResourceprivateRedisTemplate<String,Object> redisTemplate;@OverridepublicList<Student>findAll(){ValueOperations<String,Object> forValue = redisTemplate.opsForValue();Object student = forValue.get("students");if(student !=null){return(List<Student>) student;}List<Student> students = studentMapper.selectList(null);if(students !=null){
forValue.set("students", students);}return students;}@OverridepublicStudentgetById(Integer id){ValueOperations<String,Object> forValue = redisTemplate.opsForValue();Object o = forValue.get("student"+ id);if(o !=null){if(o instanceofNullObject){returnnull;}return(Student) o;}Student student = studentMapper.selectById(id);if(student !=null){
forValue.set("student"+ id, student);}else{
forValue.set("student"+ id,newNullObject(),5,TimeUnit.MINUTES);}return student;}publicIntegerinsert(Student student){
redisTemplate.delete("student"+ student.getId());return studentMapper.insert(student);}publicIntegerupdate(Student student){
redisTemplate.delete("student"+ student.getId());return studentMapper.updateById(student);}publicIntegerdelete(Integer id){
redisTemplate.delete("student"+ id);return studentMapper.deleteById(id);}}
当执行增删改操纵时必须保证缓存和数据库数据一致性。—删除缓存
3. redis使用分布式锁
(1)通过使用jmeter压测工具测试
同一个库存数被多个线程卖,线程安全问题。—思考:之间出现线程安全问题时如何解决。
可以使用锁解决:----synchronized和Lock锁
packagecom.ykq.service;importcom.ykq.dao.StockDao;importcom.ykq.entity.Stock;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.stereotype.Service;importjava.util.concurrent.TimeUnit;importjava.util.concurrent.locks.Lock;importjava.util.concurrent.locks.ReentrantLock;/**
* @program: redis-lock-qy145
* @description:
* @author: 闫克起2
* @create: 2022-03-01 11:13
**/@ServicepublicclassStockService_lock_syn{@AutowiredprivateStockDao stockDao;publicstaticObject o=newObject();Lock lock=newReentrantLock();public/*synchronized*/StringjianStock(Integer pid){try{
lock.lock();//加锁//1. 查询指定的商品库存Stock stock = stockDao.selectById(pid);if(stock.getNum()>0){//2.库存减1
stock.setNum(stock.getNum()-1);
stockDao.updateById(stock);System.out.println("库存剩余数量:"+ stock.getNum());return"减库存成功";}else{System.out.println("库存不足");return"库存减失败";}}finally{
lock.unlock();//释放锁}}}
上面的synchronized或Lock锁是否适合集群模式|分布式系统。不适合、因为synchronized都是基于JVM的本地锁。
需要在idea中跑项目的集群
配置nginx
启动nginx
jmeter压测
两台集群出现重卖问题。
3.2 使用redis来解决分布式锁
@AutowiredprivateStringRedisTemplate redisTemplate;/**
* 2.0
* @param pid
* @return
*/publicStringjianStock(Integer pid){//占锁ValueOperations<String,String> forValue = redisTemplate.opsForValue();//占锁失败while(!forValue.setIfAbsent("product::"+ pid,"",30,TimeUnit.SECONDS)){try{Thread.sleep(20);}catch(InterruptedException e){
e.printStackTrace();}}//占锁成功try{//1. 查询指定的商品库存Stock stock = stockDao.selectById(pid);if(stock.getNum()>0){//2.库存减1
stock.setNum(stock.getNum()-1);
stockDao.updateById(stock);System.out.println("库存剩余数量:"+ stock.getNum());return"减库存成功";}else{System.out.println("库存不足");return"库存减失败";}}finally{//释放锁资源
redisTemplate.delete("product::"+pid);}}
如果你的业务代码的执行时间超过30s,当前线程删除的是其他线程的锁资源。 --watchDog机制
每个10s检测当前线程是否还持有所资源,如果持有则为当前线程延迟。—可以自己设置watchDog机制,—第三方Redission完美的解决分布式锁。
3.3 redisson完美解决redis超时问题
<dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.13.4</version></dependency>
(2)main函数
@Bean//创建redisson交于spring容器来管理publicRedissonClientredisson(){Config config =newConfig();
config.useSingleServer().setAddress("redis://192.168.80.128:6379");RedissonClient redisson =Redisson.create(config);return redisson;}
(3)使用
@AutowiredprivateRedissonClient redisson;/**
* 3.0
* @param pid
* @return
*/publicStringjianStock(Integer pid){RLock lock = redisson.getLock("product::"+ pid);try{
lock.lock(30,TimeUnit.SECONDS);//加锁: 如果程序执行是出现一次//1. 查询指定的商品库存Stock stock = stockDao.selectById(pid);if(stock.getNum()>0){//2.库存减1
stock.setNum(stock.getNum()-1);
stockDao.updateById(stock);System.out.println("库存剩余数量:"+ stock.getNum());return"减库存成功";}else{System.out.println("库存不足");return"库存减失败";}}finally{
lock.unlock();}}
4. redis面试题
一、Redis是什么?
redis是使用C语言编写的一个高速缓存数据库,它以key-value形式存储数据,而且它支持的数据类型非常丰富。
二、Redis都有哪些使用场景?
- 热点数据的缓存
- 计数器
- 排行榜。
- 实现分布式锁
- 使用session的共享–后面项目时使用
三、Redis支持的数据类型有哪些?
- String 2. Hash 3.List 4.Set 5.ZSet(sorted set)
四、Redis为什么是单线程的?
因为Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且CPU不会成为瓶颈,那就顺理成章地采用单线程的方案了
五、Redis真的是单线程的吗?
并不是真的单线程,比如:RDB—Bgsave时,创建一个子线程
六、Redis持久化有几种方式?
RDB(快照):save,bgsave,配置文件自动
AOF:将每一条命令写入AOF文件中
七、什么是缓存穿透?怎么解决?
- 查询的数据在数据库中不存在,缓存中也不存在,这时有可能有人恶意访问这种数据。这些请求都会访问数据库,从而出现数据库压力过大。
情景:(1) 比如id不合法— (2)确实数据库中不存在。
解决: 1. 在controller加校验
2. 我们可以在缓存中存入一个空对象,但是对象的过期时间不要太长,一般不会
超过5分钟。
3. 可以使用布隆过滤器。
八、怎么保证缓存和数据库数据的一致性?
- 设置合理的过期时间
- 当执行增删改时需要删除缓存数据
九、Redis,什么是缓存雪崩?怎么解决?
缓存雪崩:就是在某一时刻出现大量数据过期,而这时就有大量的请求访问该数据,这种现象叫做缓存雪崩。
什么情况下会出现大量数据过期:
- 项目刚刚上线
- redis服务器宕机
- 缓存数据真实过期
解决方案:
- 上线前预热数据。
- 集群
- 设置过期时间时要分散设置。
十、Redis怎么实现分布式锁?
使用redis中的setnx命令–占锁,当业务代码执行完毕后释放锁资源,而释放锁的命令是del。
十一、 redis在实现分布式锁式有什么缺陷
超时问题: 业务代码执行时间超过锁时间。使用:watchDog机制。 我们使用第三方:redisson
十二、Redis 淘汰策略有哪些?
版权归原作者 资源码 所有, 如有侵权,请联系我们删除。