配置、注册中心nacos
网关
抽取各服务的鉴权、限流】日志输出
跨域
跨域
指的是浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对javascript施加的安全限制。
同源策略:是指协议,域名,端口都要相同,其中有一个不同都会产生跨域;
跨域流程
非简单请求(PUT,DELETE)等,需要先发送预检请求
- 预检请求,OPTIONS
- 响应允许跨域
- 发送真实请求
- 响应数据
解决跨域
使用nginx部署为同一域
配置当次请求允许跨域
在网关服务的配置类添加跨域配置
添加响应头
• Access-Control-Allow-Origin:支持哪些来源的请求跨域
• Access-Control-Allow-Methods:支持哪些方法跨域
• Access-Control-Allow-Credentials:跨域请求默认不包含cookie,设置为true可以包含cookie
• Access-Control-Expose-Headers:跨域请求暴露的字段
• CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。
• Access-Control-Max-Age:表明该响应的有效时间为多少秒。在有效时间内,浏览器无
须为同一请求再次发起预检请求。请注意,浏览器自身维护了一个最大有效时间,如果
该首部字段的值超过了最大有效时间,将不会生效。
文件存储
阿里云对象存储
服务端签名后直传
ElasticSearch
基础概念
ElasticSearch7-去掉type概念
• 关系型数据库中两个数据表示是独立的,即使他们里面有相同名称的列也不影响使用,但ES
中不是这样的。elasticsearch是基于Lucene开发的搜索引擎,而ES中不同type下名称相同
的filed最终在Lucene中的处理方式是一样的。
• 两个不同type下的两个user_name,在ES同一个索引下其实被认为是同一个filed,你必
须在两个不同的type中定义相同的filed映射。否则,不同type中的相同字段名称就会在
处理中出现冲突的情况,导致Lucene处理效率下降。
• 去掉type就是为了提高ES处理数据的效率。
• Elasticsearch 7.x
• URL中的type参数为可选。比如,索引一个文档不再要求提供文档类型。
• Elasticsearch 8.x
• 不再支持URL中的type参数。
• 解决:将索引从多类型迁移到单类型,每种类型文档一个独立索引
nginx
Nginx+Windows搭建域名访问环境
域名映射效果
• 请求接口 gulimall.com
• 请求页面 gulimall.com
• nginx直接代理给网关,网关判断
• 如果/api/****
,转交给对应的服务器
• 如果是 满足域名,转交给对应的服务
Nginx动静分离
1、以后将所有项目的静态资源都应该放在nginx里面
2、规则:/static/**所有请求都由nginx直接返回
缓存与Redis
高并发下缓存失效问题
缓存雪崩
缓存雪崩是指在我们设置缓存时key采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬时压力过重雪崩。
解决:
原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。
缓存穿透
指查询一个一定不存在的数据,由于缓存是不命中,将去查询数据库,但是数据库也无此记录,我们没有将这次查询的null写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义
风险:
利用不存在的数据进行攻击,数据库瞬时压力增大,最终导致崩溃缓存
解决:
null结果缓存,并加入短暂过期时间
缓存击穿
• 对于一些设置了过期时间的key,如果这些key可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。
• 如果这个key在大量请求同时进来前正好失效,那么所有对这个key的数据查询都落到db,我们称为缓存击穿。
解决:
加锁
大量并发只让一个去查,其他人等待,查到以后释放锁,其他人获取到锁,先查缓存,就会有数据,不用去db
分布式下加锁
本地锁,只能锁住当前进程,所以我们需要分布式锁
缓存数据一致性
双写模式
失效模式
如图所示,线程a修改完数据库库后,删除缓存,这时候线程b来对数据库进行修改,结果因为这次IO很慢很慢事务还没有提交,线程c进来的时候发现缓存没有数据,就去读取a对数据库更改的数据然后更新到缓存中,如果这次更新操作很快,线程b还没更新完数据库呢,这时,并没有什么影响,无非就是更新了一个错误数据,后面线程b对数据库操作完就会删除掉缓存,等到下一个请求进来就可以得到正确数据了。
缓存数据一致性-解决方案
无论是双写模式还是失效模式,都会导致缓存的不一致问题。即多个实例同时更新会出事。怎么办?
• 1、如果是用户纬度数据(订单数据、用户数据),这种并发几率非常小,不用考虑这个问题,缓存数据加上过期时间,每隔一段时间触发读的主动更新即可
• 2、如果是菜单,商品介绍等基础数据,也可以去使用canal订阅binlog的方式。
• 3、缓存数据+过期时间也足够解决大部分业务对于缓存的要求。
• 4、通过加锁保证并发读写,写写的时候按顺序排好队。读读无所谓。所以适合使用读写锁。(业务不关心脏数据,允许临时脏数据可忽略);
总结:
• 我们能放入缓存的数据本就不应该是实时性、一致性要求超高的。所以缓存数据的时候加上过期时间,保证每天拿到当前最新数据即可。
• 我们不应该过度设计,增加系统的复杂性
• 遇到实时性、一致性要求高的数据,就应该查数据库,即使慢点。
Redisson 完成分布式锁
分布式锁
占坑set和设置过期时间要是原子操作:lua脚本
配置
// 默认连接地址 127.0.0.1:6379
RedissonClient redisson = Redisson.create();
Config config = new Config();
//redis://127.0.0.1:7181
//可以用"rediss://"来启用 SSL 连接
config.useSingleServer().setAddress("redis://192.168.56.10:6379");
RedissonClient redisson = Redisson.create(config);
使用分布式锁
RLock lock = redisson.getLock("anyLock");// 最常见的使用方法
lock.lock();
// 加锁以后 10 秒钟自动解锁// 无需调用 unlock 方法手动解锁
lock.lock(10, TimeUnit.SECONDS);
// 尝试加锁,最多等待 100 秒,上锁以后 10 秒自动解锁
boolean res = lock.tryLock(100,10, TimeUnit.SECONDS);
if (res) {
try {
...
} finally {
lock.unlock();
}
}
Spring Cache
Cache 接口为缓存的组件规范定义,包含缓存的各种操作集合;Cache 接 口 下 Spring 提 供 了 各 种 xxxCache 的 实 现 ; 如 RedisCache , EhCacheCache , ConcurrentMapCache 等;
每次调用需要缓存功能的方法时,Spring 会检查检查指定参数的指定的目标方法是否已经被调用过;如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法并缓存结果后返回给用户。下次调用直接从缓存中获取。
注解
异步&线程池
线程池
初始化线程的 4 种方式
1)、继承 Thread
2)、实现 Runnable 接口
3)、实现 Callable 接口 + FutureTask (可以拿到返回结果,可以处理异常)
4)、线程池
方式 1 和方式 2:主进程无法获取线程的运算结果。不适合当前场景
方式 3:主进程可以获取线程的运算结果,但是不利于控制服务器中的线程资源。可以导致
服务器资源耗尽。
方式 4:通过如下两种方式初始化线程池
Executors.newFiexedThreadPool(3);
//或者
new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit unit, workQueue, threadFactory, handler);
常见的 4 种线程
newCachedThreadPool
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若
无可回收,则新建线程。
newFixedThreadPool
创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newScheduledThreadPool
创建一个定长线程池,支持定时及周期性任务执行。
newSingleThreadExecutor
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务
按照指定顺序(FIFO, LIFO, 优先级)执行。
开发中为什么使用线程池
降低资源的消耗
通过重复利用已经创建好的线程降低线程的创建和销毁带来的损耗
提高响应速度
因为线程池中的线程数没有超过线程池的最大上限时,有的线程处于等待分配任务的状态,当任务来时无需创建新的线程就能执行
提高线程的可管理性
线程池会根据当前系统特点对池内的线程进行优化处理,减少创建和销毁线程带来的系统开销。无限的创建和销毁线程不仅消耗系统资源,还降低系统的稳定性,使用线程池进行统一分配
Executor
public interface ExecutorService extends Executor
ExecutorService service = Executors.newCachedThreadPool();
CompletableFuture
CompletableFuture 和 FutureTask 同属于 Future 接口的实现类,都可以获取线程的执行结果。
创建异步对象
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier