当前位置: 首页 > news >正文

【Redis】Redis 的学习教程(五)之 SpringBoot 集成 Redis

在前几篇文章中,我们详细介绍了 Redis 的一些功能特性以及主流的 java 客户端 api 使用方法。

在当前流行的微服务以及分布式集群环境下,Redis 的使用场景可以说非常的广泛,能解决集群环境下系统中遇到的不少技术问题,在此列举几个使用 Redis 经常用到的功能:

  • 分布式缓存:在分布式的集群架构中,将缓存存储在内存中会出现很多的问题,比如用户回话信息,因为这部分信息需要与其他机器共享,此时利用 Redis 可以很好的解决机器之间数据共享的问题,缓存也是 Redis 中使用最多的场景
  • 分布式锁:在高并发的情况下,我们需要一个锁来防止并发带来的脏数据,Java 自带的锁机制显然对进程间的并发并不好使,此时利用 Redis 的单线程特性,实现分布式锁控制
  • 接口限流:在集群环境下,可以利用 Redis 的分布式自增 ID 功能,精准的统计每个接口在指定时间内的请求次数,利用这个特性,可以定向限制某个接口恶意频刷

当然 Redis 的使用场景并不仅仅只有这么多,还有很多未列出的场景,如发布/订阅,分布锁集合等。

现实中我们大部分的微服务项目,都是基于 SpringBoot 框架进行快速开发,在 SpringBoot 项目中我们应该如何使用 Redis 呢?代码实践如下。

1. 开发环境

  • IDEA:2021.3.3
  • JDK:1.8
  • SpringBoot:2.7.14
  • Maven:3.6.3

咱们通过程序是不能直接连接 Redis,得利用客户端工具才能进行连接。比较常用的有两种:Jedis、Lettuce。

在 springboot 1.5.x 版本的默认的 Redis 客户端是 Jedis 实现的,springboot 2.x 版本中默认客户端是用 lettuce实现的。

既然 LettuceJedis 的都是连接 Redis 的客户端,那么它们有什么区别呢?

  • Jedis 在实现上是直连 Redis Server,多线程环境下非线程安全,除非使用连接池,为每个 Redis 实例增加 物理连接
  • Lettuce 是 一种可伸缩,线程安全,完全非阻塞的Redis客户端,多个线程可以共享一个 RedisConnection,它利用 Netty NIO 框架来高效地管理多个连接,从而提供了异步和同步数据访问方式,用于构建非阻塞的反应性应用程序

2. 代码实战

在 SpringBoot 集成的 Redis 时,我这里采用的是 Lettuce

2.1 默认使用 Lettuce

1、引入依赖:

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2、添加配置:

spring:redis:host: localhostport: 6379password:timeout: 2000s# 配置文件中添加 lettuce.pool 相关配置,则会使用到lettuce连接池lettuce:pool:max-active: 8  # 连接池最大连接数(使用负值表示没有限制) 默认为8max-wait: -1ms # 接池最大阻塞等待时间(使用负值表示没有限制) 默认为-1msmax-idle: 8    # 连接池中的最大空闲连接 默认为8min-idle: 0    # 连接池中的最小空闲连接 默认为 0

2.2 换成 Jedis

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId><exclusions><exclusion><groupId>io.lettuce</groupId><artifactId>lettuce-core</artifactId></exclusion></exclusions>
</dependency>
<dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId>
</dependency>

jedis 中会引入 commons-pool2 依赖,如果没有引入:

<dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId>
</dependency>

添加配置:

spring:redis:host: localhostport: 6379password:timeout: 2000s# 配置文件中添加 jedis.pool 相关配置,则会使用到 jedis 连接池jedis:pool:max-active: 10max-idle: 8min-idle: 0max-wait: 60s

2.3 使用 RedisTemplate 对象操作 Redis

在 SpringBoot 中,是使用 RedisTemplate 对象来操作 Redis 的。

在 Springboot 自动配置原理中,涉及到以下两方面:

  1. SpringBoot 中所有的配置类,都有一个自动配置类。RedisAutoConfiguration
  2. 自动配置类都会绑定一个配置文件 properties。RedisProperties

RedisAutoConfiguration.class

public class RedisAutoConfiguration {public RedisAutoConfiguration() {}@Bean@ConditionalOnMissingBean(name = {"redisTemplate"})@ConditionalOnSingleCandidate(RedisConnectionFactory.class)public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {RedisTemplate<Object, Object> template = new RedisTemplate();template.setConnectionFactory(redisConnectionFactory);return template;}@Bean@ConditionalOnMissingBean@ConditionalOnSingleCandidate(RedisConnectionFactory.class)public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {return new StringRedisTemplate(redisConnectionFactory);}
}

StringRedisTemplate

public class StringRedisTemplate extends RedisTemplate<String, String> {public StringRedisTemplate() {this.setKeySerializer(RedisSerializer.string());this.setValueSerializer(RedisSerializer.string());this.setHashKeySerializer(RedisSerializer.string());this.setHashValueSerializer(RedisSerializer.string());}public StringRedisTemplate(RedisConnectionFactory connectionFactory) {this();this.setConnectionFactory(connectionFactory);this.afterPropertiesSet();}protected RedisConnection preProcessConnection(RedisConnection connection, boolean existingConnection) {return new DefaultStringRedisConnection(connection);}
}

通过上述看,注入了两个类型的 RedisTemplate 对象:

  1. 如果没有注入名称为 redisTemplate 的 RedisTemplate 对象,则注入 RedisTemplate<Object, Object> 对象
  2. 注入 StringRedisTemplate 对象。而 StringRedisTemplate 对象又是继承 RedisTemplate<String, String> 类的

使用上面两个类型 RedisTemplate 的对象操作 Redis:

@RestController
@RequestMapping("/redis")
public class RedisController {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Autowiredprivate RedisTemplate<Object, Object> redisTemplate;@GetMapping("/set")public String set() {stringRedisTemplate.opsForValue().set("name", "zzc");redisTemplate.opsForValue().set("age", "zzc");return "set";}
}

调用成功后,我们使用 Redis 客户端工具进行查看:

在这里插入图片描述
发现:

redisTemplate.opsForValue().set("age", "zzc"); 操作的 keyvalue 都变成乱码。

springboot系列——redisTemplate和stringRedisTemplate对比、redisTemplate几种序列化方式比较

通过 debug 源代码知:RedisTemplate<Object, Object> 的 key、value 序列化默认都是 JdkSerializationRedisSerializer,序列化方法如下:

default byte[] serializeToByteArray(T object) throws IOException {ByteArrayOutputStream out = new ByteArrayOutputStream(1024);this.serialize(object, out);return out.toByteArray();
}public void serialize(Object object, OutputStream outputStream) throws IOException {if (!(object instanceof Serializable)) {throw new IllegalArgumentException(this.getClass().getSimpleName() + " requires a Serializable payload but received an object of type [" + object.getClass().getName() + "]");} else {ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);objectOutputStream.writeObject(object);objectOutputStream.flush();}
}

将 key、value 进行序列化成 byte 类型,所以,看上去会乱码。(可读性差)

StringRedisTemplate 对象使用 RedisSerializer 序列的

2.4 自定义 RedisTemplate 对象

为了可读性,可以使用 StringRedisTemplate 类,但有一个要求:key、value 都要求是 String 类型。

但这就有一个问题,我们平时用得对象比较多,那又如何存储对象呢?

例如,我们这里的 User 对象:

public class User {private String id;private String userName;private Integer age;// getter/setter
}

由于 RedisTemplate<String, String> 的泛型参数都是 String 类型的,那我们只需要将 Java 对象转换为 String 对象即可:

@Override
public boolean addUser(User user) {redisTemplate.opsForValue().set("user", JSON.toJSONString(user));String strUser = redisTemplate.opsForValue().get("user1");User resultUser = JSON.parseObject(strUser, User.class);return true;
}

存 Redis 之前,将 Java 对象转换为 Json 字符串;读取后,将 Json 字符串转换为 Java 对象。

这样做确实可行,但是,如果要存储的对象较多的话,那岂不是要重复地将 Java 对象转换为 Json 字符串?这样是不是很繁琐?

继续看 RedisAutoConfiguration.class 源码,发现被注入的 RedisTemplate<Object, Object>@ConditionalOnMissingBean(name="redisTemplate") 注解修饰:如果 Spring 容器中有了 RedisTemplate 对象了,这个自动配置的 RedisTemplate 不会实例化。因此我们可以直接自己写个配置类,配置 RedisTemplate。并且,我们更希望 key 是 String 类型,value 是 Object 类型(String、int、对象等类型):

@Configuration
public class RedisConfig {@Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {RedisTemplate<String, Object> template = new RedisTemplate<>();template.setConnectionFactory(redisConnectionFactory);ObjectMapper om = new ObjectMapper();om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);// json 序列化配置Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);jackson2JsonRedisSerializer.setObjectMapper(om);// String 序列化StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();// 所有的 key 采用 string 的序列化template.setKeySerializer(stringRedisSerializer);// 所有的 value 采用 jackson 的序列化template.setValueSerializer(jackson2JsonRedisSerializer);// hash 的 key 采用 string 的序列化template.setHashKeySerializer(stringRedisSerializer);// hash 的 value 采用 jackson 的序列化template.setHashValueSerializer(jackson2JsonRedisSerializer);template.afterPropertiesSet();return template;}
}

测试:

@RestController
@RequestMapping("/redis")
public class RedisController {@Autowiredprivate RedisTemplate<String, Object> redisTemplate;@GetMapping("/set")public String set() {User user = new User();user.setName("zzc");user.setAge(18);redisTemplate.opsForValue().set("user", user);return "set";}}

2.5 RedisUtil 工具类

