Java redis使用場景介紹
1.作為緩存
1.1 為何使用
數據存儲在內存中,數據查詢速度快。可以分攤數據庫壓力。
1.2 什麼樣的數據適合放入緩存
查詢頻率比較高,修改頻率比較低。
安全系數低的數據
1.3 使用redis作為緩存
1.3.1 未使用配置類
註意要將實體類實現序列化:
@Data @AllArgsConstructor @NoArgsConstructor @TableName(value = "tb_dept") public class Dept implements Serializable { @TableId(value = "id",type = IdType.AUTO) private Integer id; private String name; private String realname; }
對應依賴:
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </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.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <!--mp的依賴--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.2</version> </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>
controller層對應代碼:
@RestController @RequestMapping("order") public class DeptController { @Resource private DeptService deptService; @GetMapping("getById/{id}") //order/getById/1 //{}可以放多個,由下面的傳參函數對應 //@PathVariable:獲取請求映射中{}的值 public Dept getById(@PathVariable Integer id){ return deptService.findById(id); } @GetMapping("deleteById/{id}") public String deleteById(@PathVariable Integer id){ int i = deptService.deleteById(id); return i>0?"刪除成功":"刪除失敗"; } @GetMapping("insert") public Dept insert(Dept dept){ Dept insert = deptService.insert(dept); return insert; } @GetMapping("update") public Dept update(Dept dept){ Dept update = deptService.update(dept); return update; } }
service層對應代碼:
@Service public class DeptService { @Resource private DeptMapper deptMapper; //當存儲的value類型為對象類型使用redisTemplate //存儲的value類型為字符串。StringRedisTemplate @Autowired private RedisTemplate redisTemplate; //業務代碼 public Dept findById(Integer id){ ValueOperations forValue = redisTemplate.opsForValue(); //查詢緩存 Object o = forValue.get("dept::" + id); //緩存命中 if(o!=null){ return (Dept) o; } Dept dept = deptMapper.selectById(id); if(dept!=null){ //存入緩存中 forValue.set("dept::"+id,dept,24, TimeUnit.HOURS); } return dept; } public int deleteById(Integer id){ redisTemplate.delete("dept::"+id); int i = deptMapper.deleteById(id); return i; } public Dept insert(Dept dept){ int insert = deptMapper.insert(dept); return dept; } public Dept update(Dept dept){ redisTemplate.delete("dept::"+dept.getId()); int i = deptMapper.updateById(dept); return dept; } }
配置源:
# 配置數據源
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/mydb?serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=root
#sql日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
#連接redis
spring.redis.host=192.168.22*.1**
spring.redis.port=6379
查看的緩存: 前部分代碼相同@before通知,後部分代碼也相同後置通知。 我們可以AOP完成緩存代碼和業務代碼分離。
spring框架它應該也能想到。–使用註解即可完成。解析該註解。
1.3.2 使用配置類
(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)使用註解
//業務代碼 //使用查詢註解:cacheNames表示緩存的名稱 key:唯一標志---dept::key //先從緩存中查看key為(cacheNames::key)是否存在,如果存在則不會執行方法體,如果不存在則執行方法體並把方法的返回值存入緩存中 @Cacheable(cacheNames = {"dept"},key="#id") public Dept findById(Integer id){ Dept dept = deptMapper.selectById(id); return dept; } //先刪除緩存在執行方法體。 @CacheEvict(cacheNames = {"dept"},key = "#id") public int deleteById(Integer id){ int row = deptMapper.deleteById(id); return row; } //這個註釋可以確保方法被執行,同時方法的返回值也被記錄到緩存中,實現緩存與數據庫的同步更新。 @CachePut(cacheNames = "dept",key="#dept.id") public Dept update(Dept dept){ int insert = deptMapper.updateById(dept); return dept; }
2.分佈式鎖
使用壓測工具測試高並發下帶來線程安全問題
2.1 壓測工具的使用
內部配置:
2.2 庫存項目
2.2.1 controller層
@RestController @RequestMapping("bucket") public class BucketController { @Autowired private BucketService bucketService; @GetMapping("update/{productId}") public String testUpdate(@PathVariable Integer productId){ String s = bucketService.updateById(productId); return s; } }
2.2.2 dao層
//此處寫就不需要在啟動類使用註解 @Mapper public interface BucketMapper extends BaseMapper<Bucket> { public Integer updateBucketById(Integer productId); }
2.2.3 entity層
@Data @AllArgsConstructor @NoArgsConstructor public class Bucket { @TableId(value = "productId",type = IdType.AUTO) private Integer productId; private Integer num; }
2.2.4 service層
@Service public class BucketService { @Resource private BucketMapper bucketMapper; public String updateById(Integer productId){ //查看該商品的庫存數量 Bucket bucket = bucketMapper.selectById(productId); if(bucket.getNum()>0){ //修改庫存每次減1 Integer integer = bucketMapper.updateBucketById(productId); System.out.println("扣減成功!剩餘庫存數:"+(bucket.getNum()-1)); return "success"; }else { System.out.println("扣減失敗!庫存數不足"); return "fail"; } } }
2.2.5 mapper
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.qy151wd.dao.BucketMapper"> <update id="updateBucketById" parameterType="int"> update bucket set num=num-1 where productId=#{productId} </update> </mapper>
2.2.6 依賴
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </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.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <!--mp的依賴--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.2</version> </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>
2.2.7 測試結果
我們看到同一個庫存被使用瞭n次。以及數據庫中庫存為負數。 線程安全問題導致。
2.3 解決方案
2.3.1 使用 synchronized 或者lock鎖
對應的service層修改為
@Service public class BucketService { @Resource private BucketMapper bucketMapper; public String updateById(Integer productId){ //加自動鎖 synchronized (this){ //查看該商品的庫存數量 Bucket bucket = bucketMapper.selectById(productId); if(bucket.getNum()>0){ //修改庫存每次減1 Integer integer = bucketMapper.updateBucketById(productId); System.out.println("扣減成功!剩餘庫存數:"+(bucket.getNum()-1)); return "success"; }else { System.out.println("扣減失敗!庫存數不足"); return "fail"; } } } }
如果搭建瞭項目集群,那麼該鎖無效 。
2.3.2 使用redisTemplate
(1)使用idea開集群項目
(2)使用nginx
(3)測試結果
發現又出現: 重復數字以及庫存為負數。
(4)解決方法
service對應代碼修改
@Service public class BucketService { @Resource private BucketMapper bucketMapper; @Autowired private RedisTemplate redisTemplate; public String updateById(Integer productId){ ValueOperations<String,String> forValue = redisTemplate.opsForValue(); Boolean flag = forValue.setIfAbsent("aaa::" + productId, "-----------------"); if(flag){ try{ //查看該商品的庫存數量 Bucket bucket = bucketMapper.selectById(productId); if(bucket.getNum()>0){ //修改庫存每次減1 Integer integer = bucketMapper.updateBucketById(productId); System.out.println("扣減成功!剩餘庫存數:"+(bucket.getNum()-1)); return "success"; }else { System.out.println("扣減失敗!庫存數不足"); return "fail"; } }finally { redisTemplate.delete("aaa::"+productId); } } return "服務器正忙,請稍後再試......."; } }
註意此處的測壓速度不易太快(推薦使用5秒100個線程)
經過測壓測試後,結果為:
到此這篇關於Java redis數據庫使用場景介紹的文章就介紹到這瞭,更多相關Java redis內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- MyBatis-Plus使用ActiveRecord(AR)實現CRUD
- springboot 集成redis哨兵主從的實現
- MyBatis-Plus詳解(環境搭建、關聯操作)
- Spring Boot如何利用攔截器加緩存完成接口防刷操作
- Redis實現短信驗證碼登錄的示例代碼