# spring-cloud-nacos **Repository Path**: qwzcys/spring-cloud-nacos ## Basic Information - **Project Name**: spring-cloud-nacos - **Description**: springcloud整合nacos - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2020-11-18 - **Last Updated**: 2021-07-27 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 项目说明 spring-cloud整合nacos,实现动态配置中心、心跳检测、feign、zuul网关静态路由以及服务熔断、seata分布式事务控制 ## 项目结构 ``` ├─load-balancing feign以及负载均衡 ├─logs 日志 ├─nacos-client-payment 服务提供者、rocketmq生产者 ├─nacos-common 通用工具模块 ├─nacos-gateway-zuul zuul网关 ├─rocketmq-client rocketmq消费者 ``` ## 服务列表 ![](.README_images/5612fb1f.png) ## 功能实现 ### 服务注册与发现 #### 1、新建配置文件 ![](.README_images/91c90a7c.png) nacos管理端——配置信息——配置管理——新建配置 ####2、引入依赖 ``` com.alibaba.cloud spring-cloud-alibaba-dependencies 2.1.0.RELEASE pom import com.alibaba.nacos nacos-client com.alibaba.nacos nacos-client 1.4.0 ``` #### 3、bootstrap.yml ``` server: port: 8090 #端口号 spring: application: name: nacos_client-dev-yaml #服务名,同时也是nacos配置文件名 #添加nacos-server框架 cloud: nacos: config: server-addr: 106.53.22.186:8848 #nacos配置中心地址 file-extension: yml ``` #### 4、启动项目 ``` @EnableDiscoveryClient @SpringBootApplication @MapperScan({"com.nacos.payment.dao.*"}) public class Application { public static void main(String[] args) { SpringApplication.run(Application.class,args); } } ``` ### 整合feign调用服务 #### 1、新建配置文件 ![](.README_images/4aa74e5b.png) #### 2、引入依赖 ``` org.springframework.cloud spring-cloud-starter-openfeign com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery 2.1.1.RELEASE ``` #### 3、bootstrap.yml配置 ``` server: port: 8095 spring: application: name: load_balancing-dev-yaml cloud: nacos: discovery: server-addr: 106.53.22.186:8848 ``` ##### 注意,feign与服务注册的格式不同,服务注册的格式为—— ``` spring: application: name: nacos_client-dev-yaml #服务名,同时也是nacos配置文件名 #添加nacos-server框架 cloud: nacos: config: server-addr: 106.53.22.186:8848 #nacos配置中心地址 file-extension: yml ``` feign格式为 ``` spring: application: name: load_balancing-dev-yaml cloud: nacos: discovery: server-addr: 106.53.22.186:8848 ``` 两者的区别主要体现在 ``` 服务注册 cloud: nacos: config: server-addr: 106.53.22.186:8848 #nacos配置中心地址 file-extension: yml feign cloud: nacos: discovery: server-addr: 106.53.22.186:8848 ``` #### 4、调用服务 ``` @FeignClient(value = "nacos-client-payment") @Component public interface TestService { /** * 访问test接口 * @return */ @GetMapping("/test/test") public BaseResult test(); } ``` #### 5、写接口 ``` @RestController @RequestMapping("test") public class PaymentController { @Autowired private TestService testService; private static final Logger log = LoggerFactory.getLogger(PaymentController.class); @GetMapping("test") public BaseResult test(){ log.info("访问test"); testService.test(); return BaseResult.successResult("访问test"); } } ``` #### 6、启动项目 ``` @SpringBootApplication @EnableDiscoveryClient @EnableFeignClients public class Application { public static void main(String[] args) { SpringApplication.run(Application.class,args); } } ``` #### 7、Feign+Hystrix熔断降级 ##### 6、application.yml开启 ``` feign: hystrix: enabled: true ``` ##### feignClient注解加上fallbackFactory ``` @FeignClient(value = "nacos-client-payment",fallbackFactory = MyFallBack.class) ``` ##### MyFallBack方法 ``` @Component public class MyFallBack implements FallbackFactory { private final TestService testService; public MyFallBack(){ this.testService=new TestService() { @Override public BaseResult test() { return BaseResult.errorResult("触发熔断"); } @Override public BaseResult towTest() { return BaseResult.errorResult("触发熔断"); } }; } @Override public TestService create(Throwable cause) { return testService; } } ``` #### sentinel规则持久化 ##### 1、nacos建立持久化规则文件——load-balancing-test 文件内容 ``` [ { "resource":"/test/test", 资源名 "limitApp":"default", #default 任何人都可以调用此规则, "grade":1, #限流阈值类型(1=QPS,0=并发线程数) "count":0, #限流阈值 "strategy":0, #调用关联策略(0=资源本身,1=关联资源,2=链路路口) "controlBenhavior":0, #流量控制效果(0=直接拒绝,1=warm up,2=匀速排队) "clusterMode":false #是否集群 } ] ``` ##### 2、服务导入依赖 ``` com.alibaba.csp sentinel-datasource-nacos ``` ##### 3、application.yml配置 ``` spring: cloud: sentinel: eager: true #是否开启饥饿加载 默认为false不开启 datasource: balancing: nacos: server-addr: 106.53.22.186:8848 dataId: load-balancing-test groupId: BALANCING_GROUP ruleType: flow #flow=流控规则,degrade=熔断规则 usename: nacos passowrd: nacos123321 ``` #### sentinel熔断和流控 ##### 1、添加注解 ``` @SentinelResource(value = "balancing-test",blockHandlerClass = MyFallBack.class,blockHandler = "blockHandler") public BaseResult test(Long time) { BaseResult test = testService.test(); try { log.info("开始休眠"); Thread.sleep(time); } catch (InterruptedException e) { e.printStackTrace(); } return test; } ``` ##### 注意,熔断的方法参数必须与接口参数一致 ##### 2、实现方法 ``` public static BaseResult blockHandler(Long time,BlockException e){ return BaseResult.errorResult("触发流控"); } ``` ### 整合zuul #### 1、添加依赖 ``` org.springframework.cloud spring-cloud-starter-netflix-zuul org.springframework.boot spring-boot-starter-actuator org.example nacos-common 1.0-SNAPSHOT org.springframework.cloud spring-cloud-starter-alibaba-nacos-discovery 0.2.1.RELEASE com.alibaba.nacos nacos-client org.springframework.cloud spring-cloud-starter-alibaba-nacos-config 0.2.1.RELEASE com.alibaba.nacos nacos-client com.alibaba.nacos nacos-client 1.4.0 ``` #### 2、添加配置信息文件 nacos_gateway_zuul-dev.yaml ``` spring: application: name: nacos_gateway_zuul zuul: # include-debug-header: true #禁用这个微服务microservicecloud-dept #ignored-services: microservicecloud-dept #网关前缀 # prefix: /atguigu #禁用所有的微服务 # ignored-services: "*" routes: # payment: # #配置路由名称对应的微服务名称,也就是说,当访问/nacos-client-payment/**就相当于文nacos-client-payment/** # service-id: nacos-client-payment # path: /payment/** # retryable: true # strip-prefix: true baidu: path: /baidu/** url: http://www.baidu.com/ # retryable: true # stripPrefix: true ``` #### 3、bootstrap设置 ``` server: port: 8060 spring: application: name: nacos_gateway_zuul-dev cloud: nacos: config: server-addr: 106.53.22.186:8848 file-extension: yaml group: DEFAULT_GROUP discovery: server-addr: 106.53.22.186:8848 ``` #### 4、启动类 ``` @SpringBootApplication @EnableDiscoveryClient @EnableZuulProxy public class GatewayApplication { public static void main(String[] args) { SpringApplication.run(GatewayApplication.class,args); } } ``` ##### 动态刷新路由(配置文件的方式) ``` @SpringBootApplication @EnableDiscoveryClient @EnableZuulProxy @RefreshScope public class GatewayApplication { public static void main(String[] args) { SpringApplication.run(GatewayApplication.class,args); } } ``` #### 5、服务熔断 ``` @Component public class ServiceFallbackProvider implements FallbackProvider { private static final Logger log = LoggerFactory.getLogger(ServiceFallbackProvider.class); @Autowired private RouteLocator routeLocator; @Autowired private UrlPathHelper urlPathHelper; @Override public String getRoute() { return null; } @Override public ClientHttpResponse fallbackResponse(String route, Throwable cause) { return new ClientHttpResponse() { /** * ClientHttpResponse的fallback的状态码 * @return * @throws IOException */ @Override public HttpStatus getStatusCode() throws IOException { return HttpStatus.OK; } /** * ClientHttpResponse的fallback的状态码 * @return * @throws IOException */ @Override public int getRawStatusCode() throws IOException { return HttpStatus.OK.value(); } /** * ClientHttpResponse的fallback的状态码 * @return * @throws IOException */ @Override public String getStatusText() throws IOException { return HttpStatus.OK.getReasonPhrase(); } @Override public void close() { } /** * 设置响应体 * @return * @throws IOException */ @Override public InputStream getBody() throws IOException { final RequestContext ctx = RequestContext.getCurrentContext(); final Route route = getRoute(ctx.getRequest()); log.error("触发熔断"+route.getLocation()); return new ByteArrayInputStream("服务异常请稍后访问".getBytes()); } /** * 设置表头 * @return */ @Override public HttpHeaders getHeaders() { HttpHeaders headers = new HttpHeaders(); MediaType mt = new MediaType("application", "json", Charset.forName("utf-8")); headers.setContentType(mt); return headers; } }; } /** * 获取路由信息 * @param request * @return */ private Route getRoute(HttpServletRequest request){ final String requestUri = urlPathHelper.getRequestUri(request); final Route route = routeLocator.getMatchingRoute(requestUri); log.info("路由配置完整路径:"+route.getFullPath()); return route; } } ``` ### 整合rocketmq #### 消息生产者 ##### 导入依赖 ``` org.apache.rocketmq rocketmq-spring-boot-starter 2.0.3 ``` 注:springcloud-alibaba最新版只支持到rocketmq4.4.0版本 ##### 发送消息实现 ``` DefaultMQProducer producer = new DefaultMQProducer("bbcOrder-group"); producer.setNamesrvAddr("localhost:9876"); producer.setSendMessageWithVIPChannel(false); producer.setInstanceName(System.currentTimeMillis()+""); producer.start(); System.out.println(producer.getCreateTopicKey()); Message msg = new Message(rocketTopic ,JacksonUtils.javaBeanForjson(bbcOrder).getBytes() // Message body ); //异步发送 producer.send(msg,RocketMqSendCallback.sendCallback(),5000); //同步发送 producer.send(msg); ``` 注:不添加属性producer.setInstanceName(System.currentTimeMillis()+""); 会报错No route info of this topic ##### 异步发送验证类 ``` public class RocketMqSendCallback { private static final Logger log = LoggerFactory.getLogger(RocketMqSendCallback.class); public static SendCallback sendCallback(){ return new SendCallback() { @Override public void onSuccess(SendResult sendResult) { log.info("成功发送消息:"+sendResult.toString()); } @Override public void onException(Throwable throwable) { log.error("发送消息失败:"+throwable); } }; } } ``` #### 消息消费者 ##### pom导入 ``` org.apache.rocketmq rocketmq-spring-boot-starter 2.0.3 ``` ##### 实现类 ``` @Component @RocketMQMessageListener(topic = "bbcOrder-topic",consumerGroup = "bbcOrder-group-consume",nameServer = "localhost:9876") public class OrderConsume implements RocketMQListener { private final Logger logger = LoggerFactory.getLogger(RocketMQListener.class); @Override public void onMessage(String s) { logger.info("接收到消息:"+s); } } ``` ### 整合seata(用nacos持久化seata配置) #### 对nacos录入配置文件并持久化(seata server端) ##### 创建文件config.txt并录入内容 ``` transport.type=TCP transport.server=NIO transport.heartbeat=true transport.enableClientBatchSendRequest=false 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 service.vgroupMapping.my_test_tx_group=default service.default.grouplist=127.0.0.1:8091 service.enableDegrade=false service.disableGlobalTransaction=false 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=false client.rm.sqlParserType=druid client.rm.reportSuccessEnable=false client.rm.sagaBranchRegisterEnable=false client.tm.commitRetryCount=5 client.tm.rollbackRetryCount=5 client.tm.defaultGlobalTransactionTimeout=60000 client.tm.degradeCheck=false client.tm.degradeCheckAllowTimes=10 client.tm.degradeCheckPeriod=2000 store.mode=file 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 store.db.datasource=druid store.db.dbType=mysql store.db.driverClassName=com.mysql.jdbc.Driver store.db.url=jdbc:mysql://312:3306/324?useUnicode=true store.db.user=4 store.db.password=fdg store.db.minConn=5 store.db.maxConn=30 store.db.globalTable=global_table store.db.branchTable=branch_table store.db.queryLimit=100 store.db.lockTable=lock_table store.db.maxWait=5000 store.redis.host=127.0.0.1 store.redis.port=6379 store.redis.maxConn=10 store.redis.minConn=1 store.redis.database=0 store.redis.password=null store.redis.queryLimit=100 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 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 log.exceptionRate=100 transport.serialization=seata transport.compressor=none metrics.enabled=false metrics.registryType=compact metrics.exporterList=prometheus metrics.exporterPrometheusPort=9898 ``` ##### 运行shell文件方法 ``` sh nacos-config.sh -h nacos的Ip地址 nacos的命名空间 -p nacos的端口号 -u nacos用户名 -w nacos密码 -g 本配置列表的所属分组(要与后面seata服务端config.nacos.group保持一致,默认为SEATA_GROUP) ``` ###### 示例 ``` sh nacos-config.sh -h 106.53.22.186 -p 8848 50edd814-d2c6-4a27-b633-93d17a9aaba6 -u nacos -w nacos123321 -g SEATA_GROUP ``` ##### 运行python文件方法 ``` python nacos-config.py 106.53.22.186:8848 50edd814-d2c6-4a27-b633-93d17a9aaba6 ``` ``` python nacos-config.py nacos的ip:nacos的端口号 nacos命名空间id ``` ##### 启动命令 ``` 直接运行{seata}/bin/seata-server.sh [options](linux)或者{seata}/bin/seata-server.bat [options] Options: --host, -h The host to bind. Default: 0.0.0.0 --port, -p The port to listen. Default: 8091 --storeMode, -m log store mode : file、db Default: file --help ``` ``` -h: 注册到注册中心的ip -p: Server rpc 监听端口 -m: 全局事务会话信息存储模式,file、db、redis,优先读取启动参数 (Seata-Server 1.3及以上版本支持redis) -n: Server node,多个Server时,需区分各自节点,用于生成不同区间的transactionId,以免冲突 -e: 多环境配置参考 http://seata.io/en-us/docs/ops/multi-configuration-isolation.html ``` ###### 示例 ``` ./seata-server.sh -h 106.53.22.186 -p 8700 -m db ``` #### seata client端 ##### 项目中必须连接数据库 ##### 数据库连接配置 ###### application.yml ``` spring: datasource: url: jdbc:mysql://106.53.22.186:3306/seate?useUnicode=true&useSSL=false username: admin321 password: admin123 driver-class-name: com.mysql.cj.jdbc.Driver platform: mysql type: com.alibaba.druid.pool.DruidDataSource ``` ###### pom.xml ``` mysql mysql-connector-java 8.0.11 com.alibaba druid 1.1.20 ``` ###### 排除springboot自动装配 ``` @SpringBootApplication(exclude = DataSourceAutoConfiguration.class) public class Application { public static void main(String[] args) { SpringApplication.run(Application.class,args); } } ``` ###### 数据库代理配置类 ``` @Configuration public class SeataDataSourceConfig { /** * @param sqlSessionFactory SqlSessionFactory * @return SqlSessionTemplate */ @Bean public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) { return new SqlSessionTemplate(sqlSessionFactory); } /** * 从配置文件获取属性构造datasource,注意前缀,这里用的是druid,根据自己情况配置, * 原生datasource前缀取"spring.datasource" * * @return */ @Bean(name = "druidDataSource") @ConfigurationProperties(prefix = "spring.datasource") public DataSource druidDataSource() { DruidDataSource druidDataSource = new DruidDataSource(); return druidDataSource; } /** * 构造datasource代理对象,替换原来的datasource * @param druidDataSource * @return */ @Primary @Bean("dataSource") public DataSourceProxy dataSourceProxy(DataSource druidDataSource) { return new DataSourceProxy(druidDataSource); } @Bean(name = "sqlSessionFactory") public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception { MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean(); bean.setDataSource(dataSourceProxy); SqlSessionFactory factory = null; try { factory = bean.getObject(); } catch (Exception e) { throw new RuntimeException(e); } return factory; } /** * MP 自带分页插件 * @return */ @Bean public PaginationInterceptor paginationInterceptor() { PaginationInterceptor page = new PaginationInterceptor(); page.setDialectType("mysql"); return page; } } ``` ##### 整合seata使用配置 ###### 1、在nacos插入service.vgroupMapping.${tx-service-group},配置文件,内容为default ###### 2、导入pom ``` io.seata seata-spring-boot-starter 1.2.0 pom com.alibaba.cloud spring-cloud-alibaba-seata 2.2.0.RELEASE io.seata seata-spring-boot-starter io.seata seata-spring-boot-starter 1.2.0 ``` ###### 3、application.yml配置 ``` seata: enabled: true application-id: seataSserv tx-service-group: nacos-client-payment #此处配置自定义的seata事务分组名称 enable-auto-data-source-proxy: true #开启数据库代理 service: vgroup-mapping: nacos-client-payment: default config: type: nacos nacos: namespace: a392391d-f5b5-4b82-a42b-3ff9fdb7d573 server-addr: 106.53.22.186:8848 group: DEFAULT_GROUP username: nacos password: nacos123321 registry: type: nacos nacos: application: seataServ server-addr: 106.53.22.186:8848 namespace: a392391d-f5b5-4b82-a42b-3ff9fdb7d573 username: nacos password: nacos123321 ``` ###### 4、添加文件file.conf、registry.conf ###### registry内容为 ``` registry { # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa type = "nacos" loadBalance = "RandomLoadBalance" loadBalanceVirtualNodes = 10 nacos { application = "seataServ" serverAddr = "106.53.22.186:8848" group = "DEFAULT_GROUP" namespace = "a392391d-f5b5-4b82-a42b-3ff9fdb7d573" cluster = "default" username = "nacos" password = "nacos123321" } eureka { serviceUrl = "http://localhost:8761/eureka" application = "default" weight = "1" } redis { serverAddr = "localhost:6379" db = 0 password = "" cluster = "default" timeout = 0 } zk { cluster = "default" serverAddr = "127.0.0.1:2181" sessionTimeout = 6000 connectTimeout = 2000 username = "" password = "" } consul { cluster = "default" serverAddr = "127.0.0.1:8500" } etcd3 { cluster = "default" serverAddr = "http://localhost:2379" } sofa { serverAddr = "127.0.0.1:9603" application = "default" region = "DEFAULT_ZONE" datacenter = "DefaultDataCenter" cluster = "default" group = "SEATA_GROUP" addressWaitTime = "3000" } file { name = "file.conf" } } config { # file、nacos 、apollo、zk、consul、etcd3 type = "nacos" nacos { serverAddr = "106.53.22.186:8848" namespace = "a392391d-f5b5-4b82-a42b-3ff9fdb7d573" group = "DEFAULT_GROUP" username = "nacos" password = "nacos123321" application = "seataServ" } consul { serverAddr = "127.0.0.1:8500" } apollo { appId = "seata-server" apolloMeta = "http://192.168.1.204:8801" namespace = "application" apolloAccesskeySecret = "" } zk { serverAddr = "127.0.0.1:2181" sessionTimeout = 6000 connectTimeout = 2000 username = "" password = "" } etcd3 { serverAddr = "http://localhost:2379" } file { name = "file.conf" } } ``` 注:nacos包含的"namespace"字段值为nacos命名空间的id而非命名空间名 file.conf内容为 ``` ## transaction log store, only used in seata-server store { ## store mode: file、db、redis mode = "file" ## file store property file { ## store location dir dir = "sessionStore" # branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions maxBranchSessionSize = 16384 # globe session size , if exceeded throws exceptions maxGlobalSessionSize = 512 # file buffer size , if exceeded allocate new buffer fileWriteBufferCacheSize = 16384 # when recover batch read size sessionReloadReadSize = 100 # async, sync flushDiskMode = async } ## database store property db { ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp)/HikariDataSource(hikari) etc. datasource = "druid" ## mysql/oracle/postgresql/h2/oceanbase etc. dbType = "mysql" driverClassName = "com.mysql.cj.jdbc.Driver" url = "jdbc:mysql://106.53.22.187:3306/seate" user = "admin321" password = "admin123" minConn = 5 maxConn = 100 globalTable = "global_table" branchTable = "branch_table" lockTable = "lock_table" queryLimit = 100 maxWait = 5000 } ## redis store property redis { host = "127.0.0.1" port = "6379" password = "" database = "0" minConn = 1 maxConn = 10 maxTotal = 100 queryLimit = 100 } } service { #transaction service group mapping vgroupMapping.my_test_tx_group = "default" #only support when registry.type=file, please don't set multiple addresses default.grouplist = "106.53.22.186:8700" } ``` ##### 使用 ###### 注入配置 ``` @GetMapping("createOrder") @GlobalTransactional(name = "createOrder",rollbackFor = Exception.class) public BaseResult createOrder(){ Order order = new Order(); PurchaseRecords purchaseRecords = new PurchaseRecords(); order.setName("a"); order.setOrderNumber("sdonjfuoisdf"); testService.createOrder(order); purchaseRecords.setGoodsCount(1); purchaseRecords.setGoodsId(2); purchaseRecords.setUserName("sdf"); purchaseRecords.setUserId(2); testService.createPurchase(purchaseRecords); return BaseResult.successResult(); } ``` 注:rollbackFor为必需品,否则seata会报错Could not register branch into global session xid = 106.53.22.186:8091:2030811323 status = Rollbacking whil