springcloud6

配置问题

  • 上一节中的对于配置的加密规则的配置,解析失败,报错是invalid secret key,这是java发布的运行环境包中对加密解密有一定的限制,所以需要下载jce的包,覆盖掉/jdk/jre/lib/security目录下的local_policy.jar和US_export_policy.jar

自定义分布式配置中心

  • spring cloud中的分布式配置中心,其中消息总线是利用mq实现的,但是mq维护成本比较大,技术选型时需要综合考虑其他的方法?
    • redis—发布与订阅
    • zookeeper—watcher 监控节点变更
    • mq

测试spring cloud自带的分布式配置中心

  • 如果没有调总线刷新接口,对应有分布式配置的类用的是单例,一旦调用了总线刷新接口,加了@RefreshScope注解的对象实例就会变化
    • 这是为了再一次触发这个里面@Value注解的依赖注入,不然这个值一直不会变化
    • 加了@RefreshScope注解,相当于打上了标识,调用总线刷新接口时,会把这些类的实例对象换掉

zookper作为发布订阅中心

  • package com.xiangxue.jack.refresh.curator;
    
    import com.xiangxue.jack.refresh.scope.RefreshScopeRegistry;
    import org.apache.curator.framework.CuratorFramework;
    import org.apache.curator.framework.CuratorFrameworkFactory;
    import org.apache.curator.framework.recipes.cache.*;
    import org.apache.curator.retry.ExponentialBackoffRetry;
    import org.apache.zookeeper.CreateMode;
    import org.apache.zookeeper.data.Stat;
    import org.springframework.beans.BeansException;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.beans.factory.config.BeanDefinition;
    import org.springframework.beans.factory.support.BeanDefinitionRegistry;
    import org.springframework.boot.env.OriginTrackedMapPropertySource;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.ApplicationContextAware;
    import org.springframework.context.ConfigurableApplicationContext;
    import org.springframework.core.env.Environment;
    import org.springframework.core.env.MutablePropertySources;
    import org.springframework.core.env.PropertySource;
    
    import javax.annotation.PostConstruct;
    import java.util.List;
    import java.util.concurrent.ConcurrentHashMap;
    import java.util.concurrent.TimeUnit;
    
    //@Component
    public class CuratorUtil implements ApplicationContextAware {
          
          
    
        private static String connnectStr = "192.168.67.139:2181";
    
        private static CuratorFramework client;
    
        private static String path = "/config";
    
        @Value(("${zookeeper.config.enable:false}"))
        private boolean enbale;
    
        @Autowired
        Environment environment;
    
        private static String zkPropertyName = "zookeeperSource";
    
        private static String scopeName = "refresh";
    
        private static ConfigurableApplicationContext applicationContext;
    
        private ConcurrentHashMap map = new ConcurrentHashMap();
    
        private BeanDefinitionRegistry beanDefinitionRegistry;
    
        @PostConstruct
        public void init() {
          
          
            if (!enbale) return;
    
            RefreshScopeRegistry refreshScopeRegistry = (RefreshScopeRegistry) applicationContext.getBean("refreshScopeRegistry");
            // 拿到BeanDefinitionRegistry对象
            beanDefinitionRegistry = refreshScopeRegistry.getBeanDefinitionRegistry();
    
            client = CuratorFrameworkFactory.
                    builder().
                    connectString(connnectStr).
                    sessionTimeoutMs(5000).
                    retryPolicy(new ExponentialBackoffRetry(1000, 3)).
                    build();
    
            client.start();
            try {
          
          
                Stat stat = client.checkExists().forPath(path);
                // zookeeper的客户端是持久性的
                if (stat == null) {
          
          
                    client.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).
                            forPath(path, "zookeeper config".getBytes());
                    TimeUnit.SECONDS.sleep(1);
                } else {
          
          
                    // 这里就是把zookeeper中配置文件的内容加载到spring容器中来
                    // ls /config
                    // [os.name, os.password, xx.name]
                    // 1、把config下面的子节点加载到spring容器的属性对象中
                    addChildToSpringProperty(client, path);
                }
    
    //            nodeCache(client,path);
                // 事件监听
                childNodeCache(client, path);
            } catch (Exception e) {
          
          
                e.printStackTrace();
            }
        }
    
        private void addChildToSpringProperty(CuratorFramework client, String path) {
          
          
            if (!checkExistsSpringProperty()) {
          
          
                //如果不存在zookeeper的配置属性对象则创建
                createZookeeperSpringProperty();
            }
    
            //把config目录下的子节点添加到 zk的PropertySource对象中
            MutablePropertySources propertySources = applicationContext.getEnvironment().getPropertySources();
            // 取出自己创建的PropertySource
            PropertySource<?> propertySource = propertySources.get(zkPropertyName);
            // 取出自己创建PropertySource中用于保存配置参数的map
            ConcurrentHashMap zkmap = (ConcurrentHashMap) propertySource.getSource();
            try {
          
          
                // 从zookeeper拿到具体的配置参数
                List<String> strings = client.getChildren().forPath(path);
                // 把配置参数装填到PropertySource的map中
                for (String string : strings) {
          
          
                    zkmap.put(string, client.getData().forPath(path + "/" + string));
                }
            } catch (Exception e) {
          
          
                e.printStackTrace();
            }
        }
    
        private void createZookeeperSpringProperty() {
          
          
            // 所有spring容器中的属性对象都会被包装到MutablePropertySources里面
            // 不仅包括resource下面的properties文件,还有jar包里面自带的
            // OriginTrackedMapPropertySource继承了MapPropertySource,MapPropertySource就是资源文件的加载
            MutablePropertySources propertySources = applicationContext.getEnvironment().getPropertySources();
            // 把自己zookeeper中的配置文件中的配置参数也封装成OriginTrackedMapPropertySource对象,并添加到spring中,但是此时并没有把实际参数添加进去,里面还是空的
            OriginTrackedMapPropertySource zookeeperSource = new OriginTrackedMapPropertySource(zkPropertyName, map);
            propertySources.addLast(zookeeperSource);
        }
    
        private boolean checkExistsSpringProperty() {
          
          
            MutablePropertySources propertySources = applicationContext.getEnvironment().getPropertySources();
            for (PropertySource<?> propertySource : propertySources) {
          
          
                if (zkPropertyName.equals(propertySource.getName())) {
          
          
                    return true;
                }
            }
            return false;
        }
    
        // zookeeper事件监听
        private void childNodeCache(CuratorFramework client, String path) {
          
          
            try {
          
          
                final PathChildrenCache pathChildrenCache = new PathChildrenCache(client, path, false);
                pathChildrenCache.start(PathChildrenCache.StartMode.POST_INITIALIZED_EVENT);
    
                pathChildrenCache.getListenable().addListener(new PathChildrenCacheListener() {
          
          
                    @Override
                    public void childEvent(CuratorFramework client, PathChildrenCacheEvent event) throws Exception {
          
          
                        // 根据事件类型做相应的处理
                        // 此处的增加节点就是增加一个配置,删除节点就是删除一个配置,更新节点就是更新配置,比如把密码从123更新成345,就是更新节点
                        switch (event.getType()) {
          
          
                            case CHILD_ADDED:
                                System.out.println("增加了节点");
                                addEnv(event.getData(), client);
                                break;
                            case CHILD_REMOVED:
                                System.out.println("删除了节点");
                                delEnv(event.getData());
                                break;
                            case CHILD_UPDATED:
                                System.out.println("更新了节点");
                                addEnv(event.getData(), client);
                                break;
                            default:
                                break;
                        }
                        //对refresh作用域的实例进行刷新
                        refreshBean();
                    }
                });
            } catch (Exception e) {
          
          
                e.printStackTrace();
            }
        }
    
        // 不管是新增节点、删除节点,还是更新节点,最后都会调用refreshBean
        private void refreshBean() {
          
          
            // 拿到所有BeanDefinitionName
            String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
            for (String beanDefinitionName : beanDefinitionNames) {
          
          
                // 拿到相应beanDefinitionName的BeanDefinition
                BeanDefinition beanDefinition = beanDefinitionRegistry.getBeanDefinition(beanDefinitionName);
                // 对指定的scopeName进行操作
                if (scopeName.equals(beanDefinition.getScope())) {
          
          
                    //先删除,,,,思考,如果这时候删除了bean,有没有问题?
                    applicationContext.getBeanFactory().destroyScopedBean(beanDefinitionName);
                    //在实例化
                    applicationContext.getBean(beanDefinitionName); 
                    // 这里的getBean是对CustomRefreshController进行实例化
                    // Controller是在DispatcherServlet中初始化的
                    // 在DispatcherServlet中接收mvc请求的是doDispatch
                    // 先根据url找对应的mappedHandler,通过getHandler方法获取,又会去调用AbstractHandlerMapping的getHandler方法,又会调用getHandlerInternal方法,lookupPath就是url,lookupHandlerMethod方法就是根据url找到相应的HandlerMethod,而HandlerMethod中的bean(此时是一个字符串),如果handlerMethod不为null,则调用HandlerMethod的createWithResolvedBean方法,如果bean是字符串,则会调用getBean实例化
                    
                }
            }
        }
    
        private void delEnv(ChildData childData) {
          
          
            ChildData next = childData;
            String childpath = next.getPath();
            MutablePropertySources propertySources = applicationContext.getEnvironment().getPropertySources();
            for (PropertySource<?> propertySource : propertySources) {
          
          
                if (zkPropertyName.equals(propertySource.getName())) {
          
          
                    OriginTrackedMapPropertySource ps = (OriginTrackedMapPropertySource) propertySource;
                    ConcurrentHashMap chm = (ConcurrentHashMap) ps.getSource();
                    chm.remove(childpath.substring(path.length() + 1));
                }
            }
        }
    
        // 新增节点
        private void addEnv(ChildData childData, CuratorFramework client) {
          
          
            ChildData next = childData;
            String childpath = next.getPath();
            String data = null;
            try {
          
          
                data = new String(client.getData().forPath(childpath));
            } catch (Exception e) {
          
          
                e.printStackTrace();
            }
            MutablePropertySources propertySources = applicationContext.getEnvironment().getPropertySources();
            for (PropertySource<?> propertySource : propertySources) {
          
          
                if (zkPropertyName.equals(propertySource.getName())) {
          
          
                    OriginTrackedMapPropertySource ps = (OriginTrackedMapPropertySource) propertySource;
                    ConcurrentHashMap chm = (ConcurrentHashMap) ps.getSource();
                    // 把zookeeper中具体的配置值赋进去
                    chm.put(childpath.substring(path.length() + 1), data);
                }
            }
        }
    
        private void nodeCache(final CuratorFramework client, final String path) {
          
          
    
            try {
          
          
                //第三个参数是是否压缩
                //就是对path节点进行监控,是一个事件模板
                final NodeCache nodeCache = new NodeCache(client, path, false);
                nodeCache.start();
    
                //这个就是事件注册
                nodeCache.getListenable().addListener(new NodeCacheListener() {
          
          
                    @Override
                    public void nodeChanged() throws Exception {
          
          
                        byte[] data = nodeCache.getCurrentData().getData();
                        String path1 = nodeCache.getCurrentData().getPath();
    
                        Object put = map.put(path1.replace("/", ""), new String(data));
                        MutablePropertySources propertySources = applicationContext.getEnvironment().getPropertySources();
                        OriginTrackedMapPropertySource zookeeperSource = new OriginTrackedMapPropertySource("zookeeper source", map);
                        propertySources.addLast(zookeeperSource);
                    }
                });
            } catch (Exception e) {
          
          
                e.printStackTrace();
            }
        }
    
        @Override
        public void setApplicationContext(ApplicationContext context) throws BeansException {
          
          
            CuratorUtil.applicationContext = (ConfigurableApplicationContext) context;
        }
    }
    
    

