springcloud项目引入nacos改造过程

springcloud项目引入nacos改造过程

文章目录

  • springcloud项目引入nacos改造过程
    • 场景
    • 更新20191226
    • 1.nacos基本用法
      • 1.1 部署nacos服务端程序
      • 1.2 客户端
      • 1.3 nacos配置
      • 1.4 启动
    • 2.项目改造
      • 2.1 配置文件
      • 2.2 配置分析
      • 2.3 nacos配置
        • 2.3.1 nacos后台管理新增配置
        • 2.3.2 项目应用增加nacos配置
      • 2.4 java代码中的配置
        • 2.4.1 datasource
        • 2.4.2 redis
        • 2.4.3 rabbitmq
        • 2.4.4 XxlSso
    • 3.项目效果

场景

  • 项目开发后期,上线部署阶段,部分配置信息需要给运维同事进行动态的维护管理
  • 比如redis,rabbitmq,数据库等等
  • 目前项目是通过yml+spring.profiles.active的方式进行配置的切换和管理

更新20191226

后期上生产环境部署发下如下问题

  • 对于部分参数起不到实时刷新的作用,比如一些在启动阶段就已经加载好的类和对象
  • 在注入Filter@RefreshScope注解和@Bean 有冲突,会导致@Bean注入的Filter不生效

具体情况可以参考 Nacos配置中心,Filter中动态获取参数

1.nacos基本用法

官网:http://nacos.io

1.1 部署nacos服务端程序

官网的教程 Nacos 快速开始

官网的教程通俗易懂,本文就不赘述了

nocos服务端启动成功后,nocos后台管理登录地址 http://127.0.0.1:8848/nacos/#/login

账号 nacos 密码 nacos
springcloud项目引入nacos改造过程_第1张图片

1.2 客户端

毕业版本依赖关系(推荐使用)

Spring Cloud Version Spring Cloud Alibaba Version Spring Boot Version
Spring Cloud Greenwich 2.1.0.RELEASE 2.1.X.RELEASE
Spring Cloud Finchley 2.0.0.RELEASE 2.0.X.RELEASE
Spring Cloud Edgware 1.5.0.RELEASE 1.5.X.RELEASE

客户端就是我们自己项目的应用,这里我们新建一个springboot测试的项目

  1. 添加依赖

    这里和官网有点不同,我们是普通的sc项目,并不是spring-cloud-alibaba,需要引入下面的依赖和版本

    spring-boot-starter-web非必要是为了做测试

          <dependency>
                <groupId>com.alibaba.cloudgroupId>
                <artifactId>spring-cloud-starter-alibaba-nacos-configartifactId>
                <version>2.1.0.RELEASEversion>
            dependency>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-webartifactId>
            dependency>
    
  2. bootstrap.yml

    application.yml文件中添加不会生效,必须要bootstrap.yml

    properties文件可以自己类比

    spring:
      application:
        name: nacos-config
      cloud:
        nacos:
          config:
            server-addr: 127.0.0.1:8848
            file-extension: yaml
    
  3. 启动类

    可以动态的获取nacos配置中心的值,进行打印

    此代码为官网的测试代码

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.context.ConfigurableApplicationContext;
    
    import java.util.concurrent.TimeUnit;
    
    @SpringBootApplication
    public class ConfigBootstrap {
        public static void main(String[] args) throws InterruptedException {
            ConfigurableApplicationContext applicationContext = SpringApplication.run(ConfigBootstrap.class,args);
            while(true) {
                String userName = applicationContext.getEnvironment().getProperty("user.name");
                String userAge = applicationContext.getEnvironment().getProperty("user.age");
                //获取当前部署的环境
                String currentEnv = applicationContext.getEnvironment().getProperty("current.env");
                System.err.println("in "+currentEnv+" enviroment; "+"user name :" + userName + "; age: " + userAge);
                TimeUnit.SECONDS.sleep(1);
            }
        }
    }
    
  4. 新增测试类

    正常配置中心的用法

    @RefreshScope来进行实时刷新

    @Value("${user.name}") 对应配置中心的值

    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.cloud.context.config.annotation.RefreshScope;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    @RefreshScope
    public class ConfigServiceController {
    
        @Value("${user.name}")
        private String username;
    
        @GetMapping("/hello")
        public String hello() {
            return "Hello:" + username;
        }
    
    }
    

