1、缓存
缓存失效问题
1、缓存穿透:
不存在数据
缓存null、设置短的过期时间
2、缓存雪崩:
大面积同时失效
失效加随机值
3、缓存击穿
热点key失效
加锁
缓存数据一致性解决
双写模式:
+ 改完数据库同时改缓存
+ 脏数据
+ 加锁
+ 是否允许暂时不一致性——缓存过期后可读正确数据
失效模式
+ 删除缓存数据
+ 等待下次查询主动更新
解决方案
+ 并发量小不考虑
+ 菜单等,用canal
+ 自动更新缓存(db从库)
+ 数据异构(访问记录表 和 商品 进行分析计算——用户推荐)
+ 过期时间保证 最终一致性
+ 加读写锁
我们系统的一致性解决方案:
1、缓存的所有数据都有过期时间,数据过期下一次查询触发主动更新
2、读写数据的时候,加上分布式的读写锁。 (经常写,经常读)
Spring Cache
使用大量aop切面编程,最终都是动态代理机制
1 2 3 4 5 6 7 8 9 10
| + @Cacheable:结果缓存 + key、value + ttl + 序列化:json + @CacheEvict:删除缓存 + @CachePut:调用方法且缓存 + 双写模式 + @Caching:多个操作 + 开启缓存空值 + 防止缓存穿透
|
1 2 3 4 5 6 7 8
| + 读模式 + 缓存穿透:缓存null + 缓存击穿:加锁 + 缓存雪崩:过期时间加随机 + 写模式(缓存与数据库一致) + 读写加锁 + canal感知数据库 + 读多写多,直接去数据库
|
2、分布式锁
阶段一
redis setnx
问题:异常退出死锁
阶段二
同时原子设置过期时间
问题:锁过期,删错锁
阶段三
唯一标识value验证
问题:获取value和删除key非原子,仍存在删错锁
阶段四
lua脚本,解锁保证原子性
问题:未执行完锁过期,续期问题
阶段五
锁时间放长,finally解锁
Redisson
使得原本作为协调单机多线程并发程序的工具包获得了协调分布式多机多线程并发系统的能力,更进一步简化了分布式环境中程序相互之间的协作。
1 2 3 4 5 6 7 8 9 10 11 12 13
| +lock锁 +自动续期 +看门狗:判断线程 + 定时任务(重设过期时间) + trylock(获取一次) + fair lock公平锁(队列) + readwritelock读写锁 + 写锁限制读和写操作(保证读数据一致性) + 读锁单独无限制(无锁) + 只要有写操作,就需要等待(读写,写读) + 信号量(固定车位) + 分布式限流 + acquire(阻塞)、Tryacquire + 闭锁(走完关门)
|
看门狗原理
3、检索
vo封装
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
| @Data public class SearchResult {
private List<SkuEsModel> product;
private Integer pageNum;
private Long total;
private Integer totalPages;
private List<Integer> pageNavs;
private List<BrandVo> brands;
private List<AttrVo> attrs;
private List<CatalogVo> catalogs;
private List<NavVo> navs;
@Data public static class NavVo { private String navName; private String navValue; private String link; }
@Data public static class BrandVo {
private Long brandId;
private String brandName;
private String brandImg; }
@Data public static class AttrVo {
private Long attrId;
private String attrName;
private List<String> attrValue; }
@Data public static class CatalogVo {
private Long catalogId;
private String catalogName; } }
|
DSL
模糊匹配、过滤(属性、分类、品牌、价格区间、库存)
排序、分页、高亮、聚合分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
| GET mall_product/_search { "query": { "bool": { "must": [ {"match": { "skuTitle": "华为" }} ], # 检索出华为 "filter": [ # 过滤 { "term": { "catalogId": "225" } }, { "terms": {"brandId": [ "2"] } }, { "term": { "hasStock": "false"} }, { "range": { "skuPrice": { # 价格1K~7K "gte": 1000, "lte": 7000 } } }, { "nested": { "path": "attrs", # 聚合名字 "query": { "bool": { "must": [ { "term": { "attrs.attrId": { "value": "6"} } } ] } } } } ] } }, "sort": [ {"skuPrice": {"order": "desc" } } ], "from": 0, "size": 5, "highlight": { "fields": {"skuTitle": {}}, # 高亮的字段 "pre_tags": "<b style='color:red'>", # 前缀 "post_tags": "</b>" }, "aggs": { # 查完后聚合 "brandAgg": { "terms": { "field": "brandId", "size": 10 }, "aggs": { # 子聚合 "brandNameAgg": { # 每个商品id的品牌 "terms": { "field": "brandName", "size": 10 } }, "brandImgAgg": { "terms": { "field": "brandImg", "size": 10 } } } }, "catalogAgg":{ "terms": { "field": "catalogId", "size": 10 }, "aggs": { "catalogNameAgg": { "terms": { "field": "catalogName", "size": 10 } } } }, "attrs":{ "nested": {"path": "attrs" }, "aggs": { "attrIdAgg": { "terms": { "field": "attrs.attrId", "size": 10 }, "aggs": { "attrNameAgg": { "terms": { "field": "attrs.attrName", "size": 10 } } } } } } } }
|
4、异步&线程池
1)、继承 Thread
2)、实现 Runnable 接口
3)、实现 Callable 接口 + FutureTask (可以拿到返回结果,可以处理异常)
4)、线程池:调度、资源控制
线程池七大参数:
核心线程数、最大线程数、超时时间、超时时间单位、阻塞队列,线程工厂、拒绝策略
1 2 3 4 5 6 7 8
| 运行流程: 1、线程池创建,准备好 core 数量的核心线程,准备接受任务 2、新的任务进来,用 core 准备好的空闲线程执行。 (1) 、core 满了,就将再进来的任务放入阻塞队列中。空闲的 core 就会自己去阻塞队 列获取任务执行 (2) 、阻塞队列满了,就直接开新线程执行,最大只能开到 max 指定的数量 (3) 、max 都执行好了。Max-core 数量空闲的线程会在 keepAliveTime 指定的时间后自 动销毁。最终保持到 core 大小 (4) 、如果线程数开到了 max 的数量,还有新任务进来,就会使用 reject 指定的拒绝策 略进行处理 3、所有的线程创建都是由指定的 factory 创建的。
|
CompletableFuture异步编排
很多语言,比如 Node.js,采用回调的方式实现异步编程。Java 的一些框架,比如 Netty,自 己扩展了 Java 的 Future
接口,提供了addListener
等多个扩展方法;Google guava 也提供了 通用的扩展 Future;Scala 也提供了简单易用且功能强大的 Future/Promise 异步编程模式。
在 Java 8 中, 新增加了一个包含 50 个方法左右的类: CompletableFuture,提供了非常强大的 Future 的扩展功能,可以帮助我们简化异步编程的复杂性,提供了函数式编程的能力,可以 通过回调的方式处理计算结果,并且提供了转换和组合 CompletableFuture 的方法。 CompletableFuture 类实现了 Future 接口,所以你还是可以像以前一样通过get
方法阻塞或 者轮询的方式获得结果,但是这种方式不推荐使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| + 启动异步任务 + runAsync + supplyAsync + 完成回调与异常感知 + whenComplete + exceptionally + whenCompleteAsync + handle最终处理 + 和 complete 一样 + 可对结果做最后的处理(可处理异常),可改变返回值 + 线程串行化方法 + thenApply + thenAccept + thenRun + 两任务组合 + 都要完成 + 一个完成 + 多任务组合 + allOf + anyOf
|