Redis断连从框架层面该如何抢救?

前言

上周发生了一件鸡飞狗跳的线上事故,六节点的Redis-Cluster集群所在的大部分机器因为网络带宽问题断连了,排查之后发现是那几台物理机带宽被占满了,导致整个集群因为槽位不满16384而请求失败。并且因为没有考虑缓存失效问题,而让有使用缓存的接口全部报错,影响了用户体验。在本地测试时,还发现因为框架中的Redisson组件初始化时会强制连接和校验槽位,而导致整个服务因Redis-Cluster断连而无法启动。
造成了一段时间的中断,当时是别的同事在负责,因为涉及到我的六脉神剑,所以我决定还是我来收个尾。经过上周五和本周一的方案思考和设计,琢磨出了一套不成熟的方案,暂行,期待读者有更好的想法能分享一下。同时我也会逐步分析我的思考过程,和最终方案设计时的考量。

现状导向问题

一开始上手比较匆忙,毕竟这个问题我一开始是抱着做甩手掌柜的心态来做旁观者。(⊙﹏⊙),有个三四天吧,可能最后还是没有个好的解法,波及到我负责的项目了,想了想,还是从框架层控制一下,做一下整体规划。
简单描述下现状,一是Redis集群不可用了,用到Redis的接口就挂了,只有少部分接口做了手动降级处理,即捕获异常走数据库。二是本地想连这个集群,发现服务根本无法启动。根据现状整理问题,得出需要解决的是集群宕机后,如何保证服务正常运行以及正常启动。

思维风暴

代码规范

框架中作为Redis的入口和客户端,提供了三种方式,分别是SpringCache、SpringDataRedis和Redisson。同事在使用的时候也是随便用,因此我针对这三种都进行了一定的配置,详情见六脉神剑-我在公司造了六个轮子(21745阅读245赞536收藏)。使用情况比较复杂的时候,就要考虑周全,避免牵一发而动全身。
从问题一来看就是一个很明显的缓存失效场景,最直接的解决方案就是捕获异常后转数据库连接,但这是开发注意事项,是最直白的规范,详见三个月前我的一篇文章,同事血压操作集锦第一弹,截图如下。
image.png
但是今天要讨论的是要从框架层面解决这个问题,而且是要尽可能小的改动。因为老项目很多不遵守开发规范的代码,所以如果要统一按照上面的案例进行修改,那将是一个非常耗时的工程。同时还要解决第二个问题,Redis集群宕机后项目无法启动,因为框架强依赖于Redis,Spring会因为无法正常加载Redis相关的Bean而导致启动失败。

统一入口,减少变量

在进行了小半天的头脑风暴和资料查询后,发现大家没有这样的困扰,并且所有的方案都指向了Redis的高可用和代码规范。诚然,这是最优解,但是基于团队目前的现状,我也想做出我的思考,给出一套应急的方案。
说干就干,我的第一想法是统一入口,因为公司框架的SpringBoot版本是2.1.X过低,所以我个人极力不推荐使用SpringDataRedis,也就是redisTemplate。因此我干掉了这个依赖,只留下了原生的redisson作为唯一的Redis客户端。因为对SpringCache做了Redis的扩展,所以也更换成了Redisson提供支持(最开始用的lettuce,因为有问题就换成了Jedis,更换的原因见六脉神剑那篇文章)。

/**
 * 定义Jedis客户端,集群和单点同时存在时优先集群配置
 */
