配置中心是什么

  配置中心,通过key=value的形式存储环境变量。配置中心的属性做了修改,项目中可以通过配置中心的依赖(sdk)立即感知到。需要做的就是如何在属性发生变化时,改变带有@ConfigurationProperties的bean的相关属性。

配置中心原理

  在读配置中心源码的时候发现,里面维护了一个Environment,以及ZookeeperPropertySource。当配置中心属性发生变化的时候,清空ZookeeperPropertySource,并放入最新的属性值。

  

public class ZookeeperPropertySource extends EnumerablePropertySource<Properties>

  

  ZookeeperPropertySource重写了equals和hahscode方法,根据这两个方法可以判定配置中心是否修改了属性。

   

动态刷新bean属性原理

实现原理图

  

动态刷新bean父类

public abstract class BaseConfigCenterBean implements InitializingBean {

    private static Logger LOGGER = LoggerFactory.getLogger(BaseConfigCenterBean.class);

    //配置中心是否生效
protected boolean cfgCenterEffect = false; public boolean isCfgCenterEffect() {
this.checkCfgCenterEffect();
return cfgCenterEffect;
} private void checkCfgCenterEffect() {
boolean tmpCfgCenterEffect = !Objects.isNull(ConfigHelper.getEnvironment());
if (tmpCfgCenterEffect) {// NOSONAR
String value = (String) ConfigHelper.getZookeeperPropertySource().getProperty("cfg.center.effect");
if (StringUtils.isBlank(value)) {
tmpCfgCenterEffect = false;
} else {
tmpCfgCenterEffect = Boolean.valueOf(value);
}
} cfgCenterEffect = tmpCfgCenterEffect; if (cfgCenterEffect) {
String prefix = this.getConfigPrefix();
cfgCenterEffect = Arrays.stream(ConfigHelper.getZookeeperPropertySource().getPropertyNames())
.filter(keyName -> keyName.indexOf(prefix) == 0)
.count() > 0;
if (!cfgCenterEffect) {
LOGGER.info(String.format("配置中心没有发现模块=%s, prefix=%s的配置,将使用本地配置...", this.getModuleName(), prefix));
}
}
} /**
* 绑定自身目标
**/
protected void doBind() {
Class<? extends BaseConfigCenterBean> clazz = this.getClass();
if (AopUtils.isCglibProxy(this)) {
clazz = (Class<? extends BaseConfigCenterBean>) AopUtils.getTargetClass(this);
}
BaseConfigCenterBean target = binding(isCfgCenterEffect(), clazz, this.getDefaultResourcePath());
this.copyProperties(target);
} private void copyProperties(BaseConfigCenterBean target) {
ReflectionUtils.doWithFields(this.getClass(), field -> {
field.setAccessible(true);
field.set(this, field.get(target));
}, field -> AnnotatedElementUtils.isAnnotated(field, ConfigField.class));
} /**
* 绑定其他目标
*
* @param clazz 目标类
**/
protected <T> T doBind(Class<T> clazz) {
T target = binding(isCfgCenterEffect(), clazz, this.getDefaultResourcePath());
if (target instanceof InitializingBean) {
try {
((InitializingBean) target).afterPropertiesSet();
} catch (Exception e) {
LOGGER.error(String.format("属性初始化失败[afterPropertiesSet], class=%s", ClassUtils.getSimpleName(clazz), e));
}
}
return target;
} private <T> T binding(boolean cfgCenterEffect, Class<T> clazz, String defaultResourcePath) {
Optional<PropertySource> propertySource = Optional.empty(); if (cfgCenterEffect) {
propertySource = Optional.ofNullable(ConfigHelper.getZookeeperPropertySource());
} else {
Optional<ResourcePropertySource> resourcePropertySource = ResourceUtils.getResourcePropertySource(defaultResourcePath);
if (resourcePropertySource.isPresent()) {
propertySource = Optional.ofNullable(resourcePropertySource.get());
}
} if (propertySource.isPresent()) {
T target;
try {
target = RelaxedConfigurationBinder
.with(clazz)
.setPropertySources(propertySource.get())
.doBind();
} catch (GeneralException e) {
LOGGER.error(String.format("属性绑定失败, class=%s", ClassUtils.getSimpleName(clazz)), e);
return null;
}
return target;
}
return null;
} @Override
public void afterPropertiesSet() {
Class<?> target = this.getClass();
if (AopUtils.isAopProxy(this)) {
target = AopUtils.getTargetClass(this);
}
LOGGER.info(String.format("%s->%s模块引入配置中心%s...", this.getModuleName(), ClassUtils.getSimpleName(target), (isCfgCenterEffect() ? "生效" : "无效")));
} public String getModuleName() {
return StringUtils.EMPTY;
} @Subscribe
public void listenRefreshEvent(ConfigCenterUtils.ConfigRefreshEvent refreshEvent) {
if (!refreshEvent.getModuleName().equals(this.getModuleName())) {
this.refreshForEvent();
}
} //通过事件进行刷新
public abstract void refreshForEvent(); //获取本地配置默认路径
public abstract String getDefaultResourcePath(); //获取配置属性的公共前缀
public abstract String getConfigPrefix();
}

  1、isCfgCenterEffect方法主要判断项目是否接入了配置中心并且配置中心配有bean中相关的属性。

  2、binding方法主要根据isCfgCenterEffect方法的返回值去加载配置中心的properties还是本地的properties。

  3、getDefaultResourcePath是主要是获取本地资源的默认路径(在没有接入配置中心的情况下)。

  4、getConfigPrefix方法返回bean中配置属性的公共前缀(等同于@ConfigurationProperties中的prefix属性)。

  5、refreshForEvent方法主要是在某个bean感知到配置中心更新属性时异步通知其他bean进行属性的更新。

bean属性绑定工具类

  动态将propertysource绑定到带有@ConfigurationProperties注解的bean中。

  参考 org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor

public class RelaxedConfigurationBinder<T> {
private final PropertiesConfigurationFactory<T> factory; public RelaxedConfigurationBinder(T object) {
this(new PropertiesConfigurationFactory<>(object));
} public RelaxedConfigurationBinder(Class<?> type) {
this(new PropertiesConfigurationFactory<>(type));
} public static <T> RelaxedConfigurationBinder<T> with(T object) {
return new RelaxedConfigurationBinder<>(object);
} public static <T> RelaxedConfigurationBinder<T> with(Class<T> type) {
return new RelaxedConfigurationBinder<>(type);
} public RelaxedConfigurationBinder(PropertiesConfigurationFactory<T> factory) {
this.factory = factory;
ConfigurationProperties properties = getMergedAnnotation(factory.getObjectType(), ConfigurationProperties.class);
javax.validation.Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
factory.setValidator(new SpringValidatorAdapter(validator));
factory.setConversionService(new DefaultConversionService());
if (!Objects.isNull(properties)) {//NOSONAR
factory.setIgnoreNestedProperties(properties.ignoreNestedProperties());
factory.setIgnoreInvalidFields(properties.ignoreInvalidFields());
factory.setIgnoreUnknownFields(properties.ignoreUnknownFields());
factory.setTargetName(properties.prefix());
factory.setExceptionIfInvalid(properties.exceptionIfInvalid());
}
} public RelaxedConfigurationBinder<T> setTargetName(String targetName) {
factory.setTargetName(targetName);
return this;
} public RelaxedConfigurationBinder<T> setPropertySources(PropertySource<?>... propertySources) {
MutablePropertySources sources = new MutablePropertySources();
for (PropertySource<?> propertySource : propertySources) {
sources.addLast(propertySource);
}
factory.setPropertySources(sources);
return this;
} public RelaxedConfigurationBinder<T> setPropertySources(Environment environment) {
factory.setPropertySources(((ConfigurableEnvironment) environment).getPropertySources());
return this;
} public RelaxedConfigurationBinder<T> setPropertySources(PropertySources propertySources) {
factory.setPropertySources(propertySources);
return this;
} public RelaxedConfigurationBinder<T> setConversionService(ConversionService conversionService) {
factory.setConversionService(conversionService);
return this;
} public RelaxedConfigurationBinder<T> setValidator(Validator validator) {
factory.setValidator(validator);
return this;
} public RelaxedConfigurationBinder<T> setResolvePlaceholders(boolean resolvePlaceholders) {
factory.setResolvePlaceholders(resolvePlaceholders);
return this;
} public T doBind() throws GeneralException {
try {
return factory.getObject();
} catch (Exception ex) {
throw new GeneralException("配置绑定失败!", ex);
}
}
}

配置中心工具类