测试

  • package com.xiangxue.jack.controller;
    
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Scope;
    import org.springframework.core.env.Environment;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @Scope("refresh")
    @Slf4j
    @RestController
    @RequestMapping("/customRefresh")
    public class CustomRefreshController {
          
          
    
        @Value("${xx.name:jack}")
        private String name;
    
        @Autowired
        private Environment environment;
    
        @RequestMapping("/queryName")
        public String queryName() {
          
          
            log.info(this.hashCode()+"");
            log.info("@Value name = " + name);
            log.info("environment name = " + environment.getProperty("xx.name"));
    
            log.info("environment password = " + environment.getProperty("xx.password"));
            return name + "--->" + environment.getProperty("xx.name");
        }
    }
    
    
    • @Value里面的值是jack,但是environment.getProperty(“xx.name”)得到的是jett,实际zookeeper节点中是jett

      • 为什么@Value没有拿到值?CustomRefreshController的实例化在CuratorUtil对象之前

        • 应该先把zookeeper的值加到environment中去,然后去实例化CustomRefreshController这个对象

        • 这就会导致以前的CustomRefreshController对象不能使用了,如果用单例,没有办法从一级缓存中去掉,我们需要想一种办法,实例的缓存不走一级缓存

        • AbstractBeanFactory$doGetBean方法,

          // Eagerly check singleton cache for manually registered singletons.
          		Object sharedInstance = getSingleton(beanName);
          
        • 如果没有加@Scope(“refresh”)这个注解,CustomRefreshController这个对象就是单例的,没有任何办法修改@Value

        • 这个时候用多例也可以,但是为了用单例,必须实现自定义的scope

自定义的scope

AbstractBeanFactory$doGetBean中关于scope部分
  • else {
          
          
        String scopeName = mbd.getScope();
        // 通过beanFactory.registerScope获取的scope
        final Scope scope = this.scopes.get(scopeName);
        if (scope == null) {
          
          
            throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
        }
        try {
          
          
            Object scopedInstance = scope.get(beanName, () -> {
          
          
                beforePrototypeCreation(beanName);
                try {
          
          
                    return createBean(beanName, mbd, args);
                }
                finally {
          
          
                    afterPrototypeCreation(beanName);
                }
            });
            bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
        }
        catch (IllegalStateException ex) {
          
          
            throw new BeanCreationException(beanName,
                                            "Scope '" + scopeName + "' is not active for the current thread; consider " +
                                            "defining a scoped proxy for this bean if you intend to refer to it from a singleton",
                                            ex);
        }
    }
    
RefreshScope
  • package com.xiangxue.jack.refresh.scope;
    
    import org.springframework.beans.factory.ObjectFactory;
    import org.springframework.beans.factory.config.Scope;
    
    import java.util.concurrent.ConcurrentHashMap;
    
    public class RefreshScope implements Scope {
          
          
    
        private ConcurrentHashMap map = new ConcurrentHashMap();
    
        @Override
        public Object get(String name, ObjectFactory<?> objectFactory) {
          
          
            if(map.containsKey(name)) {
          
          
                return map.get(name);
            }
    
            Object object = objectFactory.getObject();
            map.put(name,object);
            return object;
        }
    
        @Override
        public Object remove(String name) {
          
          
            return map.remove(name);
        }
    
        @Override
        public void registerDestructionCallback(String name, Runnable callback) {
          
          
    
        }
    
        @Override
        public Object resolveContextualObject(String key) {
          
          
            return null;
        }
    
        @Override
        public String getConversationId() {
          
          
            return null;
        }
    }
    
    
RefreshScopeRegistry
  • package com.xiangxue.jack.refresh.scope;
    
    import lombok.Data;
    import org.springframework.beans.BeansException;
    import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
    import org.springframework.beans.factory.support.BeanDefinitionRegistry;
    import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
    
    @Data
    //@Component
    public class RefreshScopeRegistry implements BeanDefinitionRegistryPostProcessor {
          
          
    
        private BeanDefinitionRegistry beanDefinitionRegistry;
    
    
        @Override
        public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
          
          
            this.beanDefinitionRegistry = registry;
        }
    
        // 设置自定义的scope
        @Override
        public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
          
          
            beanFactory.registerScope("refresh",new RefreshScope());
        }
    }
    
    
    • 这个接口的方法是在实例化之前调用的,这样就有自定义的scope了
    • 然后在CustomRefreshController上加上@Scope(“refresh”)的注解,然后这个类在注解解析时,它的BeanDefinition中的Scope会被变成refresh
    • 在getBean去实例化的时候,就会走入AbstractBeanFactory$doGetBean中关于scope的部分,就会调到scope.get方法去实例化(自定义的RefreshScope中的get方法)

微服务调用超时问题

实例

  • @RequestMapping("/timeOut")
    public String timeOutTest(@RequestParam int millis) {
          
          
    
        long t1 = System.currentTimeMillis();
        String cacheResult = studentService.queryStudentTimeout(millis);
        long t2 = System.currentTimeMillis();
    
        log.info("======调用provider耗时:" + (t2 - t1) + "ms");
        return cacheResult;
    }
    
  • #单位ms ,请求连接超时时间
    ribbon.ConnectTimeout=1000
    #单位ms ,请求处理的超时时间
    ribbon.ReadTimeout=3000
    #全局超时时间
    hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=5000
    
    • feign自带hystrix功能

测试

  • http://localhost:8083/student/timeOut?millis=1000
    • 1000既没超过3000,也没超过5000
  • 改成millis=2999,打印结果为调用provider耗时5009,
    • ribbon在调用时发现超过3秒了,触发了重试,两次累加后比5000大,又会触发hystrix的超时
    • ribbon两次调用将近6秒,但是打印只有5009,这是因为hystrix控制了ribbon总共调用的时间,会判断两次累加的时间超过了5000,会以5000为准,也就是以hystrix为准
    • ribbon没有配置重试的情况下,是有默认重试的,除非加配置重试次数为0(ribbon.MaxAutoRetries=0),最后的调用时是3567,这个时候的异常能够在hystrix中被捕获到
  • 修改hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=2000
    • hystrix的时间比ribbon的超时小
    • millis=1000时正常
    • millis=2000时,又是调用后异常都捕获不到,可以发现老大是hystrix,实际此时ribbon还没有超时,

猜你喜欢

转载自blog.csdn.net/Markland_l/article/details/115383911
今日推荐