1.3 nacos配置

可支持profile粒度的配置

spring-cloud-starter-alibaba-nacos-config 在加载配置的时候,不仅仅加载了以 dataid 为 ${spring.application.name}.${file-extension:properties} 为前缀的基础配置,还加载了dataid为 ${spring.application.name}-${profile}.${file-extension:properties} 的基础配置。在日常开发中如果遇到多套环境下的不同配置,可以通过Spring 提供的 ${spring.profiles.active} 这个配置项来配置。

  1. 登录 http://127.0.0.1:8848/nacos/#/login 后台管理

  2. 新增配置 Data ID : nacos-config.yaml

     user.name: xwf
     user.age: 11
    

springcloud项目引入nacos改造过程_第2张图片

1.4 启动

打印:

in null enviroment; user name :xwf; age: 11

springcloud项目引入nacos改造过程_第3张图片

浏览器输入http://127.0.0.1:8080/hello

如果改动nacos中的配置,这边会实时刷新
springcloud项目引入nacos改造过程_第4张图片

2.项目改造

了解了nacos的基本用法,下面我们来对真实的项目进行改造

2.1 配置文件

  • application.yml

激活版本@profileActive@ 可以在maven中进行设置 dev表示测试版本, prod表示生产版本, 本文就不多做介绍了

server:
  port: 8180
spring:
  profiles:
    active: @profileActive@
  • application-dev.yml
spring:
  zipkin:
   base-url: http://192.168.1.87:9411
   sender:
     type: web
     #数据库连接信息
  datasource:
        name: zjiaoEvalweb
        url: jdbc:mysql://192.168.1.87:3306/**
        username: **
        password: **
        # 使用druid数据源
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.jdbc.Driver
        filters: stat
        maxActive: 20
        initialSize: 1
        maxWait: 60000
        minIdle: 1
        timeBetweenEvictionRunsMillis: 60000
        minEvictableIdleTimeMillis: 300000
        validationQuery: select 'x'
        testWhileIdle: true
        testOnBorrow: false
        testOnReturn: false
        poolPreparedStatements: true
        maxOpenPreparedStatements: 20
  redis:
    database: 0
    timeout: 3000ms
    host: 192.168.1.87
    port: 6379
    password: huajie
    # 连接池最大连接数(使用负值表示没有限制)
    jedis:
      pool:
        max-idle: 500
        min-idle: 50
        max-active: 2000
        max-wait: 1000ms
  rabbitmq:
    addresses: 192.168.1.87:5672 #MQ IP 和 端口
    username: huajie #MQ登录名
    password: huajie #MQ登录密码
    virtual-host: /zjiaoMobile #MQ的虚拟主机名称