public class ConfigCenterUtils {
private static Logger LOGGER = LoggerFactory.getLogger(ConfigCenterUtils.class); private static AsyncEventBus eventBus = new AsyncEventBus(Executors.newFixedThreadPool(8));//NOSONAR private static Properties cfgProperties; private static Environment environment; static {
cfgProperties = new Properties();
cfgProperties.putAll(ConfigHelper.getZookeeperPropertySource().getProperties());
} public static void setEnvironment(Environment environment) {
ConfigCenterUtils.environment = environment;
} public static String getValue(String name) {
try {
return PropertiesUtil.getValue(name);
} catch (Exception e) {
LOGGER.info("配置中心无效, property name=" + name, e);
}
if (Objects.isNull(environment)) {
LOGGER.info("environment无效,property name=" + name);
return StringUtils.EMPTY;
}
if (!environment.containsProperty(name)) {
LOGGER.info("environment无配置 property name=" + name);
return StringUtils.EMPTY;
}
return environment.getProperty(name);
} public synchronized static boolean propertySourceShouldRefresh(String moduleName, ZookeeperPropertySource newPropertySource) {
if (!cfgProperties.equals(newPropertySource.getProperties())) {
cfgProperties.clear();
cfgProperties.putAll(newPropertySource.getProperties());
eventBus.post(new ConfigRefreshEvent(moduleName));
return true;
}
return false;
} public static <T> T createToRefreshPropertiesBean(Class<T> clazz) {
Enhancer enhancer = new Enhancer();
// 设置代理对象父类
enhancer.setSuperclass(clazz);
// 标识Spring-generated proxies
enhancer.setInterfaces(new Class[]{SpringProxy.class});
// 设置增强
enhancer.setCallback((MethodInterceptor) (target, method, args, methodProxy) -> {
ToRefresh toRefresh = AnnotationUtils.findAnnotation(method, ToRefresh.class);
if (Objects.isNull(toRefresh) || StringUtils.isBlank(toRefresh.method())) {
return methodProxy.invokeSuper(target, args);
}
Method refreshMethod = ReflectionUtils.findMethod(target.getClass(), toRefresh.method());
if (Objects.isNull(refreshMethod)) {
return methodProxy.invokeSuper(target, args);
}
refreshMethod = BridgeMethodResolver.findBridgedMethod(refreshMethod);
refreshMethod.setAccessible(true);
refreshMethod.invoke(target, null);
return methodProxy.invokeSuper(target, args);
}); T target = (T) enhancer.create();// 创建代理对象 MethodIntrospector.selectMethods(clazz, (ReflectionUtils.MethodFilter) method -> AnnotatedElementUtils.isAnnotated(method, ToInitial.class))
.stream().findFirst().ifPresent(method -> {
method.setAccessible(true);
try {
method.invoke(target, null);
} catch (Exception e) {
LOGGER.error(String.format("初始化异常,class=%s ...", ClassUtils.getSimpleName(clazz)), e);
}
}); return target;
} public static void registerListener(BaseConfigCenterBean refreshableBean) {
eventBus.register(refreshableBean);
} public static class ConfigRefreshEvent {
private String moduleName; public ConfigRefreshEvent(String moduleName) {
this.moduleName = moduleName;
} public String getModuleName() {
return moduleName;
} public void setModuleName(String moduleName) {
this.moduleName = moduleName;
}
}
}

  这个工具主要作用:

  1、判断配置中心的属性是否发生了变化

  2、为BaseConfigCenterBean子类创建代理类,使属性在getter方法时检测属性是否应该刷新。

  3、提供将BaseConfigCenterBean类型的对象的注册为guava eventbus的监听对象,使之具有根据刷新事件自动刷新自身属性。

bean后置处理器

public class ConfigCenterBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (AnnotatedElementUtils.isAnnotated(bean.getClass(), ConfigCenterBean.class)) {
BaseConfigCenterBean refreshableBean = (BaseConfigCenterBean) ConfigCenterUtils.createToRefreshPropertiesBean(bean.getClass());
ConfigCenterUtils.registerListener(refreshableBean);
return refreshableBean;
}
return bean;
} @Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}

  该后置处理器的作用是对所有BaseConfigCenterBean类型的bean进行处理,生成代理bean,并注册为guava eventbus相应的listener。

pojo属性绑定配置中心优雅方案1