@Bean
public JedisConnectionFactory redisConnectionFactory() {
    String redisHost = config.getProperty("spring.redis.host");
    String redisPort = config.getProperty("spring.redis.port");
    String cluster = config.getProperty("spring.redis.cluster.nodes");
    String redisPassword = config.getProperty("spring.redis.password");
    JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
    // 默认阻塞等待时间为无限长,源码DEFAULT_MAX_WAIT_MILLIS = -1L
    // 最大连接数, 根据业务需要设置,不能超过实例规格规定的最大连接数。
    jedisPoolConfig.setMaxTotal(100);
    // 最大空闲连接数, 根据业务需要设置,不能超过实例规格规定的最大连接数。
    jedisPoolConfig.setMaxIdle(60);
    // 关闭 testOn[Borrow|Return],防止产生额外的PING。
    jedisPoolConfig.setTestOnBorrow(false);
    jedisPoolConfig.setTestOnReturn(false);
    JedisClientConfiguration jedisClientConfiguration = JedisClientConfiguration.builder().usePooling()
            .poolConfig(jedisPoolConfig).build();
    if (StringUtils.hasText(cluster)) {
        // 集群模式
        String[] split = cluster.split(",");
        RedisClusterConfiguration clusterServers = new RedisClusterConfiguration(Arrays.asList(split));
        if (StringUtils.hasText(redisPassword)) {
            clusterServers.setPassword(redisPassword);
        }
        return new JedisConnectionFactory(clusterServers, jedisClientConfiguration);
    } else if (StringUtils.hasText(redisHost) && StringUtils.hasText(redisPort)) {
        // 单机模式
        RedisStandaloneConfiguration singleServer = new RedisStandaloneConfiguration(redisHost, Integer.parseInt(redisPort));
        if (StringUtils.hasText(redisPassword)) {
            singleServer.setPassword(redisPassword);
        }
        return new JedisConnectionFactory(singleServer, jedisClientConfiguration);
    } else {
        throw new ToolException("spring.redis.host及port或spring.redis.cluster" +
                ".nodes必填,否则不可使用RedisTool以及Redisson");
    }
}

/**
 * 配置Spring-Cache内部使用Redis,配置序列化和过期时间
 */
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
    RedisSerializer<String> redisSerializer = new StringRedisSerializer();
    Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer
            = new Jackson2JsonRedisSerializer<>(Object.class);
    ObjectMapper om = new ObjectMapper();
    // 防止在序列化的过程中丢失对象的属性
    om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
    // 开启实体类和json的类型转换,该处兼容老版本依赖,不得修改
    om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
    jackson2JsonRedisSerializer.setObjectMapper(om);
    // 配置序列化(解决乱码的问题)
    RedisCacheConfiguration config = RedisCacheConfiguration.
            defaultCacheConfig()
            .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
            .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
            .disableCachingNullValues()// 不缓存空值
            .entryTtl(Duration.ofMinutes(30));//30分钟不过期
    return RedisCacheManager
            .builder(connectionFactory)
            .cacheDefaults(config)
            .build();
}

调整后代码,就是按照官网改了改,感兴趣可以看看Redisson-WIKI

/**
 * 对 Redisson 的使用都是通过 RedissonClient 对象
 */
@ConditionalOnProperty(name = "ruijie.tool.redis-enable", havingValue = "true")
@Bean(destroyMethod = "shutdown") // 服务停止后调用 shutdown 方法。
public RedissonClient redisson() {
    String redisHost = config.getProperty("spring.redis.host");
    String redisPort = config.getProperty("spring.redis.port");
    String cluster = config.getProperty("spring.redis.cluster.nodes");
    String redisPassword = config.getProperty("spring.redis.password");
    Config config = new Config();
    //使用String序列化时会出现RBucket<Integer>转换异常
    //config.setCodec(new StringCodec());
    if (ObjectUtils.isEmpty(redisHost) && ObjectUtils.isEmpty(cluster)) {
        throw new ToolException("spring.redis.host及port或spring.redis.cluster" +
                ".nodes必填,否则不可使用Redis");
    } else {
        if (StringUtils.hasText(cluster)) {
            // 集群模式
            String[] split = cluster.split(",");
            List<String> servers = new ArrayList<>();
            for (String s : split) {
                servers.add("redis://" + s);
            }
            ClusterServersConfig clusterServers = config.useClusterServers();
            clusterServers.addNodeAddress(servers.toArray(new String[split.length]));
            if (StringUtils.hasText(redisPassword)) {
                clusterServers.setPassword(redisPassword);
            }
            //修改命令超时时间为40s,默认3s
            clusterServers.setTimeout(40000);
            //修改连接超时时间为50s,默认10s
            clusterServers.setConnectTimeout(50000);
            clusterServers.setCheckSlotsCoverage(false);
        } else {
            // 单机模式
            SingleServerConfig singleServer = config.useSingleServer();
            singleServer.setAddress("redis://" + redisHost + ":" + redisPort);
            if (StringUtils.hasText(redisPassword)) {
                singleServer.setPassword(redisPassword);
            }
            singleServer.setTimeout(40000);
            singleServer.setConnectTimeout(50000);
        }
    }
    RedissonClient redissonClient = null;
    try {
        redissonClient = Redisson.create(config);
    } catch (Exception e) {
        Log.error("初始化Redis失败", e);
    }
    return redissonClient;
}

