Nacos: Naming and Configuration Service,也就是注册和配置中心
Nacos等价于Eureka + Config + Bus
Nacos官网
可以在官网首页找到下载地址
下载完成之后,在nacos目录下的/bin
目录中
bash startup.sh -m standalone
通过单机模式启动nacos,集群模式不用加参数sh shutdown.sh
关闭nacos通过lcoalhost:8848/nacos
访问nacos
pom
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
yaml
server:
port: 9001
spring:
application:
name: nacos-payment-provider
cloud:
nacos:
discovery:
server-addr: localhost:8848 # Nacos地址
management:
endpoints:
web:
exposure:
include: '*'
主启动类: 注意使用@EnableDiscoveryClient
@SpringBootApplication
@EnableDiscoveryClient
public class PaymentMain9001 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain9001.class, args);
}
}
controller
@RestController
public class PaymentController {
@Value("${server.port}")
private String serverPort;
@GetMapping("/payment/nacos/{id}")
public String getNacos(@PathVariable("id") Integer id) {
return serverPort + "===============" + id;
}
}
pom
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
yaml
server:
port: 8080
spring:
application:
name: nacos-consumer-order
cloud:
nacos:
discovery:
server-addr: localhost:8848
主启动
@SpringBootApplication
@EnableDiscoveryClient
public class OrderNacosMain8080 {
public static void main(String[] args) {
SpringApplication.run(OrderNacosMain8080.class, args);
}
}
config: 注意@LoadBalanced
,Nacos也是使用Ribbon进行负载均衡
@Configuration
public class ApplicationContexConfig {
@Bean
@LoadBalanced
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
controller: 跟Netflix一样调用生产者
@RestController
public class OrderNacosController {
@Resource
private RestTemplate restTemplate;
private String serverURL = "http://nacos-payment-provider";
@GetMapping("/consumer/payment/nacos/{id}")
public String getMessage(@PathVariable("id") Integer id) {
return restTemplate.getForObject(serverURL + "/payment/nacos/" + id, String.class);
}
}
与Spring Cloud Config不同的是,Nacos不需要自己建一个微服务来获得远端配置文件,在Nacos中可以直接添加配置文件.操作如下
微服务中获得配置信息
pom
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-configartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
bootstrap.yaml
server:
port: 3377
spring:
application:
name: nacos-config-client
cloud:
nacos:
discovery:
server-addr: localhost:8848
config:
server-addr: localhost:8848 # 配置中心地址
file-extension: yaml # 配置文件类型
application.yaml
spring:
profiles:
active: dev # 设置开发环境
远程配置文件名(DataID)的格式要求
${spring.application.name}-${spring.profiles.active}.${spring.cloud.config.file-extension}
反过来说,当一个微服务配置内容跟某个远程配置文件的DataID相符,那这个微服务就会使用这个配置文件
主启动类
@SpringBootApplication
@EnableDiscoveryClient
public class NacosConfigClientMain3377 {
public static void main(String[] args) {
SpringApplication.run(NacosConfigClientMain3377.class, args);
}
}
controller: 不要忘记动态刷新@RefreshScope
@RestController
@RefreshScope //动态刷新
public class ConfigClientController {
@Value("${config.info}")
private String configInfo;
@GetMapping("/configInfo")
public String getConfigInfo() {
return configInfo;
}
}
Nacos的远程配置文件发生修改时,为服务直接动态修改,不需要额外的配置
DataID就是前面提到的配置文件名,格式是 s p r i n g . a p p l i c a t i o n . n a m e − {spring.application.name}- spring.application.name−{spring.profiles.active}.${spring.cloud.config.file-extension}
每个服务只能调用复合它自己的配置文件
在新建配置时可以自定义该配置文件的Goup,默认是DEFAULT_GROUP
微服务可以指定某个组的配置文件,默认选择DEFAULT_GROUP.
spring:
application:
name: nacos-config-client
cloud:
nacos:
discovery:
server-addr: localhost:8848
config:
server-addr: localhost:8848 # 配置中心地址
file-extension: yaml # 配置文件类型
# 选择分组
group: DEV_GROUP
NameSpace也叫命名空间
spring:
application:
name: nacos-config-client
cloud:
nacos:
discovery:
server-addr: localhost:8848
config:
server-addr: localhost:8848 # 配置中心地址
file-extension: yaml # 配置文件类型
# 选择分组
group: DEV_GROUP
# 选择命名空间
namespace: bebe2d24-b324-4646-a5b6-075f68feabb7
选择命名空间时要使用命名空间的ID
Nacos自带了一个小数据库,但重启后其中的数据就会消失,要想达到持久化的目标,就需要外接一个数据库.
目前Nacos持久化仅支持MySQL数据库,并且Nacos集群也需要惊Nacos数据库配置成MySQL数据库.
将数据库改成MySQL的配置如下:
在conf目录中存在一个nacos-mysql.sql
内容是这样的
按照上面的提示,创建一个名为nacos_config
的数据库,并在此库中运行上述sql文件
以后Nacos的所有信息将存储到这个数据库中
同样在conf目录下,修改application.properties
文件,追加下面一段
spring.datasource.platform=mysql
db.num=1
db.url.0=jdbc:mysql://localhost:3306/nacos_devtest?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true
db.user=你的数据库用户名
db.password=数据库密码
这样就将就将数据库调整为MySQL
修改conf/cluster.conf
集群配置文件:
拷贝cluster.conf.example
cp cluster.conf.example cluster.conf
修改/bin/startup.sh
启动脚本,让它支持-p这个参数,以便用来选择启动的端口
同样备份一份startup.sh
,防止改坏
在修改nginx.conf
上述配置完成之后,Nacos集群就会对外暴露1111端口,下次微服务注册Nacos就是用1111端口
Sentinel官方文档
Sentinel下载地址
Sentinel也是一个jar包,使用java -jar
命令就可以运行
访问Sentinel: localhost:8080
username: sentinel
password: sentinel
与Hystrix不同的是,Sentinel的配置都是在可视化平台下进行的,降低了配置与代码的耦合度.并且Sentinel提供了大量的限流、熔断规则.
QPS: 每秒钟请求的数量
线程数: 同一时间请求的线程数量
流控模式:
流控效果:
快速失败: 过了阈值直接报错
Warm Up(预热): 初始阈值是阈值/冷加载因子(默认为3),经过预热时长之后,慢慢将阈值提升到设置的阈值
当访问量巨大时,预热的方式能让流量慢慢提高,保护主机
排队等待: 让请求一个一个易均匀的速度获得响应,使用了漏桶算法,超时时间也就是排队的等待时间,等待超时就报错,用于处理间隔性突发流量
且
对应时刻的平均响应时间都超过阈值,那在接下来的时间窗口之内,对这个方法的调用会自动熔断.RT的上限是4900ms,超过此阈值会被算作4900.并且
每秒异常总数占通过量的比值超过阈值,资源进入降级状态,在接下来的窗口时间内,对这个方法的调用都会自动返回.异常比例的阈值范围是[0.0, 1.0]热点限流就是针对一个请求中的某个参数进行限流
例如下面的代码,我对第0个参数也就是p1进行限流
@GetMapping("/testHotKey")
public String testHotKey(@RequestParam(value = "p1", required = false) String p1,
@RequestParam(value = "p2", required = false) String p2) {
return "===============testHotKey====================";
}
如果你想让限流的参数有不一样的阈值,可以通过参数例外项进行配置
Sentinel 系统自适应限流从整体维度对应用入口流量进行控制,结合应用的 Load、CPU 使用率、总体平均 RT、入口 QPS 和并发线程数等几个维度的监控指标.
maxQps * minRt
估算得出。设定参考值一般是 CPU cores * 2.5
。blockHandler处理配置违规
使用@SentinelResource注解可以自定义错误页面(value)和资源名(blockHandler),没有指定blockHandler就使用系统自带的错误页面.
@GetMapping("/byResource")
@SentinelResource(value = "byResource", blockHandler = "handleException")
public CommonResult byResource() {
return new CommonResult(200, "===============按资源名限流=========", new Payment(2020L, "serial001"));
}
public CommonResult handleException(BlockException exception) {
return new CommonResult(444, exception.getClass().getCanonicalName() + "\t 服务不可用");
}
以上配置就是当出现流控或者降级时,调用handleException方法
value中可以是任意值,一般与访问路径相同但不带
/
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EF23Rfks-1590731651588)(Spring Cloud Alibaba.assets/截图录屏_选择区域_20200520014355.png)]
上述代码业务逻辑与处理类的耦合性太大,使用blockHandlerClass
可以解藕,配置如下
在另一个类中写出现错误时的方法
public class CustomerBlockHandler {
// 注意静态方法,不然Sentinel找不到这个方法
public static CommonResult handlerException(BlockException exception) {
return new CommonResult(444, "自定义的错误页面BlockHandleClass", new Payment(2020L, "serial001"));
}
}
在@SentinelResource中,使用blockHandlerClass调用这个方法
@GetMapping("/rateLimit/customerBlockHandle")
@SentinelResource(value = "customerBlockHandle",
blockHandlerClass = CustomerBlockHandler.class, //指定处理类
blockHandler = "handlerException")// 指定该类中的处理方法
public CommonResult customerBlockHandle() {
return new CommonResult(200, "=======访问成功======", new Payment(2020L, "serial001"));
}
fallback负责业务异常
exceptionsToIgnore可以忽略某个异常的降级
@GetMapping("/consumer/fallback/{id}")
@SentinelResource(value = "fallback",
fallback = "handlerFallback",
blockHandler = "blockHandler",
exceptionsToIgnore = {IllegalArgumentException.class})
public CommonResult<Payment> fallback(@PathVariable("id") Long id) {
if (id == 4) {
throw new IllegalArgumentException("存在非法参数");
}
CommonResult result = restTemplate.getForObject(SERVER_URL + "/payment/" + id, CommonResult.class);
if (result.getData() == null) {
throw new NullPointerException("记录不存在");
}
return result;
}
//注意参数应包含业务方法的参数
public CommonResult handlerFallback(@PathVariable Long id, Throwable e) {
return new CommonResult(444, "业务类出现异常 " + e.getMessage());
}
public CommonResult blockHandler(@PathVariable Long id, BlockException exception) {
return new CommonResult(445, "操作被限流");
}
fallback = "handlerFallback"
出现异常时,会执行handlerFallback()方法
exceptionsToIgnore = {IllegalArgumentException.class}
,此时IllegalArgumentException异常就不会进入降级页面
pom
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
yaml
server:
port: 8480
spring:
application:
name: nacos-order-consumer
cloud:
nacos:
discovery:
server-addr: localhost:8848
sentinel:
transport:
dashboard: localhost:8080
port: 8719
#激活sentinel对feign的支持
feign:
sentinel:
enabled: true
主启动类: 使用@EnableFeignClients
开启Feign功能
@SpringBootApplication
@EnableDiscoveryClient
//开启feign
@EnableFeignClients
public class OrderMain84 {
public static void main(String[] args) {
SpringApplication.run(OrderMain84.class, args);
}
}
service: 直接使用@FeignClient
中的fallback参数,指定降级的类,这个类必须是serivce接口的实现类,并且他实现的方法就是降级方法
//Feign
@FeignClient(value = "provider-payment-server", fallback = PaymentFallBackServer.class)
public interface PaymentService {
@GetMapping("/payment/{id}")
CommonResult<Payment> payment(@PathVariable("id") Long id);
}
@Component
public class PaymentFallBackServer implements PaymentService {
@Override
public CommonResult<Payment> payment(Long id) {
return new CommonResult<>(4444, "==========服务降级===========");
}
}
controller: 调用service
@Resource
private PaymentService paymentService;
@GetMapping("/consumer/payment/{id}")
public CommonResult<Payment> payment(@PathVariable("id") Long id) {
return paymentService.payment(id);
}
在Sentinel中配置的规则会随着服务的重启而消失
Sentinel规则的持久化就是将Sentinel的配置放入Nacos保存,访问页面时,Nacos会自动将规则还给Sentinel
pom
<dependency>
<groupId>com.alibaba.cspgroupId>
<artifactId>sentinel-datasource-nacosartifactId>
dependency>
yaml
server:
port: 8401
spring:
application:
name: cloudalibaba-sentinel-service
cloud:
nacos:
discovery:
# Nacos地址
server-addr: localhost:8848
sentinel:
transport:
# sentinel地址
dashboard: localhost:8080
# 端口被占用会自动+1,直至找到没被占用的端口
port: 8719
#持久化配置
datasource:
ds1:
nacos:
server-addr: localhost:8848
dataId: cloudalibaba-sentinel-service # 配置文件的DataID
groupId: DEFAULT_GROUP # 配置文件的Group
data_type: json # 文件类型
rule-type: flow # 规则类型
management:
endpoints:
web:
exposure:
include: '*'
[
{
"resource": "/testA",
"limitApp": "default",
"grade": 1,
"count": 1,
"strategy": 0,
"controlBehavior": 0,
"clusterMode": false
}
]
分布式事务的解决方案
Seata官网
一个ID
Transaction ID(XID): 全局唯一的事务ID
三个组件
在Seata官网就有下载入口
Seata的安装与配置
修改conf
目录下的file.conf
文件
新建数据库seata
在seata库中建表,建表文件在conf/db_store.sql
启动: 先启动Nacos,然后启动Seata: 运行bin中的seata-server.sh
现在模拟一个下订单的业务逻辑,要同时进行添加订单,减少库存和修改账户信息
建库
新建三个数据库: seata_order
seata_storage
seata_account
, 分别存储订单、库存和账户信息
建表
回滚日志表
在conf目录中有一个db_undo_log.sql
文件,这个文件可以生成回滚日志表,每个库中都要生成一个回滚日志表,也就是说每个库都运行一次这个文件.
使用@GlobalTransactional
注解启用Seata,参数name是唯一ID,参数rollbackFor表示出现什么异常时回滚。
@Service
@Slf4j
public class OrderServiceImpl implements OrderService {
@Resource
private OrderDao orderDao;
@Resource
private AccountService accountService;
@Resource
private StorageService storageService;
@Override
@GlobalTransactional(name = "create-order", rollbackFor = Exception.class)
public void create(Order order) {
log.info("===========>开始新建订单");
orderDao.creat(order);
log.info("=============>调用库存微服务,减少库存");
storageService.decrease(order.getProductId(), order.getCount());
log.info("===========>调用账户微服务,扣钱");
accountService.decrease(order.getUserId(), order.getMoney());
log.info("=============>修改订单状态");
orderDao.update(order.getUserId(), 0);
log.info("==============>订单完成");
}
}
这里演示了执行订单服务的代码,这个server中同时调用了库存、账户和订单三个微服务中的数据库,当某个调用发生异常后,Seata能将所有相关数据库都回滚到原始状态。
Seata共有 AT、TCC、SAGA 和 XA 四种事务模式,默认使用AT模式
详细过程
一阶段,seata拦截"业务SQL"
二阶段: