配置问题
- 上一节中的对于配置的加密规则的配置,解析失败,报错是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还没有超时,