@ConditionalOnProperty(name = "ruijie.tool.redis-enable", havingValue = "true")
@Bean
public CacheManager cacheManager(RedissonClient redissonClient) {
    Map<String, CacheConfig> config = new HashMap<>();
    //开辟命名空间,过期设置为1小时,连接最大存活时间为30分钟
    config.put("springCache", new CacheConfig(60 * 60 * 1000, 30 * 60 * 1000));
    return new RedissonSpringCacheManager(redissonClient, config);
}

框架层改造完了之后,现在只有一个入口即Redisson,只要Redisson稳住,就不会出现问题。那么该如何稳住呢,为了解决第二个问题,也就是Redis集群挂了还能正常启动,我的第一个想法是通过Spring的Bean生命周期入手,尝试开始。按照Redisson官方推荐的加载方式使用Redisson.create(config)创建一个RedissonClient接口的实现类,那么我们在代码中引入这个接口的话,Spring就会自动帮我们填充这个自定义的实现类。

组件可插拔

问题来了,如果这个Redis挂了的话,能不能启动呢?当然是肯定不能启动的,因为我现在就遇到这个问题了,但是为了模拟这个情况,我随便输入几个地址。
image.png
这里很明显的提示了如果要创建cacheManager(SpringCache核心类),必须要提供一个RedissonClient的实现类。顺带一提,Redisson开启懒加载在这是无意义的,因为CacheManager cacheManager(RedissonClient redissonClient)这里必须要注入一个才能初始化,并不是想象中项目启动,业务代码中使用缓存调接口才初始化。
最开始我在做组件的时候,如果我想排掉三方包中的依赖,我的首选是实现BeanDefinitionRegistryPostProcessor接口,在Spring的Bean生命周期初始化后根据名字主动移除该Bean。这个不能解决我的问题,因为我这是在Bean初始化的时候就失败了。
常见的框架排Bean的方法,还有一招暴力的,直接在依赖配置文件中干掉这个框架依赖或者在SpringBoot启动类上排掉这个依赖的自动配置类。我可以选择将Redis整个依赖单独拆分出来成为一个新的组件,这样也能算是一个可插拔的设计,并且能让臃肿的框架包轻量一些。但是我不喜欢,所以我还是采用了配置类的方式,刚好Spring有足够的定制化配置,我选择了@ConditionalOnProperty(name = “ruijie.tool.redis-enable”, havingValue = “true”)这个注解。意思是配置文件中,只有配置ruijie.tool.redis-enable=true时才初始化被注解的Bean。
用法很简单,在框架中所有用到Redis的Bean上统统加上这个注解即可。但是同样存在一个限制,那就是项目代码中不能允许引入Redis相关类,引入的话同样会导致启动失败,因为框架中所有Redis相关的Bean都没有初始化。

尝试

