赞
踩
(1)jedis ----------传统项目 ssm
(2)lettuce ------- 被springboot整合
(3)spingboot连接redis
<dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>4.3.1</version> </dependency>
注意:每次使用jedis对象时 都需要自己创建,当使用完后,需要关闭该对象。===>jedis中也存在连接池.
- @Test
- public void test01(){
- Jedis jedis = new Jedis("192.168.61.129",6379);
- //string类型
- String set = jedis.set("k4", "hello java");
- System.out.println(set);
- jedis.get("k4");
- Set<String> keys = jedis.keys("*");
- System.out.println(keys);
-
- //hash类型
- Map<String,String> map = new HashMap<>();
- map.put("name","lht");
- map.put("age","22");
- map.put("sex","男");
- long k5 = jedis.hset("k5", map);
- System.out.println(k5);
- String hget = jedis.hget("k5", "name");
- System.out.println(hget);
- Map<String, String> k51 = jedis.hgetAll("k5");
- System.out.println(k51);
-
- //list 队列
- long lpush = jedis.lpush("k6", "aaa", "sss", "ddd");
- System.out.println(lpush);
- List<String> k6 = jedis.lpop("k6", 1);
- System.out.println(k6);
-
- jedis.close();
- }
- @Test
- public void test02(){
- //连接池的配置
- JedisPoolConfig config = new JedisPoolConfig();
- config.setMaxTotal(10);//最多的连接个数
- config.setMaxIdle(10);//最多空闲个数
- config.setMinIdle(2);//最小空闲个数
- config.setTestOnBorrow(true);//在获取连接是验证连接的连通性
- //创建连接池对象
- JedisPool jedisPool = new JedisPool(config,"192.168.61.129",6379);
- Jedis jedis = jedisPool.getResource();
- String set = jedis.set("k7", "v7");
- String k7 = jedis.get("k7");
- System.out.println(k7);
- jedis.close();
- }
下面是使用jedis连接池
- @Test
- public void test03(){
- //连接池的配置信息
- JedisPoolConfig config=new JedisPoolConfig();
- config.setMaxTotal(100);//最多的连接个数
- config.setMaxIdle(10); //最多空闲的连接个数
- config.setMinIdle(2); //最小的空闲个数
- config.setTestOnBorrow(true);//在获取连接对象时是否验证该连接对象的连通性
- //创建连接池对象
- JedisPool jedisPool=new JedisPool(config,"192.168.61.129",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));//耗时:6053
- }
不使用jedis连接池
- @Test
- public void test04(){
- long start = System.currentTimeMillis();
- //Jedis(String host, int port)
- for(int i=0;i<10000;i++){
- Jedis jedis=new Jedis("192.168.61.129",6379);
- String ping = jedis.ping();
- jedis.close();
- }
- long end = System.currentTimeMillis();
- System.out.println("耗时:"+(end-start));//耗时:10581
- }
可以看出,使用连接池比不使用连接池的耗时较少。
springboot在整合redis时提高两个模板类,StringRedisTemplate和RedisTemplate.以后对redis的操作都在该模板类中。StringRedisTemplate是RedisTemplate的子类
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 测试
- @SpringBootTest
- class SpringbootRedisApplicationTests {
- //springboot整合redis时会把springredistemplate创建并交于spring容器管理
- @Autowired
- private StringRedisTemplate redisTemplate;
- @Test
- void contextLoads() {
-
- //关乎key的操作
- Set<String> keys = redisTemplate.keys("*");
- System.out.println("数据库中当前keys========"+keys);
- Boolean k1 = redisTemplate.hasKey("k1");//判断指定key是否存在
- System.out.println("指定key是否存在:"+k1);
- Boolean k11 = redisTemplate.delete("k1");//删除指定key
- System.out.println("删除指定key是否成功:"+k11);
-
- //操作字符串 StringRedisTemplate会把对每一种数据的操作单独封装为一个类
- ValueOperations<String, String> stringValue = redisTemplate.opsForValue();//value
- //set key value
- stringValue.set("k1","流浪地球2");
- //获取指定key get key
- String k12 = stringValue.get("k1");
- System.out.println("k1======"+k12);
- //setnx key value
- Boolean aBoolean = stringValue.setIfAbsent("k2", "战狼2");//key不存在就添加
- System.out.println("是否添加成功:" + aBoolean);
- //递增 k3 22 incr key
- //Long k3 = stringValue.increment("k3");
- // System.out.println(k3);//每次递增1
- Long k31 = stringValue.increment("k3", 4);//每次递增4
- System.out.println("k3========"+k31);
- //递减 decr key
- stringValue.decrement("k3",1);
- //mset key value key value
- Map<String,String> map = new HashMap<>();
- map.put("a1","v1");
- map.put("a2","v2");
- map.put("a3","v3");
- stringValue.multiSet(map);
- //mget key key key
- List<String> list = new ArrayList<>();
- list.add("a1");
- list.add("a2");
- list.add("a3");
- stringValue.multiGet(list);
-
- //hash操作
- HashOperations<String, Object, Object> hash = redisTemplate.opsForHash();
- //hset key filed value field value
- hash.put("k4","name","卢海腾");
- hash.put("k4","age","22");
- //hmset key filed value filed value
- Map<String,String> map2 = new HashMap<>();
- map2.put("name","张亚祥");
- map2.put("age","22");
- hash.putAll("k5",map2);
- //hgetall key 拿到key 所有的字段
- Map<Object, Object> k4 = hash.entries("k4");
- System.out.println(k4);
- //hkeys key 拿到指定key所有的filed属性
- Set<Object> k41 = hash.keys("k4");
- System.out.println(k41);
- //hvals key 拿到指定key所有的values
- System.out.println(hash.values("k4"));
-
- //list操作
- ListOperations<String, String> list2 = redisTemplate.opsForList();
- //lpush key value value
- list2.leftPushAll("l1","la","lb","lc");
- //lrange key start end
- list2.range("l1",0,-1);
- //lpop key 获取并移除第一个元素
- list2.leftPop("l1", Duration.ofMinutes(1));
-
- //set操作
- SetOperations<String, String> set = redisTemplate.opsForSet();
- //sadd key value value ....向集合添加一个或多个成员
- set.add("s1","ss","sss","ssss");
- set.add("s2","ss","sss","aas","aaa");
- //smemebers key 返回集合中的所有成员
- set.members("s1");
- // SRANDMEMBER KEY 随机获取一个或多个元素
- set.randomMember("s1");
- set.randomMembers("s1",2);
- //sinter key key: 返回给定所有集合的交集
- set.intersect("s1","s2");
-
- //zset有序集合 操作
- ZSetOperations<String, String> zset = redisTemplate.opsForZSet();
- //zadd key score value score value :向有序集合添加一个或多个成员,或者更新已存在成员的分数
- //添加一个
- zset.add("z1","math",90);
- //添加多个
- Set <ZSetOperations.TypedTuple<String>> set2 = new HashSet();
- DefaultTypedTuple<String> typedTuple = new DefaultTypedTuple<String>("math", 90.0);
- DefaultTypedTuple<String> typedTuple2 = new DefaultTypedTuple<String>("chinese", 80.0);
- DefaultTypedTuple<String> typedTuple3 = new DefaultTypedTuple<String>("english", 70.0);
- set2.add(typedTuple);
- set2.add(typedTuple2);
- set2.add(typedTuple3);
- zset.add("z2",set2);
- // zrange key 通过索引区间返回有序集合成指定区间内的成员
- zset.range("z2",0,-1);
- //ZREVRANK key start end 返回有序集合中指定成员的排名,有序集成员按分数值递减(从大到小)排序
- zset.rangeWithScores("z2",0,100);
- }
-
- }
它是StringRedisTemplate的父类,它类可以存储任意数据类型,但是任意类型必须序列化,默认采用的是jdk的序列化方式。jdk序列化方式阅读能力差,而且占用空间大. 我们在使用是一般需要人为指定序列化方式
- @SpringBootTest
- class SpringbootRedisApplicationTests2 {
- //springboot整合redis时会把springredistemplate创建并交于spring容器管理
- @Autowired
- private RedisTemplate redisTemplate;
- @Test
- void contextLoads() {
- redisTemplate.setKeySerializer(new StringRedisSerializer());
- redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
- ValueOperations valueOperations = redisTemplate.opsForValue();
- valueOperations.set("user2",new User(2,"张三丰","44"));
- Object user2 = valueOperations.get("user2");
- System.out.println(user2);
-
-
- }
-
- }
如果每次使用都自己指定序列化方式会比较麻烦,可以通过配置进行统一序列化。
- @Configuration
- public class RedisConfig {
- //比如验证码
- @Bean //该方法的返回对象交于spring容器管理
- public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
- RedisTemplate<String, Object> template = new RedisTemplate<>();
- 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);
- 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;
- }
- }
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
减少访问数据库的频率,提高系统的性能
(1)查询频率高的
(2)修改频率低的
(3)数据安全性要求高的
(1)添加相应的依赖
- <?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.ykq</groupId>
- <artifactId>qy163-springboot-redis02</artifactId>
- <version>0.0.1-SNAPSHOT</version>
- <name>qy163-springboot-redis02</name>
- <description>Demo project for Spring Boot</description>
- <properties>
- <java.version>8</java.version>
- </properties>
- <dependencies>
- <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>
- <dependency>
- <groupId>com.baomidou</groupId>
- <artifactId>mybatis-plus-boot-starter</artifactId>
- <version>3.5.1</version>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-web</artifactId>
- </dependency>
-
- <dependency>
- <groupId>mysql</groupId>
- <artifactId>mysql-connector-java</artifactId>
- </dependency>
- <dependency>
- <groupId>org.projectlombok</groupId>
- <artifactId>lombok</artifactId>
- <optional>true</optional>
- </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>
- <configuration>
- <excludes>
- <exclude>
- <groupId>org.projectlombok</groupId>
- <artifactId>lombok</artifactId>
- </exclude>
- </excludes>
- </configuration>
- </plugin>
- </plugins>
- </build>
-
- </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.Drivermybatis-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
当执行增删改操纵时必须保证缓存和数据库数据一致性。---删除缓存
- @Service
- public class StudentServiceImpl implements StudentService {
- @Autowired
- private StudentMapper studentMapper;
-
- @Autowired
- private RedisTemplate<String,Object> redisTemplate;
- @Override
- public Student findById(Integer id) {
- ValueOperations<String, Object> forValue = redisTemplate.opsForValue();
- //1.查询缓存
- Object o = forValue.get("student::" + id);
- if(o!=null){ //缓存命中
- return (Student) o;
- }
- Student student = studentMapper.selectById(id);
- //2.查询到应该放入缓存
- if(student!=null){
- forValue.set("student::"+id,student);
- }
-
- return student;
- }
-
- @Override
- public Integer insert(Student student) {
- int insert = studentMapper.insert(student);
- return insert;
- }
-
- @Override
- public Integer delete(Integer id) {
- //缓存与数据库数据保持一致
- redisTemplate.delete("student::"+ id );
- int i = studentMapper.deleteById(id);
- return i;
- }
-
- @Override
- public Integer update(Student student) {
- redisTemplate.delete("student::"+ student.getSid());
- int i = studentMapper.updateById(student);
- return i;
- }
- }
同一个库存数被多个线程卖,会出现线程安全问题。--------------->出现线程安全问题时如何解决--------------->可以使用锁解决:----synchronized和Lock锁
- @Service
- public class StockService_lock_syn {
- @Autowired
- private StockDao stockDao;
- public static Object o=new Object();
- Lock lock=new ReentrantLock();
-
- public String jianStock(Integer pid){
-
- //1.0版本
- 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的本地锁。
(1) 在项目中跑集群
(2) jmeter 压测
经过压测可以发现两台集群出现了重复现象
通过redis中setnx命令进行占锁,del来释放锁
- @Service
- public class StockService_lock_syn {
- @Autowired
- private StockDao stockDao;
- public static Object o=new Object();
- Lock lock=new ReentrantLock();
-
- @Autowired
- private StringRedisTemplate redisTemplate;
-
- public String jianStock(Integer pid){
-
- //2.0
- //占锁
- ValueOperations<String, String> forValue = redisTemplate.opsForValue();
- // Boolean aBoolean = forValue.setIfAbsent("product::" + pid, "", 30, TimeUnit.SECONDS);
- //占锁失败
- 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完美的解决分布式锁。
(1)引入相关依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.13.4</version>
</dependency>
(2)主函数加载
- @SpringBootApplication
- public class RedisLockQy145Application {
-
- public static void main(String[] args) {
- SpringApplication.run(RedisLockQy145Application.class, args);
- }
- @Bean //创建redisson交于spring容器来管理
- public RedissonClient redisson() {
- Config config = new Config();
- config.setLockWatchdogTimeout(300);
- config.useSingleServer().setAddress("redis://192.168.61.129:6379");
- RedissonClient redisson = Redisson.create(config);
- return redisson;
-
- }
- }
(3)测试
- @Service
- public class StockService_lock_syn {
- @Autowired
- private StockDao stockDao;
- public static Object o=new Object();
- Lock lock=new ReentrantLock();
-
- @Autowired
- private StringRedisTemplate redisTemplate;
-
- @Autowired
- private RedissonClient redisson;
- public String jianStock(Integer pid){
-
-
- //3.0 防止业务代码执行时间超过锁时间
- 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();
- }
-
- }
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。