当前位置: 首页 > news >正文

网站建设兆金手指下拉php网站连接数据库

网站建设兆金手指下拉,php网站连接数据库,公司变更法人的流程,怎样看网站是谁做的目录 前言微服务保护初识Sentinel雪崩问题及解决方案雪崩问题超时处理仓壁模式熔断降级流量控制总结 服务保护技术对比Sentinel介绍和安装微服务整合Sentinel 流量控制快速入门流控模式关联模式链路模式小结 流控效果warm up排队等待 热点参数限流全局参数限流热点参数限流案例… 目录 前言微服务保护初识Sentinel雪崩问题及解决方案雪崩问题超时处理仓壁模式熔断降级流量控制总结 服务保护技术对比Sentinel介绍和安装微服务整合Sentinel 流量控制快速入门流控模式关联模式链路模式小结 流控效果warm up排队等待 热点参数限流全局参数限流热点参数限流案例demo 隔离和降级FeignClient整合Sentinel线程隔离熔断降级慢调用异常比例 异常数 授权规则授权基本规则自定义异常结果 规则持久化 分布式事务分布式事务问题本地事务分布式事务分布式事务问题示例 理论基础CAP定理BASE理论解决分布式事务的思路小结 初识Seata架构Seata的架构部署Seata的tc-server微服务集成Seata 代码实践XA模式AT模式TCC模式SAGA模式四种模式的对比 高可用高可用架构模型实现高可用 分布式缓存单点缓存的弊端Redis持久化RDB持久化AOF持久化RDB与AOF对比 Redis主从搭建主从架构主从数据同步原理主从同步优化总结 Redis哨兵哨兵原理搭建哨兵集群RedisTemplate哨兵模式 Redis分片集群搭建分片集群散列插槽集群伸缩故障转移RedisTemplate访问分片集群集群最终结构 前言 本篇是学习微服务进阶篇的一些心得和学习笔记学习资源来自B站黑马愿我们共同进步感谢您的阅览。 微服务保护 初识Sentinel 雪崩问题及解决方案 雪崩问题 一个服务故障导致依赖它的服务也发生故障 故障部分如下图 而上面故障对于整个微服务架构而言 微服务调用链路中的某个服务故障引起整个链路中的所有微服务都不可用这就是雪崩。 超时处理 超时处理设定超时时间请求超过一定时间没有响应就返回错误信息不会无休止等待 但是它并没有解决雪崩问题只是缓解雪崩一旦访问请求速度超过等待并返回速度就会再次积累请求有可能再次发生雪崩。 仓壁模式 仓壁模式来源于船舱的设计船舱都会被隔板分离为多个独立空间当船体破损时只会导致部分空间进入将故障控制在一定范围内避免整个船体都被淹没。 于此类似我们可以限定每个业务能使用的线程数避免耗尽整个tomcat的资源因此也叫线程隔离。 熔断降级 断路器模式由断路器统计业务执行的异常比例如果超出阈值则会熔断该业务拦截访问该业务的一切请求。 断路器会统计访问某个服务的请求数量异常比例。 当发现访问服务D的请求异常比例过高时认为服务D有导致雪崩的风险会拦截访问服务D的一切请求形成熔断 流量控制 流量控制限制业务访问的QPS每秒钟服务处理请求的数量避免服务因流量的突增而故障。 前面三种方式是应对已经出现故障了如何避免故障传递 而此处是为了防止出现故障主要防止的是DOS攻击是一种预防的措施 总结 什么是雪崩问题 微服务之间相互调用因为调用链中的一个服务故障引起整个链路都无法访问的情况。 处理方案 限流是对服务的保护避免因瞬间高并发流量而导致服务故障进而避免雪崩。是一种预防措施。 超时处理、线程隔离、降级熔断是在部分服务故障时将故障控制在一定范围避免雪崩。是一种补救措施。 服务保护技术对比 早期比较流行的是Hystrix框架但目前国内实用最广泛的还是阿里巴巴的Sentinel框架这里我们做下对比 Sentinel介绍和安装 初识Sentinel Sentinel是阿里巴巴开源的一款微服务流量控制组件。官网地址https://sentinelguard.io/zh-cn/index.html Sentinel 具有以下特征: •丰富的应用场景Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景例如秒杀即突发流量控制在系统容量可以承受的范围、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。 •完备的实时监控Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据甚至 500 台以下规模的集群的汇总运行情况。 •广泛的开源生态Sentinel 提供开箱即用的与其它开源框架/库的整合模块例如与 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。 •完善的 SPI 扩展点Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。 安装Sentinel 1下载 sentinel官方提供了UI控制台方便我们对系统做限流设置。大家可以在GitHub下载。 2运行 将jar包放到任意非中文目录执行命令 java -jar sentinel-dashboard-1.8.1.jar如果要修改Sentinel的默认端口、账户、密码可以通过下列配置 例如修改端口 java -Dserver.port8090 -jar sentinel-dashboard-1.8.1.jar3访问 访问http://localhost:8080页面就可以看到sentinel的控制台了 需要输入账号和密码默认都是sentinel 登录后发现一片空白什么都没有 这是因为我们还没有与微服务整合。 微服务整合Sentinel 还是前面的那个cloud-demo工程中 项目结构 order-service中整合sentinel并连接sentinel的控制台步骤如下 1引入sentinel依赖 !--sentinel-- dependencygroupIdcom.alibaba.cloud/groupId artifactIdspring-cloud-starter-alibaba-sentinel/artifactId /dependency2配置yml文件 修改application.yaml文件添加下面内容 主要是添加sentinel的配置信息 server:port: 8088 spring:cloud: sentinel:transport:dashboard: localhost:80803访问order-service的任意端点 注意服务启动前需要先启动 nacosSentinel nacos启动方式 找到nacos的安装目录 在下面路径中键入cmd 然后控制台中输入 startup.cmd -m standaloneSentinel启动方式 在以下路径中键入cmd 命令行中输入 java -jar sentinel-dashboard-1.8.1.jar然后启动成功 打开浏览器访问http://localhost:8088/order/101这样才能触发sentinel的监控。 然后再访问sentinel的控制台查看效果 流量控制 雪崩问题虽然有四种方案但是限流是避免服务因突发的流量而发生故障是对微服务雪崩问题的预防。 快速入门 启动cloud-demo服务 访问http://localhost:8080进入Sentinel监控界面 使用jmeter来测试结果如果手速够快也可以自己手动发送3个请求 狂点刷新 每秒只通过三个请求 最后回到Sentinel监控界面可以看到监控情况变化 流控模式 在添加限流规则时点击高级选项可以选择三种流控模式 直接统计当前资源的请求触发阈值时对当前资源直接限流也是默认的模式关联统计与当前资源相关的另一个资源触发阈值时对当前资源限流链路统计从指定链路访问到本资源的请求触发阈值时对指定链路限流 快速入门测试的就是直接模式。 关联模式 关联模式统计与当前资源相关的另一个资源触发阈值时对当前资源限流 配置规则 语法说明当/write资源访问量触发阈值时就会对/read资源限流避免影响/write资源。 因为在修改业务中write优先级高于read所以就会对read进行限流 使用场景比如用户支付时需要修改订单状态同时用户要查询订单。查询和修改操作会争抢数据库锁产生竞争。业务需求是优先支付和更新订单的业务因此当修改订单业务触发阈值时需要对查询订单业务限流。 需求说明 在OrderController新建两个端点/order/query和/order/update无需实现业务 配置流控规则当/order/ update资源被访问的QPS超过5时对/order/query请求限流 1定义/order/query端点模拟订单查询 GetMapping(/query) public String queryOrder() {return 查询订单成功; }2定义/order/update端点模拟订单更新 GetMapping(/update) public String updateOrder() {return 更新订单成功; }重启服务查看sentinel控制台的簇点链路 3配置流控规则 对哪个端点限流就点击哪个端点后面的按钮。我们是对订单查询/order/query限流因此点击它后面的按钮 在表单中填写流控规则 4在Jmeter测试 选择《流控模式-关联》 可以看到1000个用户100秒因此QPS为10超过了我们设定的阈值5 查看http请求 请求的目标是/order/update这样这个断点就会触发阈值。 但限流的目标是/order/query我们在浏览器访问可以发现 确实被限流了。 5总结 链路模式 链路模式只针对从指定链路访问到本资源的请求做统计判断是否超过阈值。 配置示例 例如有两条请求链路 /test1 -- /common /test2 -- /common 如果只希望统计从/test2进入到/common的请求则可以这样配置 实战案例 购买优先询问稍后真实 需求有查询订单和创建订单业务两者都需要查询商品。针对从查询订单进入到查询商品的请求统计并设置限流。 步骤 在OrderService中添加一个queryGoods方法不用实现业务 在OrderController中改造/order/query端点调用OrderService中的queryGoods方法 在OrderController中添加一个/order/save的端点调用OrderService的queryGoods方法 给queryGoods设置限流规则从/order/query进入queryGoods的方法限制QPS必须小于2 实现 1添加查询商品方法 在order-service服务中给OrderService类添加一个queryGoods方法 public void queryGoods(){System.err.println(查询商品); }2查询订单时查询商品 在order-service的OrderController中修改/order/query端点的业务逻辑 GetMapping(/query) public String queryOrder() {// 查询商品orderService.queryGoods();// 查询订单System.out.println(查询订单);return 查询订单成功; }3新增订单查询商品 在order-service的OrderController中修改/order/save端点模拟新增订单 GetMapping(/save) public String saveOrder() {// 查询商品orderService.queryGoods();// 查询订单System.err.println(新增订单);return 新增订单成功; }4给查询商品添加资源标记 默认情况下OrderService中的方法是不被Sentinel监控的需要我们自己通过注解来标记要监控的方法。 给OrderService的queryGoods方法添加SentinelResource注解 SentinelResource(goods) public void queryGoods(){System.err.println(查询商品); }链路模式中是对不同来源的两个链路做监控。但是sentinel默认会给进入SpringMVC的所有请求设置同一个root资源会导致链路模式失效。 我们需要关闭这种对SpringMVC的资源聚合修改order-service服务的application.yml文件 spring:cloud:sentinel:web-context-unify: false # 关闭context整合重启服务访问/order/query和/order/save可以查看到sentinel的簇点链路规则中出现了新的资源 5添加流控规则 点击goods资源后面的流控按钮在弹出的表单中填写下面信息 只统计从/order/query进入/goods的资源QPS阈值为2超出则被限流。 结果如下图所示 小结 流控模式有哪些 •直接对当前资源限流 •关联高优先级资源触发阈值对低优先级资源限流。 •链路阈值统计时只统计从指定资源进入当前资源的请求是对请求来源的限流 流控效果 在流控的高级选项中还有一个流控效果选项 流控效果是指请求达到流控阈值时应该采取的措施包括三种 快速失败达到阈值后新的请求会被立即拒绝并抛出FlowException异常。是默认的处理方式。 warm up预热模式对超出阈值的请求同样是拒绝并抛出异常。但这种模式阈值会动态变化从一个较小值逐渐增加到最大阈值。 排队等待让所有的请求按照先后次序排队执行两个请求的间隔不能小于指定时长 warm up 阈值一般是一个微服务能承担的最大QPS但是一个服务刚刚启动时一切资源尚未初始化冷启动如果直接将QPS跑到最大值可能导致服务瞬间宕机。 warm up也叫预热模式是应对服务冷启动的一种方案。请求阈值初始值是 maxThreshold / coldFactor持续指定时长后逐渐提高到maxThreshold值。而coldFactor的默认值是3. 例如我设置QPS的maxThreshold为10预热时间为5秒那么初始阈值就是 10 / 3 也就是3然后在5秒后逐渐增长到10. 案例 需求给/order/{orderId}这个资源设置限流最大QPS为10利用warm up效果预热时长为5秒 1配置流控规则 2Jmeter测试 QPS为10. 刚刚启动时大部分请求失败成功的只有3个说明QPS被限定在3 随着时间推移成功比例越来越高 最后监控界面可以看到拒绝越来越少通过逐渐变多完成服务的预热 排队等待 当请求超过QPS阈值时快速失败和warm up 会拒绝新的请求并抛出异常。 而排队等待则是让所有请求进入一个队列中然后按照阈值允许的时间间隔依次执行。后来的请求必须等待前面执行完成如果请求预期的等待时间超出最大时长则会被拒绝。 工作原理 例如QPS 5每秒处理5个请求意味着每200ms处理一个队列中的请求timeout 2000意味着预期等待时长超过2000ms的请求会被拒绝并抛出异常。 那什么叫做预期等待时长呢 比如现在一下子来了12 个请求因为每200ms执行一个请求那么 第6个请求的预期等待时长 200 * 6 - 1 1000ms第12个请求的预期等待时长 200 * 12-1 2200ms 如果使用队列模式做流控所有进入的请求都要排队以固定的200ms的间隔执行QPS会变的很平滑 也是实现流量削峰的原理平滑的QPS曲线对于服务器来说是更友好的。 案例 需求给/order/{orderId}这个资源设置限流最大QPS为10利用排队的流控效果超时时长设置为5s 1添加流控规则 2Jmeter测试 QPS为15已经超过了我们设定的10。 如果是之前的 快速失败、warmup模式超出的请求应该会直接报错。 但是我们看看队列模式的运行结果 全部都通过了。 再去sentinel查看实时监控的QPS曲线 QPS非常平滑一致保持在10但是超出的请求没有被拒绝而是放入队列。因此响应时间等待时间会越来越长。 当队列满了以后才会有部分请求失败 小结 流控效果有哪些 快速失败QPS超过阈值时拒绝新的请求 warm up QPS超过阈值时拒绝新的请求QPS阈值是逐渐提升的可以避免冷启动时高并发导致服务宕机。 排队等待请求会进入队列按照阈值允许的时间间隔依次执行请求如果请求预期等待时长大于超时时间直接拒绝 热点参数限流 全局参数限流 之前的限流是统计访问某个资源的所有请求判断是否超过QPS阈值。而热点参数限流是分别统计参数值相同的请求判断是否超过QPS阈值。 例如一个根据id查询商品的接口 访问/goods/{id}的请求中id参数值会有变化热点参数限流会根据参数值分别统计QPS统计结果 当id1的请求触发阈值被限流时id值不为1的请求不受影响。 配置示例 代表的含义是对hot这个资源的0号参数第一个参数做统计每1秒相同参数值的请求数不能超过5 热点参数限流 刚才的配置中对查询商品这个接口的所有商品一视同仁QPS都会被限定为5. 而在实际开发中可能部分商品是热点商品例如秒杀商品我们希望这部分商品的QPS限制与其它商品不一样高一些。那就需要配置热点参数限流的高级选项了 结合上一个配置这里的含义是对0号的long类型参数限流每1秒相同参数的QPS不能超过5有两个例外 •如果参数值是100则每1秒允许的QPS为10 •如果参数值是101则每1秒允许的QPS为15 案例demo 案例需求给/order/{orderId}这个资源添加热点参数限流规则如下 •默认的热点参数规则是每1秒请求量不超过2 •给102这个参数设置例外每1秒请求量不超过4 •给103这个参数设置例外每1秒请求量不超过10 注意事项热点参数限流对默认的SpringMVC资源无效需要利用SentinelResource注解标记资源 1标记资源 给order-service中的OrderController中的/order/{orderId}资源添加注解 2热点参数限流规则 访问该接口可以看到标记的hot资源出现了 这时添加热点规则需要在下面位置添加 默认单机阈值要设置为2符合上面案例的需求 3Jmeter测试 分别对默认hot项和设置的两个例外项来压测做比较 默认的参数 设置的例外项参数102 102的参数QPS阈值为4 设置的例外项参数103 例外项QPS阈值为10 它的阙值15而测试模拟的QPS为5所以它处理的绰绰有余不会拒绝并返回异常 隔离和降级 限流是一种预防措施虽然限流可以尽量避免因高并发而引起的服务故障但服务还会因为其它原因而故障。 而要将这些故障控制在一定范围避免雪崩就要靠线程隔离舱壁模式和熔断降级手段了。 线程隔离之前讲到过调用者在调用服务提供者时给每个调用的请求分配独立线程池出现故障时最多消耗这个线程池内资源避免把调用者的所有资源耗尽。 熔断降级是在调用方这边加入断路器统计对服务提供者的调用如果调用的失败比例过高则熔断该业务不允许访问该服务的提供者了。 不管是线程隔离还是熔断降级都是对客户端调用方的保护。需要在调用方 发起远程调用时做线程隔离、或者服务熔断。 而微服务远程调用都是基于Feign来完成的因此我们需要将Feign与Sentinel整合在Feign里面实现线程隔离和服务熔断。 FeignClient整合Sentinel SpringCloud中微服务调用都是通过Feign来实现的因此做客户端保护必须整合Feign和Sentinel。 修改配置开启sentinel功能 feign:sentinel:enabled: true # 开启feign对sentinel的支持编写失败降级逻辑 以前业务失败就是直接拒绝并抛出异常 业务失败后不能直接报错而应该返回用户一个友好提示或者默认结果这个就是失败降级逻辑。 给FeignClient编写失败后的降级逻辑 ①方式一FallbackClass无法对远程调用的异常做处理 ②方式二FallbackFactory可以对远程调用的异常做处理我们选择这种 这里演示方式二的失败降级处理。 步骤一在feing-api项目中定义类实现FallbackFactory Slf4j public class UserClientFallbackFactory implements FallbackFactoryUserClient {Overridepublic UserClient create(Throwable throwable) {return new UserClient() {Overridepublic User findById(Long id) {log.error(查询用户异常, throwable);return new User();}};} } 步骤二在feing-api项目中的DefaultFeignConfiguration类中将UserClientFallbackFactory注册为一个Bean Bean public UserClientFallbackFactory userClientFallbackFactory(){return new UserClientFallbackFactory(); }步骤三在feing-api项目中的UserClient接口中使用UserClientFallbackFactory import cn.itcast.feign.clients.fallback.UserClientFallbackFactory; import cn.itcast.feign.pojo.User; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable;FeignClient(value userservice, fallbackFactory UserClientFallbackFactory.class) public interface UserClient {GetMapping(/user/{id})User findById(PathVariable(id) Long id); }重启order服务后访问一次订单查询业务比如http://localhost:8088/order/102然后查看sentinel控制台可以看到新的簇点链路 总结 Sentinel支持的雪崩解决方案 线程隔离仓壁模式降级熔断 Feign整合Sentinel的步骤 在application.yml中配置feign.sentienl.enabletrue给FeignClient编写FallbackFactory并注册为Bean将FallbackFactory配置到FeignClient 线程隔离 线程池隔离给每个服务调用业务分配一个线程池利用线程池本身实现隔离效果 信号量隔离不创建线程池而是计数器模式记录业务使用的线程数量达到信号量上限时禁止新的请求。 两者的优缺点 主动超时对于线程池隔离来说请求向下是各个独立的线程池如果该线程耗时很久没返回就可以通过线程池来控制把线程终止掉 异步调用线程池隔离是不使用请求本身的线程它会把请求放到自己的线程池里面去调用其他服务对于每个服务都有独立的线程所以就是异步调用。 高扇出意味着 【A服务器】会调用很多服务如果使用线程隔离意味着【A服务器】要给每个调用的服务器开辟一个线程池进而导致资源大量消耗 sentinel的线程隔离 用法说明 在添加限流规则时可以选择两种阈值类型 QPS就是每秒的请求数在快速入门中已经演示过 线程数是该资源能使用用的tomcat线程数的最大值。也就是通过限制线程数量实现线程隔离舱壁模式。 案例需求给 order-service服务中的UserClient的查询用户接口设置流控规则线程数不能超过 2。然后利用jemeter测试。 1配置隔离规则 选择feign接口后面的流控按钮 填写表单 2Jmeter测试 选择《阈值类型-线程数2》 一次发生10个请求有较大概率并发线程数超过2而超出的请求会走之前定义的失败降级逻辑。 发现虽然结果都是通过了不过部分请求得到的响应是降级返回的null信息。 总结 线程隔离的两种手段是 信号量隔离 线程池隔离 信号量隔离的特点是 基于计数器模式简单开销小 线程池隔离的特点是 基于线程池模式有额外开销但隔离控制更强 熔断降级 熔断降级是解决雪崩问题的重要手段。其思路是由断路器统计服务调用的异常比例、慢请求比例如果超出阈值则会熔断该服务。即拦截访问该服务的一切请求而当服务恢复时断路器会放行访问该服务的请求。 断路器控制熔断和放行是通过状态机来完成的 状态机包括三个状态 closed关闭状态断路器放行所有请求并开始统计异常比例、慢请求比例。超过阈值则切换到open状态open打开状态服务调用被熔断访问被熔断服务的请求会被拒绝快速失败直接走降级逻辑。Open状态5秒后会进入half-open状态half-open半开状态放行一次请求根据执行结果来判断接下来的操作。 请求成功则切换到closed状态请求失败则切换到open状态 断路器熔断策略有三种慢调用、异常比例、异常数 慢调用 慢调用业务的响应时长RT大于指定时长的请求认定为慢调用请求。在指定时间内如果请求数量超过设定的最小数量慢调用比例大于设定的阈值则触发熔断。 解读RT超过500ms的调用是慢调用统计最近10000ms内的请求如果请求量超过10次并且慢调用比例不低于0.5则触发熔断熔断时长为5秒。然后进入half-open状态放行一次请求做测试。 案例 需求给 UserClient的查询用户接口设置降级规则慢调用的RT阈值为50ms统计时间为1秒最小请求数量为5失败阈值比例为0.4熔断时长为5 1设置慢调用 修改user-service中的/user/{id}这个接口的业务。通过休眠模拟一个延迟时间 此时orderId101的订单关联的是id为1的用户调用时长超过60ms 2设置熔断规则 下面给feign接口设置降级规则 超过50ms的请求都会被认为是慢请求 3测试 在浏览器访问http://localhost:8088/order/101快速刷新5次可以发现 触发了熔断请求时长缩短至5ms快速失败了并且走降级逻辑返回的null 异常比例 异常数 异常比例或异常数统计指定时间内的调用如果调用次数超过指定请求数并且出现异常的比例达到设定的比例阈值或超过指定异常数则触发熔断。 例如一个异常比例设置 解读统计最近1000ms内的请求如果请求量超过10次并且异常比例不低于0.4则触发熔断。 例如一个异常数设置 解读统计最近1000ms内的请求如果请求量超过10次并且异常比例不低于2次则触发熔断。 案例 需求给 UserClient的查询用户接口设置降级规则统计时间为1秒最小请求数量为5失败阈值比例为0.4熔断时长为5s 1设置异常请求 首先修改user-service中的/user/{id}这个接口的业务。手动抛出异常以触发异常比例的熔断 也就是说id 为 2时就会触发异常 3测试 在浏览器快速访问http://localhost:8088/order/102快速刷新5次触发熔断 服务熔断后此时我们再去访问本来应该正常的103 由结果可以看出服务确实熔断不可调用了 授权规则 授权规则可以对请求方来源做判断和控制。 授权基本规则 授权规则可以对调用方的来源做控制来保护微服务有白名单和黑名单两种方式。 白名单来源origin在白名单内的调用者允许访问 黑名单来源origin在黑名单内的调用者不允许访问 点击左侧菜单的授权可以看到授权规则 资源名就是受保护的资源例如/order/{orderId} 流控应用是来源者的名单 如果是勾选白名单则名单中的来源被许可访问。如果是勾选黑名单则名单中的来源被禁止访问。 比如 我们允许请求从gateway到order-service不允许浏览器访问order-service那么白名单中就要填写网关的来源名称origin。 如何获取origin Sentinel是通过RequestOriginParser这个接口的parseOrigin来获取请求的来源的。 public interface RequestOriginParser {/*** 从请求request对象中获取origin获取方式自定义*/String parseOrigin(HttpServletRequest request); }这个方法的作用就是从request对象中获取请求者的origin值并返回。 默认情况下sentinel不管请求者从哪里来返回值永远是default也就是说一切请求的来源都被认为是一样的值default。 因此我们需要自定义这个接口的实现类让不同的请求返回不同的origin。 例如order-service服务中我们定义一个RequestOriginParser的实现类 Component public class HeaderOriginParser implements RequestOriginParser {Overridepublic String parseOrigin(HttpServletRequest httpServletRequest) {//获取请求头String orgin httpServletRequest.getHeader(orgin);//非空判断if(StringUtils.isEmpty(orgin)){orgin blank;}return orgin;} } 我们尝试从request-header中获取origin值。 给网关添加请求头 既然获取请求origin的方式是从reques-header中获取origin值我们必须让所有从gateway路由到微服务的请求都带上origin头。 这个需要利用之前学习的一个GatewayFilter来实现AddRequestHeaderGatewayFilter。 修改gateway服务中的application.yml添加一个defaultFilter spring:cloud:gateway:default-filters:- AddRequestHeaderorigin,gateway这样从gateway路由的所有请求都会带上origin头值为gateway。而从其它地方到达微服务的请求则没有这个头。 配置授权规则 接下来我们添加一个授权规则放行origin值为gateway的请求。 配置如下 现在我们直接跳过网关访问order-service服务 通过网关访问 自定义异常结果 默认情况下发生限流、降级、授权拦截时都会抛出异常到调用方。异常结果都是flow limmiting限流。这样不够友好无法得知是限流还是降级还是授权拦截。 自定义异常类型 而如果要自定义异常时的返回结果需要实现BlockExceptionHandler接口 public interface BlockExceptionHandler {/*** 处理请求被限流、降级、授权拦截时抛出的异常BlockException*/void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception; }这个方法有三个参数 HttpServletRequest requestrequest对象HttpServletResponse responseresponse对象BlockException e被sentinel拦截时抛出的异常 BlockException包含多个不同的子类 自定义异常处理 在order-service定义一个自定义异常处理类 Component public class SentinelExceptionHandler implements BlockExceptionHandler {Overridepublic void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws Exception {String msg 未知异常;int status 429;if (e instanceof FlowException){msg 请求被限流了;} else if (e instanceof ParamFlowException) {msg 请求被热点参数限流;} else if (e instanceof DegradeException) {msg 请求被降级了;} else if (e instanceof AuthorityException) {msg 没有权限访问;status 401;}httpServletResponse.setContentType(application/json;charsetutf-8);httpServletResponse.setStatus(status);httpServletResponse.getWriter().println({\msg\: msg ,\status\: status });} } 重启测试在不同场景下会返回不同的异常消息. 限流 授权拦截时 规则持久化 现在sentinel的所有规则都是内存存储重启后所有规则都会丢失。在生产环境下我们必须确保这些规则的持久化避免丢失。 规则管理模式 规则是否能持久化取决于规则管理模式sentinel支持三种规则管理模式 原始模式Sentinel的默认模式将规则保存在内存重启服务会丢失。pull模式保存在本地文件或数据库定时去读取push模式保存在nacos监听变更实时更新 pull模式 弊端不能实时更新时效性差会导致数据不一致的问题 push模式 微服务都会监听nacos一旦发现nacos的配置中心发生变化本地的规则就会跟着变化保证规则同步。是比较推荐的一种数据更新和持久化方式 分布式事务 分布式事务问题 本地事务 本地事务也就是传统的单机事务。在传统数据库事务中必须要满足四个原则ACID 正如下图所示 对于一个单体架构的项目来说实现事务是很简单的 分布式事务 分布式事务就是指不是在单个服务或单个数据库架构下产生的事务例如 跨数据源的分布式事务跨服务的分布式事务综合情况 以购物订单模块业务为例 在数据库水平拆分、服务垂直拆分之后一个业务操作通常要跨多个数据库、服务才能完成。例如电商行业中比较常见的下单付款案例包括下面几个行为 创建新订单扣减商品库存从用户账户余额扣除金额 完成上面的操作需要访问三个不同的微服务和三个不同的数据库。 订单的创建、库存的扣减、账户扣款在每一个服务和数据库内是一个本地事务可以保证ACID原则。 但是当我们把三件事情看做一个业务要满足保证“业务”的原子性要么所有操作全部成功要么全部失败不允许出现部分成功部分失败的现象这就是分布式系统下的事务了。 此时ACID难以满足这是分布式事务要解决的问题 分布式事务问题示例 用一个基本的购物订单服务调用来演示分布式事务问题所在 用apifox来模拟请求当传入count超过库存最大值时storage服务会失败按道理说应该整个业务都失败但是到account的数据库查看发现account的余额还是扣除了没有做到同成功同失败这也就是分布式下的微服务问题 那么在微服务架构这三个基本动作如何放入一个事务中又如何保证同成功同失败的呢 理论基础 CAP定理 分布式系统有三个指标 Consistency一致性Availability可用性Partition tolerance 分区容错性 它们的第一个字母分别是 C、A、P。 这三个指标不可能同时做到只能实现其中两个指标。这个结论就叫做 CAP 定理。 一致性 Consistency一致性用户访问分布式系统中的任意节点得到的数据必须一致。 比如现在包含两个节点其中的初始数据是一致的当我们修改其中一个节点的数据时两者的数据产生了差异 要想保住一致性就必须实现node01 到 node02的数据 同步 可用性 Availability 可用性用户访问集群中的任意健康节点必须能得到响应而不是超时或拒绝。 如图有三个节点的集群访问任何一个都可以及时得到响应;当有部分节点因为网络故障或其它原因无法访问时代表节点不可用 分区容错 Partition分区因为网络故障或其它原因导致分布式系统中的部分节点与其它节点失去连接形成独立分区。 Tolerance容错在集群出现分区时整个系统也要持续对外提供服务 矛盾之处 在分布式系统中系统间的网络不能100%保证健康一定会有故障的时候而服务有必须对外保证服务。因此Partition Tolerance不可避免。所以P是在所难免了基于CAP定理剩下一个指标就要从一致性和可用性中抉择了 当节点接收到新的数据变更时就会出现问题了 如果此时要保证一致性就必须等待网络恢复完成数据同步后整个集群才对外提供服务服务处于阻塞状态不可用。 如果此时要保证可用性就不能等待网络恢复那node01、node02与node03之间就会出现数据不一致。 也就是说在P一定会出现的情况下A和C之间只能实现一个。 BASE理论 BASE理论是对CAP的一种解决思路包含三个思想 Basically Available 基本可用分布式系统在出现故障时允许损失部分可用性即保证核心可用。**Soft State软状态**在一定时间内允许出现中间状态比如临时的不一致状态。Eventually Consistent最终一致性虽然无法保证强一致性但是在软状态结束后最终达到数据一致。 BASE是上面三个思想首字母的缩写它是对CAP理论中的A和P做了一些选择和调和 解决分布式事务的思路 分布式事务最大的问题是各个子事务的一致性问题因此可以借鉴CAP定理和BASE理论有两种解决思路 AP模式各子事务分别执行和提交允许出现结果不一致然后采用弥补措施恢复数据即可实现最终一致。 CP模式各个子事务执行后互相等待同时提交同时回滚达成强一致。但事务等待过程中处于弱可用状态。 但不管是哪一种模式都需要在子系统事务之间互相通讯协调事务状态来感知彼此的状态也就是需要一个事务协调者(TC) 如下图服务要将自己的执行结果告诉协调者如果哪个服务失败了就会通知事务协调者事务协调者就会通知所有事务做回滚。 这里的子系统事务称为分支事务有关联的各个分支事务在一起称为全局事务。 小结 简述BASE理论三个思想 基本可用软状态最终一致 解决分布式事务的思想和模型 全局事务整个分布式事务 分支事务分布式事务中包含的每个子系统的事务 最终一致思想各分支事务分别执行并提交如果有不一致的情况再想办法恢复数据 强一致思想各分支事务执行完业务不要提交等待彼此结果。而后统一提交或回滚 初识Seata架构 Seata是 2019 年 1 月份蚂蚁金服和阿里巴巴共同开源的分布式事务解决方案。致力于提供高性能和简单易用的分布式事务服务为用户打造一站式的分布式解决方案。 官网地址http://seata.io/其中的文档、博客中提供了大量的使用说明、源码分析。 Seata的架构 Seata事务管理中有三个重要的角色 TC (Transaction Coordinator) -事务协调者维护全局和分支事务的状态协调全局事务提交或回滚。 TM (Transaction Manager) - 事务管理器定义全局事务的范围、开始全局事务、提交或回滚全局事务。 RM (Resource Manager) - 资源管理器管理分支事务处理的资源与TC交谈以注册分支事务和报告分支事务的状态并驱动分支事务提交或回滚。 Seata基于上述架构提供了四种不同的分布式事务解决方案 XA模式强一致性分阶段事务模式牺牲了一定的可用性无业务侵入TCC模式最终一致的分阶段事务模式有业务侵入AT模式最终一致的分阶段事务模式无业务侵入也是Seata的默认模式SAGA模式长事务模式有业务侵入 无论哪种方案都离不开TC也就是事务的协调者。 部署Seata的tc-server 1.首先下载Seata 下载seata-server包地址在http/seata.io/zh-cn/blog/download.html 当然这里也放一个百度云的压缩包下载地址seata-server包-1.4.2下载 提取码wr5i 下载后解压即可 2.修改配置文件信息 修改后内容如下 registry {# tc服务的注册中心类这里选择nacos也可以是eureka、zookeeper等type nacosnacos {# seata tc 服务注册到 nacos的服务名称可以自定义application seata-tc-serverserverAddr 127.0.0.1:8848group DEFAULT_GROUPnamespace cluster SHusername nacospassword nacos} }config {# 读取tc服务端的配置文件的方式这里是从nacos配置中心读取这样如果tc是集群可以共享配置type nacos# 配置nacos地址等信息nacos {serverAddr 127.0.0.1:8848namespace group SEATA_GROUPusername nacospassword nacosdataId seataServer.properties} }3.在nacos中添加配置 # 数据存储方式db代表数据库 store.modedb store.db.datasourcedruid store.db.dbTypemysql store.db.driverClassNamecom.mysql.cj.jdbc.Driver store.db.urljdbc:mysql://127.0.0.1:3306/seata?useUnicodetruecharacterEncodingutf8allowMultiQueriestrueuseSSLfalseserverTimezoneUTCallowPublicKeyRetrievaltrue store.db.userroot store.db.password123456 store.db.minConn5 store.db.maxConn30 store.db.globalTableglobal_table store.db.branchTablebranch_table store.db.queryLimit100 store.db.lockTablelock_table store.db.maxWait5000 # 事务、日志等配置 server.recovery.committingRetryPeriod1000 server.recovery.asynCommittingRetryPeriod1000 server.recovery.rollbackingRetryPeriod1000 server.recovery.timeoutRetryPeriod1000 server.maxCommitRetryTimeout-1 server.maxRollbackRetryTimeout-1 server.rollbackRetryTimeoutUnlockEnablefalse server.undo.logSaveDays7 server.undo.logDeletePeriod86400000# 客户端与服务端传输方式 transport.serializationseata transport.compressornone # 关闭metrics功能提高性能 metrics.enabledfalse metrics.registryTypecompact metrics.exporterListprometheus metrics.exporterPrometheusPort98984.创建数据库表 特别注意tc服务在管理分布式事务时需要记录事务相关数据到数据库中需要提前创建好这些表。新建查询复制粘贴下面内容后直接运行 新建一个名为seata的数据库,这些表主要记录全局事务、分支事务、全局锁信息 SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS 0;-- ---------------------------- -- 分支事务表 -- ---------------------------- DROP TABLE IF EXISTS branch_table; CREATE TABLE branch_table (branch_id bigint(20) NOT NULL,xid varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,transaction_id bigint(20) NULL DEFAULT NULL,resource_group_id varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,resource_id varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,branch_type varchar(8) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,status tinyint(4) NULL DEFAULT NULL,client_id varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,application_data varchar(2000) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,gmt_create datetime(6) NULL DEFAULT NULL,gmt_modified datetime(6) NULL DEFAULT NULL,PRIMARY KEY (branch_id) USING BTREE,INDEX idx_xid(xid) USING BTREE ) ENGINE InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci ROW_FORMAT Compact;-- ---------------------------- -- 全局事务表 -- ---------------------------- DROP TABLE IF EXISTS global_table; CREATE TABLE global_table (xid varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,transaction_id bigint(20) NULL DEFAULT NULL,status tinyint(4) NOT NULL,application_id varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,transaction_service_group varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,transaction_name varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,timeout int(11) NULL DEFAULT NULL,begin_time bigint(20) NULL DEFAULT NULL,application_data varchar(2000) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,gmt_create datetime NULL DEFAULT NULL,gmt_modified datetime NULL DEFAULT NULL,PRIMARY KEY (xid) USING BTREE,INDEX idx_gmt_modified_status(gmt_modified, status) USING BTREE,INDEX idx_transaction_id(transaction_id) USING BTREE ) ENGINE InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci ROW_FORMAT Compact;SET FOREIGN_KEY_CHECKS 1;5.启动seata-server 如果nacos没有启动先启动nacos在nacos的bin目录中打开cmd 在命令行中输入以下代码 startup.cmd -m standalonenacos就启动成功了 进入bin目录运行其中的seata-server.bat即可 启动成功后seata-server应该已经注册到nacos注册中心了 打开浏览器访问nacos地址http://localhost:8848然后进入服务列表页面可以看到seata-tc-server的信息 微服务集成Seata 1.引入Seata的依赖 seata-demo工程中的order, account, storage三个服务都需要引入Seata的依赖由于它集成的版本较低需要换为1.4.2的版本 查看原来依赖的版本是1.3 !--seata-- dependencygroupIdcom.alibaba.cloud/groupIdartifactIdspring-cloud-starter-alibaba-seata/artifactIdexclusions!--版本较低1.3.0因此排除-- exclusionartifactIdseata-spring-boot-starter/artifactIdgroupIdio.seata/groupId/exclusion/exclusions /dependency dependencygroupIdio.seata/groupIdartifactIdseata-spring-boot-starter/artifactId!--seata starter 采用1.4.2版本--version${seata.version}/version /dependency2.配置TC地址 需要在seata-dmeo工程中的各个微服务中的yml里配置TC的服务信息主要就是告知它tc服务的四个信息如下 namespace命名空间group分组application服务名cluster集群名 便于微服务去nacos中结合yml中配置的地址信息获取到tc服务 seata:registry: # TC服务注册中心的配置微服务根据这些信息去注册中心获取tc服务地址type: nacos # 注册中心类型 nacosnacos:server-addr: 127.0.0.1:8848 # nacos地址namespace: # namespace默认为空group: DEFAULT_GROUP # 分组默认是DEFAULT_GROUPapplication: seata-tc-server # seata服务名称username: nacospassword: nacostx-service-group: seata-demo # 事务组名称service:vgroup-mapping: # 事务组与cluster的映射关系seata-demo: SH微服务如何根据这些配置寻找TC的地址呢 注册到Nacos中的微服务确定一个具体实例需要四个信息namespace命名空间group分组application服务名cluster集群名 以上四个信息在yaml文件中都能找到 namespace为空就是默认的public 结合起来TC服务的信息就是publicDEFAULT_GROUPseata-tc-serverSH 这样就能确定TC服务集群了然后就可以去Nacos拉取对应的实例信息了。 代码实践 下面是Seata的四种不同的事务模式 XA模式 几乎所有主流的数据库都对 XA 规范 提供了支持。 XA的特性就是基于数据库本身的特性来去实现分布式事务是一种强一致性的事务 XA是规范目前主流数据库都实现了这种规范实现的原理都是基于两阶段提交。 正常情况 异常情况 一阶段 事务协调者通知每个事物参与者执行本地事务本地事务执行完成后报告事务执行状态给事务协调者此时事务不提交继续持有数据库锁 二阶段 事务协调者基于一阶段的报告来判断下一步操作 如果一阶段都成功则通知所有事务参与者提交事务如果一阶段任意一个参与者失败则通知所有事务参与者回滚事务 Seata的XA模型 Seata对原始的XA模式做了简单的封装和改造以适应自己的事务模型基本架构如图 RM一阶段的工作 ​ ① 注册分支事务到TC ​ ② 执行分支业务sql但不提交 ​ ③ 报告执行状态到TC TC二阶段的工作 TC检测各分支事务执行状态 a.如果都成功通知所有RM提交事务 b.如果有失败通知所有RM回滚事务 RM二阶段的工作 接收TC指令提交或回滚事务 Seata和XA模式差不多只不过多了全局事务的注册和管理变得更加健壮 优缺点 XA模式的优点是什么 事务的强一致性满足ACID原则。常用数据库都支持实现简单并且没有代码侵入 XA模式的缺点是什么 因为一阶段需要锁定数据库资源等待二阶段结束才释放性能较差依赖关系型数据库实现事务 实现XA模式 Seata的starter已经完成了XA模式的自动装配实现非常简单步骤如下 1修改application.yml文件每个参与事务的微服务开启XA模式 seata:data-source-proxy-mode: XA2给发起全局事务的入口方法添加GlobalTransactional注解: 本例中是OrderServiceImpl中的create方法. 3重启服务并测试 重启order-service再次测试发现无论怎样三个微服务都能成功回滚。 AT模式 工作流程 阶段一RM的工作 注册分支事务记录undo-log数据快照执行业务sql并提交报告事务状态 阶段二提交时RM的工作 删除undo-log即可 阶段二回滚时RM的工作 根据undo-log恢复数据到更新前 第一阶段 第二阶段 二阶段正常情况 所有事务都正常删除分支事务的快照这个删快照是通过异步调用的所以不影响主线程的性能 二阶段异常情况 有事务执行失败所有事务回到快照位置然后异步删除快照 AT模式原理 例如一个分支业务的SQL是这样的update tb_account set money money - 10 where id 1 AT与XA的区别 XA模式一阶段不提交事务锁定资源AT模式一阶段直接提交不锁定资源。XA模式依赖数据库机制实现回滚AT模式利用数据快照实现数据回滚。XA模式强一致AT模式最终一致 AT模式的脏读问题 由于AT模式是执行成功后直接提交事务释放资源没有去做锁导致它在并发情况下会出现脏读问题。 解决方案 解决思路就是引入了全局锁的概念。在释放DB锁之前先拿到全局锁。避免同一时刻有另外一个事务来操作当前数据。用全局锁实现了隔离机制。 这里就会看出来AT模式和XA模式一样都是锁定事务执行完后才允许其他事务执行但实际上并非如此 数据库(DB)锁的话任何其他事务都进不来但是全局锁只锁定操作账户余额的相关事务seata管理其他事物非seata管理的事务仍可以进来例如修改账户名称等 全局锁比数据库锁所控制的范围小的多所以AT模式对性能的影响也远低于XA模式。 最后补充一个小概率事件 当事务2也是非seata管理的事务但是事务2也是对账户余额字段做修改更新操作时虽然概率很低但仍要防止 防范措施为添加自旋锁 优缺点 AT模式的优点 一阶段完成直接提交事务释放数据库资源性能比较好利用全局锁实现读写隔离没有代码侵入框架自动完成回滚和提交 AT模式的缺点 两阶段之间属于软状态属于最终一致框架的快照功能会影响性能但比XA模式要好很多 实现AT模式 AT模式中的快照生成、回滚等动作都是由框架自动完成没有任何代码侵入因此实现非常简单。 只不过AT模式需要一个表来记录全局锁、另一张表来记录数据快照undo_log。 注意数据库连接池如果使用的是druid要使用druid-starter不要使用druid。否则不生成undolog 无法回滚 1导入数据库表记录全局锁和快照表其中lock_table导入到TC服务关联的数据库undo_log表导入到微服务关联的数据库 lock_table全局锁的表 DROP TABLE IF EXISTS lock_table; CREATE TABLE lock_table (row_key varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,xid varchar(96) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,transaction_id bigint(20) NULL DEFAULT NULL,branch_id bigint(20) NOT NULL,resource_id varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,table_name varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,pk varchar(36) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,gmt_create datetime NULL DEFAULT NULL,gmt_modified datetime NULL DEFAULT NULL,PRIMARY KEY (row_key) USING BTREE,INDEX idx_branch_id(branch_id) USING BTREE ) ENGINE InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci ROW_FORMAT Compact;快照表 DROP TABLE IF EXISTS undo_log; CREATE TABLE undo_log (branch_id bigint(20) NOT NULL COMMENT branch transaction id,xid varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT global transaction id,context varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT undo_log context,such as serialization,rollback_info longblob NOT NULL COMMENT rollback info,log_status int(11) NOT NULL COMMENT 0:normal status,1:defense status,log_created datetime(6) NOT NULL COMMENT create datetime,log_modified datetime(6) NOT NULL COMMENT modify datetime,UNIQUE INDEX ux_undo_log(xid, branch_id) USING BTREE ) ENGINE InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci COMMENT AT transaction mode undo table ROW_FORMAT Compact; 2修改application.yml文件将事务模式修改为AT模式即可 不写默认就是AT seata:data-source-proxy-mode: AT # 默认就是AT把前面的XA换成AT即可 3重启服务并测试 TCC模式 TCC模式与AT模式非常相似每阶段都是独立事务不同的是TCC通过人工编码来实现数据恢复。需要实现三个方法 Try资源的检测和预留 Confirm完成资源操作业务要求 Try 成功 Confirm 一定要能成功。 Cancel预留资源释放可以理解为try的反向操作。 流程分析 举例一个扣减用户余额的业务。假设账户A原来余额是100需要余额扣减30元。 阶段一 Try 检查余额是否充足如果充足则冻结金额增加30元可用余额扣除30 初始余额 余额充足可以冻结 此时总金额 冻结金额 可用金额数量依然是100不变。事务直接提交无需等待其它事务。 阶段二Confirm)假如要提交Confirm则冻结金额扣减30 确认可以提交不过之前可用金额已经扣减过了这里只要清除冻结金额就好了 此时总金额 冻结金额 可用金额 0 70 70元 阶段二(Canncel)如果要回滚Cancel则冻结金额扣减30可用余额增加30 需要回滚那么就要释放冻结金额恢复可用金额 Seata的TCC模型 Seata中的TCC模型依然延续之前的事务架构 优缺点 TCC模式的每个阶段是做什么的 Try资源检查和预留Confirm业务执行和提交Cancel预留资源的释放 TCC的优点是什么 一阶段完成直接提交事务释放数据库资源性能好相比AT模型无需生成快照无需使用全局锁性能最强不依赖数据库事务而是依赖补偿操作可以用于非事务型数据库 TCC的缺点是什么 有代码侵入需要人为编写try、Confirm和Cancel接口太麻烦软状态事务是最终一致需要考虑Confirm和Cancel的失败情况做好幂等处理 事务悬挂和空回滚 1空回滚 当某分支事务的try阶段阻塞时可能导致全局事务超时而触发二阶段的cancel操作。在未执行try操作时先执行了cancel操作这时cancel不能做回滚就是空回滚。 执行cancel操作时应当判断try是否已经执行如果尚未执行则应该空回滚。 2业务悬挂 对于已经空回滚的业务之前被阻塞的try操作恢复继续执行try去冻结资源但第二阶段已经执行完了永远不可能confirm或cancel 事务一直处于中间状态这就是业务悬挂。 执行try操作时应当判断cancel是否已经执行过了如果已经执行应当阻止空回滚后的try操作避免悬挂 实现TCC模式 业务分析 方法模板 SAGA模式 Saga 模式是 Seata 即将开源的长事务解决方案将由蚂蚁金服主要贡献。 其理论基础是Hector Kenneth 在1987年发表的论文Sagas。 Seata官网对于Saga的指南https://seata.io/zh-cn/docs/user/saga.html 原理 在 Saga 模式下分布式事务内有多个参与者每一个参与者都是一个冲正补偿服务需要用户根据业务场景实现其正向操作和逆向回滚操作。 分布式事务执行过程中依次执行各参与者的正向操作如果所有正向操作均执行成功那么分布式事务提交。如果任何一个正向操作执行失败那么分布式事务会去退回去执行前面各参与者的逆向回滚操作回滚已提交的参与者使分布式事务回到初始状态。 四种模式的对比 高可用 Seata的TC服务作为分布式事务核心一定要保证集群的高可用性。 高可用架构模型 搭建TC服务集群实现高可用启动多个TC服务注册到nacos即可。 但集群并不能确保100%安全万一集群所在机房故障怎么办所以如果要求较高一般都会做异地多机房容灾。 比如一个TC集群在上海另一个TC集群在杭州 微服务基于事务组tx-service-group)与TC集群的映射关系来查找当前应该使用哪个TC集群。当SH集群故障时只需要将vgroup-mapping中的映射关系改成HZ。则所有微服务就会切换到HZ的TC集群了。 实现高可用 模拟异地容灾的TC集群 计划启动两台seata的tc服务节点 节点名称ip地址端口号集群名称seata127.0.0.18091SHseata2127.0.0.18092HZ 之前我们已经启动了一台seata服务端口是8091集群名为SH。 现在将seata目录复制一份起名为seata2 修改seata2/conf/registry.conf内容如下 registry {# tc服务的注册中心类这里选择nacos也可以是eureka、zookeeper等type nacosnacos {# seata tc 服务注册到 nacos的服务名称可以自定义application seata-tc-serverserverAddr 127.0.0.1:8848group DEFAULT_GROUPnamespace cluster HZusername nacospassword nacos} }config {# 读取tc服务端的配置文件的方式这里是从nacos配置中心读取这样如果tc是集群可以共享配置type nacos# 配置nacos地址等信息nacos {serverAddr 127.0.0.1:8848namespace group SEATA_GROUPusername nacospassword nacosdataId seataServer.properties} }进入seata2/bin目录然后运行命令 seata-server.bat -p 8092打开nacos控制台查看服务列表 点进详情查看 将事务组映射配置到nacos 接下来我们需要将tx-service-group与cluster的映射关系都配置到nacos配置中心。 新建一个配置 配置的内容如下 # 事务组映射关系 service.vgroupMapping.seata-demoSHservice.enableDegradefalse service.disableGlobalTransactionfalse # 与TC服务的通信配置 transport.typeTCP transport.serverNIO transport.heartbeattrue transport.enableClientBatchSendRequestfalse transport.threadFactory.bossThreadPrefixNettyBoss transport.threadFactory.workerThreadPrefixNettyServerNIOWorker transport.threadFactory.serverExecutorThreadPrefixNettyServerBizHandler transport.threadFactory.shareBossWorkerfalse transport.threadFactory.clientSelectorThreadPrefixNettyClientSelector transport.threadFactory.clientSelectorThreadSize1 transport.threadFactory.clientWorkerThreadPrefixNettyClientWorkerThread transport.threadFactory.bossThreadSize1 transport.threadFactory.workerThreadSizedefault transport.shutdown.wait3 # RM配置 client.rm.asyncCommitBufferLimit10000 client.rm.lock.retryInterval10 client.rm.lock.retryTimes30 client.rm.lock.retryPolicyBranchRollbackOnConflicttrue client.rm.reportRetryCount5 client.rm.tableMetaCheckEnablefalse client.rm.tableMetaCheckerInterval60000 client.rm.sqlParserTypedruid client.rm.reportSuccessEnablefalse client.rm.sagaBranchRegisterEnablefalse # TM配置 client.tm.commitRetryCount5 client.tm.rollbackRetryCount5 client.tm.defaultGlobalTransactionTimeout60000 client.tm.degradeCheckfalse client.tm.degradeCheckAllowTimes10 client.tm.degradeCheckPeriod2000# undo日志配置 client.undo.dataValidationtrue client.undo.logSerializationjackson client.undo.onlyCareUpdateColumnstrue client.undo.logTableundo_log client.undo.compress.enabletrue client.undo.compress.typezip client.undo.compress.threshold64k client.log.exceptionRate100微服务读取nacos配置 接下来需要修改每一个微服务的application.yml文件让微服务读取nacos中的client.properties文件 seata:config:type: nacosnacos:server-addr: 127.0.0.1:8848username: nacospassword: nacosgroup: SEATA_GROUPdata-id: client.properties重启微服务现在微服务到底是连接tc的SH集群还是tc的HZ集群都统一由nacos的client.properties来决定了。 分布式缓存 单点缓存的弊端 单机的Redis存在四大问题 ①数据丢失问题 Redis是内存存储服务重启可能会丢失数据 ②并发能力问题 单节点Redis并发能力虽然不错但也无法满足如双十一和618这样的高并发场景 ③故障恢复问题 如果Redis宕机则服务不可用需要一种自动的故障恢复手段 ⑤存储能力问题 Redis基于内存单节点能存储的数据量难以满足海量数据需求 Redis持久化 持久化是解决上面四大问题中的数据丢失问题因为redis的数据是基于内存存储的当服务重启会丢失数据所以来做持久化策略来避免重启导致数据丢失问题 Redis有两种持久化方案 RDB持久化AOF持久化 RDB持久化 RDB全称Redis Database Backup fileRedis数据备份文件也被叫做Redis数据快照。简单来说就是把内存中的所有数据都记录到磁盘中。当Redis实例故障重启后从磁盘读取快照文件恢复数据。快照文件称为RDB文件默认是保存在当前运行目录。 执行时机 RDB持久化在四种情况下会执行 ①执行save命令②执行bgsave命令③Redis停机时④触发RDB条件时 1save命令 执行下面的命令可以立即执行一次RDB save命令会导致主进程执行RDB这个过程中其它所有命令都会被阻塞。只有在数据迁移时可能用到。 2bgsave命令 下面的命令可以异步执行RDB 这个命令执行后会开启独立进程完成RDB主进程可以持续处理用户请求不受影响。 3停机时 Redis停机时会执行一次save命令实现RDB持久化。 我们来启动redis来测试一下 先启动redis的服务端前台霸屏模式启动 redis-server再启动redis的客户端命令如下 redis-cli如果有密码先密码登录 如果忘记密码就到redis安装目录中的 redis.conf查找密码 vim redis.conf最后测试客户端登陆后是否连通 最后ctrl c停止redis服务可以看到命令行的显示信息 4触发RDB条件 Redis内部有触发RDB的机制可以在redis.conf文件中找到格式如下 # 900秒内如果至少有1个key被修改则执行bgsave 如果是save 则表示禁用RDB save 900 1 save 300 10 save 60 10000 比如上面第二条的含义就是在300秒内如果有10次或者以上的修改就执行bgsave命令来做持久化 RDB的其它配置也可以在redis.conf文件中设置 # 是否压缩 ,建议不开启压缩也会消耗cpu磁盘空间不值钱所以推荐不压缩以空间换性能 rdbcompression yes# RDB文件名称 dbfilename dump.rdb # 文件保存的路径目录 dir ./ bgsave适合多读少写的场景如果你的业务场景是多写多读或多写少读那么建议使用save RDB原理 异步的调用做持久化对主进程几乎零阻塞只有在fork的过程中阻塞得到子进程后就无阻塞了 bgsave开始时会fork主进程得到子进程子进程共享主进程的内存数据。完成fork后读取内存数据并写入 RDB 文件。 fork采用的是copy-on-write技术 当主进程执行读操作时访问共享内存当主进程执行写操作时则会拷贝一份数据执行写操作。 小结 RDB方式bgsave的基本流程 fork主进程得到一个子进程共享内存空间子进程读取内存数据并写入新的RDB文件用新RDB文件替换旧的RDB文件 RDB会在什么时候执行save 60 1000代表什么含义 默认是服务停止时代表60秒内至少执行1000次修改则触发RDB RDB的缺点 RDB执行间隔时间长两次RDB之间写入数据有丢失的风险fork子进程、压缩、写出RDB文件都比较耗时 AOF持久化 AOF全称为Append Only File追加文件。Redis处理的每一个写命令都会记录在AOF文件可以看做是命令日志文件。大大提高数据安全性弥补RDB的缺陷。 AOF配置 AOF默认是关闭的需要修改redis.conf配置文件来开启AOF # 是否开启AOF功能默认是no这里改成yes开启 appendonly yes # AOF文件的名称 appendfilename appendonly.aofAOF的命令记录的频率也可以通过redis.conf文件来配 # 表示每执行一次写命令立即记录到AOF文件 appendfsync always # 写命令执行完先放入AOF缓冲区然后表示每隔1秒将缓冲区数据写到AOF文件是默认方案 appendfsync everysec # 写命令执行完先放入AOF缓冲区由操作系统决定何时将缓冲区内容写回磁盘 appendfsync no三种记录频率的策略对比 默认的记录策略everysec性能较好的原因 原因在于主线程只负责将命令先写入缓冲区中主进程依然是内存中读写性能损耗极低然后子进程每秒钟一股脑的将缓冲区中命令写入磁盘中 测试结果 AOF文件重写 因为是记录命令AOF文件会比RDB文件大的多。而且AOF会记录对同一个key的多次写操作但只有最后一次写操作才有意义。通过执行bgrewriteaof命令可以让AOF文件执行重写功能用最少的命令达到相同效果。 如图AOF原本有三个命令但是set num 123 和 set num 666都是对num的操作第二次会覆盖第一次的值因此第一个命令记录下来没有意义。 所以重写命令后AOF文件内容就是mset name jack num 666 Redis也会在触发阈值时自动去重写AOF文件。阈值也可以在redis.conf中配置 # AOF文件比上次文件 增长超过多少百分比则触发重写 auto-aof-rewrite-percentage 100 # AOF文件体积最小多大以上才触发重写 auto-aof-rewrite-min-size 64mb RDB与AOF对比 RDB和AOF各有自己的优缺点如果对数据安全性要求较高在实际开发中往往会结合两者来使用。 Redis主从 搭建主从架构 集群结构 从节点既可以叫slave也可以叫replica在5.0以前是叫slave后来改成replica 准备实例和配置 要在同一台虚拟机开启3个实例必须准备三份不同的配置文件和目录配置文件所在目录也就是工作目录。为准备三份不同的配置文件需再开一台虚拟机来配置所以大抵需要4台虚拟机 需要在第四台虚拟机中做如下配置 1创建目录 我们创建三个文件夹名字分别叫7001、7002、7003 # 进入/tmp目录 cd /tmp # 创建目录 mkdir 7001 7002 7003如图 2恢复原始配置 如果修改过持久化策略这里需要更改回RDB模式 修改redis-6.2.4/redis.conf文件将其中的持久化模式改为默认的RDB模式AOF保持关闭状态。 # 开启RDB # save save 3600 1 save 300 100 save 60 10000# 关闭AOF appendonly no操作如下图 3拷贝配置文件到每个实例目录 将第四台已经配置好的redis.conf配置文件拷贝到创建的目录中 然后将redis-6.2.4/redis.conf文件拷贝到三个目录中在/tmp目录执行下列命令 # 方式一逐个拷贝 cp redis-6.2.4/redis.conf 7001 cp redis-6.2.4/redis.conf 7002 cp redis-6.2.4/redis.conf 7003 # 方式二管道组合命令一键拷贝 echo 7001 7002 7003 | xargs -t -n 1 cp redis-6.2.4/redis.conf4修改每个实例的端口、工作目录 修改每个文件夹内的配置文件将redis端口分别修改为7001、7002、7003将rdb文件保存位置都修改为自己所在目录在/tmp目录执行下列命令 sed -i -e s/6379/7001/g -e s/dir .\//dir \/tmp\/7001\//g 7001/redis.conf sed -i -e s/6379/7002/g -e s/dir .\//dir \/tmp\/7002\//g 7002/redis.conf sed -i -e s/6379/7003/g -e s/dir .\//dir \/tmp\/7003\//g 7003/redis.conf5修改每个实例的声明IP 虚拟机本身有多个IP为了避免将来混乱我们需要在redis.conf文件中指定每一个实例的绑定ip信息格式如下 每个目录都要改我们一键完成修改在/tmp目录执行下列命令 # 逐一执行 sed -i 1a replica-announce-ip 这里换成你虚拟机的IP下面也是一样 7001/redis.conf sed -i 1a replica-announce-ip 虚拟机的IP 7002/redis.conf sed -i 1a replica-announce-ip 虚拟机的IP 7003/redis.conf如下图 启动 为了方便查看日志我们打开3个ssh窗口分别启动3个redis实例启动命令 # 第1个 redis-server 7001/redis.conf # 第2个 redis-server 7002/redis.conf # 第3个 redis-server 7003/redis.conf启动后 如果要一键停止可以运行下面命令 printf %s\n 7001 7002 7003 | xargs -I{} -t redis-cli -p {} shutdown开启主从关系 现在三个实例还没有任何关系要配置主从可以使用replicaof 或者slaveof5.0以前命令。 有临时和永久两种模式 修改配置文件永久生效 在redis.conf中添加一行配置slaveof masterip masterport 使用redis-cli客户端连接到redis服务执行slaveof命令重启后失效 slaveof 主节点ip 主节点端口代码如下 通过redis-cli命令连接7002执行下面命令 # 连接 7002 redis-cli -p 7002 # 执行slaveof slaveof 192.168.150.101 7001通过redis-cli命令连接7003执行下面命令 # 连接 7003 redis-cli -p 7003 # 执行slaveof slaveof 192.168.150.101 7001注连接拒绝可能是因为你三个配置文件里设置了requirepass那个属性密码可以直接注释掉那个属性 master设置密码的情况下同步数据,其实很简单我们只要让slave能连上master就可以了,我们在slave的配置文件中加一句话即可。 master 服务器设置了密码所以slave 还需要加上 masterauth 123456 验证master的密码此密码为master的密码 requirepass 123456 给slave设置密码可设置可不设置自己选择 重启redis服务器先杀死进程在加载配置文件启动 # 连接 7001 redis-cli -p 7001 # 查看状态 info replication结果 测试 可以发现只有在7001这个master节点上可以执行写操作7002和7003这两个slave节点只能执行读操作。 主从数据同步原理 全量同步 主从第一次建立连接时会执行全量同步将master节点的所有数据都拷贝给slave节点流程 但是全量同步对性能的影响是极大的 这里有一个问题master如何得知salve是第一次来连接呢 有几个概念可以作为判断依据 Replication Id简称replid是数据集的标记id一致则说明是同一数据集。每一个master都有唯一的replidslave则会继承master节点的replidoffset偏移量随着记录在repl_baklog中的数据增多而逐渐增大。slave完成同步时也会记录当前同步的offset。如果slave的offset小于master的offset说明slave数据落后于master需要更新。 思路分析如下 因此slave做数据同步必须向master声明自己的replication id 和offsetmaster才可以判断到底需要同步哪些数据。 因为slave原本也是一个master有自己的replid和offset当第一次变成slave与master建立连接时发送的replid和offset是自己的replid和offset。 master判断发现slave发送来的replid与自己的不一致说明这是一个全新的slave就知道要做全量同步了。 master会将自己的replid和offset都发送给这个slaveslave保存这些信息。以后slave的replid就与master一致了。 因此master判断一个节点是否是第一次同步的依据就是看replid是否一致。 完整流程描述 slave节点请求增量同步master节点判断replid发现不一致拒绝增量同步master将完整内存数据生成RDB发送RDB到slaveslave清空本地数据加载master的RDBmaster将RDB期间的命令记录在repl_baklog并持续将log中的命令发送给slaveslave执行接收到的命令保持与master之间的同步 增量同步 主从第一次同步是全量同步但如果slave重启后同步则执行增量同步 全量同步需要先做RDB然后将RDB文件通过网络传输个slave成本太高了。因此除了第一次做全量同步其它大多数时候slave与master都是做增量同步。只更新slave与master存在差异的部分数据。 repl_backlog原理 repl_backlog有点类似于循环队列的结构 全量同步时的repl_baklog文件是一个固定大小的数组只不过数组是环形也就是说角标到达数组末尾后会再次从0开始读写这样数组头部的数据就会被覆盖。 repl_baklog中会记录Redis处理过的命令日志及offset包括master当前的offset和slave已经拷贝到的offset slave与master的offset之间的差异就是salve需要增量拷贝的数据了。 随着不断有数据写入master的offset逐渐变大slave也不断的拷贝追赶master的offset 直到数组被填满 此时如果有新的数据写入就会覆盖数组中的旧数据。不过旧的数据只要是绿色的说明是已经被同步到slave的数据即便被覆盖了也没什么影响。因为未同步的仅仅是红色部分。 repl_baklog同步会出现的问题 但是如果slave出现网络阻塞导致master的offset远远超过了slave的offset 如果master继续写入新数据其offset就会覆盖旧的数据直到将slave现在的offset也覆盖 棕色框中的红色部分就是尚未同步但是却已经被覆盖的数据。此时如果slave恢复需要同步却发现自己的offset都没有了无法完成增量同步了。只能做全量同步。 主从同步优化 可以从以下几个方面来优化Redis主从就集群 在master中配置repl-diskless-sync yes启用无磁盘复制避免全量同步时的磁盘IO。(只适合在带宽特别高的情况才适合Redis单节点上的内存占用不要太大留余内存来生成RDB文件减少RDB导致的过多磁盘IO适当提高repl_baklog的大小发现slave宕机时尽快实现故障恢复尽可能避免全量同步限制一个master上的slave节点数量如果实在是太多slave则可以采用主-从-从链式结构减少master压力 主从从架构图 简言之就是发展下线减轻总部压力不然所有从节点都找主节点同步数据主节点压力巨大 总结 简述全量同步和增量同步区别 全量同步master将完整内存数据生成RDB发送RDB到slave。后续命令则记录在repl_baklog逐个发送给slave。增量同步slave提交自己的offset到mastermaster获取repl_baklog中从offset之后的命令给slave 什么时候执行全量同步 slave节点第一次连接master节点时slave节点断开时间太久repl_baklog中的offset已经被覆盖时 什么时候执行增量同步 slave节点断开又恢复并且在repl_baklog中能找到offset时 Redis哨兵 slave节点宕机恢复后可以找master节点同步数据那master节点宕机怎么办 如果master做了持久化即使它宕机了重启仍然是主节点但是在它宕机恢复阶段整个服务是无法访问的导致可用性降低 因为slave一直在和master做数据同步所以当主节点发生故障时立即选出slave来当master保证服务的可用 当master恢复后直接做slave就好了 然而这个集群的健康检测和重启是由谁来完成的呢 就是由下面的哨兵机制来完成的 哨兵原理 集群结构和作用 集群监控原理 Sentinel基于心跳机制监测服务状态每隔1秒向集群的每个实例发送ping命令 •主观下线如果某sentinel节点发现某实例未在规定时间响应则认为该实例主观下线。 •客观下线若超过指定数量quorum的sentinel都认为该实例主观下线则该实例客观下线。quorum值最好超过Sentinel实例数量的一半。 就是有一半的sentinel检测到节点主观下线就是客观下线真的下线了就要选主了 集群故障恢复原理 一旦发现master故障sentinel需要在salve中选择一个作为新的master选择依据是这样的 首先会判断slave节点与master节点断开时间长短如果超过指定值down-after-milliseconds * 10则会排除该slave节点然后判断slave节点的slave-priority值越小优先级越高如果是0则永不参与选举默认大家都一样如果slave-prority一样则判断slave节点的offset值越大说明数据越新优先级越高最后是判断slave节点的运行id大小越小优先级越高。这个不重要因为offset一样那它们的数据同步都差不多选哪个都行这里就按id大小来选了 小结 Sentinel的三个作用是什么 监控故障转移通知 Sentinel如何判断一个redis实例是否健康 每隔1秒发送一次ping命令如果超过一定时间没有相向则认为是主观下线如果大多数sentinel都认为实例主观下线则判定服务下线 故障转移步骤有哪些 首先选定一个slave作为新的master执行slaveof no one然后让所有节点都执行slaveof 新master修改故障节点配置添加slaveof 新master 搭建哨兵集群 搭建一个三节点形成的Sentinel集群来监管之前的Redis主从集群 三个sentinel实例信息如下 节点IPPORTs1192.168.150.10127001s2192.168.150.10127002s3192.168.150.10127003 准备实例和配置 要在同一台虚拟机开启3个实例必须准备三份不同的配置文件和目录配置文件所在目录也就是工作目录。 我们创建三个文件夹名字分别叫s1、s2、s3 # 进入/tmp目录 cd /tmp # 创建目录 mkdir s1 s2 s3然后我们在s1目录创建一个sentinel.conf文件添加下面的内容 port 27001 sentinel announce-ip 192.168.150.101 sentinel monitor mymaster 192.168.150.101 7001 2 sentinel down-after-milliseconds mymaster 5000 sentinel failover-timeout mymaster 60000 dir /tmp/s1命令解读 port 27001是当前sentinel实例的端口sentinel monitor mymaster 192.168.150.101 7001 2指定主节点信息 mymaster主节点名称自定义任意写192.168.150.101 7001主节点的ip和端口2选举master时的quorum值 然后将s1/sentinel.conf文件拷贝到s2、s3两个目录中在/tmp目录执行下列命令 # 方式一逐个拷贝 cp s1/sentinel.conf s2 cp s1/sentinel.conf s3 # 方式二管道组合命令一键拷贝 echo s2 s3 | xargs -t -n 1 cp s1/sentinel.conf如果是s2或者s3那么port就要修改2700227003dir也要修改 修改s2、s3两个文件夹内的配置文件将端口分别修改为27002、27003 dir工作目录改为对应的 sed -i -e s/27001/27002/g -e s/s1/s2/g s2/sentinel.conf sed -i -e s/27001/27003/g -e s/s1/s3/g s3/sentinel.conf启动 为了方便查看日志我们打开3个ssh窗口分别启动3个redis实例启动命令 # 第1个 redis-sentinel s1/sentinel.conf # 第2个 redis-sentinel s2/sentinel.conf # 第3个 redis-sentinel s3/sentinel.conf启动后 最终测试结果 尝试让master节点7001宕机查看sentinel日志 查看新选主7003的日志 查看从节点7002的日志 RedisTemplate哨兵模式 在Sentinel集群监管下的Redis主从集群其节点会因为自动故障转移而发生变化Redis的客户端必须感知这种变化及时更新连接信息。Spring的RedisTemplate底层利用lettuce实现了节点的感知和自动切换。 下面我们通过一个测试来实现RedisTemplate集成哨兵机制。 导入Demo工程 导入redis-demo工程其中就只有一个controller的get和set方法(这个不重要只是用来测试服务是否正常运行以及查看服务控制台中找哪个主节点信息的 引入依赖 在项目的pom文件中引入依赖 dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-data-redis/artifactId /dependency配置Redis地址 然后在配置文件application.yml中指定redis的sentinel相关信息 由于主从地址是有可能变更的所以这里是配置redis的哨兵集群地址让sentinel做地址的发现 spring:redis:sentinel:master: mymaster #这个是指定master节点的名称nodes: #指定redis-sentinel集群信息就是前面配置哨兵集群的ip和端口- 192.168.150.101:27001- 192.168.150.101:27002- 192.168.150.101:27003配置读写分离 在项目的启动类中添加一个新的bean Bean public LettuceClientConfigurationBuilderCustomizer clientConfigurationBuilderCustomizer(){return clientConfigurationBuilder - clientConfigurationBuilder.readFrom(ReadFrom.REPLICA_PREFERRED); }这个bean中配置的就是读写策略包括四种 MASTER从主节点读取MASTER_PREFERRED优先从master节点读取master不可用才读取replicaREPLICA从slavereplica节点读取REPLICA _PREFERRED优先从slavereplica节点读取所有的slave都不可用才读取master Redis分片集群 主从和哨兵可以解决高可用、高并发读的问题。但是依然有两个问题没有解决 海量数据存储问题 高并发写的问题 使用分片集群可以解决上述问题如图: 搭建分片集群 集群结构 分片集群需要的节点数量较多这里我们搭建一个最小的分片集群包含3个master节点每个master包含一个slave节点结构如下 这里我们会在同一台虚拟机中开启6个redis实例模拟分片集群信息如下 IPPORT角色192.168.150.1017001master192.168.150.1017002master192.168.150.1017003master192.168.150.1018001slave192.168.150.1018002slave192.168.150.1018003slave 准备实例和配置 删除之前搭建哨兵集群的7001、7002、7003这几个目录重新创建出7001、7002、7003、8001、8002、8003目录 # 进入/tmp目录 cd /tmp # 删除旧的避免配置干扰 rm -rf 7001 7002 7003 # 创建目录 mkdir 7001 7002 7003 8001 8002 8003在/tmp下创建一个新的redis.conf文件内容如下 port 6379 # 开启集群功能 cluster-enabled yes # 集群的配置文件名称不需要我们创建由redis自己维护 cluster-config-file /tmp/6379/nodes.conf # 节点心跳失败的超时时间 cluster-node-timeout 5000 # 持久化文件存放目录 dir /tmp/6379 # 绑定地址 bind 0.0.0.0 # 让redis后台运行 daemonize yes # 注册的实例ip replica-announce-ip 192.168.150.101 # 保护模式 protected-mode no # 数据库数量 databases 1 # 日志 logfile /tmp/6379/run.log将这个文件拷贝到每个目录下 # 进入/tmp目录 cd /tmp # 执行拷贝 echo 7001 7002 7003 8001 8002 8003 | xargs -t -n 1 cp redis.conf修改每个目录下的redis.conf将其中的6379修改为与所在目录一致就是把端口改为70017002等等 # 进入/tmp目录 cd /tmp # 修改配置文件 printf %s\n 7001 7002 7003 8001 8002 8003 | xargs -I{} -t sed -i s/6379/{}/g {}/redis.conf启动 因为已经配置了后台启动模式所以可以直接启动服务 # 进入/tmp目录 cd /tmp # 一键启动所有服务 printf %s\n 7001 7002 7003 8001 8002 8003 | xargs -I{} -t redis-server {}/redis.conf通过ps查看状态 ps -ef | grep redis发现服务都已经正常启动 如果要关闭所有进程可以执行命令 ps -ef | grep redis | awk {print $2} | xargs kill或者推荐这种方式 printf %s\n 7001 7002 7003 8001 8002 8003 | xargs -I{} -t redis-cli -p {} shutdown创建集群 虽然服务启动了但是目前每个服务之间都是独立的没有任何关联。 我们需要执行命令来创建集群在Redis5.0之前创建集群比较麻烦5.0之后集群管理命令都集成到了redis-cli中。 1Redis5.0之前 Redis5.0之前集群命令都是用redis安装包下的src/redis-trib.rb来实现的。因为redis-trib.rb是有ruby语言编写的所以需要安装ruby环境。 # 安装依赖 yum -y install zlib ruby rubygems gem install redis然后通过命令来管理集群 # 进入redis的src目录 cd /tmp/redis-6.2.4/src # 创建集群 ./redis-trib.rb create --replicas 1 192.168.150.101:7001 192.168.150.101:7002 192.168.150.101:7003 192.168.150.101:8001 192.168.150.101:8002 192.168.150.101:80032Redis5.0以后 我们使用的是Redis6.2.4版本集群管理以及集成到了redis-cli中格式如下 前3个是主后三个是从 redis-cli --cluster create --cluster-replicas 1 192.168.150.101:7001 192.168.150.101:7002 192.168.150.101:7003 192.168.150.101:8001 192.168.150.101:8002 192.168.150.101:8003命令说明 redis-cli --cluster或者./redis-trib.rb代表集群操作命令create代表是创建集群--replicas 1或者--cluster-replicas 1 指定集群中每个master的副本个数为1此时节点总数 ÷ (replicas 1) 得到的就是master的数量。因此节点列表中的前n个就是master其它节点都是slave节点随机分配到不同master 运行后的样子 这里输入yes则集群开始创建 通过命令可以查看集群状态 redis-cli -p 7001 cluster nodes测试 尝试连接7001节点存储一个数据 # 连接 redis-cli -p 7001 # 存储数据 set num 123 # 读取数据 get num # 再次存储 set a 1结果就会报错 集群操作时需要给redis-cli加上-c参数才可以 redis-cli -c -p 7001散列插槽 插槽原理 Redis会把每一个master节点映射到0~16383共16384个插槽hash slot上查看集群信息时就能看到 数据key不是与节点绑定而是与插槽绑定。redis会根据key的有效部分计算插槽值分两种情况 key中包含{}且“{}”中至少包含1个字符“{}”中的部分是有效部分key中不包含“{}”整个key都是有效部分 例如key是num那么就根据num计算如果是{itcast}num则根据itcast计算。计算方式是利用CRC16算法得到一个hash值然后对16384取余得到的结果就是slot值。 每个节点有一定的插槽范围根据数据的插槽值slot来判断该值应该放在哪个节点上查找时也方便去哪个节点上找这也是请求路由到正确节点的原因 数据与插槽绑定而不与节点绑定的原因是节点可能会扩容或下线但插槽一直是不变的无论何时都额可以根据插槽来找到对应的节点访问该值不受节点扩容或宕机的影响节点宕机其插槽范围就交由其他节点扩容类似 小结 Redis如何判断某个key应该在哪个实例 将16384个插槽分配到不同的实例根据key的有效部分计算哈希值对16384取余余数作为插槽寻找插槽所在实例即可 如何将同一类数据固定的保存在同一个Redis实例比如手机商品信息放同一个节点空调放一个节点 这一类数据使用相同的有效部分例如key都以{typeId}为前缀 集群伸缩 redis-cli --cluster提供了很多操作集群的命令可以通过下面方式查看 需求分析 需求向集群中添加一个新的master节点并向其中存储 num 10 启动一个新的redis实例端口为7004添加7004到之前的集群并作为一个master节点给7004节点分配插槽使得num这个key可以存储到7004实例 难点在于插槽的分配因为num是在7001的插槽范围中 这里需要两个新的功能 添加一个节点到集群中将部分插槽分配到新插槽 创建新的redis实例 创建一个文件夹 mkdir 7004拷贝配置文件 cp redis.conf /7004修改配置文件 sed -i /s/6379/7004/g 7004/redis.conf但现在还没有成为集群中的节点只是把7004启动起来了 添加新节点到redis 添加节点的语法如下 执行命令 第一个是要新添加的ip和端口第二个是指定一个集群中存在的ip和端口这里不是配置它出生即奴配置成奴的参数不一样 redis-cli --cluster add-node 192.168.150.101:7004 192.168.150.101:7001通过命令查看集群状态 redis-cli -p 7001 cluster nodes如图7004加入了集群并且默认是一个master节点 但是可以看到7004节点的插槽数量为0因此没有任何数据可以存储到7004上 转移插槽 我们要将num存储到7004节点因此需要先看看num的插槽是多少 如上图所示num的插槽为2765.如此我们移动0~3000的插槽即可 我们可以将0~3000的插槽从7001转移到7004命令格式如下 具体命令如下 接下里就会让你输入转移插槽的相关配置 通过命令查看集群状态 redis-cli -p 7001 cluster nodes从而找到7004和7001的节点对应的id 移动完毕后再通过以下命令查看结果 可以看到 目的达成。 注如果要删除7004节点 需要先移除7004节点上的插槽 然后才可以移除7004节点 故障转移 集群初始状态是这样的 其中7001、7002、7003都是master我们计划让7002宕机。 自动故障转移 当集群中有一个master宕机会发生什么呢 直接停止一个redis实例例如7002 redis-cli -p 7002 shutdown1首先是该实例与其它实例失去连接 2然后是疑似宕机 3最后是确定下线自动提升一个slave为新的master 4当7002再次启动就会变为一个slave节点了 手动故障转移 利用cluster failover命令可以手动让集群中的某个master宕机切换到执行cluster failover命令的这个slave节点实现无感知的数据迁移。其流程如下 这种failover命令可以指定三种模式 缺省默认的流程如图1~6歩force省略了对offset的一致性校验就是省略23步takeover直接执行第5歩忽略数据一致性、忽略master状态和其它master的意见 案例需求在7002这个slave节点执行手动故障转移重新夺回master地位 步骤如下 1利用redis-cli连接7002这个节点 2执行cluster failover命令 RedisTemplate访问分片集群 RedisTemplate底层同样基于lettuce实现了分片集群的支持而使用的步骤与哨兵模式基本一致 1引入redis的starter依赖 2配置分片集群地址 3配置读写分离 与哨兵模式相比其中只有分片集群的配置方式略有差异如下 spring:redis:cluster:nodes: #哨兵模式配置的是哨兵集群的地址而集群配置的就是每个主从节点的地址- 192.168.150.101:7001- 192.168.150.101:7002- 192.168.150.101:7003- 192.168.150.101:8001- 192.168.150.101:8002- 192.168.150.101:8003集群最终结构 redis集群自动具备这种主从故障切换
http://www.laogonggong.com/news/113705.html

