问题4-10
问题4.session存储到redis中,保存实体内容进入redis报错。
解决方案
解决1:保存的数据实体(MemberResponseVo)实现implements Serializable。
解决2:其实我们还可以使用JSON的序列化方式来序列化对象到Redis中。直接 JSON.toJSONString(MemberResponseVo);
总结:
Serializable这个接口其实是个空接口。当我们让实体类实现Serializable接口时,其实是在告诉JVM此类可被序列化,可被默认的序列化机制序列化,不需要我们实现
问题5.多系统-单点登录问题(分布式单点登录框架:xxl-sso)
一、windows文件夹里测试sso项目
1.下载xxl-sso项目,修改为本地配置,并且修改本地hosts地址,域名都指向127.0.0.1
2.当前文件夹下cmd,然后输入打包命令:mvn clean package -Dmaven.skip.test=true(打包跳过测试)
3.启动jar包,启动tagret下jar包,启动命令:java -jar xxl-sso-server-1.1.1-SNAPSHOT.jar
4.登录http://ssoserver.com:8080/xxl-sso-server/login
5.模拟启动另外两个web服务,java -jar xxxx --server.post=8081 java -jar xxxx --server.post=8082
二、单点登录流程
1.clent1去登录,跳转到登录页面接口login.html,判断是否携带cookie,没有就重定向到登录页面,输入密码登录
- 2.账号密码校验成功后
1).把用户信息存到redis中
2).根据uuid生成一个cookie,放入cookie中( response.addCookie(sso_token);)浏览器在当前域名会话上保存cookie。
3.client1后面直接拿token判断是否登录成功,如果判断登录成功后,直接用token去redis拿用户信息,然后存到当前服务session中,
自己系统将用户保存在自己的会话中,最后页面直接取session中的数据。(session不能跨域)
4.其它client2登录的时候,由于浏览器已经保存了cookie,可以直接免登录。如果cookie过时失效,则重新登录。
问题6.购物车添加-重复刷新页面提交,商品数量增加问题。
解决方案:重定向新页面,刷新页面就不会添加数据
解决:/addCartItem接口,添加成功后,不应该直接展示成功页面,让他重定向到成功页面,这样我们成功添加以后就是跳转到了一个新页面,这样就可以只刷新页面不添加数据。
/**
* 添加商品到购物车
* attributes.addFlashAttribute():将数据放在session中,可以在页面中取出,但是只能取一次
* attributes.addAttribute():将数据放在url后面
* @return
*/
问题7.订单服务-feign远程调用丢失请求头(Cookie)问题
场景问题:
auth服务登录成功,订单服务跳转到结算页面,浏览器发送请求时,自动带了Cooike,然后订单服务需要调用其他远程服务获取数据,但进入购物车服务时,没有用户信息
解决方案:加feign远程调用请求拦截器
加上 feign 远程调用的请求拦截器,在每次发送远程请求之前,把老请求的数据同步过来,这样就可以解决请求头的丢失问题了。
@Configuration
public class FeignConfig {
/**
* 解决远程服务丢失请求头的问题
* fix #I414SW
* @return
*/
@Bean("requestInterceptor")
public RequestInterceptor requestInterceptor() {
return template -> {
/*
通过请求的上下文环境,拿到刚进来的请求数据
*/
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes != null) {
HttpServletRequest request = attributes.getRequest();
if (request != null) {
// 将老请求的请求头放到新请求的请求头里面
template.header("Cookie", request.getHeader("Cookie"));
}
}
};
}
}
问题8.订单服务-异步任务丢失上下文问题
场景问题:
获取address、cart所使用的线程,与主任务的线程不同,所以异步任务无法获取主任务的上下文环境。(重新开启线程时,新的线程没有上下文环境信息)
解决方案:拿到原线程上下文,在新线程重新设置
将主线程中原始请求的上下文数据共享出来,在新开启的异步任务中重新设置上下文数据,即可解决
// 1.共享主线程中拿到原请求数据
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
// 查询会员服务地址信息
CompletableFuture<Void> memberFuture = CompletableFuture.runAsync(() -> {
// 2.重新设置异步任务的请求数据
RequestContextHolder.setRequestAttributes(requestAttributes);
R r = memberFeignService.getAddress(memberTO.getId());
if (r.getCode() == 0) {
List<OrderConfirmVO.MemberAddressVO> address = r.getData(new TypeReference<List<OrderConfirmVO.MemberAddressVO>>() {
});
orderConfirmVO.setAddress(address);
}
}, executor);
问题9:提交订单(重复提交)-幂等性问题
场景问题:
订单服务-提交订单按钮需要防止重复提交,造成生成多条订单?
解决方案:token 机制
- 服务端提供了发送 token 的接口。我们在分析业务的时候,哪些业务是存在幂等问题的,就必须在执行业务前,先去获取 token,服务器会把 token 保存到 redis 中。
- 然后调用业务接口请求时,把 token 携带过去,一般放在请求头部。
- 服务器判断 token 是否存在 redis 中,存在表示第一次请求,然后删除 token,继续执行业务。
- 如果判断 token 不存在 redis 中,就表示是重复操作,直接返回重复标记给 client,这样就保证了业务代码,不被重复执行。
//一、调用跳转订单确认页面接口时-生成防重令牌,并返回前端页面
String orderToken = UUID.randomUUID().toString().replace("-", "");
orderConfirmVO.setOrderToken(orderToken);
stringRedisTemplate.opsForValue().set(OrderConstant.ORDER_TOKEN_PREFIX + memberTO.getId(), orderToken, 30,
TimeUnit.MINUTES);
//二、调用提交订单接口时,需提交防重令牌(token)
String orderToken = orderSubmitVO.getOrderToken();
//Token 获取、比较和删除必须是原子性 在redis 使用 lua 脚本完成这个操作
// 这段脚本的意思是,如果获取key对应的value是传过来的值,那就调用删除方法返回1,否则返回0
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
// 原子删锁
/*
实现RedisScript接口的实现类(脚本内容,执行完之后的返回值类型)
一个存key的list
要对比的value
*/
Long result = stringRedisTemplate.execute(new DefaultRedisScript<>(script, Long.class),
Arrays.asList(OrderConstant.ORDER_TOKEN_PREFIX + memberTO.getId()), orderToken);
// 1、校验令牌
if (result == 0L) {
// 验证失败
responseVO.setCode(1);
} else {
// 验证成功
}
问题10.订单服务调用feign远程服务,出现异常,事务如何保证?
场景问题
场景1:远程服务其实成功了,由于网络故障等没有返回 导致:订单回滚,库存却扣减
场景2:远程服务执行完成,下面的其他方法出现问题 导致:已执行的远程请求,肯定不能回滚。因为远程服务调用的数据库都不一样,建立的一个新连接,肯定不能回滚。
解决方案:利用消息队列实现最终一致
库存服务锁定成功后发给消息队列消息(当前库 存工作单),过段时间自动解锁,解锁时先查询 订单的支付状态。解锁成功修改库存工作单详情 项状态为已解锁