Spring Cloud Zero 学习笔记
人生自是有情痴,此恨不关风与月。
What’s your problems
Gateway
启动失败
Description:
Parameter 0 of method modifyResponseBodyGatewayFilterFactory in org.springframework.cloud.gateway.config.GatewayAutoConfiguration required a bean of type ‘org.springframework.http.codec.ServerCodecConfigurer’ that could not be found.
Action:
Consider defining a bean of type ‘org.springframework.http.codec.ServerCodecConfigurer’ in your configuration.
springcloud gateway不需要web starter,在pom文件中移除:
<!-- SpringBoot整合Web组件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
strict-origin-when-cross-origin
问题描述:
将Vue前端部署到服务器的Nginx以后,浏览器访问资源时就会产生跨域问题,随后使用gateway做了网关配置。配置完成后使用谷歌浏览器访问资源时控制台报错strict-origin-when-cross-origin
随后使用火狐浏览器访问资源时为200
而直接在地址栏输入请求url也是200
解决思路
网站当前访问是使用 https
,而提交表单或 ajax
请求却使用的是 http
,可以归类为跨域问题。只需要将表单或ajax
请求由http
也修改为https
即可
解决方案
谷歌浏览器,输入: chrome://flags/#block-insecure-private-network-requests,将 Block insecure private network requests 这个插件设置为 Disabled
就行了
Chrome 错误代码:ERR_UNSAFE_PORT
提示错误代码:ERR_UNSAFE_PORT
不想修改浏览器设置的就改用其它端口吧,搜索了一下,Firefox也有类似的端口限制;如果非要使用类似的端口,
我们要做的是允许访问非常规端口地址,解决办法:选中Google Chrome 快捷方式,右键属性,在”目标”对应文本框添加:
--explicitly-allowed-ports=87,6666,556,6667
允许多个端口以逗号隔开,最终如下:
C:\Users\Huoqing\AppData\Local\Google\Chrome\Application\chrome.exe --explicitly-allowed-ports=6666,556
Connecttion time out
版本:
nacos:2.1.2
springboot:2.2.2
springcloud:Hoxton.SR1
springcloud alibaba:2.2.9.RELEASE
服务注册到nacos时为内网ip,在window下ip发生变化出现的问题:gateway以及其它服务都正常注册进 nacos,并且通过网关可以成功访问到其它服务,但是随着电脑本机的ip发生变化时整个注册进nacos的服务都调用失败,connection time out
ip变化前(gateway跑在6000端口):
网关的ip:
访问网关服务也是能够正常响应的:
将电脑网络断开后重连,nacos上显示各个服务都是up的(已经暴露健康检查端点):
management:
endpoints:
web:
exposure:
include: '*'
如果继续访问之前的ip:
使用localhost:6000进行访问,依然正常响应:
因此如果其它服务通过localhost:6000访问网关没有问题,但是网关获得到其它服务的地址还是之前的,因此触发连接超时
2022-11-30 23:44:17.389 INFO 22088 --- [erListUpdater-0] c.netflix.config.ChainedDynamicProperty : Flipping property: cloud-guli-product.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647
2022-11-30 23:44:21.644 ERROR 22088 --- [ctor-http-nio-5] a.w.r.e.AbstractErrorWebExceptionHandler : [453e5166] 500 Server Error for HTTP GET "/admin/product/attr/base/list/0?t=1669812012957&page=1&limit=10&key="
io.netty.channel.ConnectTimeoutException: connection timed out: /192.168.39.86:5000
at io.netty.channel.nio.AbstractNioChannel$AbstractNioUnsafe$1.run(AbstractNioChannel.java:261) ~[netty-transport-4.1.43.Final.jar:4.1.43.Final]
Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:
Error has been observed at the following site(s):
|_ checkpoint ⇢ org.springframework.web.cors.reactive.CorsWebFilter [DefaultWebFilterChain]
|_ checkpoint ⇢ org.springframework.cloud.gateway.filter.WeightCalculatorWebFilter [DefaultWebFilterChain]
|_ checkpoint ⇢ org.springframework.boot.actuate.metrics.web.reactive.server.MetricsWebFilter [DefaultWebFilterChain]
|_ checkpoint ⇢ HTTP GET "/admin/product/attr/base/list/0?t=1669812012957&page=1&limit=10&key=" [ExceptionHandlingWebHandler]
在nacos上试图将其下线:
简单的解决方案:
spring:
cloud:
nacos:
discovery:
# ip: 10.2.11.11
ip: 127.0.0.1
Stream
Rabbitmq连接
- 错误的配置:
spring:
application:
name: cloud-stream-provider
cloud:
stream:
binders: # 在此处配置要绑定的rabbitmq的服务信息;
defaultRabbit: # 表示定义的名称,用于于binding整合
type: rabbit # 消息组件类型
environment: # 设置rabbitmq的相关的环境配置
#注意仅在这里配置rabbitmq服务并没有生效,会连接Attempting to connect to: [localhost:5672]
#还需要在spring.rabbitmq下配置连接属性,基表如此也不能将其删除
spring:
rabbitmq:
host: 192.168.10.111
port: 5672
username: admin
password: 123
bindings: # 服务的整合处理
output: # 这个名字是一个通道的名称
binder: defaultRabbit # 设置要绑定的消息服务的具体设置
destination: studyExchange # 表示要使用的Exchange名称定义
content-type: application/json # 设置消息类型,本次为json,文本则设置“text/plain”
按以上配置启动程序后,日志输出:
2022-10-28 09:31:36.477 INFO 10736 --- [192.168.112.230] o.s.a.r.c.CachingConnectionFactory : Attempting to connect to: [localhost:5672]
2022-10-28 09:31:40.585 WARN 10736 --- [192.168.112.230] o.s.b.a.amqp.RabbitHealthIndicator : Rabbit health check failed
org.springframework.amqp.AmqpConnectException: java.net.ConnectException: Connection refused: connect
可以看到连接的rabbitmq并不是machine111,这是由于引入了actuactor的监控检测默认配置,若不设置默认rabbitmq,则会使用默认配置进行尝试,localhost:5672
-
正确写法
需要在spring.rabbitmq下配置连接属性,即使如此也不能将其删除
YAMLserver: port: 8801 spring: rabbitmq: host: machine111 port: 5672 username: admin password: 123 application: name: cloud-stream-provider cloud: stream: binders: # 在此处配置要绑定的rabbitmq的服务信息; defaultRabbit: # 表示定义的名称,用于于binding整合 type: rabbit # 消息组件类型 environment: # 设置rabbitmq的相关的环境配置 #注意仅在这里配置rabbitmq服务并没有生效,会连接Attempting to connect to: [localhost:5672] #还需要在spring.rabbitmq下配置连接属性,即使如此也不能将其删除 spring: rabbitmq: host: 192.168.10.111 port: 5672 username: admin password: 123 bindings: # 服务的整合处理 output: # 这个名字是一个通道的名称 binder: defaultRabbit # 设置要绑定的消息服务的具体设置 destination: studyExchange # 表示要使用的Exchange名称定义 content-type: application/json # 设置消息类型,本次为json,文本则设置“text/plain” eureka: client: service-url: defaultZone: http://eureka7001.com:7001/eureka register-with-eureka: true fetch-registry: true instance: instance-id: send-8001.com prefer-ip-address: true lease-renewal-interval-in-seconds: 2 lease-expiration-duration-in-seconds: 5
此时日志打印信息:
JAVA2022-10-28 09:51:57.238 INFO 5488 --- [192.168.112.230] o.s.a.r.c.CachingConnectionFactory : Attempting to connect to: [machine111:5672] 2022-10-28 09:51:57.259 INFO 5488 --- [192.168.112.230] o.s.a.r.c.CachingConnectionFactory : Created new connection: rabbitConnectionFactory#26b9c5b9:0/SimpleConnection@62fc9a44 [delegate=amqp://admin@192.168.10.111:5672/, localPort= 9505]
```
##### 2. 定义配置文件
```javascript
spring:
cloud:
stream:
binders:
test:
type: rabbit
environment:
spring:
rabbitmq:
addresses: 10.0.20.132
port: 5672
username: root
password: root
virtual-host: /unicode-pay
bindings:
testOutPut:
destination: testRabbit
content-type: application/json
default-binder: test
现在来解释一下这些配置的含义
- binders: 这是一组binder的集合,这里配置了一个名为test的binder,这个binder中是包含了一个rabbit的连接信息
- bindings:这是一组binding的集合,这里配置了一个名为testOutPut的binding,这个binding中配置了指向名test的binder下的一个交换机testRabbit。
- 扩展: 如果我们项目中不仅集成了rabbit还集成了kafka那么就可以新增一个类型为kafka的binder、如果项目中会使用多个交换机那么就使用多个binding,
3.创建通道
public interface MqMessageSource {
String TEST_OUT_PUT = "testOutPut";
@Output(TEST_OUT_PUT)
MessageChannel testOutPut();
}
这个通道的名字就是上方binding的名字
##### 4. 发送消息
@EnableBinding(MqMessageSource.class)
public class MqMessageProducer {
@Autowired
@Output(MqMessageSource.TEST_OUT_PUT)
private MessageChannel channel;
public void sendMsg(String msg) {
channel.send(MessageBuilder.withPayload(msg).build());
System.err.println("消息发送成功:"+msg);
}
}
这里就是使用上方的通道来发送到指定的交换机了。需要注意的是withPayload方法你可以传入任何类型的对象,但是需要实现序列化接口
##### 5. 创建测试接口
EnableBinding注解绑定的类默认是被Spring管理的,我们可以在controller中注入它
@Autowired
private MqMessageProducer mqMessageProducer;
@GetMapping(value = "/testMq")
public String testMq(@RequestParam("msg")String msg){
mqMessageProducer.sendMsg(msg);
return "发送成功";
}
生产者的代码到此已经完成了。
### 创建消费者
##### 1. 引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
##### 2. 定义配置文件
spring:
cloud:
stream:
binders:
test:
type: rabbit
environment:
spring:
rabbitmq:
addresses: 10.0.20.132
port: 5672
username: root
password: root
virtual-host: /unicode-pay
bindings:
testInPut:
destination: testRabbit
content-type: application/json
default-binder: test
这里与生产者唯一不同的地方就是testIntPut了,相信你已经明白了,它是binding的名字,也是通道与交换机绑定的关键
3.创建通道
public interface MqMessageSource {
String TEST_IN_PUT = "testInPut";
@Input(TEST_IN_PUT)
SubscribableChannel testInPut();
}
##### 4. 接受消息
@EnableBinding(MqMessageSource.class)
public class MqMessageConsumer {
@StreamListener(MqMessageSource.TEST_IN_PUT)
public void messageInPut(Message<String> message) {
System.err.println(" 消息接收成功:" + message.getPayload());
}
}
这个时候启动Eureka、消息生产者和消费者,然后调用生产者的接口应该就可以接受到来自mq的消息了。
Nacos
版本2.1.2
启动出错
1.1 Caused by: java.net.UnknownHostException: jmenv.tbsite.net
解决: 以单机模式启动
startup.cmd -m standalone
1.2 No DataSource set
Caused by: java.lang.IllegalStateException: No DataSource set
at org.springframework.util.Assert.state(Assert.java:76)
at org.springframework.jdbc.support.JdbcAccessor.obtainDataSource(JdbcAccessor.java:86)
at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:376)
at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:465)
at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:475)
at org.springframework.jdbc.core.JdbcTemplate.queryForObject(JdbcTemplate.java:508)
at org.springframework.jdbc.core.JdbcTemplate.queryForObject(JdbcTemplate.java:515)
at com.alibaba.nacos.config.server.service.repository.extrnal.ExternalStoragePersistServiceImpl.findConfigMaxId(ExternalStoragePersistServiceImpl.java:674)
at com.alibaba.nacos.config.server.service.dump.processor.DumpAllProcessor.process(DumpAllProcessor.java:51)
at com.alibaba.nacos.config.server.service.dump.DumpService.dumpConfigInfo(DumpService.java:282)
at com.alibaba.nacos.config.server.service.dump.DumpService.dumpOperate(DumpService.java:195)
... 65 common frames omitted
配置文件(数据库及连接池部分):
#*************** Config Module Related Configurations ***************#
### If use MySQL as datasource:
spring.datasource.platform=mysql
### Count of DB:
db.num=1
### Connect URL of DB:
db.url.0=jdbc:mysql://localhost:13306/nacos_config?characterEncoding=utf8&useUnicode=true&useSSL=false
db.user=root
db.password=root
### Connection pool configuration: hikariCP
db.pool.config.connectionTimeout=30000
db.pool.config.validationTimeout=10000
db.pool.config.maximumPoolSize=20
db.pool.config.minimumIdle=2
可能原因:
未初始化创建配置nacos数据库及相关配置信息表,
数据库用户密码填写错误(这里使用mysql5.x),nacos自带的jdbc驱动的mysql8.x,同时兼容mysql5.x
创建表:
CREATE DATABASE `nacos_config` CHARACTER SET 'utf8' COLLATE 'utf8_general_ci';
执行sql脚本:mysq-schema.sql
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = config_info */
/******************************************/
CREATE TABLE `config_info` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
`data_id` varchar(255) NOT NULL COMMENT 'data_id',
`group_id` varchar(255) DEFAULT NULL,
`content` longtext NOT NULL COMMENT 'content',
`md5` varchar(32) DEFAULT NULL COMMENT 'md5',
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
`src_user` text COMMENT 'source user',
`src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip',
`app_name` varchar(128) DEFAULT NULL,
`tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',
`c_desc` varchar(256) DEFAULT NULL,
`c_use` varchar(64) DEFAULT NULL,
`effect` varchar(64) DEFAULT NULL,
`type` varchar(64) DEFAULT NULL,
`c_schema` text,
`encrypted_data_key` text NOT NULL COMMENT '秘钥',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_configinfo_datagrouptenant` (`data_id`,`group_id`,`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info';
/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = config_info_aggr */
/******************************************/
CREATE TABLE `config_info_aggr` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
`data_id` varchar(255) NOT NULL COMMENT 'data_id',
`group_id` varchar(255) NOT NULL COMMENT 'group_id',
`datum_id` varchar(255) NOT NULL COMMENT 'datum_id',
`content` longtext NOT NULL COMMENT '内容',
`gmt_modified` datetime NOT NULL COMMENT '修改时间',
`app_name` varchar(128) DEFAULT NULL,
`tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_configinfoaggr_datagrouptenantdatum` (`data_id`,`group_id`,`tenant_id`,`datum_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='增加租户字段';
/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = config_info_beta */
/******************************************/
CREATE TABLE `config_info_beta` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
`data_id` varchar(255) NOT NULL COMMENT 'data_id',
`group_id` varchar(128) NOT NULL COMMENT 'group_id',
`app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',
`content` longtext NOT NULL COMMENT 'content',
`beta_ips` varchar(1024) DEFAULT NULL COMMENT 'betaIps',
`md5` varchar(32) DEFAULT NULL COMMENT 'md5',
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
`src_user` text COMMENT 'source user',
`src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip',
`tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',
`encrypted_data_key` text NOT NULL COMMENT '秘钥',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_configinfobeta_datagrouptenant` (`data_id`,`group_id`,`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info_beta';
/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = config_info_tag */
/******************************************/
CREATE TABLE `config_info_tag` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
`data_id` varchar(255) NOT NULL COMMENT 'data_id',
`group_id` varchar(128) NOT NULL COMMENT 'group_id',
`tenant_id` varchar(128) DEFAULT '' COMMENT 'tenant_id',
`tag_id` varchar(128) NOT NULL COMMENT 'tag_id',
`app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',
`content` longtext NOT NULL COMMENT 'content',
`md5` varchar(32) DEFAULT NULL COMMENT 'md5',
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
`src_user` text COMMENT 'source user',
`src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_configinfotag_datagrouptenanttag` (`data_id`,`group_id`,`tenant_id`,`tag_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info_tag';
/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = config_tags_relation */
/******************************************/
CREATE TABLE `config_tags_relation` (
`id` bigint(20) NOT NULL COMMENT 'id',
`tag_name` varchar(128) NOT NULL COMMENT 'tag_name',
`tag_type` varchar(64) DEFAULT NULL COMMENT 'tag_type',
`data_id` varchar(255) NOT NULL COMMENT 'data_id',
`group_id` varchar(128) NOT NULL COMMENT 'group_id',
`tenant_id` varchar(128) DEFAULT '' COMMENT 'tenant_id',
`nid` bigint(20) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`nid`),
UNIQUE KEY `uk_configtagrelation_configidtag` (`id`,`tag_name`,`tag_type`),
KEY `idx_tenant_id` (`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_tag_relation';
/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = group_capacity */
/******************************************/
CREATE TABLE `group_capacity` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`group_id` varchar(128) NOT NULL DEFAULT '' COMMENT 'Group ID,空字符表示整个集群',
`quota` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '配额,0表示使用默认值',
`usage` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '使用量',
`max_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个配置大小上限,单位为字节,0表示使用默认值',
`max_aggr_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '聚合子配置最大个数,,0表示使用默认值',
`max_aggr_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个聚合数据的子配置大小上限,单位为字节,0表示使用默认值',
`max_history_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '最大变更历史数量',
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_group_id` (`group_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='集群、各Group容量信息表';
/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = his_config_info */
/******************************************/
CREATE TABLE `his_config_info` (
`id` bigint(20) unsigned NOT NULL,
`nid` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`data_id` varchar(255) NOT NULL,
`group_id` varchar(128) NOT NULL,
`app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',
`content` longtext NOT NULL,
`md5` varchar(32) DEFAULT NULL,
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`src_user` text,
`src_ip` varchar(50) DEFAULT NULL,
`op_type` char(10) DEFAULT NULL,
`tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',
`encrypted_data_key` text NOT NULL COMMENT '秘钥',
PRIMARY KEY (`nid`),
KEY `idx_gmt_create` (`gmt_create`),
KEY `idx_gmt_modified` (`gmt_modified`),
KEY `idx_did` (`data_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='多租户改造';
/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = tenant_capacity */
/******************************************/
CREATE TABLE `tenant_capacity` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`tenant_id` varchar(128) NOT NULL DEFAULT '' COMMENT 'Tenant ID',
`quota` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '配额,0表示使用默认值',
`usage` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '使用量',
`max_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个配置大小上限,单位为字节,0表示使用默认值',
`max_aggr_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '聚合子配置最大个数',
`max_aggr_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个聚合数据的子配置大小上限,单位为字节,0表示使用默认值',
`max_history_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '最大变更历史数量',
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_tenant_id` (`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='租户容量信息表';
CREATE TABLE `tenant_info` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
`kp` varchar(128) NOT NULL COMMENT 'kp',
`tenant_id` varchar(128) default '' COMMENT 'tenant_id',
`tenant_name` varchar(128) default '' COMMENT 'tenant_name',
`tenant_desc` varchar(256) DEFAULT NULL COMMENT 'tenant_desc',
`create_source` varchar(32) DEFAULT NULL COMMENT 'create_source',
`gmt_create` bigint(20) NOT NULL COMMENT '创建时间',
`gmt_modified` bigint(20) NOT NULL COMMENT '修改时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_tenant_info_kptenantid` (`kp`,`tenant_id`),
KEY `idx_tenant_id` (`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='tenant_info';
CREATE TABLE `users` (
`username` varchar(50) NOT NULL PRIMARY KEY,
`password` varchar(500) NOT NULL,
`enabled` boolean NOT NULL
);
CREATE TABLE `roles` (
`username` varchar(50) NOT NULL,
`role` varchar(50) NOT NULL,
UNIQUE INDEX `idx_user_role` (`username` ASC, `role` ASC) USING BTREE
);
CREATE TABLE `permissions` (
`role` varchar(50) NOT NULL,
`resource` varchar(255) NOT NULL,
`action` varchar(8) NOT NULL,
UNIQUE INDEX `uk_role_permission` (`role`,`resource`,`action`) USING BTREE
);
INSERT INTO users (username, password, enabled) VALUES ('nacos', '$2a$10$EuWPZHzz32dJN7jexM34MOeYirDdFAZm2kuWj7VEOJhhZkDrxfvUu', TRUE);
INSERT INTO roles (username, role) VALUES ('nacos', 'ROLE_ADMIN');
完成以上步骤再次启动,成功.
默认的用户名和密码为nacos
1.3 windows启动jar nacos yaml配置文件中包含中文问题
org.yaml.snakeyaml.error.YAMLException: java.nio.charset.MalformedInputException: Input length
背景:使用nacos作为配置中心,idea中运行服务时没有问题,maven打包后运行jar包出现异常,
原因: 从nacos中拉取的配置文件中含有中文字符注释
windows使用cmd命令窗口启动jar nacos yaml配置文件中包含中文无法启动问题 windows系统下使用cmd 执行 java -jar xx.jar,jar里面配置文件使用的yaml,配置文件中包含了中文,一直启动不了,报错 YAMLException: java.nio.charset.MalformedInputException: Input length = 1
解决:
启动的时候要添加 -Dfile.encoding=utf-8作为启动参数才行,主要报错原因是读取到的配置中有中文,而在windows运行时候控制台默认编码为GBK,而读取到的配置文件为UTF-8编码,导致的报错,用GBK去解析UTF-8,没有中文的话是没问题,有中文就报错,即使是中文注释也不行
如下启动即可解决yaml配置文件中有中文的问题: java -Dfile.encoding=utf-8 -jar xx.jar
另外cmd窗口显示中文乱码可以在cmd窗口执行: chcp 65001解决cmd中文乱码,但是只在当前窗口有效,窗口关闭重新打开会失效。
配置参数
单机模式下运行Nacos
Linux/Unix/Mac
- Standalone means it is non-cluster Mode. * sh startup.sh -m standalone
Windows
- Standalone means it is non-cluster Mode. * cmd startup.cmd -m standalone
单机模式支持mysql
在0.7版本之前,在单机模式时nacos使用嵌入式数据库实现数据的存储,不方便观察数据存储的基本情况。0.7版本增加了支持mysql数据源能力,具体的操作步骤:
- 1.安装数据库,版本要求:5.6.5+
- 2.初始化mysql数据库,数据库初始化文件:mysql-schema.sql
- 3.修改conf/application.properties文件,增加支持mysql数据源配置(目前只支持mysql),添加mysql数据源的url、用户名和密码。
spring.datasource.platform=mysql
db.num=1
db.url.0=jdbc:mysql://11.162.196.16:3306/nacos_devtest?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true
db.user=nacos_devtest
db.password=youdontknow
再以单机模式启动nacos,nacos所有写嵌入式数据库的数据都写到了mysql
Config模块
参数名 | 含义 | 可选值 | 默认值 | 支持版本 |
---|---|---|---|---|
db.num | 数据库数目 | 正整数 | 0 | >= 0.1.0 |
db.url.0 | 第一个数据库的URL | 字符串 | 空 | >= 0.1.0 |
db.url.1 | 第二个数据库的URL | 字符串 | 空 | >= 0.1.0 |
db.user | 数据库连接的用户名 | 字符串 | 空 | >= 0.1.0 |
db.password | 数据库连接的密码 | 字符串 | 空 | >= 0.1.0 |
spring.datasource.platform | 数据库类型 | 字符串 | mysql | >=1.3.0 |
db.pool.config.xxx | 数据库连接池参数,使用的是hikari连接池,参数与hikari连接池相同,如db.pool.config.connectionTimeout 或db.pool.config.maximumPoolSize
|
字符串 | 同hikariCp对应默认配置 | >=1.4.1 |
当前数据库配置支持多数据源。通过db.num
来指定数据源个数,db.url.index
为对应的数据库的链接。db.user
以及db.password
没有设置index
时,所有的链接都以db.user
和db.password
用作认证。如果不同数据源的用户名称或者用户密码不一样时,可以通过符号,
来进行切割,或者指定db.user.index
,db.user.password
来设置对应数据库链接的用户或者密码。需要注意的是,当db.user
和db.password
没有指定下标时,因为当前机制会根据,
进行切割。所以当用户名或者密码存在,
时,会把,
切割后前面的值当成最后的值进行认证,会导致认证失败。
Nacos从1.3版本开始使用HikariCP连接池,但在1.4.1版本前,连接池配置由系统默认值定义,无法自定义配置。在1.4.1后,提供了一个方法能够配置HikariCP连接池。 db.pool.config
为配置前缀,xxx
为实际的hikariCP配置,如db.pool.config.connectionTimeout
或db.pool.config.maximumPoolSize
等。更多hikariCP的配置请查看HikariCP 需要注意的是,url,user,password会由db.url.n
,db.user
,db.password
覆盖,driverClassName则是默认的MySQL8 driver(该版本mysql driver支持mysql5.x)
服务注册
配置项 | Key | 默认值 | 说明 |
---|---|---|---|
服务端地址 | spring.cloud.nacos.discovery.server-addr |
Nacos Server 启动监听的ip地址和端口 | |
服务名 | spring.cloud.nacos.discovery.service |
${spring.application.name} |
注册的服务名 |
权重 | spring.cloud.nacos.discovery.weight |
1 |
取值范围 1 到 100,数值越大,权重越大 |
网卡名 | spring.cloud.nacos.discovery.network-interface |
当IP未配置时,注册的IP为此网卡所对应的IP地址,如果此项也未配置,则默认取第一块网卡的地址 | |
注册的IP地址 | spring.cloud.nacos.discovery.ip |
优先级最高 | |
注册的IP地址类型 | spring.cloud.nacos.discovery.ip-type |
IPv4 |
可以配置IPv4和IPv6两种类型,如果网卡同类型IP地址存在多个,希望制定特定网段地址,可使用spring.cloud.inetutils.preferred-networks 配置筛选地址 |
注册的端口 | spring.cloud.nacos.discovery.port |
-1 |
默认情况下不用配置,会自动探测 |
命名空间 | spring.cloud.nacos.discovery.namespace |
常用场景之一是不同环境的注册的区分隔离,例如开发测试环境和生产环境的资源(如配置、服务)隔离等 | |
AccessKey | spring.cloud.nacos.discovery.access-key |
当要上阿里云时,阿里云上面的一个云账号名 | |
SecretKey | spring.cloud.nacos.discovery.secret-key |
当要上阿里云时,阿里云上面的一个云账号密码 | |
Metadata | spring.cloud.nacos.discovery.metadata |
使用Map格式配置,用户可以根据自己的需要自定义一些和服务相关的元数据信息 | |
日志文件名 | spring.cloud.nacos.discovery.log-name |
||
集群 | spring.cloud.nacos.discovery.cluster-name |
DEFAULT |
Nacos集群名称 |
接入点 | spring.cloud.nacos.discovery.endpoint |
地域的某个服务的入口域名,通过此域名可以动态地拿到服务端地址 | |
是否集成LoadBalancer | spring.cloud.loadbalancer.nacos.enabled |
false |
|
是否开启Nacos Watch | spring.cloud.nacos.discovery.watch.enabled |
true |
可以设置成false来关闭 watch |
问题
Nacos服务注册地址为内网IP
仔细一查才发现,网关去访问了一个莫名其妙的IP地址, 去Nacos服务详情去看,果然,我的微服务注册到Nacos的IP地址上也是这个地址, 然后我去我电脑查找这个IP地址,还真有这么一个地址,那么问题来了,Nacos为什么会随意找个本机IP地址然后注册上去?
Nacos服务注册的IP
Nacos注册中心是: https://github.com/alibaba/nacos
各个服务通过Nacos客户端将服务信息注册到Nacos上
当Nacos服务注册的IP默认选择出问题时,可以通过查阅对应的客户端文档,来选择配置不同的网卡或者IP
(参考org.springframework.cloud.alibaba.nacos.NacosDiscoveryProperties的配置
)
解决办法
例如,使用了Spring cloud alibaba(官方文档)作为Nacos客户端, 服务默认获取了内网IP 192.168.1.21, 可以通过配置spring.cloud.inetutils.preferred-networks=10.34.12,使服务获取内网中前缀为10.34.12的IP
如何配置
# 如果选择固定Ip注册可以配置
spring.cloud.nacos.discovery.ip = 10.2.11.11
spring.cloud.nacos.discovery.port = 9090
# 如果选择固定网卡配置项
spring.cloud.nacos.discovery.networkInterface = eth0
# 如果想更丰富的选择,可以使用spring cloud 的工具 InetUtils进行配置
# 具体说明可以自行检索: https://github.com/spring-cloud/spring-cloud-commons/blob/master/docs/src/main/asciidoc/spring-cloud-commons.adoc
spring.cloud.inetutils.default-hostname
spring.cloud.inetutils.default-ip-address
spring.cloud.inetutils.ignored-interfaces[0]=eth0 # 忽略网卡,eth0
spring.cloud.inetutils.ignored-interfaces=eth.* # 忽略网卡,eth.*,正则表达式
spring.cloud.inetutils.preferred-networks=10.34.12 # 选择符合前缀的IP作为服务注册IP
spring.cloud.inetutils.timeout-seconds
spring.cloud.inetutils.use-only-site-local-interfaces
更多配置
spring.cloud.nacos.discovery.server-addr #Nacos Server 启动监听的ip地址和端口
spring.cloud.nacos.discovery.service #给当前的服务命名
spring.cloud.nacos.discovery.weight #取值范围 1 到 100,数值越大,权重越大
spring.cloud.nacos.discovery.network-interface #当IP未配置时,注册的IP为此网卡所对应的IP地址,如果此项也未配置,则默认取第一块网卡的地址
spring.cloud.nacos.discovery.ip # 优先级最高
spring.cloud.nacos.discovery.port # 默认情况下不用配置,会自动探测
spring.cloud.nacos.discovery.namespace # 常用场景之一是不同环境的注册的区分隔离,例如开发测试环境和生产环境的资源(如配置、服务)隔离等。
spring.cloud.nacos.discovery.access-key # 当要上阿里云时,阿里云上面的一个云账号名
spring.cloud.nacos.discovery.secret-key # 当要上阿里云时,阿里云上面的一个云账号密码
spring.cloud.nacos.discovery.metadata #使用Map格式配置,用户可以根据自己的需要自定义一些和服务相关的元数据信息
spring.cloud.nacos.discovery.log-name # 日志文件名
spring.cloud.nacos.discovery.enpoint # 地域的某个服务的入口域名,通过此域名可以动态地拿到服务端地址
ribbon.nacos.enabled # 是否集成Ribbon 默认为true
Ribbon
RestTemplate
使用RestTemplate时首先要往容器中注入bean,并添加 @loadbalance 注解
@Configuration
public class FeignLogConfiguration {
@Bean
public Logger.Level level() {
return Logger.Level.FULL;
}
}
OpenFeign
异常处理
org.springframework.web.client.UnknownContentTypeException: Could not extract response: no suitable HttpMessageConverter found for response type [class XXX] and content type [XXX;XXX]
情形一:
以 content type[text/html;charset=utf-8] 为例子 可以自行建立一个config包,在config包下写下MyRestTemplate类即可:
@Bean("restTemplate")
public RestTemplate restTemplate(){
RestTemplate restTemplate = new RestTemplate();
MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
mappingJackson2HttpMessageConverter.setSupportedMediaTypes(Arrays.asList(
MediaType.TEXT_HTML, //配了text/html
MediaType.TEXT_PLAIN)); //配了 text/plain
restTemplate.getMessageConverters().add(mappingJackson2HttpMessageConverter);
return restTemplate;
}
//类上面记得加@configuration
情景二
在远程调用方法的时候,feign是会重构造请求的,而feign重构造请求会丢失请求头和丢失上下文,换句话说,就算你登录了,远程调用的时候由于丢失了session,系统也会判断你没登录。
因此,在调用有登录拦截器/过滤器的接口时会被执行拦截/过滤方法,导致返回的数据无法转换为需要的类型
解决: 泛型该接口请求,或者为其设置必要session
基本使用
若未集成,则需要手动添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
在配置类中添加 @EnableFeignClients
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class ConsumerMain83 {
public static void main(String[] args) {
SpringApplication.run(ConsumerMain83.class, args);
}
}
服务接口中添加: @Component,根据springcloud版本可省略
@FeignClient(name = "${my.remote-server}")
@Component
public interface FeignTestService {
@GetMapping("/nacos/payment/test")
String test();
}
当注册中心不存在服务时(第三方接口调用),也需要写上name,同时还需要url
@FeignClient(name = "https://gitee.com/api/v5",url = "https://gitee.com/api/v5")
public interface GiteeFeignService {
@GetMapping("emails")
String getAllEmail(@RequestParam("access_token") String access_token);
}
设置请求头
@Configuration
public class MyFeignConfig {
@Bean
public RequestInterceptor requestInterceptor() {
return template -> {
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (requestAttributes != null) {
String cookie = requestAttributes.getRequest().getHeader("Cookie");
template.header("Cookie", cookie);
}
};
}
}
开启异步任务前先获取request,并在执行异步任务前设置request的header等信息
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> {
//为异步任务线程设置request
RequestContextHolder.setRequestAttributes(requestAttributes);
...
});
自定义编解码器
需求:实现将返回结果 json 响应中小写下划线转小写驼峰映射
解码器配置类
package com.pika.vm.github.config;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.google.common.base.CaseFormat;
import com.ruoyi.common.core.utils.StringUtils;
import feign.FeignException;
import feign.Response;
import feign.Util;
import feign.codec.Decoder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.cloud.openfeign.support.SpringDecoder;
import org.springframework.context.annotation.Bean;
import java.io.IOException;
import java.lang.reflect.Type;
import java.nio.charset.Charset;
import java.util.Objects;
import java.util.Set;
import static java.nio.charset.StandardCharsets.UTF_8;
/**
* Desc:
*
* @since 2024/01
*/
@Slf4j
public class GithubFeignConfig {
private static final ObjectMapper objectMapper = new ObjectMapper();
static {
objectMapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);
objectMapper.disable(JsonGenerator.Feature.IGNORE_UNKNOWN);
objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
objectMapper.disable(MapperFeature.DEFAULT_VIEW_INCLUSION);
}
@Bean
public Decoder feignClientDecoder(HttpMessageConverters httpMessageConverters) {
return new FeignClientDecoder(() -> httpMessageConverters);
}
public static class FeignClientDecoder extends SpringDecoder {
public FeignClientDecoder(ObjectFactory<HttpMessageConverters> messageConverters) {
super(messageConverters);
}
@Override
public Object decode(Response response, Type type) throws IOException, FeignException {
final Response.Body body = response.body();
final String bodyClassName = body.getClass().getName();
//Response.ByteArrayBody feign.Response$InputStreamBody feign.Response$ByteArrayBody
//因为ByteArrayBody是private 所以不能instance of
if (Objects.equals(bodyClassName, "feign.Response$ByteArrayBody") || Objects.equals(bodyClassName, "feign.Response$InputStreamBody")) {
String result;
if (Objects.equals(bodyClassName, "feign.Response$ByteArrayBody")) {
result = body.toString();
} else {
byte[] bodyData = Util.toByteArray(body.asInputStream());
result = Util.decodeOrDefault(bodyData, UTF_8, "Binary data");
}
final Response.Builder respBuild = Response.builder()
.headers(response.headers())
.reason(response.reason())
.status(response.status())
.request(response.request());
if (StringUtils.isNoneBlank(result) && JSON.isValid(result)) {
final Object jsonObject = JSON.parse(result);
lowerUnderScore2LowCamelCase(jsonObject);
respBuild.body(JSON.toJSONString(jsonObject), Charset.defaultCharset());
} else {
respBuild.body(result, Charset.defaultCharset());
}
return super.decode(respBuild.build(), type);
}
return super.decode(response, type);
}
}
/**
* 小写驼峰小写下划线
*/
public static void lowCameCase2LowerUnderScore(Object json) {
if (json instanceof JSONArray) {
JSONArray arr = (JSONArray) json;
for (Object obj : arr) {
lowCameCase2LowerUnderScore(obj);
}
} else if (json instanceof JSONObject) {
JSONObject jo = (JSONObject) json;
Set<String> keys = jo.keySet();
String[] array = keys.toArray(new String[0]);
for (String key : array) {
Object value = jo.get(key);
final String underLineKey = CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, key);
jo.remove(key);
jo.put(underLineKey, value);
lowCameCase2LowerUnderScore(value);
}
}
}
/**
* 小写下划线转小写驼峰
*/
public static void lowerUnderScore2LowCamelCase(Object json) {
if (json instanceof JSONArray) {
JSONArray arr = (JSONArray) json;
for (Object obj : arr) {
lowerUnderScore2LowCamelCase(obj);
}
} else if (json instanceof JSONObject) {
JSONObject jo = (JSONObject) json;
Set<String> keys = jo.keySet();
String[] array = keys.toArray(new String[0]);
for (String key : array) {
Object value = jo.get(key);
final String lowCaseKey = CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, key);
jo.remove(key);
jo.put(lowCaseKey, value);
lowerUnderScore2LowCamelCase(value);
}
}
}
}
@Feignclient 配置
@FeignClient(name = "gitHubFeignService",url = "https://api.github.com",configuration = GithubFeignConfig.class)
public interface GitHubFeignService {
@GetMapping("/user/repos")
List<Repository> get_own_repos(@RequestHeader(value = "Authorization", defaultValue = "") String token,
@RequestHeader(value = "Accept", defaultValue = "application/vnd.github+json") String accept,
@RequestHeader(value = "X-GitHub-Api-Version", defaultValue = "2022-11-28") String version);
}
Sentinel
Seata
client启动失败
前提准备:
seata: 1.5.1 (事务管理)
nacos: 2.1.2 (注册和配置中心)
pom文件信息
<!--nacos-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2.1.2.RELEASE</version>
<exclusions>
<exclusion>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--nacos-config-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<version>2.1.2.RELEASE</version>
<exclusions>
<exclusion>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba.nacos/nacos-client -->
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
<version>2.1.0</version>
</dependency>
<!--seata-->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.5.1</version>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
<version>1.5.1</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<version>2.2.0.RELEASE</version>
<exclusions>
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
yaml文件配置:
nacos服务注册到 nacos的namespace,group,application,cluster要与client端一致
server:
server:
port: 7091
spring:
application:
name: seata-server
logging:
config: classpath:logback-spring.xml
file:
path: ${user.home}/logs/seata
extend:
logstash-appender:
destination: 127.0.0.1:4560
kafka-appender:
bootstrap-servers: 127.0.0.1:9092
topic: logback_to_logstash
console:
user:
username: seata
password: seata
seata:
tx-service-group: my_test_tx_group
config:
# support: nacos, consul, apollo, zk, etcd3
type: nacos
nacos:
server-addr: 127.0.0.1:8848
namespace: 1008611
group: SEATA_GROUP
username: nacos
password: nacos
##if use MSE Nacos with auth, mutex with username/password attribute
#access-key: ""
#secret-key: ""
data-id: seata.properties
registry:
# support: nacos, eureka, redis, zk, consul, etcd3, sofa
type: nacos
preferred-networks: 30.240.*
nacos:
application: seata-server
server-addr: 127.0.0.1:8848
group: SEATA_GROUP
namespace: 1008611
username: nacos
password: nacos
cluster: seata-server
store:
# support: file 、 db 、 redis
mode: db
db:
datasource: druid
db-type: mysql
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:13306/seata?rewriteBatchedStatements=true
user: root
password: root
min-conn: 5
max-conn: 100
global-table: global_table
branch-table: branch_table
lock-table: lock_table
distributed-lock-table: distributed_lock
query-limit: 100
max-wait: 5000
# server:
# service-port: 8091 #If not configured, the default is '${server.port} + 1000'
security:
secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017
tokenValidityInMilliseconds: 1800000
ignore:
urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/api/v1/auth/login
client:
server:
port: 3001
spring:
application:
name: seata-order-service
cloud:
nacos:
discovery:
server-addr: localhost:8848
namespace: 1008611
group: SEATA_GROUP
register-enabled: true
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:13306/seata_order
username: root
password: root
feign:
hystrix:
enabled: false
logging:
level:
io:
seata: info
mybatis:
mapperLocations: classpath:mapper/*.xml
type-aliases-package: classpath:com.pika.domain.*
#自定义事务组名称需要与seata-server中的对应
seata:
application-id: seata-order-service
registry:
type: nacos
preferred-networks: 30.240.*
nacos:
application: seata-server
server-addr: 127.0.0.1:8848
group: SEATA_GROUP
namespace: 1008611
username: nacos
password: nacos
cluster: seata-server
enable-auto-data-source-proxy: false
config:
type: nacos # !!!!不要忘了指定配置类型
nacos:
data-id: seata.properties
username: nacos
password: nacos
group: SEATA_GROUP
namespace: 1008611
server-addr: localhost:8848
enabled: true
tx-service-group: my_test_tx_group
事务分组
i.s.c.r.netty.NettyClientChannelManager : can not get cluster name in registry config 'service.vgroupMapping.seata-order-service-fescar-service-group', please make sure registry config correct
解决:在nacos的seata.properties中添加:
service.vgroupMapping.seata-order-service-fescar-service-group=seata-server
从v1.4.2版本开始,已支持从一个Nacos dataId中获取所有配置信息,你只需要额外添加一个dataId配置项。
1.4.2版本以下的可以使用官方脚本逐个上传配置(本次的1.5.1使用的是dataId,使用脚本上传会逐个设置每个配置项作为单独dataId,不便管理)
https://github.com/seata/seata/tree/develop/script/config-center
nacos中完整的seata.properties,源文件地址:https://github.com/seata/seata/blob/develop/script/config-center/config.txt
#补充配置
service.vgroupMapping.seata-order-service-fescar-service-group=seata-server
# suppress inspection "SpringBootApplicationProperties" for whole file
#For details about configuration items, see https://seata.io/zh-cn/docs/user/configurations.html
#Transport configuration, for client and server
transport.type=TCP
transport.server=NIO
transport.heartbeat=true
transport.enableTmClientBatchSendRequest=false
transport.enableRmClientBatchSendRequest=true
transport.enableTcServerBatchSendResponse=false
transport.rpcRmRequestTimeout=30000
transport.rpcTmRequestTimeout=30000
transport.rpcTcRequestTimeout=30000
transport.threadFactory.bossThreadPrefix=NettyBoss
transport.threadFactory.workerThreadPrefix=NettyServerNIOWorker
transport.threadFactory.serverExecutorThreadPrefix=NettyServerBizHandler
transport.threadFactory.shareBossWorker=false
transport.threadFactory.clientSelectorThreadPrefix=NettyClientSelector
transport.threadFactory.clientSelectorThreadSize=1
transport.threadFactory.clientWorkerThreadPrefix=NettyClientWorkerThread
transport.threadFactory.bossThreadSize=1
transport.threadFactory.workerThreadSize=default
transport.shutdown.wait=3
transport.serialization=seata
transport.compressor=none
#Transaction routing rules configuration, only for the client
service.vgroupMapping.default_tx_group=seata-server
#If you use a registry, you can ignore it
service.default.grouplist=127.0.0.1:8091
service.enableDegrade=false
service.disableGlobalTransaction=false
#Transaction rule configuration, only for the client
client.rm.asyncCommitBufferLimit=10000
client.rm.lock.retryInterval=10
client.rm.lock.retryTimes=30
client.rm.lock.retryPolicyBranchRollbackOnConflict=true
client.rm.reportRetryCount=5
client.rm.tableMetaCheckEnable=true
client.rm.tableMetaCheckerInterval=60000
client.rm.sqlParserType=druid
client.rm.reportSuccessEnable=false
client.rm.sagaBranchRegisterEnable=false
client.rm.sagaJsonParser=fastjson
client.rm.tccActionInterceptorOrder=-2147482648
client.tm.commitRetryCount=5
client.tm.rollbackRetryCount=5
client.tm.defaultGlobalTransactionTimeout=60000
client.tm.degradeCheck=false
client.tm.degradeCheckAllowTimes=10
client.tm.degradeCheckPeriod=2000
client.tm.interceptorOrder=-2147482648
client.undo.dataValidation=true
client.undo.logSerialization=jackson
client.undo.onlyCareUpdateColumns=true
server.undo.logSaveDays=7
server.undo.logDeletePeriod=86400000
client.undo.logTable=undo_log
client.undo.compress.enable=true
client.undo.compress.type=zip
client.undo.compress.threshold=64k
#For TCC transaction mode
tcc.fence.logTableName=tcc_fence_log
tcc.fence.cleanPeriod=1h
#Log rule configuration, for client and server
log.exceptionRate=100
#Transaction storage configuration, only for the server. The file, db, and redis configuration values are optional.
store.mode=db
store.lock.mode=db
store.session.mode=db
#Used for password encryption
store.publicKey=
#If `store.mode,store.lock.mode,store.session.mode` are not equal to `file`, you can remove the configuration block.
#store.file.dir=file_store/data
#store.file.maxBranchSessionSize=16384
#store.file.maxGlobalSessionSize=512
#store.file.fileWriteBufferCacheSize=16384
#store.file.flushDiskMode=async
#store.file.sessionReloadReadSize=100
#These configurations are required if the `store mode` is `db`. If `store.mode,store.lock.mode,store.session.mode` are not equal to `db`, you can remove the configuration block.
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.jdbc.Driver
store.db.url=jdbc:mysql://127.0.0.1:13306/seata?useUnicode=true&rewriteBatchedStatements=true
store.db.user=root
store.db.password=root
store.db.minConn=5
store.db.maxConn=30
store.db.globalTable=global_table
store.db.branchTable=branch_table
store.db.distributedLockTable=distributed_lock
store.db.queryLimit=100
store.db.lockTable=lock_table
store.db.maxWait=5000
#These configurations are required if the `store mode` is `redis`. If `store.mode,store.lock.mode,store.session.mode` are not equal to `redis`, you can remove the configuration block.
#store.redis.mode=single
#store.redis.single.host=127.0.0.1
#store.redis.single.port=6379
#store.redis.sentinel.masterName=
#store.redis.sentinel.sentinelHosts=
#store.redis.maxConn=10
#store.redis.minConn=1
#store.redis.maxTotal=100
#store.redis.database=0
#store.redis.password=
#store.redis.queryLimit=100
#Transaction rule configuration, only for the server
server.recovery.committingRetryPeriod=1000
server.recovery.asynCommittingRetryPeriod=1000
server.recovery.rollbackingRetryPeriod=1000
server.recovery.timeoutRetryPeriod=1000
server.maxCommitRetryTimeout=-1
server.maxRollbackRetryTimeout=-1
server.rollbackRetryTimeoutUnlockEnable=false
server.distributedLockExpireTime=10000
server.xaerNotaRetryTimeout=60000
server.session.branchAsyncQueueSize=5000
server.session.enableBranchAsyncRemove=false
server.enableParallelRequestHandle=false
#Metrics configuration, only for the server
metrics.enabled=false
metrics.registryType=compact
metrics.exporterList=prometheus
metrics.exporterPrometheusPort=9898
重新启动后:控制台已无Error
i.s.c.r.netty.NettyClientChannelManager : will connect to 192.168.231.130:8091
i.s.core.rpc.netty.NettyPoolableFactory : NettyPool create channel to transactionRole:TMROLE,address:192.168.231.130:8091,msg:< RegisterTMRequest{applicationId='seata-order-service', transactionServiceGroup='seata-order-service-fescar-service-group'} >
nacos 拉取配置
no available service found in cluster ‘seata-server’, please make sure registry config correct and keep your seata server running
seata-server服务注册在nacos的集群名称是否为seata-server(互相对应)
将seata.config.type配置为nacos后依然出错
Failed to get available servers: {}
检查client的seata.config.type有没有配置为nacos(默认为file),以及seata.config.nacos.*的各项配置,
或者进入io.seata.core.rpc.netty.NettyClientChannelManager类中调试availInetSocketAddressList是否为null
private List<String> getAvailServerList(String transactionServiceGroup) throws Exception {
List<InetSocketAddress> availInetSocketAddressList = RegistryFactory.getInstance()
.lookup(transactionServiceGroup);
if (CollectionUtils.isEmpty(availInetSocketAddressList)) {
return Collections.emptyList();
}
return availInetSocketAddressList.stream()
.map(NetUtil::toStringAddress)
.collect(Collectors.toList());
}
调试io.seata.config.nacos.NacosConfiguration查看配置是否生效
Feign远程调用时全局事务失效
使用的是@GlobalTransactional+Feign
在测试时发现A服务feign调用B服务,出现异常时只能A回滚,B不回滚,在A和B服务接口打印RootContext.getXID(),发现A是能打印的,B打印出来为null,说明XID未能传递到B服务,在搜索资料之后发现可能是feign没有传递xid的原因,也可能是nginx默认过滤掉了_字符,因为我的AB服务都注册在nacos,而nacos集群经过nginx代理
解决方法1:在调用方新增配置,手动在header添加xid参数
@Configuration
public class MyFeignConfig {
@Bean
public RequestInterceptor requestInterceptor() {
return template -> {
// 解决seata的xid未传递
String xid = RootContext.getXID();
template.header(RootContext.KEY_XID, xid);
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (requestAttributes != null) {
String cookie = requestAttributes.getRequest().getHeader("Cookie");
template.header("Cookie", cookie);
}
};
}
}
2.nginx默认request的header的那么中包含“__”时,会自动忽略掉。 自测还是不行,采取第一种 解决方法是:在nginx里的 nginx.conf 配置文件中的http部分中添加如下配置: underscores_in_headers on; (默认 underscores_in_headers 为off)
参考
Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。
AT 模式
前提
- 基于支持本地 ACID 事务的关系型数据库。
- Java 应用,通过 JDBC 访问数据库。
整体机制
两阶段提交协议的演变:
- 一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。
- 二阶段:
- 提交异步化,非常快速地完成。
- 回滚通过一阶段的回滚日志进行反向补偿。
写隔离
- 一阶段本地事务提交前,需要确保先拿到 全局锁 。
- 拿不到 全局锁 ,不能提交本地事务。
- 拿 全局锁 的尝试被限制在一定范围内,超出范围将放弃,并回滚本地事务,释放本地锁。
以一个示例来说明:
两个全局事务 tx1 和 tx2,分别对 a 表的 m 字段进行更新操作,m 的初始值 1000。
tx1 先开始,开启本地事务,拿到本地锁,更新操作 m = 1000 - 100 = 900。本地事务提交前,先拿到该记录的 全局锁 ,本地提交释放本地锁。 tx2 后开始,开启本地事务,拿到本地锁,更新操作 m = 900 - 100 = 800。本地事务提交前,尝试拿该记录的 全局锁 ,tx1 全局提交前,该记录的全局锁被 tx1 持有,tx2 需要重试等待 全局锁 。
tx1 二阶段全局提交,释放 全局锁 。tx2 拿到 全局锁 提交本地事务。
如果 tx1 的二阶段全局回滚,则 tx1 需要重新获取该数据的本地锁,进行反向补偿的更新操作,实现分支的回滚。
此时,如果 tx2 仍在等待该数据的 全局锁,同时持有本地锁,则 tx1 的分支回滚会失败。分支的回滚会一直重试,直到 tx2 的 全局锁 等锁超时,放弃 全局锁 并回滚本地事务释放本地锁,tx1 的分支回滚最终成功。
因为整个过程 全局锁 在 tx1 结束前一直是被 tx1 持有的,所以不会发生 脏写 的问题。
读隔离
在数据库本地事务隔离级别 读已提交(Read Committed) 或以上的基础上,Seata(AT 模式)的默认全局隔离级别是 读未提交(Read Uncommitted) 。
如果应用在特定场景下,必需要求全局的 读已提交 ,目前 Seata 的方式是通过 SELECT FOR UPDATE 语句的代理。
Read Isolation: SELECT FOR UPDATE
SELECT FOR UPDATE 语句的执行会申请 全局锁 ,如果 全局锁 被其他事务持有,则释放本地锁(回滚 SELECT FOR UPDATE 语句的本地执行)并重试。这个过程中,查询是被 block 住的,直到 全局锁 拿到,即读取的相关数据是 已提交 的,才返回。
出于总体性能上的考虑,Seata 目前的方案并没有对所有 SELECT 语句都进行代理,仅针对 FOR UPDATE 的 SELECT 语句。
工作机制
以一个示例来说明整个 AT 分支的工作过程。
业务表:product
Field | Type | Key |
---|---|---|
id | bigint(20) | PRI |
name | varchar(100) | |
since | varchar(100) |
AT 分支事务的业务逻辑:
update product set name = 'GTS' where name = 'TXC';
一阶段
过程:
- 解析 SQL:得到 SQL 的类型(UPDATE),表(product),条件(where name = ‘TXC’)等相关的信息。
- 查询前镜像:根据解析得到的条件信息,生成查询语句,定位数据。
select id, name, since from product where name = 'TXC';
得到前镜像:
id | name | since |
---|---|---|
1 | TXC | 2014 |
- 执行业务 SQL:更新这条记录的 name 为 ‘GTS’。
- 查询后镜像:根据前镜像的结果,通过 主键 定位数据。
select id, name, since from product where id = 1;
得到后镜像:
id | name | since |
---|---|---|
1 | GTS | 2014 |
- 插入回滚日志:把前后镜像数据以及业务 SQL 相关的信息组成一条回滚日志记录,插入到
UNDO_LOG
表中。
{
"branchId": 641789253,
"undoItems": [{
"afterImage": {
"rows": [{
"fields": [{
"name": "id",
"type": 4,
"value": 1
}, {
"name": "name",
"type": 12,
"value": "GTS"
}, {
"name": "since",
"type": 12,
"value": "2014"
}]
}],
"tableName": "product"
},
"beforeImage": {
"rows": [{
"fields": [{
"name": "id",
"type": 4,
"value": 1
}, {
"name": "name",
"type": 12,
"value": "TXC"
}, {
"name": "since",
"type": 12,
"value": "2014"
}]
}],
"tableName": "product"
},
"sqlType": "UPDATE"
}],
"xid": "xid:xxx"
}
- 提交前,向 TC 注册分支:申请
product
表中,主键值等于 1 的记录的 全局锁 。 - 本地事务提交:业务数据的更新和前面步骤中生成的 UNDO LOG 一并提交。
- 将本地事务提交的结果上报给 TC。
二阶段-回滚
- 收到 TC 的分支回滚请求,开启一个本地事务,执行如下操作。
- 通过 XID 和 Branch ID 查找到相应的 UNDO LOG 记录。
- 数据校验:拿 UNDO LOG 中的后镜与当前数据进行比较,如果有不同,说明数据被当前全局事务之外的动作做了修改。这种情况,需要根据配置策略来做处理,详细的说明在另外的文档中介绍。
- 根据 UNDO LOG 中的前镜像和业务 SQL 的相关信息生成并执行回滚的语句:
update product set name = 'TXC' where id = 1;
- 提交本地事务。并把本地事务的执行结果(即分支事务回滚的结果)上报给 TC。
二阶段-提交
- 收到 TC 的分支提交请求,把请求放入一个异步任务的队列中,马上返回提交成功的结果给 TC。
- 异步任务阶段的分支提交请求将异步和批量地删除相应 UNDO LOG 记录。
附录
回滚日志表
UNDO_LOG Table:不同数据库在类型上会略有差别。
以 MySQL 为例:
Field | Type |
---|---|
branch_id | bigint PK |
xid | varchar(100) |
context | varchar(128) |
rollback_info | longblob |
log_status | tinyint |
log_created | datetime |
log_modified | datetime |
-- 注意此处0.7.0+ 增加字段 context
CREATE TABLE `undo_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`branch_id` bigint(20) NOT NULL,
`xid` varchar(100) NOT NULL,
`context` varchar(128) NOT NULL,
`rollback_info` longblob NOT NULL,
`log_status` int(11) NOT NULL,
`log_created` datetime NOT NULL,
`log_modified` datetime NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
TCC 模式
回顾总览中的描述:一个分布式的全局事务,整体是 两阶段提交 的模型。全局事务是由若干分支事务组成的,分支事务要满足 两阶段提交 的模型要求,即需要每个分支事务都具备自己的:
- 一阶段 prepare 行为
- 二阶段 commit 或 rollback 行为
Overview of a global transaction
根据两阶段行为模式的不同,我们将分支事务划分为 Automatic (Branch) Transaction Mode 和 Manual (Branch) Transaction Mode.
AT 模式(参考链接 TBD)基于 支持本地 ACID 事务 的 关系型数据库:
- 一阶段 prepare 行为:在本地事务中,一并提交业务数据更新和相应回滚日志记录。
- 二阶段 commit 行为:马上成功结束,自动 异步批量清理回滚日志。
- 二阶段 rollback 行为:通过回滚日志,自动 生成补偿操作,完成数据回滚。
相应的,TCC 模式,不依赖于底层数据资源的事务支持:
- 一阶段 prepare 行为:调用 自定义 的 prepare 逻辑。
- 二阶段 commit 行为:调用 自定义 的 commit 逻辑。
- 二阶段 rollback 行为:调用 自定义 的 rollback 逻辑。
所谓 TCC 模式,是指支持把 自定义 的分支事务纳入到全局事务的管理中。
Saga 模式
Saga模式是SEATA提供的长事务解决方案,在Saga模式中,业务流程中每个参与者都提交本地事务,当出现某一个参与者失败则补偿前面已经成功的参与者,一阶段正向服务和二阶段补偿服务都由业务开发实现。
理论基础:Hector & Kenneth 发表论⽂ Sagas (1987)
适用场景:
- 业务流程长、业务流程多
- 参与者包含其它公司或遗留系统服务,无法提供 TCC 模式要求的三个接口
优势:
- 一阶段提交本地事务,无锁,高性能
- 事件驱动架构,参与者可异步执行,高吞吐
- 补偿服务易于实现
缺点:
- 不保证隔离性(应对方案见用户文档)
版权声明:如无特别声明,本站收集的文章归 HuaJi66/Others 所有。 如有侵权,请联系删除。
联系邮箱: GenshinTimeStamp@outlook.com
本文标题:《 Spring Cloud Zero 学习笔记 》
本文链接:/%E5%BE%AE%E6%9C%8D%E5%8A%A1/springcloud/%E9%94%A6%E5%9B%8A/springcloud-zero.html