因为还是没能从根源上解决无法启动的问题,于是我有了极端的想法,能不能注册一个假的Redis?首先我尝试在@Bean注解的Redisson注册方法中,提供一个null,妄图通过一个空的Bean来保证至少启动时不报错。经测试无效,当传入null时,Spring根本不加载该Bean,和上面初始化失败时报一样的错误,即找不到一个实现类。
因为是用Redisson初始化,所以我想着能不能通过构造方法做一个不进行初始化连接的Bean注入到Spring,跟一下Redisson的初始化源码
image.png
发现走的还是构造方法,点到构造方法后发现是protected修饰,还没有开放的重载
image.png
org.redisson.config.ConfigSupport#createConnectionManager这个方法就是创建连接的方法,连不上就报错…这条路算是堵死了
在多次尝试后,放弃了,毕竟从本质上来讲,Redis是个NoSql,就是个数据库。极端一点,类比MySQL,我本来就拿你当数据库使用,你项目数据库都挂了,那启动起来还有什么业务意义呢?与其思考这个,不如保证高可用,当然本期讨论的不是这个。

另一个问题

接下来要解决另一个问题,如何保证项目运行中Redis断连还能正常使用,同时保证老代码的改动最小。
之前也说过,解决缓存挂掉的最好方法就是捕获异常后转数据库连接,但是从框架层来讲,我哪里知道你连的是什么数据库,更不知道你要的是什么数据了。因此得转换思路,记得之前看过一个技术理论,如果遇到不好解决的问题,那么最便捷的方法就是加一个中间层。所以我选择了二级缓存,第二级缓存是本地缓存,用的caffeine,本质上是个ConcurrentHashMap,这肯定挂不了了。新建一个统一的缓存类,以后项目中引入该缓存类即可。

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;

import java.time.Duration;

/**
 * spring redis 工具类
 *
 * @author ruoyi
 **/
@Component
@Slf4j
@ConditionalOnProperty(name = "ruijie.tool.redis-enable",havingValue = "true")
public class CachePlusTool {
    @Autowired
    public RedissonClient redissonClient;
    /**
     * 本地缓存设置时不需要太长TTL以及太大容量,会占用过多内存造成OOM,得不偿失
     * 设置初始容量为1000,最大容量为10000,普通业务足够,对于过多元素的可采用json压缩为一个元素
     * 设置默认超时时间为4小时的考虑是经验值,过长的缓存没有意义,用户几乎在白天操作,且夜晚很多项目需要跑定时,会占用大量内存
     */
    private final Cache<String, Object> caffeine = Caffeine.newBuilder()
            .initialCapacity(1000).expireAfterAccess(Duration.ofHours(4))
            .maximumSize(10000).build();

    /**
     * 缓存基本的对象,Integer、String、实体类等,默认超时时间4小时
     *
     * @param key   缓存的键值
     * @param value 缓存的值
     */
    public <T> void put(String key, T value) {
        try {
            redissonClient.getBucket(key).set(value, Duration.ofHours(4));
        } catch (Exception e) {
            log.error("Redis的Put连接失败,降级处理使用本地缓存Caffeine", e);
            caffeine.put(key, value);
        }
    }

    /**
     * 重载版本,仅支持对redis的ttl设置,对caffeine无效
     *
     * @param key     缓存的键值
     * @param value   缓存的值
     * @param timeout 过期时间
     */
    public <T> void put(String key, T value, Duration timeout) {
        try {
            redissonClient.getBucket(key).set(value, timeout);
        } catch (Exception e) {
            log.error("Redis的Put连接失败,降级处理使用本地缓存Caffeine", e);
            caffeine.put(key, value);
        }
    }

    /**
     * 获得缓存的基本对象。
     * 可能返回null值--因为对于缓存来说类似于数据库,分不清是程序错误导致的null还是数据为空的null,所以需要开发人员自行判断
     *
     * @param key 缓存键值
     * @return 缓存键值对应的数据
     */
    public <T> T get(String key) {
        Object value = null;
        try {
            value = redissonClient.getBucket(key).get();
        } catch (Exception e) {
            log.error("Redis的Get连接失败,降级处理使用本地缓存Caffeine", e);
            //没有返回null
            value = caffeine.getIfPresent(key);
        }
        return (T) value;
    }
}