xxl:
  sso:
    server: http://127.0.0.1:8083
    logout:
      path: /logout
    excluded:
      paths: /zjiaoweb/api/**,/hystrix.stream
    redis:
      address: redis://192.168.1.87:6379
eureka:
  client:
    serviceUrl:
      defaultZone: http://127.0.0.1:8761/eureka/
  instance:
    instance-id: ${spring.cloud.client.ip-address}:${server.port}
    prefer-ip-address: true
  • application-prod.yml

这里就不做展示了,和application-dev.yml类似,只是ip为真实地址

2.2 配置分析

这里我们主要是将application-dev.yml中的配置迁移到nacos

application-dev.yml配置文件可以分为六部分

  1. zipkin 链路追踪
  2. datasource 数据源
  3. redis 缓存内存数据库
  4. rabbitmq 消息队列
  5. xxl-sso 单点登录
  6. eureka 注册中心

1.zipkin6.eureka为基础组件,目前还未考虑如何进行迁移,如果以后了解到会更新在本文中

本文只讨论一般情况,及datasource/redis/rabbitmq/xxl-sso的配置

2.3 nacos配置

将传统yml配置文件迁移到nacos

2.3.1 nacos后台管理新增配置

本项目服务名为eval-zjiaoweb-xwf

  • 配置Data ID : {应用服务名}-{激活版本}.{配置文件类型} --> eval-zjiaoweb-xwf-dev.yaml

  • Group : 可以自行配置 默认也可以

  • 配置内容就是 原配置文件application-dev.yml中的相关信息
    springcloud项目引入nacos改造过程_第5张图片

2.3.2 项目应用增加nacos配置

bootstrap.yml 增加

spring:
  application:
    name: eval-zjiaoweb-xwf
  cloud:
    nacos:
      config:
        server-addr: 127.0.0.1:8848
        file-extension: yaml
        group: zjiao_group

pom.xml依赖,父项目中进行了依赖版本管理,所以这边不需要写版本号

        <dependency>
            <groupId>com.alibaba.cloudgroupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-configartifactId>
        dependency>

2.4 java代码中的配置

  • 将相关的配置修改成@Value("${xxx.xx}")的形式,进行组件的初始化
  • 只需要在配置类上增加@RefreshScope,实时对配置进行刷新

2.4.1 datasource

DruidConfig.java

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

import javax.sql.DataSource;
import java.sql.SQLException;

/**
 * @author lc
 * @date: 2017/11/16 19:29
 * @description: druid数据库连接池配置加载类
 */
@Configuration
@RefreshScope
public class DruidConfig {
    private Logger logger = LoggerFactory.getLogger(getClass());
 
    @Value("${druid.login.enabled}")
    private boolean druidLoginEnabled;
 
    @Value("${druid.login.username}")
    private String druidLoginUsername;
 
    @Value("${druid.login.password}")
    private String druidLoginPassword;
 
    @Value("${spring.datasource.url}")
    private String dbUrl;
 
    @Value("${spring.datasource.username}")
    private String username;
 
    @Value("${spring.datasource.password}")
    private String password;
 
    @Value("${spring.datasource.driver-class-name}")
    private String driverClassName;
 
    @Value("${spring.datasource.initialSize}")
    private int initialSize;
 
    @Value("${spring.datasource.minIdle}")
    private int minIdle;
 
    @Value("${spring.datasource.maxActive}")
    private int maxActive;
 
    @Value("${spring.datasource.maxWait}")
    private int maxWait;
 
    @Value("${spring.datasource.timeBetweenEvictionRunsMillis}")
    private int timeBetweenEvictionRunsMillis;
 
    @Value("${spring.datasource.minEvictableIdleTimeMillis}")
    private int minEvictableIdleTimeMillis;
 
    @Value("${spring.datasource.validationQuery}")
    private String validationQuery;
 
    @Value("${spring.datasource.testWhileIdle}")
    private boolean testWhileIdle;
 
    @Value("${spring.datasource.testOnBorrow}")
    private boolean testOnBorrow;
 
    @Value("${spring.datasource.testOnReturn}")
    private boolean testOnReturn;
 
    @Value("${spring.datasource.poolPreparedStatements}")
    private boolean poolPreparedStatements;
 
    @Value("${spring.datasource.filters}")
    private String filters;
 
    @Bean
    public ServletRegistrationBean druidServlet() {
        ServletRegistrationBean reg = new ServletRegistrationBean();
        reg.setServlet(new StatViewServlet());
        reg.addUrlMappings("/druid/*");
        //web访问是否需要登录
        if(druidLoginEnabled) {
            reg.addInitParameter("loginUsername", druidLoginUsername);
            reg.addInitParameter("loginPassword", druidLoginPassword);
        }
        return reg;
    }
 
