指的是浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对javascript施加的安全限制。
同源策略:是指协议,域名,端口都要相同,其中有一个不同都会产生跨域;
非简单请求(PUT,DELETE)等,需要先发送预检请求
在网关服务的配置类添加跨域配置
添加响应头
• 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:表明该响应的有效时间为多少秒。在有效时间内,浏览器无
须为同一请求再次发起预检请求。请注意,浏览器自身维护了一个最大有效时间,如果
该首部字段的值超过了最大有效时间,将不会生效。
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参数。
• 解决:将索引从多类型迁移到单类型,每种类型文档一个独立索引
• 请求接口 gulimall.com
• 请求页面 gulimall.com
• nginx直接代理给网关,网关判断
• 如果/api/****
,转交给对应的服务器
• 如果是 满足域名,转交给对应的服务
1、以后将所有项目的静态资源都应该放在nginx里面
2、规则:/static/**所有请求都由nginx直接返回
缓存雪崩是指在我们设置缓存时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、通过加锁保证并发读写,写写的时候按顺序排好队。读读无所谓。所以适合使用读写锁。(业务不关心脏数据,允许临时脏数据可忽略);
总结:
• 我们能放入缓存的数据本就不应该是实时性、一致性要求超高的。所以缓存数据的时候加上过期时间,保证每天拿到当前最新数据即可。
• 我们不应该过度设计,增加系统的复杂性
• 遇到实时性、一致性要求高的数据,就应该查数据库,即使慢点。
分布式锁
占坑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();
}
}
Cache 接口为缓存的组件规范定义,包含缓存的各种操作集合;Cache 接 口 下 Spring 提 供 了 各 种 xxxCache 的 实 现 ; 如 RedisCache , EhCacheCache , ConcurrentMapCache 等;
每次调用需要缓存功能的方法时,Spring 会检查检查指定参数的指定的目标方法是否已经被调用过;如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法并缓存结果后返回给用户。下次调用直接从缓存中获取。
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);
newCachedThreadPool
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若
无可回收,则新建线程。
newFixedThreadPool
创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newScheduledThreadPool
创建一个定长线程池,支持定时及周期性任务执行。
newSingleThreadExecutor
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务
按照指定顺序(FIFO, LIFO, 优先级)执行。
降低资源的消耗
通过重复利用已经创建好的线程降低线程的创建和销毁带来的损耗
提高响应速度
因为线程池中的线程数没有超过线程池的最大上限时,有的线程处于等待分配任务的状态,当任务来时无需创建新的线程就能执行
提高线程的可管理性
线程池会根据当前系统特点对池内的线程进行优化处理,减少创建和销毁线程带来的系统开销。无限的创建和销毁线程不仅消耗系统资源,还降低系统的稳定性,使用线程池进行统一分配
public interface ExecutorService extends Executor
ExecutorService service = Executors.newCachedThreadPool();
CompletableFuture 和 FutureTask 同属于 Future 接口的实现类,都可以获取线程的执行结果。
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier);
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor);
public static CompletableFuture<Void> runAsync(Runnable runnable);
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor);
1、runXxxx 都是没有返回结果的,supplyXxx 都是可以获取返回结果的
2、可以传入自定义的线程池,否则就用默认的线程池;
//计算完成后续操作1——complete
public CompletableFuture<T> whenComplete(BiConsumer<? super T,? super Throwable> action)
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action)
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action, Executor executor)
public CompletableFuture<T> exceptionally(Function<Throwable,? extends T> fn)
whenComplete 可以处理正常和异常的计算结果,exceptionally 处理异常情况。
whenComplete 和 whenCompleteAsync 的区别:
whenComplete:是执行当前任务的线程执行继续执行 whenComplete 的任务。
whenCompleteAsync:是执行把 whenCompleteAsync 这个任务继续提交给线程池来进行执行。
方法不以 Async 结尾,意味着 Action 使用相同的线程执行,而 Async 可能会使用其他线程执行(如果是使用相同的线程池,也可能会被同一个线程选中执行
public <U> CompletableFuture<U> handle(BiFunction<? super T,Throwable,? extends U> fn)
public <U> CompletableFuture<U> handleAsync(BiFunction<? super T,Throwable,? extends U> fn)
public <U> CompletableFuture<U> handleAsync(BiFunction<? super T,Throwable,? extends U> fn, Executor executor)
public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs)
allOf:等待所有任务完成
anyOf:只要有一个任务完成
SpringSession主要是使用了装饰者模式,将原生的request和response进行了封装,原来我们获取session对象是用request.getSession()获取,而现在是使用wrapperRequest.getSession从RedisOperationsSessionRepository中获取Session,这样我们对session的增删改查就是在Redis中进行的了。
一个网站登录,其他也能登录
单点登录服务器(SSO Server)
如图所示,图中有4个系统,分别是Application1、Application2、Application3、和SSO。Application1、Application2、Application3没有登录模块,而SSO只有登录模块,没有其他的业务模块,当Application1、Application2、Application3需要登录时,将跳到SSO系统,SSO系统完成登录,其他的应用系统也就随之登录了。这完全符合我们对单点登录(SSO)的定义。
简单来说,单点登录就是在多个系统中,用户只需一次登录,各个系统即可感知该用户已经登录。
跨域下的单点登录
如果是不同域呢?不同域之间Cookie是不共享的,怎么办?
这里我们就要说一说CAS流程了,这个流程是单点登录的标准流程。
具体流程如下:
1)用户访问app系统,app系统是需要登录的,但用户现在没有登录。
2)跳转到CAS server,即SSO登录系统,以后图中的CAS Server我们统一叫做SSO系统。 SSO系统也没有登录,弹出用户登录页。
3)用户填写用户名、密码,SSO系统进行认证后,将登录状态写入SSO的session,浏览器(Browser)中写入SSO域下的Cookie。
4)SSO系统登录完成后会生成一个ST(Service Ticket),然后跳转到app系统,同时将ST作为参数传递给app系统。
5)app系统拿到ST后,从后台向SSO发送请求,验证ST是否有效。
6)验证通过后,app系统将登录状态写入session并设置app域下的Cookie。
至此,跨域单点登录就完成了。以后我们再访问app系统时,app就是登录的。接下来,我们再看看访问app2系统时的流程。
1)用户访问app2系统,app2系统没有登录,跳转到SSO。
2)由于SSO已经登录了,不需要重新登录认证。
3)SSO生成ST,浏览器跳转到app2系统,并将ST作为参数传递给app2。
4)app2拿到ST,后台访问SSO,验证ST是否有效。
5)验证成功后,app2将登录状态写入session,并在app2域下写入Cookie。
这样,app2系统不需要走登录流程,就已经是登录了。SSO,app和app2在不同的域,它们之间的session不共享也是没问题的。
保证消息不丢失,可靠抵达,可以使用事务消息,性能下降250倍,为此引入确认机制
• publisher confirmCallback 确认模式
• publisher returnCallback 未投递到 queue 退回模式
• consumer ack机制
**解决:**被远程调用服务器设置请求拦截器,加入请求头,借助RequestContextHolder里的ThreadLocal
@Bean("requestInterceptor")
public RequestInterceptor requestInterceptor() {
RequestInterceptor requestInterceptor = new RequestInterceptor() {
@Override
public void apply(RequestTemplate template) {
//1、使用RequestContextHolder拿到刚进来的请求数据
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (requestAttributes != null) {
//老请求
HttpServletRequest request = requestAttributes.getRequest();
if (request != null) {
//2、同步请求头的数据(主要是cookie)
//把老请求的cookie值放到新请求上来,进行一个同步
String cookie = request.getHeader("Cookie");
template.header("Cookie", cookie);
}
}
}
};
return requestInterceptor;
}
RequestContextHolder用到了ThreadLocal(与线程有关)
**解决:**给每个异步任务里加RequestContextHolder
//TODO :获取当前线程请求头信息(解决Feign异步调用丢失请求头问题)
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
//开启第一个异步任务
CompletableFuture<Void> addressFuture = CompletableFuture.runAsync(() -> {
//每一个线程都来共享之前的请求数据
RequestContextHolder.setRequestAttributes(requestAttributes);
//1、远程查询所有的收获地址列表
List<MemberAddressVo> address = memberFeignService.getAddress(memberResponseVo.getId());
confirmVo.setMemberAddressVos(address);
}, threadPoolExecutor);
有详细文档
接口幂等性就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的
1、 服务端提供了发送 token 的接口。 我们在分析业务的时候, 哪些业务是存在幂等问题的,就必须在执行业务前, 先去获取 token, 服务器会把 token 保存到 redis 中。
2、 然后调用业务接口请求时, 把 token 携带过去, 一般放在请求头部。
3、 服务器判断 token 是否存在 redis 中, 存在表示第一次请求, 然后删除 token,继续执行业
务。
4、 如果判断 token 不存在 redis 中, 就表示是重复操作, 直接返回重复标记给 client, 这样就保证了业务代码, 不被重复执行。
危险性:
1、 先删除 token 还是后删除 token;
(1) 先删除可能导致, 业务确实没有执行, 重试还带上之前 token, 由于防重设计导致,请求还是不能执行。
(2) 后删除可能导致, 业务处理成功, 但是服务闪断, 出现超时, 没有删除 token, 别人继续重试, 导致业务被执行两边
(3) 我们最好设计为先删除 token, 如果业务调用失败, 就重新获取 token 再次请求。
2、 Token 获取、 比较和删除必须是原子性
(1) redis.get(token) 、 token.equals、 redis.del(token)如果这两个操作不是原子, 可能导致, 高并发下, 都 get 到同样的数据, 判断都成功, 继续业务并发执行
(2) 可以在 redis 使用 lua 脚本完成这个操作
if redis.call(‘get’, KEYS[1]) == ARGV[1] then return redis.call(‘del’, KEYS[1]) else return 0 end
1、 数据库悲观锁
select * from xxxx where id = 1 for update;
2、 数据库乐观锁
update t_goods set count = count -1 , version = version + 1 where good_id=2 and version = 1
3、 业务层分布式锁
1、 数据库唯一约束
2、 redis set 防重
调用接口时, 生成一个唯一 id, redis 将数据保存到集合中(去重) , 存在即处理过。
可以使用 nginx 设置每一个请求的唯一 id;
proxy_set_header X-Request-Id $request_id;
//TODO 5、防重令牌(防止表单重复提交)
//为用户设置一个token,三十分钟过期时间(存在redis)
String token = UUID.randomUUID().toString().replace("-", "");
redisTemplate.opsForValue().set(USER_ORDER_TOKEN_PREFIX+memberResponseVo.getId(),token,30, TimeUnit.MINUTES);
confirmVo.setOrderToken(token);
//1、验证令牌是否合法【令牌的对比和删除必须保证原子性】
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
String orderToken = vo.getOrderToken();
//通过lure脚本原子验证令牌和删除令牌
Long result = redisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class),
Arrays.asList(USER_ORDER_TOKEN_PREFIX + memberResponseVo.getId()),
orderToken);
if (result == 0L) {
//令牌验证失败
responseVo.setCode(1);
return responseVo;
} else {
//令牌验证成功
1、 PROPAGATION_REQUIRED: 如果当前没有事务, 就创建一个新事务, 如果当前存在事务,
就加入该事务, 该设置是最常用的设置。
2、 PROPAGATION_SUPPORTS: 支持当前事务, 如果当前存在事务, 就加入该事务, 如果当
前不存在事务, 就以非事务执行。
3、 PROPAGATION_MANDATORY: 支持当前事务, 如果当前存在事务, 就加入该事务, 如果
当前不存在事务, 就抛出异常。
4、 PROPAGATION_REQUIRES_NEW: 创建新事务, 无论当前存不存在事务, 都创建新事务。
5、 PROPAGATION_NOT_SUPPORTED: 以非事务方式执行操作, 如果当前存在事务, 就把当
前事务挂起。
6、 PROPAGATION_NEVER: 以非事务方式执行, 如果当前存在事务, 则抛出异常。
7、 PROPAGATION_NESTED: 如果当前存在事务, 则在嵌套事务内执行。 如果当前没有事务,
则执行与 PROPAGATION_REQUIRED 类似的操作。
数据库支持的 2PC【 2 phase commit 二阶提交】 , 又叫做 XA Transactions。MySQL 从 5.5 版本开始支持, SQL Server 2005 开始支持, Oracle 7 开始支持。其中, XA 是一个两阶段提交协议, 该协议分为以下两个阶段:
第一阶段: 事务协调器要求每个涉及到事务的数据库预提交(precommit)此操作, 并反映是否可以提交.
第二阶段: 事务协调器要求每个数据库提交数据。其中, 如果有任何一个数据库否决此次提交, 那么所有数据库都会被要求回滚它们在此事务中的那部分信息。
XA 协议比较简单, 而且一旦商业数据库实现了 XA 协议, 使用分布式事务的成本也比较低。
XA 性能不理想, 特别是在交易下单链路, 往往并发量很高, XA 无法满足高并发场景
XA 目前在商业数据库支持的比较理想, 在 mysql 数据库中支持的不太理想, mysql 的XA 实现, 没有记录 prepare 阶段日志, 主备切换回导致主库与备库数据不一致。
许多 nosql 也没有支持 XA, 这让 XA 的应用场景变得非常狭隘。
也有 3PC, 引入了超时机制( 无论协调者还是参与者, 在向对方发送请求后, 若长时间
未收到回应则做出相应处理)
刚性事务: 遵循 ACID 原则, 强一致性。
柔性事务: 遵循 BASE 理论, 最终一致性;高并发
与刚性事务不同, 柔性事务允许一定时间内, 不同节点的数据不一致, 但要求最终一致。
一阶段 prepare 行为: 调用 自定义 的 prepare 逻辑。
二阶段 commit 行为: 调用 自定义 的 commit 逻辑。
二阶段 rollback 行为: 调用 自定义 的 rollback 逻辑。
所谓 TCC 模式, 是指支持把 自定义 的分支事务纳入到全局事务的管理中。
按规律进行通知, 不保证数据一定能通知成功, 但会提供可查询操作接口进行核对。 这种方案主要用在与第三方系统通讯时, 比如: 调用微信或支付宝支付后的支付结果通知。 这种方案也是结合 MQ 进行实现, 例如: 通过 MQ 发送 http 请求, 设置最大通知次数。 达到通知次数后即不再通知。
案例: 银行通知、 商户通知等( 各大交易业务平台间的商户通知: 多次通知、 查询校对、 对账文件) , 支付宝的支付成功异步回调
实现: 业务处理服务在业务事务提交之前, 向实时消息服务请求发送消息, 实时消息服务只记录消息数据, 而不是真正的发送。 业务处理服务在业务事务提交之后, 向实时消息服务确认发送。 只有在得到确认发送指令后, 实时消息服务才会真正发送。
防止消息丢失:
1、 做好消息确认机制( pulisher, consumer【 手动 ack】 )
2、 每一个发送的消息都在数据库做好记录。 定期将失败的消息再次发送一遍
[https://blog.csdn.net/weixin_46120888/article/details/123626096]
分布式事务处理过程的一ID+三组件模型,一ID即Transaction ID XID,全局唯一的事务ID。
三组件:
1.TC (Transaction Coordinator) - 事务协调者
维护全局和分支事务的状态,驱动全局事务提交或回滚。
2.TM (Transaction Manager) - 事务管理器
定义全局事务的范围:开始全局事务、提交或回滚全局事务。
3.RM (Resource Manager) - 资源管理器
管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
如图所示一个TC管理全局,一个XID带着TC,TM,RM。
1.TM向TC申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的XID。
2.XID在微服务调用链路的上下文中传播。
3.RM向TC注册分支事务,将其纳入XID对应的全局事务的管辖。
4.TM向TC发起针对XID的全局提交或回滚决议。
5.TC调度XID下管辖的全部分支事务完成提交或者回滚请求。
业务表
USE
seata_order;
DROP TABLE IF EXISTS `t_order`;
CREATE TABLE `t_order`
(
`int` bigint(11) NOT NULL AUTO_INCREMENT,
`user_id` bigint(20) DEFAULT NULL COMMENT '用户id',
`product_id` bigint(11) DEFAULT NULL COMMENT '产品id',
`count` int(11) DEFAULT NULL COMMENT '数量',
`money` decimal(11, 0) DEFAULT NULL COMMENT '金额',
`status` int(1) DEFAULT NULL COMMENT '订单状态: 0:创建中 1:已完结',
PRIMARY KEY (`int`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '订单表' ROW_FORMAT = Dynamic;
回滚表
DROP TABLE IF EXISTS `undo_log`;
CREATE TABLE `undo_log`
(
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`branch_id` bigint(20) NOT NULL,
`xid` varchar(100) NOT NULL,
`context` varchar(128) NOT NULL,
`rollback_info` longblob NOT NULL,
`log_status` int(11) NOT NULL,
`log_created` datetime NOT NULL,
`log_modified` datetime NOT NULL,
`ext` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
@GlobalTransactional
@Transactional
AT 模式
前提
基于支持本地 ACID 事务的关系型数据库。
Java 应用,通过 JDBC 访问数据库。
整体机制
两阶段提交协议的演变:
一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。
二阶段:
提交异步化,非常快速地完成。
回滚通过一阶段的回滚日志进行反向补偿。
• 1、Queue、Exchange、Binding可以@Bean进去
• 2、监听消息的方法可以有三种参数(不分数量,顺序)
Object content, Message message, Channel channel
• 3、channel可以用来拒绝消息,否则自动ack;
消息发送出去,由于网络问题没有抵达服务器
• 做好容错方法(try-catch),发送消息可能会网络失败,失败后要有重试机
制,可记录到数据库,采用定期扫描重发的方式
CREATE TABLE `mq_message` (
`message_id` char(32) NOT NULL,
`content` text,
`to_exchane` varchar(255) DEFAULT NULL,
`routing_key` varchar(255) DEFAULT NULL,
`class_type` varchar(255) DEFAULT NULL,
`message_status` int(1) DEFAULT '0' COMMENT '0-新建 1-已发送 2-错误抵达 3-已抵达',
`create_time` datetime DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
PRIMARY KEY (`message_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
• 做好日志记录,每个消息状态是否都被服务器收到都应该记录
• 做好定期重发,如果消息没有发送成功,定期去数据库扫描未成功的消息进行重发
消息抵达Broker,Broker要将消息写入磁盘(持久化)才算成功。此Broker尚未持久化完成,宕机。
• publisher也必须加入确认回调机制,确认成功的消息,修改数据库消息状态。
自动ACK的状态下。消费者收到消息,但没来得及消息然后宕机
• 一定开启手动ACK,消费成功才移除,失败或者没来得及处理就noAck并重新入队
消息消费成功,事务已经提交,ack时,机器宕机。导致没有ack成功Broker的消息重新由unack变为ready,并发送给其他消费者
消息消费失败,由于重试机制,自动又将消息发送出去
成功消费,ack时宕机,消息由unack变为ready,Broker又重新发送
• 消费者的业务消费接口应该设计为幂等性的。比如扣库存有工作单的状态标志
• 使用防重表(redis/mysql),发送消息每一个都有业务的唯一标识,处理过就不用处理
• rabbitMQ的每一个消息都有redelivered字段,可以获取是否是被重新投递过来的,而不是第一次投递过来的
消费者宕机积压
消费者消费能力不足积压
发送者发送流量太大
• 上线更多的消费者,进行正常消费
• 上线专门的队列消费服务,将消息先批量取出来,记录数据库,离线慢慢处理
公钥和私钥是一个相对概念
它们的公私性是相对于生成者来说的。
一对密钥生成后, 保存在生成者手里的就是私钥,
生成者发布出去大家用的就是公钥
加密是指:
我们使用一对公私钥中的一个密钥来对数据进行加密, 而使用另一个密钥来进行解
密的技术。
公钥和私钥都可以用来加密, 也都可以用来解密。
但这个加解密必须是一对密钥之间的互相加解密, 否则不能成功。
加密的目的是:
为了确保数据传输过程中的不可读性, 就是不想让别人看到。
给我们将要发送的数据, 做上一个唯一签名(类似于指纹)
用来互相验证接收方和发送方的身份;
在验证身份的基础上再验证一下传递的数据是否被篡改过。 因此使用数字签名可以
用来达到数据的明文传输。
支付宝为了验证请求的数据是否商户本人发的,
商户为了验证响应的数据是否支付宝发的
内网穿透功能可以允许我们使用外网的网址来访问主机;
正常的外网需要访问我们项目的流程是:
1、 买服务器并且有公网固定 IP
2、 买域名映射到服务器的 IP
3、 域名需要进行备案和审核
1、 开发测试(微信、 支付宝)
2、 智慧互联
3、 远程控制
4、 私有云
listen 80;
server_name gulimall.com *.gulimall.com 497n86m7k7.52http.net;
#charset koi8-r;
#access_log /var/log/nginx/log/host.access.log main;
location /static/ {
root /usr/share/nginx/html;
}
location /payed/ {
proxy_set_header Host order.gulimall.com;
proxy_pass http://gulimall;
}
1、订单在支付页,不支付,一直刷新,订单过期了才支付,订单状态改为已支付了,但是库存解锁了。
• 使用支付宝自动收单功能解决。只要一段时间不支付,就不能支付了。
2、由于时延等问题。订单解锁完成,正在解锁库存的时候,异步通知才到
• 订单解锁,手动调用收单
3、网络阻塞问题,订单支付成功的异步通知一直不到达
• 查询订单列表时,ajax获取当前未支付的订单状态,查询订单状态时,再获取一下支付宝此订单的状态
4、其他各种问题
• 每天晚上闲时下载支付宝对账单,一一进行对账
秒杀具有瞬间高并发的特点, 针对这一特点, 必须要做限流 + 异步 + 缓存(页面静态化)+ 独立部署。
@EnableScheduling
@Scheduled
秒 分 时 日 月 周 年(spring 没有) ,且spring中周几对应数字几
特殊字符:
, : 枚举;
(cron="7,9,23 * * * * ?"): 任意时刻的 7,9, 23 秒启动这个任务;
-: 范围:
(cron="7-20 * * * * ?"):任意时刻的 7-20 秒之间, 每秒启动一次
*: 任意;
指定位置的任意时刻都可以
/: 步长;
(cron="7/5 * * * * ?"): 第 7 秒启动, 每 5 秒一次;
(cron="*/5 * * * * ?"): 任意秒启动, 每 5 秒一次;
? : (出现在日和周几的位置) : 为了防止日和周冲突, 在周和日上如果要写通配符使
用?
(cron="* * * 1 * ?"): 每月的 1 号, 启动这个任务;
L: (出现在日和周的位置) ”,
last: 最后一个
(cron="* * * ? * 3L"): 每月的最后一个周二
W:
Work Day: 工作日
(cron="* * * W * ?"): 每个月的工作日触发
(cron="* * * LW * ?"): 每个月的最后一个工作日触发
#: 第几个
(cron="* * * ? * 5#2"): 每个月的第 2 个周 4
什么是熔断
A 服务调用 B 服务的某个功能, 由于网络不稳定问题, 或者 B 服务卡机, 导致功能时间超长。 如果这样子的次数太多。 我们就可以直接将 B 断路了(A 不再请求 B 接口) , 凡是调用 B 的直接返回降级数据, 不必等待 B 的超长执行。 这样 B 的故障问题, 就不会级联影响到 A。
什么是降级
整个网站处于流量高峰期, 服务器压力剧增, 根据当前业务情况及流量, 对一些服务和页面进行有策略的降级[停止服务, 所有的调用直接返回降级数据]。 以此缓解服务器资源的的压力, 以保证核心业务的正常运行, 同时也保持了客户和大部分客户的得到正确的相应。
异同:
相同点:
1、 为了保证集群大部分服务的可用性和可靠性, 防止崩溃, 牺牲小我
2、 用户最终都是体验到某个功能不可用
不同点:
1、 熔断是被调用方故障, 触发的系统主动规则
2、 降级是基于全局考虑, 停止一些正常服务, 释放资源
什么是限流
对打入服务的请求流量进行控制, 使服务能够承担不超过自己能力的流量压力
Sentinel 和 Hystrix 的原则是一致的: 当检测到调用链路中某个资源出现不稳定的表现, 例如请求响应时间长或异常比例升高的时候, 则对这个资源的调用进行限制, 让请求快速失败,
避免影响到其它的资源而导致级联故障。
熔断降级设计理念
在限制的手段上, Sentinel 和 Hystrix 采取了完全不一样的方法。
Hystrix 通过 线程池隔离 的方式, 来对依赖( 在 Sentinel 的概念中对应 资源) 进行了隔离。 这样做的好处是资源和资源之间做到了最彻底的隔离。 缺点是除了增加了线程切换的成本(过多的线程池导致线程数目过多) , 还需要预先给各个资源做线程池大小的分配。
Sentinel 对这个问题采取了两种手段:
通过并发线程数进行限制
和资源池隔离的方法不同, Sentinel 通过限制资源并发线程的数量, 来减少不稳定资源对其它资源的影响。 这样不但没有线程切换的损耗, 也不需要您预先分配线程池的大小。 当某个资源出现不稳定的情况下, 例如响应时间变长, 对资源的直接影响就是会造成线程数的逐步堆积。 当线程数在特定资源上堆积到一定的数量之后, 对该资源的新请求就会被拒绝。 堆积的线程完成任务后才开始继续接收请求。
通过响应时间对资源进行降级
除了对并发线程数进行控制以外, Sentinel 还可以通过响应时间来快速降级不稳定的资源。当依赖的资源出现响应时间过长后, 所有对该资源的访问会被直接拒绝, 直到过了指定的时间窗口之后才重新恢复。
微服务架构是一个分布式架构, 它按业务划分服务单元, 一个分布式系统往往有很多个服务单元。 由于服务单元数量众多, 业务的复杂性, 如果出现了错误和异常, 很难去定位。 主要体现在, 一个请求可能需要调用很多个服务, 而内部服务的调用复杂性, 决定了问题难以定位。 所以微服务架构中, 必须实现分布式链路追踪, 去跟进一个请求到底有哪些服务参与,参与的顺序又是怎样的, 从而达到每个请求的步骤清晰可见, 出了问题, 很快定位。链路追踪组件有 Google 的 Dapper, Twitter 的 Zipkin, 以及阿里的 Eagleeye (鹰眼) 等, 它们都是非常优秀的链路追踪开源组件。