分布式下使用本地Map作为缓存主要以下问题
1、数据一致性问题
2、缓存失效问题
缓存在某些时刻并没有缓解数据库压力。全发给数据库,导致请求堆积问题,造成服务雪崩
思路:
虽然咱们实现了页面需要的功能,但是考虑到该页面是被用户高频访问的,所以性能需要优化。
一般一个系统最大的性能瓶颈,就是数据库的io操作。从数据库入手也是调优性价比最高的切入点。
一般分为两个层面,一是提高数据库sql本身的性能,二是尽量避免直接查询数据库。
重点要讲的是另外一个层面:尽量避免直接查询数据库。
解决办法就是:缓存
数据尽量不“回源(回到MySQL查)
开发期间:
打包后:
未来我们的调用过程应该是这样的:
web-all(需要 service-item 返回的数据)=> service-item(组装后面的数据)=> service-product(4、5个接口)
@RestController
@RequestMapping("/api/item")
public class SkuInfoController {
@Autowired
SpuFeignClient spuFeignClient;
@Autowired
ItemService itemService;
/**
* 获取sku详情信息:聚合接口
*
* @param skuId
* @return
*/
@GetMapping("/{skuId}")
public Result getItem(@PathVariable Long skuId) {
// item-service 远程调用 service-product 获取当前商品的所有详情
Map<String, Object> skuInfo = itemService.getSkuInfo(skuId);
return Result.ok(skuInfo);
}
}
//TODO RPC 查询 skuDetail
//TODO 1、Sku基本信息 以及 所有sku图片
//TODO 2,Sku图片信息(sku的默认图片[sku_info],sku_image[一组图片])
//TODO 3,Sku分类信息(sku_info[只有三级分类],根据这个三级分类查出所在的一级,二级分类内容,连上三张分类表继续查)
//TODO 4,Sku销售属性相关信息(查出自己的sku组合,还要查出这个sku所在的spu定义了的所有销售属性和属性值)
//TODO 5,Sku价格信息(平台可以单独修改价格,sku后续会放入缓存,为了回显最新价格,所以单独获取)
新建 service-feign-client 项目
新建 com.atguigu.gmall.feign.product.SkuInfoFeignClient
把 service-product 的 api 包下的 ProductApiController 中写好的远程接口暴露到 SkuInfoFeignClient 中(复制 + 粘贴)
@RequestMapping("/api/product")
@FeignClient(value = "service-product",fallback = SkuInfoFeignClientFallback.class)
public interface SkuInfoFeignClient {
@GetMapping("/inner/getSkuInfo/{skuId}")
SkuInfo getSkuInfo(@PathVariable("skuId") Long skuId);
@GetMapping("/inner/getCategoryView/{category3Id}")
BaseCategoryView getCategoryView(@PathVariable("category3Id") Long category3Id);
@GetMapping("/inner/getSkuPrice/{skuId}")
BigDecimal getSkuPrice(@PathVariable Long skuId);
@GetMapping("/inner/getSpuSaleAttrListCheckBySku/{skuId}/{spuId}")
List<SpuSaleAttr> getSpuSaleAttrListCheckBySku(@PathVariable("skuId") Long skuId,
@PathVariable("spuId") Long spuId);
@GetMapping("/inner/getSkuValueIdsMap/{spuId}")
Map getSkuValueIdsMap(@PathVariable("spuId") Long spuId);
}
新建 com.atguigu.gmall.feign.product.impl.SkuInfoFeignClientFallback
兜底的熔断类
@Component
public class SkuInfoFeignClientFallback implements SkuInfoFeignClient {
@Override
public SkuInfo getSkuInfo(Long skuId) {
return null;
}
@Override
public BaseCategoryView getCategoryView(Long category3Id) {
return null;
}
@Override
public BigDecimal getSkuPrice(Long skuId) {
return null;
}
@Override
public List<SpuSaleAttr> getSpuSaleAttrListCheckBySku(Long skuId, Long spuId) {
return null;
}
@Override
public Map getSkuValueIdsMap(Long spuId) {
return null;
}
}
做好 feign 相关配置
<dependency>
<groupId>com.atguigu.gmallgroupId>
<artifactId>service-feign-clientartifactId>
<version>1.0version>
dependency>
此时 feign 的调用是不过网关的
顺便把 VM options 配置一下
Consider defining a bean of type ‘com.atguigu.gmall.feign.product.SpuFeignClient’ in your configuration.
springboot 启动只能扫到它所在的包,以及子包下的东西,以前还没有把 feign 抽取出来而是放在 service-item 中的时候,它是可以被 springboot 扫描到的,而现在根本就不在同一个包下,当然就扫描不到了
抽取feign以后,每个微服务要引用,要自己声明feign所在的包
ItemApplication
所有和商品有关的数据 由service-product来做的,我们不操作,远程调用
//抽取feign以后,每个微服务要引用,要自己声明feign所在的包
@EnableFeignClients(basePackages = "com.atguigu.gmall.feign.product") //开启feign远程调用
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@EnableDiscoveryClient
@EnableCircuitBreaker
public class ItemApplication {
public static void main(String[] args) {
SpringApplication.run(ItemApplication.class,args);
}
}
application.yaml
service/service-item/src/main/resources/application.yaml
spring:
main:
allow-bean-definition-overriding: true #允许bean定义信息的重写
zipkin:
base-url: http://192.168.200.188:9411/
sender:
type: web
包名重复了,为了解决这个问题,要么就在 application.yaml 中允许 bean 的重新定义,要么就把包的路径名设置得不同
成功
/**
* 所有和商品有关的数据 由service-product来做的,我们不操作,远程调用
* 1、feign-client被抽取以后一定用 @EnableFeignClients(basePackages = "com.atguigu.gmall.product")
* 2、还要加
* spring:
* main:
* allow-bean-definition-overriding: true #允许bean定义信息的重写
*
* 以后任意项目起报名
* com.atguigu.gmall.模块.mvc三层包
*/
@Service
@Slf4j
public class ItemServiceImpl implements ItemService {
@Autowired
SkuInfoFeignClient skuInfoFeignClient;
// RPC 查询 skuDetail
@Override
public HashMap<String, Object> getFromServiceItemFeign01(Long skuId) {
// 准备一个 map 存储要返回的值
HashMap<String, Object> result = new HashMap<>();
// 1、Sku基本信息 以及 所有sku图片
// 2,Sku图片信息(sku的默认图片[sku_info],sku_image[一组图片])
SkuInfo skuInfo = skuInfoFeignClient.getSkuInfo(skuId);
if (skuInfo != null) {
result.put("skuInfo", skuInfo);
// 3,Sku分类信息(sku_info[只有三级分类],根据这个三级分类查出所在的一级,二级分类内容,连上三张分类表继续查)
BaseCategoryView skuCategorys = skuInfoFeignClient.getCategoryView(skuInfo.getCategory3Id());
result.put("categoryView", skuCategorys);
// 4,Sku销售属性相关信息(查出自己的sku组合,还要查出这个sku所在的spu定义了的所有销售属性和属性值)
List<SpuSaleAttr> spuSaleAttrListCheckBySku = skuInfoFeignClient.getSpuSaleAttrListCheckBySku(skuId, skuInfo.getSpuId());
result.put("spuSaleAttrList", spuSaleAttrListCheckBySku);
// 5,Sku价格信息(平台可以单独修改价格,sku后续会放入缓存,为了回显最新价格,所以单独获取)
BigDecimal skuPrice = skuInfoFeignClient.getSkuPrice(skuId);
result.put("price", skuPrice);
// 6,Spu下面的所有存在的sku组合信息{"121|123|156":65,"122|123|111":67}
// 前端这里还需要把map转成json字符串
Map map = skuInfoFeignClient.getSkuValueIdsMap(skuInfo.getSpuId());
ObjectMapper mapper = new ObjectMapper();
try {
String jsonStr = mapper.writeValueAsString(map);
log.info("valuesSkuJson 内容:{}", jsonStr);
result.put("valuesSkuJson", jsonStr);
} catch (JsonProcessingException e) {
log.error("商品sku组合数据转换异常:{}", e);
}
}
return result;
}
}
server.port=10000
spring.application.name=web-all
spring.cloud.nacos.server-addr=192.168.200.188:8848
spring:
zipkin:
base-url: http://192.168.200.188:9411/
sender:
type: web
main:
allow-bean-definition-overriding: true
thymeleaf:
cache: false
servlet:
content-type: text/html
encoding: UTF-8
mode: HTML5
prefix: classpath:/templates/
suffix: .html
新建 com.atguigu.gmall.web.all.ServiceWebApplication
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@EnableDiscoveryClient
@EnableCircuitBreaker
public class ServiceWebApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceWebApplication.class, args);
}
}
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-thymeleafartifactId>
dependency>
dependencies>
新建 com.atguigu.gmall.feign.item.SkuItemFeignClient
@RequestMapping("/api/item")
@FeignClient("service-item")
public interface SkuItemFeignClient {
@GetMapping("/{skuId}")
Result getItem(@PathVariable Long skuId);
}
service/web-all/pom.xml
<dependency>
<groupId>com.atguigu.gmallgroupId>
<artifactId>service-feign-clientartifactId>
<version>1.0version>
dependency>
给启动类加上 @EnableFeignClients(“com.atguigu.gmall.feign.item”),把 item 扫描进来
@EnableFeignClients("com.atguigu.gmall.feign.item")
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@EnableDiscoveryClient
@EnableCircuitBreaker
public class ServiceWebApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceWebApplication.class, args);
}
}
新建 com.atguigu.gmall.web.all.controller.SkuItemController
因为要跳转页面,所以不用 @RestController
@Controller
public class SkuItemController {
@Autowired
SkuItemFeignClient skuItemFeignClient;
@GetMapping("/{skuId}.html")
public String getSkuInfo(@PathVariable("skuId") Long skuId,
Model model){
//把当前sku的所有数据交给页面
Result<Map<String,Object>> item = skuItemFeignClient.getItem(skuId);
Map<String,Object> data = item.getData();
//把远程调用得到的所有内容全部交给 model,放在页面的请求域中
model.addAllAttributes(data);
return "item/index";
}
}
为什么要用 item.getData()?为什么要把 Result item = skuItemFeignClient.getItem(skuId);
转换成Result
?
Result item = skuItemFeignClient.getItem(skuId);
因为原来的返回值类型是 Result,里面是 code、message、data,其中 code、message 没啥用,我们这里只需要 data,而 Map
所以自然而然就转换成Map
当我们点击切换其他颜色或套餐的时候并没有成功跳转
前端解析出来的:
我们实际查到的
解决方法:
封装 SkuAllSaleValue 时就用 String 定义,不要用 Long
spring.application.name=api-gateway
server.port=80
spring.cloud.nacos.server-addr=192.168.200.188:8848
访问这些域名都会来到 192.168.200.1(主机目录),意味着所有的请求都会发到主机目录的 80 端口
那么既然主机的 80 端口接到了,为什么会是 404
我们先想清楚,这个页面是谁给我们返回的?
这是网关给我们返回的,因为 item.gmall.com 来到本机,本机只有网关监听了 80 端口,但是你现在又要访问别人,所以就要在网关的 application.yaml 配置好你要访问的映射路径
spring:
cloud:
gateway:
globalcors:
cors-configurations:
'[/**]':
allowedOrigins: "*"
allowedMethods: "*"
allowedHeaders: "*" #允许复杂请求跨域
allowCredentials: true #允许跨域带cookie
routes:
- id: admin-product
uri: lb://service-product
predicates:
- Path=/admin/product/**
- id: web-all
uri: lb://web-all
predicates:
- Host=item.gmall.com
item.gmall.com 这个域名下的所有请求都路由到 web-all
为啥呢?
例子:当我们访问 item.gmall.com/50.html 时,在请求头里面有 Host 主机:item.gmall.com,所以我们网关收到这个请求的时候,会拿到请求头中的字段,看如果是 item.gmall.com 时就给负载均衡到了 web-all
链路通了
高并发(吞吐量)?有三宝
缓存(分担数据库压力)、”缓存是否高并发的银弹???”
异步(结合线程池)、
队排好(消息队列)
虽然咱们实现了页面需要的功能,但是考虑到该页面是被用户高频访问的,所以性能需要优化。
一般一个系统最大的性能瓶颈,就是数据库的io操作。从数据库入手也是调优性价比最高的切入点。
一般分为两个层面,一是提高数据库sql本身的性能,二是尽量避免直接查询数据库。
重点要讲的是另外一个层面:尽量避免直接查询数据库。
解决办法就是:缓存
数据尽量不“回源(回到MySQL查)”
使用ab测试工具:httpd-tools(安装:yum install -y httpd-tools)
ab -n(一次发送的请求数) -c(请求的并发数) 访问路径
测试如下:5000请求,100并发
ab -n 5000 -c 100 http://192.168.200.1:9000/api/item/50
#压力测试?
ab -n 5000 -c 100 http://192.168.200.1:9000/api/item/50
Server Software:
Server Hostname: 192.168.200.1
Server Port: 9000
Document Path: /api/item/50
Document Length: 2491 bytes
Concurrency Level: 100
Time taken for tests: 10.667 seconds #总耗费时间
Complete requests: 5000
Failed requests: 0
Write errors: 0
Total transferred: 12980000 bytes
HTML transferred: 12455000 bytes
Requests per second: 468.72 [#/sec] (mean) #每秒并发 V
Time per request: 213.347 [ms] (mean) #平均请求耗费的时间 V
Time per request: 2.133 [ms] (mean, across all concurrent requests)
Transfer rate: 1188.28 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 1 1.5 1 13 #方差越大,代表网络波动越大
Processing: 39 210 69.0 197 724
Waiting: 32 210 68.8 196 708
Total: 39 212 69.6 198 728
Percentage of the requests served within a certain time (ms)
50% 198
66% 217
75% 233
80% 244
90% 282 #90%的请求都在282ms内处理完成 V
95% 331
98% 420
99% 528 #99%的请求在528ms内处理完成 V
100% 728 (longest request)
项目 | 并发指标 | 响应速度指标 |
---|---|---|
service-item | 468.72/s | 90% 282 99% 528 |
service-product | ||
域名访问商品详情 | 253.99/s | 90% 542 99% 722 |
加本地缓存:service-item | 3258/s map要比redis |
90% 23 99% 49 |
加分布式缓存:service-item,所有数据存到redis | 4096.75/s | 90% 32 99% 208 |
1、压测域名多过了一层网关
网关 — 微服务
2、直接压微服务
微服务
中间件过的越多,速度越慢,并发越小
ItemServiceImpl
@Service
@Slf4j
public class ItemServiceImpl implements ItemService {
// 临时保存数据,本地缓存
private Map<String,Object> cache = new HashMap<>();
@Autowired
SkuInfoFeignClient skuInfoFeignClient;
// RPC 查询 skuDetail
@Override
public HashMap<String, Object> getFromServiceItemFeign(Long skuId) {
/**
* 1、所有查询之前,先看缓存
* 1.1)、如果缓存中有就使用缓存的
* 1.2)、如果缓存没有,就查数据库
*/
if (cache.get("data") != null) {
// TODO 缓存中有
return (HashMap<String, Object>) cache.get("data"); // 强转 (HashMap)
} else {
// 缓存中没有
HashMap<String, Object> result = new HashMap<>();
//skuInfo信息
//RPC 查询 skuDetail
// 1、Sku基本信息(名字,id,xxx,价格,sku_描述) sku_info
// 2,Sku图片信息(sku的默认图片[sku_info],sku_image[一组图片
SkuInfo skuInfo = skuInfoFeignClient.getSkuInfo(skuId);
if (skuInfo != null) {
result.put("skuInfo", skuInfo);
// 3,Sku分类信息(sku_info[只有三级分类],根据这个三级分类查出所在的一级,二级分类内容,连上三张分类表继续查)
BaseCategoryView skuCategorys = skuInfoFeignClient.getCategoryView(skuInfo.getCategory3Id());
result.put("categoryView", skuCategorys);
// 4,Sku销售属性相关信息(查出自己的sku组合,还要查出这个sku所在的spu定义了的所有销售属性和属性值)
List<SpuSaleAttr> spuSaleAttrListCheckBySku = skuInfoFeignClient.getSpuSaleAttrListCheckBySku(skuId, skuInfo.getSpuId());
result.put("spuSaleAttrList", spuSaleAttrListCheckBySku);
// 5,Sku价格信息(平台可以单独修改价格,sku后续会放入缓存,为了回显最新价格,所以单独获取)
BigDecimal skuPrice = skuInfoFeignClient.getSkuPrice(skuId);
result.put("price", skuPrice);
// 6,Spu下面的所有存在的sku组合信息{"121|123|156":65,"122|123|111":67}
//前端这里还需要把map转成json字符串
Map map = skuInfoFeignClient.getSkuValueIdsMap(skuInfo.getSpuId());
ObjectMapper mapper = new ObjectMapper();
try {
String jsonStr = mapper.writeValueAsString(map);
log.info("valuesSkuJson 内容:{}", jsonStr);
result.put("valuesSkuJson", jsonStr);
} catch (JsonProcessingException e) {
log.error("商品sku组合数据转换异常:{}", e);
}
}
// TODO 缓存没有,查到数据再放入缓存
cache.put("data",result);
return result;
}
}
}
加本地缓存后压 service-item 的效果
Server Software:
Server Hostname: 192.168.200.1
Server Port: 9000
Document Path: /api/item/50
Document Length: 2491 bytes
Concurrency Level: 100
Time taken for tests: 1.535 seconds
Complete requests: 5000
Failed requests: 0
Write errors: 0
Total transferred: 12980000 bytes
HTML transferred: 12455000 bytes
Requests per second: 3258.29 [#/sec] (mean)
Time per request: 30.691 [ms] (mean)
Time per request: 0.307 [ms] (mean, across all concurrent requests)
Transfer rate: 8260.26 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 5 2.7 5 17
Processing: 2 10 13.4 7 802
Waiting: 2 9 13.0 6 790
Total: 6 15 13.5 12 803
Percentage of the requests served within a certain time (ms)
50% 12
66% 15
75% 17
80% 19
90% 23
95% 28
98% 41
99% 49
分布式下使用本地Map作为缓存主要以下问题
1、数据一致性问题
2、缓存失效问题
缓存在某些时刻并没有缓解数据库压力。全发给数据库,导致请求堆积问题,造成服务雪崩
// 伪代码
if(redisCache.get(“hello”)) {
Return redisCache.get(“hello”)
} else {
// 缓存没数据
Synchronized(this){
// 双检查
if(!redisCache.get(“hello”) {
Object data = queryFromDb();
redisCache.put(“hello”,data );
} else {
Return redisCache.get(“hello”);
}
}
// 把结果保存到缓存
redisCache.put(“hello”,data );
}
// 改一条数据?
UpdateXXX(Long dataId){
//1、修改数据库
updateDb(dataId);
//2、更新缓存
// 2.1)、双写模式:把刚才的最新数据查出来,再放到缓存,覆盖之前的结果
// 2.2)、失效模式:redisCache.delete(dataId),删除redis中的数据,让下一次的查询自己再查一遍
}
1、null结果也应该缓存:缓存穿透
2、缓存的数据都加上过期时间,防止缓存没有被代码更新,数据一直是错误的
3、查询数据库要加锁
4、把所有数据的失效时间设置成随机的【不用做】 30min; 只要加了失效时间就行不用随机
着重注意两点:
1、加锁(解决击穿、雪崩(录入数据的时间本就不一样,时间维度扩散的,只需要解决击穿)?)
2、数据有过期时间(数据过期一定要加)
1、查多改少【菜单、sku、spu、优惠券】
2、热点(查多)
3、一致性要求不高【指不用强一致,缓存和数据库的数据是弱一致的】
强一致: 分布式系统 Raft, 数据改完以后,都是同步的。MySQL - Redis 同步的
弱一致: 最终一致性。redis的数据最终和mysql的是一致的就行
1、改多
2、和钱相关
3、库存(实时校验库存)
sku的销量saleCount? 销量即使数据库变了,缓存没有更新过来无所谓
sku数据在缓存中有。看销量(对用户不重要,每个月进行账单统计的时候才需要精确查)?
在 service 中引入缓存中间件
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
server:
port: 9000
#怎么抽取全微服务都能用
spring:
main:
allow-bean-definition-overriding: true #允许bean定义信息的重写
zipkin:
base-url: http://192.168.200.188:9411/
sender:
type: web
redis:
host: 192.168.200.188
port: 6379
password: yangfan
RedisAutoConfiguration
RedisTemplate
object = 给 redis 存的是 json StringRedisTemplate: extends RedisTemplate
用这个
RedisTempalteTest
新建 com.atguigu.gmall.item.test.RedisTempalteTest
@SpringBootTest
public class RedisTempalteTest {
@Autowired
StringRedisTemplate stringRedisTemplate;
@Test
void test01(){
ValueOperations<String, String> stringStringValueOperations = stringRedisTemplate.opsForValue();
stringStringValueOperations.set("hello","world");
String hello = stringStringValueOperations.get("hello");
System.out.println("查到的值" + hello);
}
}
先考虑简化业务逻辑,把整个业务逻辑查询好的一堆东西都缓存,而不是挨个缓存
@Service
@Slf4j
public class ItemServiceImpl implements ItemService {
@Autowired
SkuInfoFeignClient skuInfoFeignClient;
@Autowired
StringRedisTemplate stringRedisTemplate;
/**
* 远程调用 service-product 查询出和当前 skuId 对应的所有信息
* 缓存的使用逻辑是固定,我们完全可以抽取
*
* 就可以使用AOP直接抽取出来,数据改了,缓存也要改? 如何使用SpringCache简化缓存开发
*
* @param skuId
* @return
*/
@Override
public Map<String, Object> getSkuInfo(Long skuId) {
ObjectMapper mapper = new ObjectMapper();
/**
* 1、所有查询之前,先看缓存
* 1.1)、如果缓存中有就使用缓存的
* 1.2)、如果缓存没有,就查数据库
*/
ValueOperations<String, String> operations = stringRedisTemplate.opsForValue();
//1、先去缓存确定是否存在
String redisContent = operations.get("sku:info:" + skuId);
if (StringUtils.isEmpty(redisContent)){
//2、缓存中没有,远程查询
HashMap<String, Object> fromServiceItemFeign = getFromServiceItemFeign(skuId);
String jsonStr = null;
try {
//对象——>String:序列化
jsonStr = mapper.writeValueAsString(fromServiceItemFeign);
} catch (JsonProcessingException e) {
log.error("skuId 序列化异常:{}",e);
}
//2.1、给redis中存一份
operations.set("sku:info:"+skuId,jsonStr);
//2.2、返回数据
return fromServiceItemFeign;
}else {
Map<String, Object> stringObjectMap = null;
try {
// redis拿到的是string,还要转对象
// 反序列化
// 是 fasterxml.jackson 包下的 TypeReference
stringObjectMap = mapper.readValue(redisContent, new TypeReference<Map<String, Object>>() {
});
} catch (JsonProcessingException e) {
log.error("map 反序列化异常:{}",e);
}
return stringObjectMap;
}
}
/**
* 远程查询sku详细信息(01版)
*
* @param skuId
* @return
*/
private HashMap<String, Object> getFromServiceItemFeign(Long skuId) {
log.info("开始远程查询,远程会操作数据库-------");
HashMap<String, Object> result = new HashMap<>();
//skuInfo信息
//RPC 查询 skuDetail
// 1、Sku基本信息(名字,id,xxx,价格,sku_描述) sku_info
// 2,Sku图片信息(sku的默认图片[sku_info],sku_image[一组图片
SkuInfo skuInfo = skuInfoFeignClient.getSkuInfo(skuId);
if (skuInfo != null) {
result.put("skuInfo", skuInfo);
// 3,Sku分类信息(sku_info[只有三级分类],根据这个三级分类查出所在的一级,二级分类内容,连上三张分类表继续查)
BaseCategoryView skuCategorys = skuInfoFeignClient.getCategoryView(skuInfo.getCategory3Id());
result.put("categoryView", skuCategorys);
// 4,Sku销售属性相关信息(查出自己的sku组合,还要查出这个sku所在的spu定义了的所有销售属性和属性值)
List<SpuSaleAttr> spuSaleAttrListCheckBySku = skuInfoFeignClient.getSpuSaleAttrListCheckBySku(skuId, skuInfo.getSpuId());
result.put("spuSaleAttrList", spuSaleAttrListCheckBySku);
// 5,Sku价格信息(平台可以单独修改价格,sku后续会放入缓存,为了回显最新价格,所以单独获取)
BigDecimal skuPrice = skuInfoFeignClient.getSkuPrice(skuId);
result.put("price", skuPrice);
// 6,Spu下面的所有存在的sku组合信息{"121|123|156":65,"122|123|111":67}
//前端这里还需要把map转成json字符串
Map map = skuInfoFeignClient.getSkuValueIdsMap(skuInfo.getSpuId());
ObjectMapper mapper = new ObjectMapper();
try {
String jsonStr = mapper.writeValueAsString(map);
log.info("valuesSkuJson 内容:{}", jsonStr);
result.put("valuesSkuJson", jsonStr);
} catch (JsonProcessingException e) {
log.error("商品sku组合数据转换异常:{}", e);
}
}
return result;
}
}
项目 | 并发指标 | 响应速度指标 |
---|---|---|
service-item | 468.72/s | 90% 282 99% 528 |
service-product | ||
域名访问商品详情 | 253.99/s | 90% 542 99% 722 |
加本地缓存:service-item | 3258/s map要比redis |
90% 23 99% 49 |
加分布式缓存:service-item,所有数据存到redis | 4096.75/s | 90% 32 99% 208 |
redis 官方给的数据时每秒可以有10w的并发
缓存的使用逻辑是固定,我们完全可以抽取,使用AOP直接抽取出来
数据改了,缓存也要改?
如何使用SpringCache简化缓存开发?
server:
port: 9000
tomcat:
accept-count: 10000 # tomcat线程池的队列长度 ServerProperties.Tomcat
threads:
max: 5000
# IO密集型【一般都是这种】: disk、network 淘宝 调 内存,线程池的大小
# CPU密集型【人工智能】: 计算; 内存占用不多, 线程池大小, 关注cpu