相关文章:

  • 兰州金建工程建设监理网站课程网站开发的开题报告
  • 商城网站公司哪里有网站建设流程
  • 阳泉网站建设上海网站建设在哪
  • 技术支持 长沙网站建设-创研科技福州市台江区网站
  • y2学年做的租房网站wordpress帝国哪个安全
  • 网站建设属于无形资产吗更新网站的步骤
  • 网站开发和游戏开发哪个难wordpress表单创建插件
  • 湖北聚四方建设有限公司网站杭州微网站开发
  • 做电影网站量刑标准如何在国外网站上做外贸
  • WordPress网站仿制注册网站英语怎么说
  • 网站建设视频教程phpwordpress问题解决方法
  • vs2015 网站开发这样做网站
  • 公司内部网站建设兰州建设厅评职称网站
  • 创可贴网站怎么做图片大全东莞建设网下载app
  • 一个好的网站是什么样的怎么免费网做百度收录的网站
  • 灵犀科技-网站开发一个平台怎么推广
  • 长沙好的网站建设公司标识标牌设计公司
  • 做一网站要学些什么软件网站维护是不是很难做
  • 门户网站还能建设么个人如何建设网站
  • 郑州建站多少钱保定做网站百度推广
  • 免费自助建站网站一览自助建网站wordpress怎么改登陆
  • 电子商务网站设计原理书籍学做网站需要多久时间
  • 桂林小学网站建设企业如何建公司网站
  • 外贸网站做流量163企业邮箱费用多少
  • 如何在网站上做404页面火车头 wordpress4.9
  • 做游戏本测评的网站免费服务器地址和ip
  • 怎么做点击图片进入网站网站环境搭建好后怎么做网站
  • 湖南长沙招聘青岛网络优化公司
  • 怎么做装饰公司网站宣传如何做淘宝直播教学视频网站
  • 康定网站建设公司1688货源网下载app