@ConfigCenterBean
@ConfigurationProperties(prefix = "wx.temporary.qrcode")
@Component
public class QrcodeConstants extends BaseConfigCenterBean { private static Logger LOGGER = LoggerFactory.getLogger(QrcodeConstants.class); //渠道
@ConfigField //标识该属性来自配置中心
private List<Scene> channels; //业务
@ConfigField
private List<Scene> bizs; //业务和渠道映射关系
@ConfigField
private Map<String, String> biz2Channel; private Map<String, Scene> channelMap; private Map<String, Scene> bizMap; public List<Scene> getChannels() {
return channels;
} public void setChannels(List<Scene> channels) {
this.channels = channels;
} public List<Scene> getBizs() {
return bizs;
} public void setBizs(List<Scene> bizs) {
this.bizs = bizs;
} @ToRefresh(method = "toRefresh")
public Map<String, Scene> getChannelMap() {
return channelMap;
} @ToRefresh(method = "toRefresh")
public Map<String, Scene> getBizMap() {
return bizMap;
} @ToRefresh(method = "toRefresh")
public Map<String, String> getBiz2Channel() {
return biz2Channel;
} public void setBiz2Channel(Map<String, String> biz2Channel) {
this.biz2Channel = biz2Channel;
} @ToInitial
private void refreshQrcodeProperties() {
try {
super.doBind(); //属性处理
if (CollectionUtils.isEmpty(channels)) {
this.channelMap = Maps.newHashMap();
} else {
this.channelMap = channels.stream()
.collect(Collectors.toMap(channel -> channel.getType(), Function.identity()));
} if (CollectionUtils.isEmpty(bizs)) {
this.bizMap = Maps.newHashMap();
} else {
this.bizMap = bizs.stream()
.collect(Collectors.toMap(biz -> biz.getType(), Function.identity()));
} LOGGER.info(String.format("%s 刷新成功..., 当前配置=%s...", this.getModuleName(), this));
} catch (Exception e) {
LOGGER.error("QrcodeConstants 对象属性绑定失败...", e);
}
} private void toRefresh() {
try {
if (isCfgCenterEffect()) {
ZookeeperPropertySource propertySource = ConfigHelper.getZookeeperPropertySource();
if (ConfigCenterUtils.propertySourceShouldRefresh(this.getModuleName(), propertySource)) {
this.refreshQrcodeProperties();
}
}
} catch (Exception e) {
LOGGER.error("QrcodeConstants 对象属性刷新失败", e);
}
} //刷新事件调用
@Override
public void refreshForEvent() {
this.refreshQrcodeProperties();
} //本地资源文件
@Override
public String getDefaultResourcePath() {
return "config/qrcode.properties";
} //属性配置 公共前缀(和@ConfigurationProperties prefix 属性一致)
@Override
public String getConfigPrefix() {
return "wx.temporary.qrcode";
} //模块名称
@Override
public String getModuleName() {
return "微信临时二维码配置";
} @Override
public String toString() {
return ReflectionToStringBuilder.toString(this
, ToStringStyle.JSON_STYLE
, false
, false
, QrcodeConstants.class);
} public static class Scene {
private String type;
private String desc; public String getType() {
return type;
} public void setType(String type) {
this.type = type;
} public String getDesc() {
return desc;
} public void setDesc(String desc) {
this.desc = desc;
} @Override
public String toString() {
return ReflectionToStringBuilder.toString(this
, ToStringStyle.JSON_STYLE
, false
, false
, Scene.class);
}
}
}

pojo属性绑定配置中心优雅方案2