image.png
上图为测试结果,在Redis尝试无望后转而使用本地缓存,并会打印日志进行提示。并且该方法对于老项目比较友好,基本上只需替换引入的Redis类为新的CachePlusTool即可,使用方式如下。
image.png
后续可以继续扩容该工具了,如果有特殊的需求,那么说明强依赖于Redis,那就得承担强依赖带来的问题,不在本方案考虑范围内。

Redis单机快速部署

测试主要是测了一个Redis在项目运行中,突然断掉的情况,这里按照官方流程快速安装一个单机Redis,命令不用改
:::info
官网下载安装包
wget https://download.redis.io/redis-stable.tar.gz
解压缩
tar -xzvf redis-stable.tar.gz
到解压后根目录
cd redis-stable
编译
make
安装
make install
:::
修改redis.conf
:::info
bind XXXX(IP地址)
protected-mode no(关掉保护模式)
requirepass 123(编一个123的简单密码)
:::
这个部署倒是出人意料的简单,照着官网我就随便弄弄,没想到就好了。

方案总结

代码规范

解决的问题是项目运行中Redis挂了,项目依旧能提供本地缓存或者转数据库连接的服务,接口不会挂掉
对于常见的缓存存取,使用框架提供的CachePlusTool即可,代码会发到DevOps上,有补充的自行填充

@Autowired
public CachePlusTool cachePlusTool;

cachePlusTool.put(key, value);
cachePlusTool.put(key, value, Duration.ofHours(4));
cachePlusTool.get(key);

如果有特殊需求,比如分布式锁,或者项目是多节点部署且对数据正确性要求高的接口,一定要捕获异常,有必要的转数据库连接,代码片如下

@Autowired
private RedissonClient redissonClient;

public String getProductLine(String itemNo) {
    String cacheKey = "order:getProductLine:" + itemNo;
    String cacheValue = null;
    RBucket<String> bucket = redissonClient.getBucket(cacheKey);
    try {
        cacheValue = bucket.get();
    } catch (Exception e) {
        //捕获异常记得打印日志
        log.error("redis连接异常", e);
    }
    if (cacheValue != null) {
        return cacheValue;
    } else {
        //有必要的话转数据库查询
        String res = ptmErpMapper.getProductLine(itemNo);
        bucket.set(res, 16, TimeUnit.HOURS);
        return res;
    }
 }

解耦Redis

解决的问题是完全不依赖Redis,但是依赖了框架,想要解耦Redis,避免因为Redis挂了而导致服务无法启动。
:::info
导入依赖

com.ruijie
tool-spring-boot-starter
2.3.3

在配置文件中加上开关配置
ruijie.tool.redis-enable=false
:::
如上配置就让框架中所有Redis相关的Bean不会初始化,也就是彻底解耦,不会受到Redis集群状态的影响。

微醺码头

本期微醺码头主要是针对以上方案的一个回顾与发散,讨论下方案本身的问题以及我的一些构想。
首先我想说的是二级缓存方案中的本地缓存,对于多节点部署的项目来说是存在问题的,打个比方,如果是A节点使用了本地缓存获得了数据a,但是数据库此时如果变成了b,那么B节点通过本地缓存就得到了b,这样多节点给前端的数据就会变成一会儿是a,一会儿是b。当然真实场景,为了保证缓存一致性,可以选择旁路缓存模式,及时更新,甚至使用延时双删策略保证较强的一致性。
关于Redis这层解耦,其实我还有个想法,既然都加了一个中间层,为啥不把这个中间层扩大呢?构建一个Redis独立微服务,统一管理所有的Redis,提供通用的熔断和降级处理。当然,这光是想想就觉得问题很多,更期待读者能给我带来更优质的想法和建议。

写在最后

本来没想写的,花了上周五和本周一的时间来解决这个问题,找了下网上没啥好的想法,索性就我来写个。因为要给团队内写个使用文档,考虑到部分同事希望我多讲讲原理,喜欢听,那我就多写写,反正也不碍事。有个同事还要让我搞技术分享会,那算了,给我整活,之前搞过几次,效果不好,还老是下班搞,这不行。写写博客吧,想学的人自然会学,不想学的按着头也不会学,顺其自然。我最近也是受到了我目标的激励,重新唤起了动力,加油,Fighting!!!未来模模糊糊,总是有点犹豫不决,那就先往前走,行动起来总没错,诸君共勉!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/510669.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

