分布式微服务系统架构第145集:Jeskson文档-微服务分布式系统架构

发布于:2025-06-12 ⋅ 阅读:(16) ⋅ 点赞:(0)

加群联系作者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是模块化角色集合。

实现原理

  1. 创建Future列表:首先创建一个List<Future<?>>类型的列表futures,用于存放每个异步任务的执行结果。

  2. 异步执行任务:使用CompletableFuture.runAsync方法异步执行每个任务,并将返回的Future对象添加到futures列表中。每个任务都在指定的Executorexec)中执行。

  3. 等待所有任务完成:通过遍历futures列表,调用每个Future对象的get方法,阻塞当前线程,直到所有任务完成。如果某个任务执行失败,会捕获异常并记录错误日志。

用途

该方法的主要用途是在后台并行执行多个同步任务,以提高系统性能和响应速度。例如,在一个数据同步系统中,可能需要同时同步多个数据源,以提高数据同步的效率。

注意事项

  1. 异常处理:在等待任务完成时,需要捕获并处理可能抛出的异常,以避免程序因未处理的异常而崩溃。

  2. 线程池:使用Executor来管理线程池,可以避免创建过多的线程,提高资源利用率。

  3. 任务依赖:如果任务之间存在依赖关系,需要特别注意任务的执行顺序,避免因并行执行导致的逻辑错误。

  4. 日志记录:在异常处理中记录错误日志,有助于后续的问题追踪和调试。

@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)));
    }
}