目录
1. 热点数据缓存
1.1 什么是缓存?
为了把一些经常访问的数据,放入缓存中以减少对数据库的访问频率。从而减少数据库的压力,提高程序的性能。(内存中存储)
1.2 缓存的原理
1.3 什么样的数据适合放入缓存中
- 查询频率高且修改频率低
- 数据安全性低
1.4 哪个组件可以作为缓存
- redis组件
- memory组件
- ehcache组件
1.5 java使用redis如何实现缓存功能
1.5.1 需要的依赖
<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>com.baomidou</groupid>
<artifactid>mybatis-plus-boot-starter</artifactid>
<version>3.4.3</version>
</dependency>
<dependency>
<groupid>mysql</groupid>
<artifactid>mysql-connector-java</artifactid>
</dependency>
<dependency>
<groupid>org.projectlombok</groupid>
<artifactid>lombok</artifactid>
<optional>true</optional>
</dependency>
1.5.2 配置文件
server.port=8080
#数据源
spring.datasource.driver-class-name=com.mysql.jdbc.driver
spring.datasource.url=jdbc:mysql://localhost:3306/1suo?useunicode=true&characterencoding=utf-8&usessl=false
spring.datasource.username=root
spring.datasource.password=12345678
#mybatisplus的sql日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.stdoutimpl
#mapper映射文件
mybatis-plus.mapper-locations=classpath*:mapper/*.xml
#redis配置
spring.redis.host=172.16.7.192
spring.redis.port=6379
spring.redis.database=1
1.5.3 代码
控制层
package com.ls.controller;
import com.ls.entity.clazz;
import com.ls.service.clazzservice;
import com.ls.vo.r;
import org.springframework.beans.factory.annotation.autowired;
import org.springframework.web.bind.annotation.*;
/**
* @program: springboot-redis-cache
* @description:
* @author: 1suo
* @create: 2024-07-24 19:56
**/
@restcontroller
@requestmapping("clazz")
public class clazzcontroller {
@autowired
private clazzservice clazzservice;
@deletemapping("delete")
public r delete(integer id) {
return clazzservice.delete(id);
}
@getmapping("get")
public r get(integer id) {
return clazzservice.get(id);
}
@getmapping("getall")
public r getall() {
return clazzservice.getall();
}
@postmapping("save")
public r save(@requestbody clazz clazz) {
return clazzservice.save(clazz);
}
@putmapping("update")
public r update(clazz clazz) {
return clazzservice.update(clazz);
}
}
业务层
package com.ls.service.impl;
import com.ls.dao.clazzdao;
import com.ls.entity.clazz;
import com.ls.service.clazzservice;
import com.ls.vo.r;
import org.springframework.beans.factory.annotation.autowired;
import org.springframework.data.redis.core.redistemplate;
import org.springframework.data.redis.core.valueoperations;
import org.springframework.stereotype.service;
import sun.dc.pr.prerror;
import java.util.arraylist;
import java.util.list;
/**
* @program: springboot-redis-cache
* @description:
* @author: 1suo
* @create: 2024-07-24 19:57
**/
@service
public class clazzserviceimpl implements clazzservice {
@autowired
private clazzdao clazzdao;
@autowired
private redistemplate<string, object> redistemplate;
@override
public r save(clazz clazz) {
int insert = clazzdao.insert(clazz);
return new r(200,"保存成功",clazz);
}
@override
public r update(clazz clazz) {
int update = clazzdao.updatebyid(clazz);
if (update>0){
redistemplate.opsforvalue().set("clazz::" + clazz.getcid(),clazz);
}
return new r(200,"更新成功",clazz);
}
@override
public r delete(integer id) {
int delete = clazzdao.deletebyid(id);
if (delete>0){
redistemplate.delete("clazz::" + id);
}
return new r(200,"删除成功",id);
}
@override
public r get(integer id) {
valueoperations<string, object> forvalue = redistemplate.opsforvalue();
object o = forvalue.get("clazz::" + id);
if(o!=null){
return new r(200,"查询成功",(clazz)o);
}
clazz clazz = clazzdao.selectbyid(id);
if (clazz!=null){
forvalue.set("clazz::" + id,clazz);
}
return new r(500,"查询成功",clazz);
}
@override
public r getall() {
list<clazz> list = clazzdao.selectlist(null);
return new r(200,"查询成功",list);
}
}
dao层
package com.ls.dao;
import com.baomidou.mybatisplus.core.mapper.basemapper;
import com.ls.entity.clazz;
/**
* @program: springboot-redis-cache
* @description:
* @author: 1suo
* @create: 2024-07-25 08:41
**/
public interface clazzdao extends basemapper<clazz> {
}
实体类
package com.ls.entity;
import com.baomidou.mybatisplus.annotation.idtype;
import com.baomidou.mybatisplus.annotation.tableid;
import com.baomidou.mybatisplus.annotation.tablename;
import lombok.allargsconstructor;
import lombok.data;
import lombok.noargsconstructor;
/**
* @program: springboot-redis-cache
* @description:
* @author: 1suo
* @create: 2024-07-24 19:56
**/
@data
@noargsconstructor
@allargsconstructor
@tablename("tbl_clazz")
public class clazz {
@tableid(type = idtype.auto)
private integer cid;
private string cname;
}
配置类
package com.ls.config;
import com.fasterxml.jackson.annotation.jsonautodetect;
import com.fasterxml.jackson.annotation.propertyaccessor;
import com.fasterxml.jackson.databind.objectmapper;
import org.springframework.context.annotation.bean;
import org.springframework.context.annotation.configuration;
import org.springframework.data.redis.connection.redisconnectionfactory;
import org.springframework.data.redis.core.redistemplate;
import org.springframework.data.redis.core.stringredistemplate;
import org.springframework.data.redis.serializer.jackson2jsonredisserializer;
import org.springframework.data.redis.serializer.stringredisserializer;
@configuration
public class redisconfig {
@bean
public redistemplate<string,object> redistemplate(redisconnectionfactory factory){
redistemplate<string, object> template = new redistemplate<>();//创建redistemplate对象
stringredisserializer serializer = new stringredisserializer();//字符串序列化对象
jackson2jsonredisserializer jackson2jsonredisserializer = new jackson2jsonredisserializer(object.class);//jackson的序列化对象
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(serializer);
// value序列化
template.setvalueserializer(jackson2jsonredisserializer);
// value hashmap序列化
template.sethashvalueserializer(jackson2jsonredisserializer);
template.setkeyserializer(serializer);
return template;
}
}
@mapperscan("com.ls.dao") 注解,将dao层自动代理为mapper代理对象。
1.5.4 发现
业务层代码除了要维护核心业务功能外,额外还要维护缓存的代码。
解决: 使用aop面向切面编程。(spring框架用aop切面实现)
1.6 使用缓存注解完成缓存功能
必须spring缓存使用的组件。
config:为解决序列化的问题
@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;
}
开启缓存注解
使用
package com.ls.service.impl;
import com.ls.dao.clazzdao;
import com.ls.entity.clazz;
import com.ls.service.clazzservice;
import com.ls.vo.r;
import org.springframework.beans.factory.annotation.autowired;
import org.springframework.cache.annotation.cacheevict;
import org.springframework.cache.annotation.cacheput;
import org.springframework.cache.annotation.cacheable;
import org.springframework.cache.annotation.enablecaching;
import org.springframework.data.redis.core.redistemplate;
import org.springframework.data.redis.core.valueoperations;
import org.springframework.stereotype.service;
import sun.dc.pr.prerror;
import java.util.arraylist;
import java.util.list;
/**
* @program: springboot-redis-cache
* @description:
* @author: 1suo
* @create: 2024-07-24 19:57
**/
@service
public class clazzserviceimpl implements clazzservice {
@autowired
private clazzdao clazzdao;
@autowired
private redistemplate<string, object> redistemplate;
@override
public r save(clazz clazz) {
int insert = clazzdao.insert(clazz);
return new r(200,"保存成功",clazz);
}
@cacheput(cachenames = "clazz", key = "#clazz.cid")
@override
public clazz update(clazz clazz) {
int update = clazzdao.updatebyid(clazz);
return clazz;
}
@cacheevict(cachenames = "clazz", key = "#id")
@override
public r delete(integer id) {
int delete = clazzdao.deletebyid(id);
return new r(200,"删除成功",id);
}
@cacheable(cachenames = "clazz", key = "#id")
@override
public clazz get(integer id) {
clazz clazz = clazzdao.selectbyid(id);
return clazz;
}
@override
public r getall() {
list<clazz> list = clazzdao.selectlist(null);
return new r(200,"查询成功",list);
}
}
2. 分布式锁
2.1模拟高并发
使用jmeter压测工具:
第一步:
第二步:
第三步:
通过压测发现库存超卖和重卖了,解决办法使用锁。
2.2 使用syn和lock锁
public string decrement(integer productid) {
//根据id查询商品的库存
int num = stockdao.findbyid(productid);
synchronized (this) {
if (num > 0) {
//修改库存
stockdao.update(productid);
system.out.println("商品编号为:" + productid + "的商品库存剩余:" + (num - 1) + "个");
return "商品编号为:" + productid + "的商品库存剩余:" + (num - 1) + "个";
} else {
system.out.println("商品编号为:" + productid + "的商品库存不足。");
return "商品编号为:" + productid + "的商品库存不足。";
}
}
}
上面使用syn和lock虽然解决了并发问题,但是我们未来项目部署时可能要部署集群模式。
2.3 模拟多服务器
点击增加一个服务器,同时启动后,实现模拟多服务器。 下面是服务器配置。
启动俩个服务器:
2.4 nginx代理集群
下载window版本的nginx实现代理集群。
nginx.conf配置文件:
通过压测发现本地锁 无效了,使用redis解决分布式锁文件。
2.5 使用redis解决分布式锁文件
核心代码:
@service
public class stockservice {
@autowired
private stockdao stockdao;
@autowired
private redissonclient redisson;
//
public string decrement(integer productid) {
rlock lock = redisson.getlock("product::" + productid);
lock.lock();
try {
//根据id查询商品的库存: 提前预热到redis缓存中
int num = stockdao.findbyid(productid);
if (num > 0) {
//修改库存---incr---定时器[redis 数据库同步]
stockdao.update(productid);
system.out.println("商品编号为:" + productid + "的商品库存剩余:" + (num - 1) + "个");
return "商品编号为:" + productid + "的商品库存剩余:" + (num - 1) + "个";
} else {
system.out.println("商品编号为:" + productid + "的商品库存不足。");
return "商品编号为:" + productid + "的商品库存不足。";
}
}finally {
lock.unlock();
}
}
}
发表评论