AI改写文案的注意事项

AI改写文案的注意事项 随着人工智能技术的不断发展&#xff0c;AI改写文案成为了一种新兴的应用场景。通过AI改写文案&#xff0c;可以快速生成大量内容&#xff0c;节省时间和人力成本&#xff0c;但在实际应用中也需要注意一些问题和注意事项。 1. 确保内容原创性 尽管AI改…

NumPy创建ndarray数组大揭秘

1.使用 np.array() 创建 使用 np.array() 由 python list 创建 n np.array(list) 注意 numpy 默认 ndarray 的所有元素的类型是相同的 如果传进来的列表中包含不同的类型&#xff0c;则统一为同一类型&#xff0c;优先级&#xff1a;str > float > int ndarray 的常…

HarmonyOS 应用开发之同步任务开发指导 (TaskPool和Worker)

同步任务是指在多个线程之间协调执行的任务&#xff0c;其目的是确保多个任务按照一定的顺序和规则执行&#xff0c;例如使用锁来防止数据竞争。 同步任务的实现需要考虑多个线程之间的协作和同步&#xff0c;以确保数据的正确性和程序的正确执行。 由于TaskPool偏向于单个独…

java中split(“.“)失效问题

来源&#xff1a;比较版本号_牛客题霸_牛客网 在写到这道算法题的时候&#xff0c;发现一个问题&#xff0c; String[] leftversion1.split("."); 返回结果为空&#xff0c;经过查阅得知&#xff0c;是split中的正则表达式里的问题&#xff0c;这个 . 代表的意思是…

2024最新软件测试【测试理论+ app 测试】面试题(内附答案)

一、测试理论 3.1 你们原来项目的测试流程是怎么样的? 我们的测试流程主要有三个阶段&#xff1a;需求了解分析、测试准备、测试执行。 1、需求了解分析阶段 我们的 SE 会把需求文档给我们自己先去了解一到两天这样&#xff0c;之后我们会有一个需求澄清会议&#xff0c; …

Vulnhub靶机:scream

一、介绍 运行环境&#xff1a;Virtualbox(攻击机)和VMware(靶机) 攻击机&#xff1a;kali&#xff08;192.168.56.101&#xff09; 靶机&#xff1a;/dev/random: scream&#xff08;192.168.56.110&#xff09; 目标&#xff1a;获取靶机root权限和flag 靶机下载地址&am…

RUST Rover 条件编译 异常处理

按官方处理发现异常 会报异常 error: failed to parse manifest at C:\Users\topma\RustroverProjects\untitled2\Cargo.toml 修改模式如下才能正常编译 网上说明 这样处理 [features] print-a [] print-b [] full ["print-a","print-b"]

力扣Lc27--队列-- 387. 字符串中的第一个唯一字符(java版)-2024年4月02日

1.题目描述 2.知识点 注1&#xff1a; String类提供了一个repeat方法&#xff0c;该方法用于将指定的字符串重复指定的次数。 public class Main {public static void main(String[] args) {String repeatedString "abc".repeat(3);System.out.println(repeatedS…

【折腾笔记】Windows系统运行ChatGLM3-6B模型实验

【折腾笔记】Windows系统运行ChatGLM3-6B模型实验 准备工作 硬件环境 笔记本电脑CPU&#xff1a;AMD R9 7940HS 8核16线程内存&#xff1a;16G16G DDR5双通道 4800MHzGPU&#xff1a;NVIDIA RTX4060 8G显存 软件环境 操作系统版本&#xff1a;Windows 10 企业版 22H2显卡驱…

C++核心高级编程 --- 1、内存分区模型 2、引用