@Component
public class RedisUtil {@Autowiredprivate RedisTemplate<String, Object> redisTemplate;// ============================Common=============================public void setHashValueSerializer(RedisSerializer serializer) {redisTemplate.setHashValueSerializer(serializer);}/*** 指定缓存失效时间** @author zzc* @date 2023/8/2 11:06* @param key    键* @param time   时间(秒)* @return boolean*/public boolean expire(String key, long time) {try {if (time > 0) {redisTemplate.expire(key, time, TimeUnit.SECONDS);}return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 根据key 获取过期时间** @author zzc* @date 2023/8/2 11:07* @param key    键 不能为null* @return long  时间(秒) 返回0代表为永久有效*/public Long getExpire(String key) {return redisTemplate.getExpire(key, TimeUnit.SECONDS);}/*** 判断key是否存在** @author zzc* @date 2023/8/2 11:07* @param key      键* @return boolean 存在 false不存在*/public boolean hasKey(String key) {try {return Boolean.TRUE.equals(redisTemplate.hasKey(key));} catch (Exception e) {e.printStackTrace();return false;}}/*** 删除缓存** @author zzc* @date 2023/8/2 11:08* @param key   可以传一个值 或多个*/@SuppressWarnings("unchecked")public void del(String... key) {if (key != null && key.length > 0) {if (key.length == 1) {redisTemplate.delete(key[0]);} else {//springboot2.4后用法redisTemplate.delete(Arrays.asList(key));}}}/*** 获取指定前缀的一系列key* 使用scan命令代替keys, Redis是单线程处理,keys命令在KEY数量较多时,* 操作效率极低【时间复杂度为O(N)】,该命令一旦执行会严重阻塞线上其它命令的正常请求** @author zzc* @date 2023/8/2 11:53* @param keyPrefix* @return java.util.Set<java.lang.String>*/public Set<String> keys(String keyPrefix) {String realKey = keyPrefix + "*";try {return redisTemplate.execute((RedisCallback<Set<String>>) connection -> {Set<String> binaryKeys = new HashSet<>();//springboot2.4后用法Cursor<byte[]> cursor = connection.scan(ScanOptions.scanOptions().match(realKey).count(Integer.MAX_VALUE).build());while (cursor.hasNext()) {binaryKeys.add(new String(cursor.next()));}return binaryKeys;});} catch (Throwable e) {e.printStackTrace();}return null;}/*** 删除指定前缀的一系列key** @author zzc* @date 2023/8/2 11:53* @param keyPrefix*/public void removeAll(String keyPrefix) {try {Set<String> keys = keys(keyPrefix);redisTemplate.delete(keys);} catch (Throwable e) {e.printStackTrace();}}// 执行 lua 脚本public <T> T execute(RedisScript<T> script, List<String> keys, Object... args) {return redisTemplate.execute(script, keys, args);}public boolean convertAndSend(String channel, Object message) {if (!StringUtils.hasText(channel)) {return false;}try {redisTemplate.convertAndSend(channel, message);return true;} catch (Exception e) {e.printStackTrace();}return false;}// ============================String=============================/*** 普通缓存获取** @author zzc* @date 2023/8/2 11:08* @param key                   键* @return java.lang.Object     值*/public Object get(String key) {return key == null ? null : redisTemplate.opsForValue().get(key);}/*** 普通缓存放入* @author zzc* @date 2023/8/2 11:09* @param key           键* @param value         值* @return boolean      true成功 false失败*/public boolean set(String key, Object value) {try {redisTemplate.opsForValue().set(key, value);return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 普通缓存放入并设置过期时间** @author zzc* @date 2023/8/2 11:09* @param key     键* @param value   值* @param time    时间(秒) time要大于0 如果time小于等于0 将设置无限期* @return boolean  true成功 false 失败*/public boolean set(String key, Object value, long time) {try {if (time > 0) {redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);} else {set(key, value);}return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 递增* @author zzc* @date 2023/8/2 11:10* @param key         键* @param delta       要增加几(大于0)* @return java.lang.Long*/public Long incr(String key, long delta) {if (delta < 0) {throw new RuntimeException("递增因子必须大于0");}return redisTemplate.opsForValue().increment(key, delta);}/*** 递减** @author zzc* @date 2023/8/2 11:11* @param key                 键* @param delta               要减少几(小于0)* @return java.lang.Long*/public Long decr(String key, long delta) {if (delta < 0) {throw new RuntimeException("递减因子必须大于0");}return redisTemplate.opsForValue().increment(key, -delta);}public boolean setNx(String key, Object value) {return Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(key, value));}public boolean setNx(String key, Object value, long time) {return Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(key, value, time, TimeUnit.SECONDS));}public void multiSet(Map<String, Object> map) {redisTemplate.opsForValue().multiSet(map);}public List<Object> multiGet(List<String> keys) {return redisTemplate.opsForValue().multiGet(keys);}// ================================Hash=================================/*** Hash Get* @author zzc* @date 2023/8/2 11:12* @param key                键 不能为null* @param item               项 不能为null* @return java.lang.Object*/public Object hget(String key, String item) {return redisTemplate.opsForHash().get(key, item);}/*** 获取Key对应的所有键值** @author zzc* @date 2023/8/2 11:12* @param key                                                  键* @return java.util.Map<java.lang.Object,java.lang.Object>    对应的多个键值*/public Map<Object, Object> hmget(String key) {return redisTemplate.opsForHash().entries(key);}/*** Hash Set** @author zzc* @date 2023/8/2 11:13* @param key         键* @param map         对应多个键值* @return boolean    true 成功 false 失败*/public boolean hmset(String key, Map<String, Object> map) {try {redisTemplate.opsForHash().putAll(key, map);return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** Hash Set 并设置过期时间* @author zzc* @date 2023/8/2 11:13* @param key        键* @param map        对应多个键值* @param time       时间(秒)* @return boolean   true成功 false失败*/public boolean hmset(String key, Map<String, Object> map, long time) {try {redisTemplate.opsForHash().putAll(key, map);if (time > 0) {expire(key, time);}return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 向一张hash表中放入数据,如果不存在将创建** @param key   键* @param item  项* @param value 值* @return true 成功 false失败*/public boolean hset(String key, String item, Object value) {try {redisTemplate.opsForHash().put(key, item, value);return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 向一张hash表中放入数据,如果不存在将创建** @param key   键* @param item  项* @param value 值* @param time  时间(秒) 注意:如果已存在的hash表有过期时间,这里将会替换原有的过期时间* @return true 成功 false失败*/public boolean hset(String key, String item, Object value, long time) {try {redisTemplate.opsForHash().put(key, item, value);if (time > 0) {expire(key, time);}return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 删除hash表中的项** @author zzc* @date 2023/8/2 11:38* @param key   键 不能为null* @param item  项 可以使多个 不能为null*/public void hdel(String key, Object... item) {redisTemplate.opsForHash().delete(key, item);}/*** 判断 hash 表中是否有该项的值** @author zzc* @date 2023/8/2 11:38* @param key    键 不能为null* @param item   项 不能为null* @return boolean  true 存在 false不存在*/public boolean hHasKey(String key, String item) {return redisTemplate.opsForHash().hasKey(key, item);}/*** hash 递增 如果不存在,就会创建一个 并把新增后的值返回** @author zzc* @date 2023/8/2 11:40* @param key    键* @param item   项* @param by     要增加几(大于0)* @return double*/public double hincr(String key, String item, double by) {return redisTemplate.opsForHash().increment(key, item, by);}public Long hincr(String key, String item, long by) {return redisTemplate.opsForHash().increment(key, item, by);}/*** hash 递减** @author zzc* @date 2023/8/2 11:40* @param key    键* @param item   项* @param by     要减少几(小于0)* @return double*/public double hdecr(String key, String item, double by) {return redisTemplate.opsForHash().increment(key, item, -by);}public List<Object> hmultiGet(String key, List<Object> items) {return redisTemplate.opsForHash().multiGet(key, items);}// ============================set=============================/*** 根据key获取Set中的所有值** @author zzc* @date 2023/8/2 11:41* @param key* @return java.util.Set<java.lang.Object>*/public Set<Object> sGet(String key) {try {return redisTemplate.opsForSet().members(key);} catch (Exception e) {e.printStackTrace();return null;}}/*** 根据value从一个set中查询,是否存在** @author zzc* @date 2023/8/2 11:41* @param key       键* @param value     值* @return boolean  true 存在 false不存在*/public boolean sHasKey(String key, Object value) {try {return Boolean.TRUE.equals(redisTemplate.opsForSet().isMember(key, value));} catch (Exception e) {e.printStackTrace();return false;}}/*** 将数据放入set缓存** @author zzc* @date 2023/8/2 11:42* @param key       键* @param values    值 可以是多个* @return long     成功个数*/public Long sSet(String key, Object... values) {try {return redisTemplate.opsForSet().add(key, values);} catch (Exception e) {e.printStackTrace();return 0L;}}/*** 将 set 数据放入缓存** @author zzc* @date 2023/8/2 11:42* @param key     键* @param time    时间(秒)* @param values  值 可以是多个* @return long   成功个数*/public Long sSetAndTime(String key, long time, Object... values) {try {Long count = redisTemplate.opsForSet().add(key, values);if (time > 0) {expire(key, time);}return count;} catch (Exception e) {e.printStackTrace();return 0L;}}/*** 获取set缓存的长度** @author zzc* @date 2023/8/2 11:45* @param key* @return long*/public Long sGetSetSize(String key) {try {return redisTemplate.opsForSet().size(key);} catch (Exception e) {e.printStackTrace();return 0L;}}/*** 移除值为value的** @author zzc* @date 2023/8/2 11:45* @param key    键* @param values 值 可以是多个* @return long  移除的个数*/public Long setRemove(String key, Object... values) {try {return redisTemplate.opsForSet().remove(key, values);} catch (Exception e) {e.printStackTrace();return 0L;}}// ===============================List=================================/*** 获取list缓存的内容** @author zzc* @date 2023/8/2 11:46* @param key      键* @param start    开始* @param end      结束 0 到 -1代表所有值* @return java.util.List<java.lang.Object>*/public List<Object> lGet(String key, long start, long end) {try {return redisTemplate.opsForList().range(key, start, end);} catch (Exception e) {e.printStackTrace();return null;}}/*** 获取list缓存的长度** @author zzc* @date 2023/8/2 11:47* @param key* @return long*/public Long lGetListSize(String key) {try {return redisTemplate.opsForList().size(key);} catch (Exception e) {e.printStackTrace();return 0L;}}/*** 通过索引 获取list中的值** @author zzc* @date 2023/8/2 11:47* @param key     键* @param index   索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推* @return java.lang.Object*/public Object lGetIndex(String key, long index) {try {return redisTemplate.opsForList().index(key, index);} catch (Exception e) {e.printStackTrace();return null;}}/*** 将list放入缓存* @author zzc* @date 2023/8/2 11:48* @param key       键* @param value     值* @return boolean*/public boolean lSet(String key, Object value) {try {redisTemplate.opsForList().rightPush(key, value);return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 将list放入缓存* @author zzc* @date 2023/8/2 11:48* @param key       键* @param value     值* @param time  时间(秒)* @return boolean*/public boolean lSet(String key, Object value, long time) {try {redisTemplate.opsForList().rightPush(key, value);if (time > 0) {expire(key, time);}return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 将list放入缓存** @author zzc* @date 2023/8/2 11:49* @param key        键* @param value      值* @return boolean   时间(秒)*/public boolean lSet(String key, List<Object> value) {try {redisTemplate.opsForList().rightPushAll(key, value);return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 将list放入缓存** @author zzc* @date 2023/8/2 11:49* @param key        键* @param value      值* @param time       时间(秒)* @return boolean*/public boolean lSet(String key, List<Object> value, long time) {try {redisTemplate.opsForList().rightPushAll(key, value);if (time > 0) {expire(key, time);}return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 根据索引修改list中的某条数据** @author zzc* @date 2023/8/2 11:51* @param key     键* @param index   索引* @param value   值* @return boolean*/public boolean lUpdateIndex(String key, long index, Object value) {try {redisTemplate.opsForList().set(key, index, value);return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 移除N个值为value** @author zzc* @date 2023/8/2 11:51* @param key    键* @param count  移除多少个* @param value  值* @return long  移除的个数*/public Long lRemove(String key, long count, Object value) {try {return redisTemplate.opsForList().remove(key, count, value);} catch (Exception e) {e.printStackTrace();return 0L;}}}

相关文章:

【Redis】Redis 的学习教程(五)之 SpringBoot 集成 Redis

在前几篇文章中&#xff0c;我们详细介绍了 Redis 的一些功能特性以及主流的 java 客户端 api 使用方法。 在当前流行的微服务以及分布式集群环境下&#xff0c;Redis 的使用场景可以说非常的广泛&#xff0c;能解决集群环境下系统中遇到的不少技术问题&#xff0c;在此列举几…...

github以及上传代码处理

最近在github上传代码的时候出现了&#xff1a; /video_parser# git push -u origin main Username for https://github.com: gtnyxxx Password for https://gtny2010github.com: remote: Support for password authentication was removed on August 13, 2021. remote: Plea…...

【PACS源码】认识PACS的架构和工作流程

&#xff08;一&#xff09;PACS系统的组成及架构 PACS系统的基本组成部分包括&#xff1a;数字影像采集、通讯和网络、医学影像存储、医学影像管理、各类工作站五个部分。 而目前PACS系统的软件架构选型上看&#xff0c;主要有C/S和B/S两种形式。 C/S架构&#xff0c;即Client…...

【C++】开源:跨平台Excel处理库-libxlsxwriter配置使用

&#x1f60f;★,:.☆(&#xffe3;▽&#xffe3;)/$:.★ &#x1f60f; 这篇文章主要介绍Excel处理库-libxlsxwriter配置使用。 无专精则不能成&#xff0c;无涉猎则不能通。——梁启超 欢迎来到我的博客&#xff0c;一起学习&#xff0c;共同进步。 喜欢的朋友可以关注一下&…...

前端-轮询

一、轮询定义 轮询是指在一定的时间间隔内&#xff0c;定时向服务器发送请求&#xff0c;获取最新数据的过程。轮询通常用于从服务器获取实时更新的数据。 二、轮询和长轮询区别 轮询是在固定的时间间隔内向服务器发送请求&#xff0c;即使服务器没有数据更新也会继续发送请求…...

Python “贪吃蛇”游戏,在不断改进中学习pygame编程

目录 前言 改进过程一 增加提示信息 原版帮助摘要 pygame.draw pygame.font class Rect class Surface 改进过程二 增加显示得分 改进过程三 增加背景景乐 增加提示音效 音乐切换 静音切换 mixer.music.play 注意事项 原版帮助摘要 pygame.mixer pygame.mix…...

Linux网络编程_Ubuntu环境配置安装

文章目录&#xff1a; 一&#xff1a;基于vmware虚拟机安装Ubuntu系统&#xff08;虚拟机&#xff09; 1.vmware下载 2.Ubuntu系统下载 3.配置 3.1 无法连网&#xff1a;这里很容易出现问题 3.2 更换国内源 3.3 无法屏幕适配全屏 3.4 汉化 二&#xff1a;直接安装Ubun…...

gradle java插件

gradle java插件 1. 由来 Gradle是一种现代化的构建工具&#xff0c;Java插件是Gradle官方提供的插件&#xff0c;用于支持和管理Java项目的构建过程。 2. 常见五种示例和说明 示例1&#xff1a;配置源代码目录和编译选项 plugins {id java }sourceSets {main {java {srcD…...

神经网络基础-神经网络补充概念-48-rmsprop

概念## 标题 RMSProp&#xff08;Root Mean Square Propagation&#xff09;是一种优化算法&#xff0c;用于在训练神经网络等机器学习模型时自适应地调整学习率&#xff0c;以加速收敛并提高性能。RMSProp可以有效地处理不同特征尺度和梯度变化&#xff0c;对于处理稀疏数据和…...

分析Flink,源和算子并行度不一致时,运行一段时间后,看似不再继续消费的问题,提供解决思路。

文章目录 背景分析 问题来了比较一开始的情况解决方式 背景 之前有分析过一次类似问题&#xff0c;最终结论是在keyby之后&#xff0c;其中有一个key数量特别庞大&#xff0c;导致对应的subtask压力过大&#xff0c;进而使得整个job不再继续运作。在这个问题解决之后&#xff…...

PyTorch训练深度卷积生成对抗网络DCGAN

文章目录 DCGAN介绍代码结果参考 DCGAN介绍 将CNN和GAN结合起来&#xff0c;把监督学习和无监督学习结合起来。具体解释可以参见 深度卷积对抗生成网络(DCGAN) DCGAN的生成器结构&#xff1a; 图片来源&#xff1a;https://arxiv.org/abs/1511.06434 代码 model.py impor…...

Spring-4-掌握Spring事务传播机制

今日目标 能够掌握Spring事务配置 Spring事务管理 1 Spring事务简介【重点】 1.1 Spring事务作用 事务作用&#xff1a;在数据层保障一系列的数据库操作同成功同失败 Spring事务作用&#xff1a;在数据层或业务层保障一系列的数据库操作同成功同失败 1.2 案例分析Spring…...

[PyTorch][chapter 49][创建自己的数据集 1]

前言&#xff1a; 后面几章主要利用DataSet 创建自己的数据集&#xff0c;实现建模&#xff0c; 训练&#xff0c;迁移等功能。 目录: pokemon 数据集深度学习工程步骤 一 pokemon 数据集介绍 1.1 pokemon: 数据集地址&#xff1a; 百度网盘路径: https://pan.baidu.com/s/1…...

中间件(二)dubbo负载均衡介绍

一、负载均衡概述 支持轮询、随机、一致性hash和最小活跃数等。 1、轮询 ① sequences&#xff1a;内部的序列计数器 ② 服务器接口方法权重一样&#xff1a;&#xff08;sequences1&#xff09;%服务器的数量&#xff08;决定调用&#xff09;哪个服务器的服务。 ③ 服务器…...

springboot异步文件上传获取输入流提示找不到文件java.io.FileNotFoundException

springboot上传文件&#xff0c;使用异步操作处理上传的文件数据&#xff0c;出现异常如下&#xff1a; 这个是在异步之后使用传过来的MultipartFile对象尝试调用getInputStream方法发生的异常。 java.io.FileNotFoundException: C:\Users\Administrator\AppData\Local\Temp\to…...

安装jenkins-cli

1、要在 Linux 操作系统上安装 jcli curl -L https://github.com/jenkins-zh/jenkins-cli/releases/latest/download/jcli-linux-amd64.tar.gz|tar xzv sudo mv jcli /usr/local/bin/ 在用户根目录下&#xff0c;增加 jcli 的配置文件&#xff1a; jcli config gen -ifalse …...

linux通过NC工具启动临时端口监听

1.安装nc工具 yum install nc -y2. 启动监听指定端口 #例如监听8080端口 nc -lk 8080#后台监听 nc -lk 8080 &3. 验证 #通过另外一台网络能通的机器&#xff0c;telnet 该机器ip 监听端口能通&#xff0c;并且能接手数据 telnet 192.xxx.xxx.xx 8080...

开源语音聊天软件Mumble

网友 大气 告诉我&#xff0c;Openblocks在国内还有个版本叫 码匠&#xff0c;更贴合国内软件开发的需求&#xff0c;如接入了国内常用的身份认证&#xff0c;接入了国内的数据库和云服务&#xff0c;也对小程序、企微 sdk 等场景做了适配。 在 https://majiang.co/docs/docke…...

JDK 1.6与JDK 1.8的区别

ArrayList使用默认的构造方式实例 jdk1.6默认初始值为10jdk1.8为0,第一次放入值才初始化&#xff0c;属于懒加载 Hashmap底层 jdk1.6与jdk1.8都是数组链表 jdk1.8是链表超过8时&#xff0c;自动转为红黑树 静态方式不同 jdk1.6是先初始化static后执行main方法。 jdk1.8是懒加…...

单片机实训报告

这周我们进行了单片机实训&#xff0c;一周中我们通过七个项目1&#xff1a;P1 口输入/输出 2&#xff1a;继电器控制 3 音频控制 4&#xff1a;子程序设计 5&#xff1a;字符碰头程序设计 6&#xff1a;外部中断 7&#xff1a; 急救车与交通信号灯&#xff0c;练习编写了子程…...

【编织时空四:探究顺序表与链表的数据之旅】

本章重点 链表的分类 带头双向循环链表接口实现 顺序表和链表的区别 缓存利用率参考存储体系结构 以及 局部原理性。 一、链表的分类 实际中链表的结构非常多样&#xff0c;以下情况组合起来就有8种链表结构&#xff1a; 1. 单向或者双向 2. 带头或者不带头 3. 循环或者非…...

PHP8的字符串操作1-PHP8知识详解

字符串是php中最重要的数据之一&#xff0c;字符串的操作在PHP编程占有重要的地位。在使用PHP语言开发web项目的过程中&#xff0c;为了实现某些功能&#xff0c;经常需要对某些字符串进行特殊的处理&#xff0c;比如字符串的格式化、字符串的连接与分割、字符串的比较、查找等…...

电脑提示msvcp140.dll丢失的解决方法,dll组件怎么处理

Windows系统有时在打开游戏或者软件时&#xff0c; 系统会弹窗提示缺少“msvcp140.dll.dll”文件 或者类似错误提示怎么办&#xff1f; 错误背景&#xff1a; msvcp140.dll是Microsoft Visual C Redistributable Package中的一个动态链接库文件&#xff0c;它在运行软件时提…...

stable diffusion基础

整合包下载&#xff1a;秋叶大佬 【AI绘画8月最新】Stable Diffusion整合包v4.2发布&#xff01; 参照&#xff1a;基础04】目前全网最贴心的Lora基础知识教程&#xff01; VAE 作用&#xff1a;滤镜微调 VAE下载地址&#xff1a;C站&#xff08;https://civitai.com/models…...

Greiner–Hormann裁剪算法深度探索:C++实现与应用案例

介绍 在计算几何中&#xff0c;裁剪是一个核心的主题。特别是&#xff0c;多边形裁剪已经被广泛地应用于计算机图形学&#xff0c;地理信息系统和许多其他领域。Greiner-Hormann裁剪算法是其中之一&#xff0c;提供了一个高效的方式来计算两个多边形的交集、并集等。在本文中&…...

Automatically Correcting Large Language Models

本文是大模型相关领域的系列文章&#xff0c;针对《Automatically Correcting Large Language Models: Surveying the landscape of diverse self-correction strategies》的翻译。 自动更正大型语言模型&#xff1a;综述各种自我更正策略的前景 摘要1 引言2 自动反馈校正LLM的…...

【学习FreeRTOS】第8章——FreeRTOS列表和列表项

1.列表和列表项的简介 列表是 FreeRTOS 中的一个数据结构&#xff0c;概念上和链表有点类似&#xff0c;列表被用来跟踪 FreeRTOS中的任务。列表项就是存放在列表中的项目。 列表相当于链表&#xff0c;列表项相当于节点&#xff0c;FreeRTOS 中的列表是一个双向环形链表列表的…...

分布式图数据库 NebulaGraph v3.6.0 正式发布,强化全文索引能力

本次 v3.6.0 版本&#xff0c;主要强化全文索引能力&#xff0c;以及优化部分场景下的 MATCH 性能。 强化 强化增强全文索引功能&#xff0c;具体 pr 参见&#xff1a;#5567、#5575、#5577、#5580、#5584、#5587 优化 支持使用 MATCH 子句检索 VID 或属性索引时使用变量&am…...

在 ubuntu 18.04 上使用源码升级 OpenSSH_7.6p1到 OpenSSH_9.3p1

1、检查系统已安装的当前 SSH 版本 使用命令 ssh -V 查看当前 ssh 版本&#xff0c;输出如下&#xff1a; OpenSSH_7.6p1 Ubuntu-4ubuntu0.7, OpenSSL 1.0.2n 7 Dec 20172、安装依赖&#xff0c;依次执行以下命令 sudo apt update sudo apt install build-essential zlib1g…...

python中可以处理word文档的模块:docx模块

前言 大家早好、午好、晚好吖 ❤ ~欢迎光临本文章 话不多说&#xff0c;直接开搞&#xff0c;如果有什么疑惑/资料需要的可以点击文章末尾名片领取源码 一.docx模块 Python可以利用python-docx模块处理word文档&#xff0c;处理方式是面向对象的。 也就是说python-docx模块…...

TikTok或将于8月底关闭半闭环、速卖通或将推出“半托管”模式

《出海周报》是运营坛为外贸企业主和外贸人独家打造的重要资讯栏目&#xff0c;聚焦企业出海、海外市场动态、海外监管政策等方面&#xff0c;以简捷的方式&#xff0c;提升读者获取资讯的效率。 接下来运营坛为大家带来第15期出海周报&#xff0c;快来看看这周国内外市场发生了…...

《凤凰架构》第二章——访问远程服务

前言 这章挺难的&#xff0c;感觉离我比较远&#xff0c;不太好懂&#xff0c;简单记录吧。 这章主要讲访问远程服务&#xff0c;主要对比了RPC和REST的区别&#xff0c;可以结合知乎上的文章《既然有 HTTP 请求&#xff0c;为什么还要用 RPC 调用&#xff1f;》 这篇文章进行…...

【Diffusion】李宏毅2023机器学习Diffusion笔记

文章目录 1 想法概述2 实际过程阶段1 Add Noise阶段2 Denoise 3 数学原理4 为什么推理时要额外加入noise5 一些不知道对不对的Summary 1 想法概述 从一张充满噪声的图中不断denoise&#xff0c;最终得到一张clear的图片。为了确定当前图片中噪声占比的大小&#xff0c;同时输入…...

CloudEvents—云原生事件规范

我们的系统中或多或少都会用到如下两类业务技术&#xff1a; 异步任务&#xff0c;用于降低接口时延或削峰&#xff0c;提升用户体验&#xff0c;降低系统并发压力&#xff1b;通知类RPC&#xff0c;用于微服务间状态变更&#xff0c;用户行为的联动等场景&#xff1b; 以上两种…...

神经网络基础-神经网络补充概念-51-局部最优问题

概念 局部最优问题是在优化问题中常见的一个挑战&#xff0c;特别是在高维、非凸、非线性问题中。局部最优问题指的是算法在优化过程中陷入了一个局部最小值点&#xff0c;而不是全局最小值点。这会导致优化算法在某个局部区域停止&#xff0c;而无法找到更好的解。 解决方案…...

深度学习中,什么是batch-size?如何设置?

什么是batch-size? batch-size 是深度学习模型在训练过程中一次性输入给模型的样本数量。它在训练过程中具有重要的意义&#xff0c;影响着训练速度、内存使用以及模型的稳定性等方面。 以下是 batch-size 大小的一些影响和意义&#xff1a; 训练速度&#xff1a;较大的 bat…...

[保研/考研机试] KY26 10进制 VS 2进制 清华大学复试上机题 C++实现

题目链接&#xff1a; 10进制 VS 2进制http://www.nowcoder.com/share/jump/437195121691738172415 描述 对于一个十进制数A&#xff0c;将A转换为二进制数&#xff0c;然后按位逆序排列&#xff0c;再转换为十进制数B&#xff0c;我们称B为A的二进制逆序数。 例如对于十进制…...

JSP-学习笔记

文章目录 1.JSP介绍2 JSP快速入门3 JSP 脚本3.1 JSP脚本案例3.2 JSP缺点 4 EL表达式4.1 快速入门案例 5. JSTL标签6. MVC模式和三层架构6.1 MVC6.2 三层架构 7. 案例-基于MVC和三层架构实现商品表的增删改查 1.JSP介绍 概念 JSP&#xff08;JavaServer Pages&#xff09;是一种…...

Golang协程,通道详解

进程、线程以及并行、并发 关于进程和线程 进程&#xff08;Process&#xff09;就是程序在操作系统中的一次执行过程&#xff0c;是系统进行资源分配和调度的基本单位&#xff0c;进程是一个动态概念&#xff0c;是程序在执行过程中分配和管理资源的基本单位&#xff0c;每一…...

unity 之 Vector 数据类型

文章目录 Vector 1Vector 2Vector 3Vector 4 Vector 1 在Unity中&#xff0c;Vector1 并不是一个常见的向量类型。 如果您需要表示标量&#xff08;单个值&#xff09;或者只需要一维的数据&#xff0c;通常会直接使用浮点数&#xff08;float&#xff09;或整数&#xff08;in…...

私密数据采集:隧道爬虫IP技术的保密性能力探究

作为一名专业的爬虫程序员&#xff0c;今天要和大家分享一个关键的技术&#xff0c;它能够为私密数据采集提供保密性能力——隧道爬虫IP技术。如果你在进行敏感数据采集任务时需要保护数据的私密性&#xff0c;那么这项技术将是你的守护神。 在进行私密数据采集任务时&#xff…...

使用git rebase 之后的如何恢复到原始状态

我们常常喜欢使用git rebase去切换分支提交代码,操作流程就是: 先切换分支:比如当前是master 我们修改了一堆代码产生一个commit id :5555555567777 那么我们常常比较懒就直接切换了:git checkout dev 然后呢?使用命令git rebase 5555555567777,想把这笔修改提交到d…...

matlab相机标定知识整理

matlab相机标定知识整理 单目相机标定 单目相机标定 内参矩阵&#xff1a;cameraParams.Intrinsics.K 或者 cameraParams.K旋转矩阵&#xff1a;cameraParams.RotationMatrices 有待确定 cameraParams.RotationVectors平移矩阵&#xff1a;cameraParams.TranslationVectors径向…...

win11安装ubuntu 子系统安装过程及注意事项

第一步 &#xff1a;安装系统必须组件 由于子系统是系统自带组件&#xff0c;需要安装软件支持 第二步&#xff1a;应用商店安装 ubuntu 编辑 编辑 这个时候打开会报错 第三步&#xff0c;运行linux子系统 选择Windows PowerShell 以管理员身份运行&#xff09; 输入&#…...

torch.cat((A,B),dim=1)解析

官方说明torch.cat 引用自&#xff1a;Pytorch中的torch.cat()函数 torch.cat(tensors, dim0, *, outNone) → Tensor # 连接给定维数的给定序列的序列张量。所有张量要么具有相同的形状(除了连接维度)&#xff0c;要么为空。示例 输入&#xff1a; import torch a torch.Tens…...

apache配置安全证书https踩坑记录

apache配置安全证书有如下几步 一、申请证书 这个网上有很多免费的&#xff0c;我用的是阿里云的服务器&#xff0c;在阿里云后台就可以申请免费证书。 二、上传证书 申请好证书后&#xff0c;根据服务器用的什么软件&#xff0c;是apache还是ngnix&#xff0c;下载相应的证书…...

SQL Server Express 自动备份方案

文章目录 SQL Server Express 自动备份方案前言方案原理SQL Server Express 自动备份1.创建存储过程2.设定计划任务3.结果检查sqlcmd 参数说明SQL Server Express 自动备份方案 前言 对于许多小型企业和个人开发者来说,SQL Server Express是一个经济实惠且强大的数据库解决方…...

Docker资源控制

目录 一、CPU 资源控制 1.设置CPU使用率上限 2.设置CPU资源占用比&#xff08;设置多个容器时才有效&#xff09; 3.设置容器绑定指定的CPU 二、对内存使用的限制 三、对磁盘IO配额控制&#xff08;blkio&#xff09;的限制 一、CPU 资源控制 cgroups&#xff0c;是一个非常强…...

微服务中间件-分布式缓存Redis

分布式缓存 a.Redis持久化1) RDB持久化1.a) RDB持久化-原理 2) AOF持久化3) 两者对比 b.Redis主从1) 搭建主从架构2) 数据同步原理&#xff08;全量同步&#xff09;3) 数据同步原理&#xff08;增量同步&#xff09; c.Redis哨兵1) 哨兵的作用2) 搭建Redis哨兵集群3) RedisTem…...

java面试强基(16)

目录 clone方法的保护机制 Java中由SubString方法是否会引起内存泄漏&#xff1f; Java中提供了哪两种用于多态的机制? 程序计数器(线程私有) 如何判断对象是否是垃圾&#xff1f; clone方法的保护机制 clone0方法的保护机制在Object中是被声明为 protected的。以User…...