    @Bean
    public FilterRegistrationBean filterRegistrationBean() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        filterRegistrationBean.setFilter(new WebStatFilter());
        filterRegistrationBean.addUrlPatterns("/*");
        filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
        filterRegistrationBean.addInitParameter("profileEnable", "true");
        filterRegistrationBean.addInitParameter("principalCookieName", "USER_COOKIE");
        filterRegistrationBean.addInitParameter("principalSessionName", "USER_SESSION");
        return filterRegistrationBean;
    }
 
    @Bean
    @Primary
    public DataSource druidDataSource() {
        logger.info("开始配置druidDataSource");
        DruidDataSource datasource = new DruidDataSource();
        datasource.setUrl(this.dbUrl);
        datasource.setUsername(username);
        datasource.setPassword(password);
        datasource.setDriverClassName(driverClassName);
        datasource.setInitialSize(initialSize);
        datasource.setMinIdle(minIdle);
        datasource.setMaxActive(maxActive);
        datasource.setMaxWait(maxWait);
        datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
        datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
        datasource.setValidationQuery(validationQuery);
        datasource.setTestWhileIdle(testWhileIdle);
        datasource.setTestOnBorrow(testOnBorrow);
        datasource.setTestOnReturn(testOnReturn);
        datasource.setPoolPreparedStatements(poolPreparedStatements);
        try {
            datasource.setFilters(filters);
        } catch (SQLException e) {
            logger.error("druid configuration initialization filter", e);
        }
        logger.info("druidDataSource配置成功");
        return datasource;
    }
 
}

2.4.2 redis

redis有两个类RedisConfigRedisTemplateConfig

之前可以写在一个类里面,但是加入nacos之后会出现循环依赖的问题,所以拆成两个配置类

  • RedisConfig.java
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.jedis.JedisClientConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import redis.clients.jedis.JedisPoolConfig;

/**
 * Redis缓存配置类
 */
@Configuration
@RefreshScope
public class RedisConfig {

    @Value("${spring.redis.host}")
    public String host;
    @Value("${spring.redis.port}")
    public int port;
    @Value("${spring.redis.password}")
    public String password;
    @Value("${spring.redis.database}")
    public int database;
    @Value("${spring.redis.jedis.pool.max-idle}")
    public int maxIdle;
    @Value("${spring.redis.jedis.pool.min-idle}")
    public int minIdle;
    @Value("${spring.redis.jedis.pool.max-active}")
    public int maxActive;
    @Value("${spring.redis.jedis.pool.max-wait}")
    public String maxWait;
    @Value("${spring.redis.timeout}")
    public String timeout;

    @Bean
    public JedisConnectionFactory jedisConnectionFactory() {
        RedisStandaloneConfiguration rf = new RedisStandaloneConfiguration();
        rf.setDatabase(database);
        rf.setHostName(host);
        rf.setPort(port);
        rf.setPassword(RedisPassword.of(password));
        int to = Integer.parseInt(timeout.substring(0, timeout.length() - 2));
        //JedisClientConfiguration.JedisClientConfigurationBuilder jedisClientConfiguration = JedisClientConfiguration.builder();
        //jedisClientConfiguration.connectTimeout(Duration.ofMillis(to));
        JedisClientConfiguration.JedisPoolingClientConfigurationBuilder jpb =
                (JedisClientConfiguration.JedisPoolingClientConfigurationBuilder) JedisClientConfiguration.builder();
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMaxIdle(maxIdle);
        jedisPoolConfig.setMinIdle(minIdle);
        jedisPoolConfig.setMaxTotal(maxActive);
        int l = Integer.parseInt(maxWait.substring(0, maxWait.length() - 2));
        jedisPoolConfig.setMaxWaitMillis(l);
        jpb.poolConfig(jedisPoolConfig);
        JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(rf, jpb.build());
        return jedisConnectionFactory;
    }

}
  • RedisTemplateConfig.java
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * RedisTemplate配置类
 */
@Configuration
public class RedisTemplateConfig {

    @Autowired
    JedisConnectionFactory jedisConnectionFactory;

    @Bean
    public RedisTemplate redisTemplate() {
        RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
        template.setConnectionFactory(jedisConnectionFactory);
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        // key采用String的序列化方式
        template.setKeySerializer(stringRedisSerializer);
        // hash的key也采用String的序列化方式
        template.setHashKeySerializer(stringRedisSerializer);
        // value序列化方式采用jackson
        template.setValueSerializer(jackson2JsonRedisSerializer);
        // hash的value序列化方式采用jackson
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }

}

2.4.3 rabbitmq

@Data注解是lombok中的,不需要可以去掉

