加群联系作者vx:xiaoda0423
仓库地址:https://webvueblog.github.io/JavaPlusDoc/
https://1024bat.cn/
https://github.com/webVueBlog/fastapi_plus
https://webvueblog.github.io/JavaPlusDoc/
# 负载均衡是什么
在业务初期,通常采用单台服务器即可满足需求,随着用户流量增长,单台服务器逐渐难以应对压力,这时我们会将多台服务器组成集群来提升处理能力,为了统一管理流量入口,需要通过负载均衡器将海量请求按照预设算法,智能分发到集群中不同服务器,这就是负载均衡。
广义的负载均衡器可分为3中:
1.DNS负载均衡:通过DNS解析,将域名解析到不同服务器IP,从而将流量分发到不同服务器,但DNS解析结果可能存在缓存,导致分发结果不准确。
用户访问域名www.aq.com通过DNS解析到多个IP,然后访问每个IP对应的服务器实例,就完成了流量调度。它没有使用常规的负载均衡器,但也的确完成了简单负载均衡的功能,优点是简单,成本低。缺点是,服务器故障切换延迟大,DNS与用户之间是层层的缓存,即便故障发生时,(域名解析缓存:浏览器缓存,操作系统缓存,/etc/hosts缓存,DNS缓存)DNS解析结果可能仍会分发到故障服务器,导致用户访问失败。
通过及时修改DNS或摘除故障服务器,但由于中间经过运营商的DNS缓存,且缓存很有可能不遵循TTL规则,导致DNS生效时间缓慢,仍访问到故障服务器。另外,它的流量调度策略简单,支持的算法较少。DNS一般只支持RR的轮询方式。实际上生产环境中很少用这种方式来实现负载均衡。描述DNS负载均衡方式,只是为了让你能够更清楚了解负载均衡的概念。
一般大公司也会使用DNS来实现地理级别的负载均衡,实现就近访问,提高访问速度,这种方式一般是入口流量的基础负载均衡。下层会有更专业的负载均衡设备实现负载架构。
2.硬件负载均衡:通过硬件设备实现负载均衡,如F5,硬件负载均衡性能稳定,但价格昂贵。
专门的硬件设备来实现,类似于交换机路由器,是一个负载均衡专用的网络设备,目前业界典型的硬件负载均衡设备主要有两款:F5,和A10.这类设备性能好,功能强大,但价格非常昂贵。优点,性能强,功能强大,价格贵。
3.软件负载均衡:通过软件实现负载均衡,如Nginx,LVS,HAProxy,软件负载均衡灵活,成本低廉,但性能不及硬件负载均衡。
它是指可以在普通服务器上运行的负载均衡软件,实现负载均衡功能,Nginx,LVS,HAProxy
Nginx是7层负载均衡 支持HTTP协议。(OSI:七层模型有应用层,表示层,会话层,传输层,网络层,数据链路层,物理层)
HAproxy也是7层负载均衡软件,性能也很不错,而LVS是纯4层的负载均衡,运行在内核态,性能是软件负载均衡中最高的,因为是在四层,所以也更通用一些。
软件负载均衡的特点:部署简单,便宜,灵活
LVS开源软件,节省成本。
# 对象存储是什么
超级仓库,传统存储文件像整理抽屉,分门别类建立文件夹。但对象存储直接给每个数据,贴个身份证-叫全局唯一标识符。照片,视频,代码包,全被打包成一个个对象仍进这个仓库里。不用管位置,只用记住名字。
1. 元数据自定义,给你的数据挂上智能标签。
2. 分布式架构,数据被拆分碎片,存在几千台服务器上,坏几台都不丢
3. 纠删码技术,把文件切分成乐高块,多存几块多余,恢复数据比Raid还稳
用:
1. 海量非结构化数据,比如短视频平台的10亿条视频,用对象存储成本直降50%;
2. 跨地域备份,AW3的S3阿某云OSS都是对象存储,传文件像发快递一样简单
3. AI训练集存储,百万张图片秒级调用
4. CDN缓存,把热门视频缓存到离用户最近的服务器,秒开
# 块存储是什么
想象一下,硬盘里存储一堆乐高积木,块存储就是把数据切成固定大小的块,比如4KB的小方块,每个数据块都有专属身份证--LBA逻辑块地址
就像乐高积木按照编号存放仓库里,当你需要读取文件时,存储控制器会通过SCSI或NVMe协议,像快递小哥一样精准找到对应区块。
为什么企业愿意花大价格买块存储?
1. 裸金属性能,直接访问物理磁盘,比文件存储少了两层协议开销;
2. 支持随机读写,数据库事务日志狂飙时,SSD+Raid5阵列能扛住每秒数万IOPS
3. 卷管理,黑科技,LUN逻辑单元让你像玩橡皮泥一样在线扩容
Oracle数据库靠San存储扛住百万并发;wmware集群通过iSCSI协议动态分配存储资源
# 什么是灰度发布
灰度发布,蓝绿发布,滚动发布:
灰度发布,它的原理就像矿井用金丝雀探毒气,程序员先让1%的用户用新版本,其他99%的用旧版本,通过AB测试对比数据,通过流量分流,比如用Nginx的权重配置,没问题再逐渐扩大流量,这样既能验证新功能。又不会让所有人一起踩坑,它可以用于电商大促前,上新功能,用灰度发布,崩了也只影响小部分用户。
蓝绿发布,土豪的双倍快乐。原理:直接部署两套环境,蓝环境跑旧版,绿环境跑新版,通过负载均衡一键切换全部流量。关键技术是基础设施即代码(IaC),和DNS解析切换。
比如Kuberneses,它通过Deployment,可以快速部署一套新版本,通过Service,可以快速切换流量。蓝绿发布,它需要双倍资源
比如Kuberneses里同时部署两套Deployment,瞬间切换Service的流量,就可以完成蓝绿发布。但代价是资源翻倍消耗
同时它也有致命弱点:如果新版数据库有兼容问题?全量回滚要30分钟起步!
滚动发布,穷鬼的极限操作:原理:像火车换轮子,先启动1台新服务器,关1台旧服务器,循环直到全切换完;关键技术是健康检查和滚动更新策略。比如Docker Swarm 的--update-parallelism参数,这种发布方式最省资源,但回滚速度慢。
也有致命陷阱,新旧版本会同时在线。用户可能上午看到新版界面,下午又变回旧版。
1. 资源消耗:蓝绿大于滚动大于灰度
2. 回滚速度:蓝绿最快(秒级)灰度中级(流量切换)滚动最慢(得重新部署
3. 适用场景,灰度:金融,电商等高风险系统;蓝绿:土豪公司发工资系统升级;滚动:小公司半夜偷偷更新
# WAF和DDOS的区别是什么
两大网络安全神器,WAF和DDOS防护:
WAF,它是Web Application Firewall,Web应用防火墙,它主要防护的是Web应用的安全,比如SQL注入,XSS攻击,CSRF攻击等,它通过分析HTTP协议,识别出恶意请求,然后阻止它。WAF一般部署在Web服务器前,通过反向代理的方式,将恶意请求拦截,从而保护Web应用的安全。
是Web应用防火墙。专门拦截“伪装成正常用户”的攻击。比如黑客用SQL注入代码偷数据库,或者用XSS跨站脚本盗取用户账户。Waf会像安检员一样。
逐条扫描HTTP请求里的恶意内容。
拦截不符合规则的数据包。
DDOS攻击,攻击者用成千上万的“肉鸡”设备,用海量垃圾流量冲击你的服务器。
Waf防的是应用层攻击(比如篡改网页,数据窃取)DDoS防的是网络层洪水攻击(比如TCP、UDP洪流)防护策略不同:Waf靠规则引擎识别恶意代码,DDoS靠流量清洗中心过滤异常流量。
选Waf:电商,银行等需要防数据泄露的场景;选DDoS防护,游戏,直播等易受流量攻击的领域。
# Jenkins是什么
持续集成工具,它用主从节点结构,通过Webhook监听底阿妈仓库变化,自动触发Pipeline脚本,完成编译,测试,打包,部署四连击。
第一:分布式构建让10台服务器同时干活;第二插件系统能对接Docker K8s甚至钉钉;第三,它的Pipeline脚本用Groovy语言写,支持条件判断,循环,函数,可以写复杂的逻辑。把部署流程写成代码。
用微服务拆了200个模块的架构师,要在测试,预发,生产环境反复横跳的运维,还有每次发版都手抖的新人。它甚至能帮你自动生成测试报告。钉钉直接@责任人。
# DevOps是什么
DevOps是一种文化,强调开发(Dev)和运维(Ops)的协同工作,通过自动化工具和流程,提高软件交付的效率和质量。
让代码从开发到部署全程自动化流水线。持续集成CI,持续交付CD,基础设施即代码。监控告警系统。
现实小时级别上线。故障率直降80%,微服务架构的团队。
# cookie-session-token
登录的三大护法:Session Cookie Token
为什么你关闭浏览器再打开淘宝,购物车还在?这就是Cookie在搞事情,它就像你逛超市时的小票,浏览器帮你把账号ID,浏览记录这些信息存在本地,下次访问时自动亮出会员卡。
致命弱点:黑客要偷你的Cookie,就能直接冒充你登录。
这就是为什么千万别点陌生链接。
但Session可就不一样了。服务器会给每个用户发个临时工牌,Session ID ,用户访问时,带上工牌,服务器就能知道你是谁了。Session ID存在服务器,用户关掉浏览器,Session ID就没了,下次访问,再领张新工牌。
所有隐私数据都锁在服务器保险柜。
Token,(JWT)加密通行证,把用户信息,有效期全加密成字符串。最牛的事服务器不用存数据,靠解密就能证明(这就叫无状态验证),现在微信,钉钉这些APP都在用。
1. 要简单用Cookie
2. 要安全用Session
3. 要灵活用Token(高并发,分布式系统)
# Ansible是什么
Ansible是什么, 自动化运维
能让你用一句话控制成百上千台服务器
底层靠SSH协议通信,Yaml语言写“剧本”,把复杂操作变成可重复的代码。核心技术叫“幂等性”,同一个任务重复执行。结果永远一致,比如批量安装软件。
Ansible会自动检测是否装过,避免重复劳动,批量部署,自动化运维,紧急修复漏洞,关键组件:Inventory定义服务器清单,modules是现成的功能模块。Playbook是任务剧本,roles是模块化角色集合。
实现原理
创建Future列表:首先创建一个
List<Future<?>>
类型的列表futures
,用于存放每个异步任务的执行结果。异步执行任务:使用
CompletableFuture.runAsync
方法异步执行每个任务,并将返回的Future
对象添加到futures
列表中。每个任务都在指定的Executor
(exec
)中执行。等待所有任务完成:通过遍历
futures
列表,调用每个Future
对象的get
方法,阻塞当前线程,直到所有任务完成。如果某个任务执行失败,会捕获异常并记录错误日志。
用途
该方法的主要用途是在后台并行执行多个同步任务,以提高系统性能和响应速度。例如,在一个数据同步系统中,可能需要同时同步多个数据源,以提高数据同步的效率。
注意事项
异常处理:在等待任务完成时,需要捕获并处理可能抛出的异常,以避免程序因未处理的异常而崩溃。
线程池:使用
Executor
来管理线程池,可以避免创建过多的线程,提高资源利用率。任务依赖:如果任务之间存在依赖关系,需要特别注意任务的执行顺序,避免因并行执行导致的逻辑错误。
日志记录:在异常处理中记录错误日志,有助于后续的问题追踪和调试。
@Slf4j
@Controller
@RequestMapping("/alipay")
@Api(tags = "支付宝虚拟支付接口")
public class AlipayController {
// 注入AlipayService
private final AlipayService alipayService;
// 构造函数,注入AlipayService
public AlipayController(AlipayService alipayService) {
this.alipayService = alipayService;
}
/*
https://xxx.xxx.xxx/alipay/create?orderNo=10060&orderName=商城-华为手机支付订单&payPrice=4000
*/
@ResponseBody
@PostMapping(value = "/create")
@ApiOperation("创建订单")
public String create(@ApiParam("订单号") @RequestParam("orderNo") String orderNo,
@ApiParam("订单名称") @RequestParam("orderName") String orderName,
@ApiParam("支付金额") @RequestParam("payPrice") String payPrice) {
//发起支付
return alipayService.create(orderNo, orderName, payPrice);
}
/*
https://xxx.xxx.xxx/alipay/refund?orderNo=10060&payPrice=4000
*/
@ResponseBody
@PostMapping(value = "/refund")
@ApiOperation("订单退款")
public ResultVO refund(@ApiParam("订单号") @RequestParam("orderNo") String orderNo,
@ApiParam("退款金额") @RequestParam("payPrice") String payPrice) {
//发起支付
try {
alipayService.refund(orderNo, payPrice);
} catch (AlipayApiException e) {
log.error("【支付宝支付】退款失败 error={}", e.toString());
return ResultVO.error("退款失败");
}
return ResultVO.success("退款成功");
}
@GetMapping(value = "/paySuccess")
@ApiOperation("支付成功同步回调接口")
public void success(@RequestParam Map<String, String> map, HttpServletResponse response) {
try {
// 获取订单号
String tradeNo = map.get("out_trade_no");
// 打印订单号
System.out.println("订单号:" + tradeNo);
// 重定向到支付成功页面
response.sendRedirect("/paySuccess");
} catch (IOException e) {
// 记录错误日志
log.error("支付成功,但重定向失败 error={}", e.toString());
}
}
@ResponseBody
@PostMapping(value = "/payNotify")
@ApiOperation("支付成功异步回调接口")
public void payNotify(@RequestParam Map<String, String> map) {
// 获取交易状态
String tradeStatus = map.get("trade_status");
// 如果交易状态为TRADE_SUCCESS,则记录支付成功信息
if (tradeStatus.equals("TRADE_SUCCESS")) {
// 获取支付时间
String payTime = map.get("gmt_payment");
// 获取订单号
String tradeNo = map.get("out_trade_no");
// 获取订单名称
String tradeName = map.get("subject");
// 获取交易金额
String payAmount = map.get("buyer_pay_amount");
// 记录支付成功信息
log.info("[支付成功] {交易时间:{},订单号:{},订单名称:{},交易金额:{}}", payTime, tradeNo, tradeName, payAmount);
}
}
}
@GetMapping(value = "/paySuccess")
@ApiOperation("支付成功同步回调接口")
public void success(@RequestParam Map<String, String> map, HttpServletResponse response) {
Optional.ofNullable(map.get("out_trade_no"))
.ifPresent(tradeNo -> {
try {
// 打印订单号
log.info("订单号:{}", tradeNo);
// 重定向到支付成功页面
response.sendRedirect("/paySuccess");
} catch (IOException e) {
// 记录错误日志
log.error("支付成功,但重定向失败", e);
// 返回错误响应给客户端
try {
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "支付成功,但重定向失败");
} catch (IOException ioException) {
log.error("发送错误响应失败", ioException);
}
}
});
}
@GetMapping(value = "/paySuccess")
@ApiOperation("支付成功同步回调接口")
public void success(@RequestParam Map<String, String> map, HttpServletResponse response) {
try {
// 获取订单号
String tradeNo = map.get("out_trade_no");
if (tradeNo == null) {
throw new IllegalArgumentException("订单号不能为空");
}
// 打印订单号
System.out.println("订单号:" + tradeNo);
// 重定向到支付成功页面
response.sendRedirect("/paySuccess");
} catch (IllegalArgumentException e) {
// 记录错误日志
log.error("支付成功,但订单号无效 error={}", e.getMessage());
// 向客户端返回错误信息
try {
response.sendError(HttpServletResponse.SC_BAD_REQUEST, "订单号不能为空");
} catch (IOException ioException) {
log.error("向客户端发送错误信息失败 error={}", ioException.toString());
}
} catch (IOException e) {
// 记录错误日志
log.error("支付成功,但重定向失败 error={}", e.toString());
// 向客户端返回错误信息
try {
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "重定向失败");
} catch (IOException ioException) {
log.error("向客户端发送错误信息失败 error={}", ioException.toString());
}
}
}
@GetMapping(value = "/paySuccess")
@ApiOperation("支付成功同步回调接口")
public void success(@RequestParam Map<String, String> map, HttpServletResponse response) {
try {
// 获取订单号
String tradeNo = map.get("out_trade_no");
// 打印订单号
System.out.println("订单号:" + tradeNo);
// 重定向到支付成功页面
response.sendRedirect("/paySuccess");
} catch (IOException e) {
// 记录错误日志
log.error("支付成功,但重定向失败 error={}", e.toString());
}
}
@Configuration
// 配置类,用于配置Spring Boot应用程序
public class RedisConfig {
@Bean
// 定义一个Bean,用于创建RedisTemplate实例
@SuppressWarnings("all")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
//创建RedisTemplate对象
RedisTemplate<String, Object> template = new RedisTemplate<>();
//设置Redis连接工厂
template.setConnectionFactory(redisConnectionFactory);
//Json序列化配置
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
//设置所有属性可见
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
//启用默认类型
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
//设置ObjectMapper
jackson2JsonRedisSerializer.setObjectMapper(om);
//String的序列化
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
//key采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
//hash的key也采用String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
//value序列化方式采用jackson
template.setValueSerializer(jackson2JsonRedisSerializer);
//hash的value的序列化方式采用jackson
template.setHashValueSerializer(jackson2JsonRedisSerializer);
//初始化RedisTemplate
template.afterPropertiesSet();
return template;
}
}
@Service
public class MyUserDetailsService implements UserDetailsService {
//注入ManagerRoleMapper
@Autowired
private ManagerRoleMapper managerAuthMapper;
//注入ManagerService
@Autowired
private ManagerService managerService;
@Override
public UserDetails loadUserByUsername(String userName){
//查询用户信息
Manager manager;
//如果用户名包含@,则按账号查询,否则按姓名查询
if(userName.contains("@")){
manager = managerService.queryManagerByAccount(userName);
}else{
manager = managerService.queryManagerByName(userName);
}
if(manager==null){
throw new UsernameNotFoundException("用户不存在");
}
//获取权限列表
List<String> auths = managerAuthMapper.queryRole(manager.getManagerId());
//添加默认权限
auths.add("default");
// 使用Spring Security内部UserDetails的实现类User,来创建User对象
/**
* 参数1:用户名
* 参数2:密码
* 参数3:账户是否过期
* 参数4:账户是否锁定
* 参数5:凭证是否过期
* 参数6:账户是否可用
* 参数7:权限列表
*
*/
// 返回一个新的User对象,参数分别为用户名、密码、是否删除、是否启用、是否锁定、权限列表
return new User(userName, manager.getPassWord(), !manager.getDeleteState(),
true,true,
!manager.getLockedState(),
AuthorityUtils.commaSeparatedStringToAuthorityList(String.join(",", auths)));
}
}