@ConfigCenterBean
@Component
public class QrcodeConstants extends BaseConfigCenterBean { private static Logger LOGGER = LoggerFactory.getLogger(QrcodeConstants.class); //业务和渠道映射关系
private Map<String, String> biz2Channel; //渠道
private Map<String, Scene> channelMap; //业务
private Map<String, Scene> bizMap; private QrcodeProperties qrcodeProperties; @ToRefresh(method = "toRefresh")
public Map<String, Scene> getChannelMap() {
return channelMap;
} @ToRefresh(method = "toRefresh")
public Map<String, Scene> getBizMap() {
return bizMap;
} @ToRefresh(method = "toRefresh")
public Map<String, String> getBiz2Channel() {
return biz2Channel;
} public void setBiz2Channel(Map<String, String> biz2Channel) {
this.biz2Channel = biz2Channel;
} public QrcodeProperties getRawQrcodeProperties() {
return qrcodeProperties;
} @ToInitial
private void refreshQrcodeProperties() {
try {
QrcodeProperties qrcodeProperties = super.doBind(QrcodeProperties.class);
if (Objects.isNull(qrcodeProperties)) {
LOGGER.error(String.format("没有加载到%s配置,请检查配置...", this.getModuleName()));
return;
} this.qrcodeProperties = qrcodeProperties; //属性处理
if (CollectionUtils.isEmpty(qrcodeProperties.channels)) {
this.channelMap = Maps.newHashMap();
} else {
this.channelMap = qrcodeProperties.channels.stream()
.collect(Collectors.toMap(channel -> channel.getType(), Function.identity()));
} if (CollectionUtils.isEmpty(qrcodeProperties.bizs)) {
this.bizMap = Maps.newHashMap();
} else {
this.bizMap = qrcodeProperties.bizs.stream()
.collect(Collectors.toMap(biz -> biz.getType(), Function.identity()));
} if (CollectionUtils.isEmpty(qrcodeProperties.getBiz2Channel())) {
this.biz2Channel = Maps.newHashMap();
} else {
this.biz2Channel = qrcodeProperties.getBiz2Channel();
} LOGGER.info(String.format("%s 刷新成功..., 当前配置=%s...", this.getModuleName(), this));
} catch (Exception e) {
LOGGER.error("QrcodeConstants 对象属性绑定失败...", e);
}
} private void toRefresh() {
try {
if (isCfgCenterEffect()) {
ZookeeperPropertySource propertySource = ConfigHelper.getZookeeperPropertySource();
if (ConfigCenterUtils.propertySourceShouldRefresh(this.getModuleName(), propertySource)) {
this.refreshQrcodeProperties();
}
}
} catch (Exception e) {
LOGGER.error("QrcodeConstants 对象属性刷新失败", e);
}
} @Override
public void refreshForEvent() {
this.refreshQrcodeProperties();
} @Override
public String getDefaultResourcePath() {
return "config/qrcode.properties";
} @Override
public String getConfigPrefix() {
return "wx.temporary.qrcode";
} @Override
public String getModuleName() {
return "微信临时二维码配置";
} @Override
public String toString() {
return new ToStringBuilder(this)
.append("biz2Channel", biz2Channel)
.append("channelMap", channelMap)
.append("bizMap", bizMap)
.toString();
} @ConfigurationProperties(prefix = "wx.temporary.qrcode")
public static class QrcodeProperties {
//渠道
private List<Scene> channels; //业务
private List<Scene> bizs; //业务和渠道映射关系
private Map<String, String> biz2Channel; public List<Scene> getChannels() {
return channels;
} public void setChannels(List<Scene> channels) {
this.channels = channels;
} public List<Scene> getBizs() {
return bizs;
} public void setBizs(List<Scene> bizs) {
this.bizs = bizs;
} public Map<String, String> getBiz2Channel() {
return biz2Channel;
} public void setBiz2Channel(Map<String, String> biz2Channel) {
this.biz2Channel = biz2Channel;
}
} public static class Scene {
private String type;
private String desc; public String getType() {
return type;
} public void setType(String type) {
this.type = type;
} public String getDesc() {
return desc;
} public void setDesc(String desc) {
this.desc = desc;
} @Override
public String toString() {
return ReflectionToStringBuilder.toString(this
, ToStringStyle.JSON_STYLE
, false
, false
, Scene.class);
}
}
}

  方案1和方案2略有不同,针对一些属性,我们需要做一些逻辑处理。方案1中将源属性和逻辑之后的属性都放在了同一类中,方案二则是将源属性单独放到一个静态类中,最终处理过后的属性放在了目标类中。另外二者的doBind方法也是有区别的,仔细看一下BaseConfigCenterBean这个类就可以了。

     就先分享这么多了,更多分享请关注我们的技术公众吧!!!

  参考文章:算法和技术SHARING