import com.huajie.utils.RabbitUtil;
import lombok.Data;
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitMessagingTemplate;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.listener.RabbitListenerContainerFactory;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.converter.MappingJackson2MessageConverter;

/**
 * Created by admin on 2017/6/1 11:26.
 * 容器中要有RabbitAdmin和RabbitTemplate实例
 */
@Configuration
@Data
@RefreshScope
public class RabbitMQConfig {
    /**
     * 注入配置文件属性
     */
    @Value("${spring.rabbitmq.addresses}")
    String addresses;//MQ地址
    @Value("${spring.rabbitmq.username}")
    String username;//MQ登录名
    @Value("${spring.rabbitmq.password}")
    String password;//MQ登录密码
    @Value("${spring.rabbitmq.virtual-host}")
    String vHost;//MQ的虚拟主机名

    @Bean
    public ConnectionFactory connectionFactory() throws Exception {
        return RabbitUtil.connectionFactory(addresses, username, password, vHost);
    }

    @Bean
    public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory) throws Exception {
        RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
        return rabbitAdmin;
    }

    /**
     * 消费消息,设置序列化
     */
    @Bean
    public RabbitListenerContainerFactory<?> rabbitListenerContainerFactory(ConnectionFactory connectionFactory){
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factory.setConnectionFactory(connectionFactory);
        factory.setMessageConverter(new Jackson2JsonMessageConverter());
        return factory;
    }

    /**
     * 发送消息,设置序列化
     */
    @Bean
    public static RabbitMessagingTemplate simpleMessageTemplate(ConnectionFactory connectionFactory) {
        RabbitTemplate template = new RabbitTemplate(connectionFactory);
        RabbitMessagingTemplate rabbitMessagingTemplate = new RabbitMessagingTemplate();
        rabbitMessagingTemplate.setMessageConverter(new MappingJackson2MessageConverter());
        rabbitMessagingTemplate.setRabbitTemplate(template);
        return rabbitMessagingTemplate;
    }

}

2.4.4 XxlSso

import com.huajie.conf.Conf;
import com.huajie.filter.XxlSsoWebFilter;
import com.huajie.util.JedisUtil;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author xuxueli 2018-11-15
 */
@Configuration
@RefreshScope
public class XxlSsoConfig implements DisposableBean {

	@Value("${xxl.sso.server}")
	private String xxlSsoServer;

	@Value("${xxl.sso.logout.path}")
	private String xxlSsoLogoutPath;

	@Value("${xxl.sso.excluded.paths}")
	private String xxlSsoExcludedPaths;

	@Value("${xxl.sso.redis.address}")
	private String xxlSsoRedisAddress;

	@Value("${spring.redis.password}")
	private String redisPassword;

	@Bean
	public FilterRegistrationBean xxlSsoFilterRegistration() {

		// xxl-sso, redis init
		JedisUtil.init(xxlSsoRedisAddress,redisPassword);

		// xxl-sso, filter init
		FilterRegistrationBean registration = new FilterRegistrationBean();

		registration.setName("XxlSsoWebFilter");
		registration.setOrder(1);
		registration.addUrlPatterns("/*");
		registration.setFilter(new XxlSsoWebFilter());
		registration.addInitParameter(Conf.SSO_SERVER, xxlSsoServer);
		registration.addInitParameter(Conf.SSO_LOGOUT_PATH, xxlSsoLogoutPath);
		registration.addInitParameter(Conf.SSO_EXCLUDED_PATHS, xxlSsoExcludedPaths);

		return registration;
	}

	@Override
	public void destroy() throws Exception {
		JedisUtil.close();
	}

}

3.项目效果

  1. application-dev.yml 最终简化成
spring:
  zipkin:
   base-url: http://192.168.1.87:9411
   sender:
     type: web
eureka:
  client:
    serviceUrl:
      defaultZone: http://127.0.0.1:8761/eureka/
  instance:
    instance-id: ${spring.cloud.client.ip-address}:${server.port}
    prefer-ip-address: true
  1. 项目配置文件可以在http://127.0.0.1:8848/nacos中进行动态配置

springcloud项目引入nacos改造过程_第6张图片

你可能感兴趣的:(springcloud项目引入nacos改造过程)