文章目录 第一章&#xff1a;1.内存分区模型1.1 程序运行前1.2 程序运行后1.3 new操作符 第二章&#xff1a;2.引用2.1 使用2.2 注意事项2.3 做函数参数2.4 做函数返回值2.5 本质2.6 常量引用 第一章&#xff1a; 1.内存分区模型 4个区域&#xff1a; 代码区&#xff1a;存放…

SpringBean生命周期之五、七、十步(详解)

目录 前提 一.Bean的完整周期 1.1什么是Bean的生命周期 二.SpringBean的五步分析法 2.1理论分析 2.2代码实现 三.Bean周期之七步分析法 3.1理论分析 3.2代码实现 四.Bean生命周期之十步分析法 4.1理论分析 4.2代码实现 五.总结 5.1五步、七步、十步的差别 5.2S…

IPC 进程间通信

IPC InterProcess Communication The concept of IPC Each process has a differnt user addess space,and local variables 各自看不见,so 进程间通信 need kernel(内核), so a buffer is opened in the kernel,process 1 copies data from user space to this buffer,and …

易语言控件绑定数据库

易语言是一门中文编程语言&#xff0c;由国人开发&#xff0c;虽然比较冷门&#xff0c;但是在有些场合却非常流行&#xff0c;比如自动化脚本&#xff0c;还有开发外挂。 在易语言中&#xff0c;只要控件的属性里有数据源的都可以与数据库的数据绑定&#xff0c;以下将演示易…

消息存储与同步策略设计

消息存储与同步策略 https://github.com/robinfoxnan/BirdTalkServer 思路&#xff1a; 私聊写扩散&#xff0c;以用户为中心&#xff0c;存储2次&#xff1b;群聊读扩散&#xff0c;以群组为中心&#xff0c;存储一次&#xff1b;scylladb易于扩展&#xff0c;适合并发&…

蚁剑流量分析

蚁剑流量分析 在靶机上面上传一个一句话木马&#xff0c;并使用蚁剑连接&#xff0c;进行抓包, 一句话木马内容 <?php eval($_POST[1]); defalut编码器 在使用蚁剑连接的时候使用default编码器 连接之后进行的操作行为是查看当前目录(/var/www/html)下的文件&#xff0…

网易云首页单页面html+css

网页设计与网站建设作业htmlcss 预览 源码查看https://hpc.baicaitang.cn/2083.html

书生 浦语 大模型趣味 Demo

目录 一. 部署 InternLM2-Chat-1.8B 模型进行智能对话 1. 环境准备 2. 下载模型参数 3. 运行Demo 二. 部署实战营 八戒-Chat-1.8B 模型 1. 下载Demo仓库 2. 启动web服务端加载八戒模型&#xff1a; 3. 将SSH远程端口映射到本地 4. 在本地浏览器打开&#xff1a;http:/…

【C++第二阶段】案例-职工管理系统

以下内容仅为当前认识&#xff0c;可能有不足之处&#xff0c;欢迎讨论&#xff01; 文章目录 案例>职工管理系统0.退出功能1.增加职工功能2.显示职工信息3.删除职工信息4.修改职工信息5.查找职工信息6.排序职工7.清空所有文档 案例>职工管理系统 首先写一个workmanager…

Adobe ColdFusion 任意文件读取漏洞复现(CVE-2024-20767)

0x01 产品简介 Adobe ColdFusion是美国奥多比(Adobe)公司的一套快速应用程序开发平台。该平台包括集成开发环境和脚本语言,将可扩展、改变游戏规则且可靠的产品的愿景变为现实。 0x02 漏洞概述 由于 Adobe ColdFusion 的访问控制不当,未经身份认证的远程攻击者可以构造恶…

夜晚兼职好选择:六大副业助你增收

晚上兼职&#xff0c;无疑是许多寻求额外收入人群的理想选择。以下为您精心推荐的六个副业&#xff0c;既适合晚间操作&#xff0c;又能让您在轻松愉悦中赚取额外收益。 网络调查与市场研究&#xff1a;利用晚上的闲暇时光&#xff0c;参与网络调查与市场研究&#xff0c;为企业…