依赖配置中心实现注有@ConfigurationProperties的bean相关属性刷新的更多相关文章

  1. Spring Boot 2.0 整合携程Apollo配置中心

    原文:https://www.jianshu.com/p/23d695af7e80 Apollo(阿波罗)是携程框架部门研发的分布式配置中心,能够集中化管理应用不同环境.不同集群的配置,配置修改后能够 ...

  2. SpringCloud学习系列之五-----配置中心(Config)和消息总线(Bus)完美使用版

    前言 在上篇中介绍了SpringCloud Config的使用,本篇则介绍基于SpringCloud(基于SpringBoot2.x,.SpringCloud Finchley版)中的分布式配置中心( ...

  3. SpringCloud学习系列之四-----配置中心(Config)使用详解

    前言 本篇主要介绍的是SpringCloud中的分布式配置中心(SpringCloud Config)的相关使用教程. SpringCloud Config Config 介绍 Spring Clou ...

  4. 基于zookeeper实现分布式配置中心(二)

    上一篇(基于zookeeper实现分布式配置中心(一))讲述了zookeeper相关概念和工作原理.接下来根据zookeeper的特性,简单实现一个分布式配置中心. 配置中心的优势 1.各环境配置集中 ...

  5. Spring Cloud第十篇 | 分布式配置中心Config

    ​ 本文是Spring Cloud专栏的第十篇文章,了解前九篇文章内容有助于更好的理解本文: Spring Cloud第一篇 | Spring Cloud前言及其常用组件介绍概览 Spring Clo ...

  6. spring cloud 入门系列七:基于Git存储的分布式配置中心

    我们前面接触到的spring cloud组件都是基于Netflix的组件进行实现的,这次我们来看下spring cloud 团队自己创建的一个全新项目:Spring Cloud Config.它用来为 ...

  7. Spring Cloud(Dalston.SR5)--Config 集群配置中心

    Spring Cloud Config 是一个全新的项目,用来为分布式系统中的基础设施和微服务应用提供集中化的外部配置支持,他分为服务端和客户端两个部分.服务端也称为分布式配置中心,是一个独立的微服务 ...

  8. spring boot 2.0.3+spring cloud (Finchley)6、配置中心Spring Cloud Config

    https://www.cnblogs.com/cralor/p/9239976.html Spring Cloud Config 是用来为分布式系统中的基础设施和微服务应用提供集中化的外部配置支持, ...

  9. spring cloud 入门系列七:基于Git存储的分布式配置中心--Spring Cloud Config

    我们前面接触到的spring cloud组件都是基于Netflix的组件进行实现的,这次我们来看下spring cloud 团队自己创建的一个全新项目:Spring Cloud Config.它用来为 ...

随机推荐

  1. iOS项目国际化详解

    现在的开发中难免会遇到项目国际化处理,下面把我理解到的国际化相关的知识点进行总结归纳 1 首先是对项目名称,系统性的文字进行名字化,比如程序名字 1,先给项目添加语言 2 添加InfoPlist.st ...

  2. window 上安装 Scala

    第一步:Java 设置 检测方法前文已说明,这里不再描述. 如果还为安装,可以参考我们的Java 开发环境配置. 接下来,我们可以从 Scala 官网地址 http://www.scala-lang. ...

  3. mysql 安装问题三:FATAL ERROR: please install the following Perl modules before executing ./scripts/mysql_install_db: Data::Dumper

    解决方法是安装autoconf库,执行命令:yum -y install autoconf 安装完成之后继续执行安装mysql的命令:./scripts/mysql_install_db --user ...

  4. 【linux】centos6.9通过virtualenv安装python3.5

    参考:http://www.linuxidc.com/Linux/2015-08/121352.htm wget https://www.python.org/ftp/python/3.5.4/Pyt ...

  5. 【kafka】celery与kafka的联用问题

    背景:一个小应用,用celery下发任务,任务内容为kafka生产一些数据. 问题:使用confluent_kafka模块时,单独启用kafka可以正常生产消息,但是套上celery后,kafka就无 ...

  6. burpsuite使用教程和实战详解(一)

    1.最近做渗透测试,其实使用一种方式很难全面的对一个web或者app等安全服务器做安全评估,所以要尽可能的对网络安全的渗透测试有一个较全面的认知.不光要熟悉前端和 后天的编程,还有掌握基于这两种编程的 ...

  7. SQLmap注入启发式检测算法

    1.经过setTargetEnv()就进入了checkWaf()的环节 def checkWaf():     """     Reference: http://sec ...

  8. Nginx详解十七:Nginx深度学习篇之动静分离

    动静分离:通过中间件将动态请求和静态请求分离 作用:分离资源,减少不必要的请求消耗,减少请求延时 动静分离还有个好处就是,当动态请求的后端服务出问题了,只会影响动态的部分,静态资源不影响,照样加载 如 ...

  9. log4j2的配置文件log4j2.xml笔记

    一.背景 最近由于项目的需要,我们把log4j 1.x的版本全部迁移成log4j 2.x 的版本,那随之而来的slf4j整合log4j的配置(使用Slf4j集成Log4j2构建项目日志系统的完美解决方 ...

  10. CA认证的原理和流程及https原理

    1.什么是CA证书. 看过一些博客,写的比较形象具体. ◇ 普通的介绍信 想必大伙儿都听说过介绍信的例子吧?假设 A 公司的张三先生要到 B 公司去拜访,但是 B 公司的所有人都不认识他,他咋办捏?常 ...