Springboot搭配Redis实现接口限流

news2024/11/18 6:38:59

目录

介绍

限流的思路

代码示例

必需pom依赖

自定义注解

redis工具类

redis配置类

主拦截器

注册拦截器


介绍

限流的需求出现在许多常见的场景中:
秒杀活动,有人使用软件恶意刷单抢货,需要限流防止机器参与活动
api 被各式各样系统广泛调用,严重消耗网络、内存等资源,需要合理限流
淘宝获取 ip 所在城市接口、微信公众号识别微信用户等开发接口,免费提供给用户时需要限流,更
具有实时性和准确性的接口需要付费。

限流的思路

通过 ip:api 路径 的作为 key ,访问次数为 value 的方式对某一用户的某一请求进行唯一标识
每次访问的时候判断 key 是否存在,是否 count 超过了限制的访问次数
若访问超出限制,则应 response 返回 msg: 请求过于频繁 给前端予以展示

代码示例

准备一个springboot项目

必需pom依赖

 <dependencies>
<!--        web开发场景启动器-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
<!--        redis-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>2.9.3</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

自定义注解

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

//运行时执行
@Retention(RetentionPolicy.RUNTIME)
//指定注解的适用目标 表示该注解适用于方法上。
@Target(ElementType.METHOD)
public @interface AccessLimit {
    /**
     * 限制时长 秒
     */
    int seconds();

    /**
     * 最大访问次数
     */
    int maxCount();

    /**
     * 是否需要登录
     */
    boolean needLogin() default true;
}

redis工具类


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import redis.clients.jedis.Protocol;
import redis.clients.util.SafeEncoder;

import java.io.Serializable;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

@Component
public class RedisUtil {

	@Autowired
	private RedisTemplate<String, Object> redisTemplate;

	// =============================common============================
	/**
	 * 指定缓存失效时间
	 * @param key 键
	 * @param time 时间(秒)
	 * @return
	 */
	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 获取过期时间
	 * @param key 键 不能为null
	 * @return 时间(秒) 返回0代表为永久有效
	 */
	public long getExpire(String key) {
		return redisTemplate.getExpire(key, TimeUnit.SECONDS);
	}

	/**
	 * 判断key是否存在
	 * @param key 键
	 * @return true 存在 false不存在
	 */
	public boolean hasKey(String key) {
		try {
			return redisTemplate.hasKey(key);
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
	}

	/**
	 * 删除缓存
	 * @param key 可以传一个值 或多个
	 */
	@SuppressWarnings("unchecked")
	public void del(String... key) {
		if (key != null && key.length > 0) {
			if (key.length == 1) {
				redisTemplate.delete(key[0]);
			} else {
				redisTemplate.delete((Collection<String>) CollectionUtils.arrayToList(key));
			}
		}
	}

	/**
	 * 删除缓存
	 * @param keys 可以传一个值 或多个
	 */
	public void del(Collection<String> keys) {
		if (CollectionUtils.isEmpty(keys)) {
			redisTemplate.delete(keys);
		}
	}

	// ============================String=============================
	/**
	 * 普通缓存获取
	 * @param key 键
	 * @return 值
	 */
	public Object get(String key) {
		return key == null ? null : redisTemplate.opsForValue().get(key);
	}

	/**
	 * 普通缓存放入
	 * @param key 键
	 * @param value 值
	 * @return true成功 false失败
	 */
	public boolean set(String key, Object value) {
		try {
			redisTemplate.opsForValue().set(key, value);
			return true;
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
	}

	/**
	 * 普通缓存放入并设置时间
	 * @param key 键
	 * @param value 值
	 * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
	 * @return 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;
		}
	}

	/**
	 * 递增
	 * @param key 键
	 * @param delta 要增加几(大于0)
	 * @return
	 */
	public long incr(String key, long delta) {
		if (delta < 0) {
			throw new RuntimeException("递增因子必须大于0");
		}
		return redisTemplate.opsForValue().increment(key, delta);
	}

	/**
	 * 递减
	 * @param key 键
	 * @param delta 要减少几(小于0)
	 * @return
	 */
	public long decr(String key, long delta) {
		if (delta < 0) {
			throw new RuntimeException("递减因子必须大于0");
		}
		return redisTemplate.opsForValue().increment(key, -delta);
	}

	// ================================Map=================================
	/**
	 * HashGet
	 * @param key 键 不能为null
	 * @param item 项 不能为null
	 * @return 值
	 */
	public Object hget(String key, String item) {
		return redisTemplate.opsForHash().get(key, item);
	}

	/**
	 * 获取hashKey对应的所有键值
	 * @param key 键
	 * @return 对应的多个键值
	 */
	public Map<Object, Object> hmget(String key) {
		return redisTemplate.opsForHash().entries(key);
	}

	/**
	 * HashSet
	 * @param key 键
	 * @param map 对应多个键值
	 * @return 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;
		}
	}

	/**
	 * HashSet 并设置时间
	 * @param key 键
	 * @param map 对应多个键值
	 * @param time 时间(秒)
	 * @return 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表中的值
	 * @param key 键 不能为null
	 * @param item 项 可以使多个 不能为null
	 */
	public void hdel(String key, Object... item) {
		redisTemplate.opsForHash().delete(key, item);
	}

	/**
	 * 删除hash表中的值
	 * @param key 键 不能为null
	 * @param items 项 可以使多个 不能为null
	 */
	public void hdel(String key, Collection items) {
		redisTemplate.opsForHash().delete(key, items.toArray());
	}

	/**
	 * 判断hash表中是否有该项的值
	 * @param key 键 不能为null
	 * @param item 项 不能为null
	 * @return true 存在 false不存在
	 */
	public boolean hHasKey(String key, String item) {
		return redisTemplate.opsForHash().hasKey(key, item);
	}

	/**
	 * hash递增 如果不存在,就会创建一个 并把新增后的值返回
	 * @param key 键
	 * @param item 项
	 * @param delta 要增加几(大于0)
	 * @return
	 */
	public double hincr(String key, String item, double delta) {
		if (delta < 0) {
			throw new RuntimeException("递增因子必须大于0");
		}
		return redisTemplate.opsForHash().increment(key, item, delta);
	}

	/**
	 * hash递减
	 * @param key 键
	 * @param item 项
	 * @param delta 要减少记(小于0)
	 * @return
	 */
	public double hdecr(String key, String item, double delta) {
		if (delta < 0) {
			throw new RuntimeException("递减因子必须大于0");
		}
		return redisTemplate.opsForHash().increment(key, item, -delta);
	}

	// ============================set=============================
	/**
	 * 根据key获取Set中的所有值
	 * @param key 键
	 * @return
	 */
	public Set<Object> sGet(String key) {
		try {
			return redisTemplate.opsForSet().members(key);
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}

	/**
	 * 根据value从一个set中查询,是否存在
	 * @param key 键
	 * @param value 值
	 * @return true 存在 false不存在
	 */
	public boolean sHasKey(String key, Object value) {
		try {
			return redisTemplate.opsForSet().isMember(key, value);
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
	}

	/**
	 * 将数据放入set缓存
	 * @param key 键
	 * @param values 值 可以是多个
	 * @return 成功个数
	 */
	public long sSet(String key, Object... values) {
		try {
			return redisTemplate.opsForSet().add(key, values);
		} catch (Exception e) {
			e.printStackTrace();
			return 0;
		}
	}

	/**
	 * 将数据放入set缓存
	 * @param key 键
	 * @param values 值 可以是多个
	 * @return 成功个数
	 */
	public long sSet(String key, Collection values) {
		try {
			return redisTemplate.opsForSet().add(key, values.toArray());
		} catch (Exception e) {
			e.printStackTrace();
			return 0;
		}
	}

	/**
	 * 将set数据放入缓存
	 * @param key 键
	 * @param time 时间(秒)
	 * @param values 值 可以是多个
	 * @return 成功个数
	 */
	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 0;
		}
	}

	/**
	 * 获取set缓存的长度
	 * @param key 键
	 * @return
	 */
	public long sGetSetSize(String key) {
		try {
			return redisTemplate.opsForSet().size(key);
		} catch (Exception e) {
			e.printStackTrace();
			return 0;
		}
	}

	/**
	 * 移除值为value的
	 * @param key 键
	 * @param values 值 可以是多个
	 * @return 移除的个数
	 */
	public long setRemove(String key, Object... values) {
		try {
			Long count = redisTemplate.opsForSet().remove(key, values);
			return count;
		} catch (Exception e) {
			e.printStackTrace();
			return 0;
		}
	}

	// ===============================list=================================
	/**
	 * 获取list缓存的内容
	 * @param key 键
	 * @param start 开始
	 * @param end 结束 0 到 -1代表所有值
	 * @return
	 */
	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缓存的长度
	 * @param key 键
	 * @return
	 */
	public long lGetListSize(String key) {
		try {
			return redisTemplate.opsForList().size(key);
		} catch (Exception e) {
			e.printStackTrace();
			return 0;
		}
	}

	/**
	 * 通过索引 获取list中的值
	 * @param key 键
	 * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
	 * @return
	 */
	public Object lGetIndex(String key, long index) {
		try {
			return redisTemplate.opsForList().index(key, index);
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}

	/**
	 * 将list放入缓存
	 * @param key 键
	 * @param value 值
	 * @return
	 */
	public boolean lSet(String key, Object value) {
		try {
			redisTemplate.opsForList().rightPush(key, value);
			return true;
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
	}

	/**
	 * 将list放入缓存
	 * @param key 键
	 * @param value 值
	 * @param time 时间(秒)
	 * @return
	 */
	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放入缓存
	 * @param key 键
	 * @param value 值
	 * @return
	 */
	public boolean lSet(String key, List<Object> value) {
		try {
			redisTemplate.opsForList().rightPushAll(key, value);
			return true;
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
	}

	/**
	 * 将list放入缓存
	 *
	 * @param key 键
	 * @param value 值
	 * @param time 时间(秒)
	 * @return
	 */
	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中的某条数据
	 * @param key 键
	 * @param index 索引
	 * @param value 值
	 * @return
	 */
	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
	 * @param key 键
	 * @param count 移除多少个
	 * @param value 值
	 * @return 移除的个数
	 */
	public long lRemove(String key, long count, Object value) {
		try {
			Long remove = redisTemplate.opsForList().remove(key, count, value);
			return remove;
		} catch (Exception e) {
			e.printStackTrace();
			return 0;
		}
	}

	/**
	 * RedisTemplate 实现 setnx exptime (扩展 redisTemplate.setIfAbsent)
	 *
	 * 之前用 redisTemplate 实现setnx exptime 时 是分两步的
	 * 1. redisTemplate.setIfAbsent
	 * 2. redisTemplate.expire
	 * 这样的不是原子性的 可能在第一步与第二步之间 重新发布了或者服务器重启了
	 * 这个key就永远不会消失了 可以采用以下的方法
	 *
	 * @param key
	 * @param value
	 * @param exptime
	 * @return
	 */
	public boolean setIfAbsent(final String key, final Serializable value, final long exptime) {
		Boolean b = (Boolean) redisTemplate.execute(new RedisCallback<Boolean>() {
			@Override
			public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
				RedisSerializer valueSerializer = redisTemplate.getValueSerializer();
				RedisSerializer keySerializer = redisTemplate.getKeySerializer();
				Object obj = connection.execute("set", keySerializer.serialize(key),
						valueSerializer.serialize(value),
						SafeEncoder.encode("NX"),
						//EX 秒,PX 毫秒
						SafeEncoder.encode("EX"),
						Protocol.toByteArray(exptime));
				return obj != null;
			}
		});
		return b;
	}
}

redis配置类


import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {

    /**
     * retemplate相关配置
     * @param factory
     * @return
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {

        RedisTemplate<String, Object> template = new RedisTemplate<>();
        // 配置连接工厂
        template.setConnectionFactory(factory);

        //使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式)
        Jackson2JsonRedisSerializer jacksonSeial = new Jackson2JsonRedisSerializer(Object.class);

        ObjectMapper om = new ObjectMapper();
        // 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        // 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会跑出异常
        // om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
//        jacksonSeial.setObjectMapper(om);

        // 值采用json序列化
        template.setValueSerializer(jacksonSeial);
        //使用StringRedisSerializer来序列化和反序列化redis的key值
        template.setKeySerializer(new StringRedisSerializer());

        // 设置hash key 和value序列化模式
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(jacksonSeial);
        template.afterPropertiesSet();
        return template;
    }

    /**
     * 对hash类型的数据操作
     *
     * @param redisTemplate
     * @return
     */
    @Bean
    public HashOperations<String, String, Object> hashOperations(RedisTemplate<String, Object> redisTemplate) {
        return redisTemplate.opsForHash();
    }

    /**
     * 对redis字符串类型数据操作
     *
     * @param redisTemplate
     * @return
     */
    @Bean
    public ValueOperations<String, Object> valueOperations(RedisTemplate<String, Object> redisTemplate) {
        return redisTemplate.opsForValue();
    }

    /**
     * 对链表类型的数据操作
     *
     * @param redisTemplate
     * @return
     */
    @Bean
    public ListOperations<String, Object> listOperations(RedisTemplate<String, Object> redisTemplate) {
        return redisTemplate.opsForList();
    }

    /**
     * 对无序集合类型的数据操作
     *
     * @param redisTemplate
     * @return
     */
    @Bean
    public SetOperations<String, Object> setOperations(RedisTemplate<String, Object> redisTemplate) {
        return redisTemplate.opsForSet();
    }

    /**
     * 对有序集合类型的数据操作
     *
     * @param redisTemplate
     * @return
     */
    @Bean
    public ZSetOperations<String, Object> zSetOperations(RedisTemplate<String, Object> redisTemplate) {
        return redisTemplate.opsForZSet();
    }
}

主拦截器


import com.example.demo.service.AccessLimit;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;


@Component
public class AccessLimitInterceptor implements HandlerInterceptor {

    @Resource
    private RedisUtil redisUtil;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            //拿到限流注解类的参数
            AccessLimit accessLimit = handlerMethod.getMethodAnnotation(AccessLimit.class);
            if(accessLimit==null){
                return true;
            }
            //时间
            int seconds = accessLimit.seconds();
            //最大次数
            int maxCount = accessLimit.maxCount();
            boolean needLogin = accessLimit.needLogin();

            if(needLogin){
             //判断是否登录
            }

            //获取访问ip和路径
            String ip=request.getRemoteAddr();
            String key=ip+":"+request.getServletPath();

            Integer count = (Integer)redisUtil.get(key);
            //首次进入
            if(count==null||-1==count){
                redisUtil.set(key,1);
                //设置过期时间
                redisUtil.expire(key,seconds);
                return true;
            }

            //如果访问次数<最大次数,则做加1操作
            if(count<maxCount){
                redisUtil.incr(key,1);
                return true;
            }

            //此时访问次数大于等于最大次数
            if(count>=maxCount){
                System.out.println("已经达到该接口限制最大次数========"+count);
                response.setContentType("text/html;charset=utf-8");
                response.getWriter().write("请求过于频繁,请稍后再试!");
                return false;
            }
        }
        return true;
    }
}

注册拦截器

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class InteceptorConfig implements WebMvcConfigurer {

    @Autowired
    private AccessLimitInterceptor accessLimitInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(accessLimitInterceptor)
                //添加限流拦截的接口路径
                .addPathPatterns("/access/accessLimit")
                .addPathPatterns("/main/hello")
                //添加不限流拦截的路径
                .excludePathPatterns("/access/login");
    }
}

控制层

import com.example.demo.service.AccessLimit;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("access")
public class AccessController {

    @ResponseBody
    @GetMapping("accessLimit")
    @AccessLimit(seconds = 60,maxCount = 5)
    public String accessLimit(){
        return "Hellp world!";
    }
}
@RestController
@RequestMapping("/main")
@Slf4j
public class MainCOntroller {

    @Autowired
    StringRedisTemplate redisTemplate;

    @GetMapping("/hello")
    @AccessLimit(seconds = 10,maxCount = 2)
    public String helloo(){
        return "你好啊";
    }
}

启动测试:

 可以看到,两个接口都在限制的时间内有一定数量的访问限制,在redis里也能找到响应的key,从而实现了简单的接口限流

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

人工智能系统的技术架构

一、架构图 1.基础层包括&#xff1a; 硬件设施、软件设施、数据资源。其中在硬件设施方面&#xff0c;做深度学习和神经网络训练时候往往会涉及到模型训练是在CPU还是GPU上面&#xff0c;在这个里面GPU就是做计算加速的&#xff0c;第二个是智能芯片&#xff0c;市面上出现的…

linux 系统 最详细 启动流程

文章目录 详细分析 系统启动过程主要流程阶段说明BIOSMBR(Stage 1 bootloader)GROUB(Stage 2 bootloader)kernelvmlinuzinitrd.img Init 详细分析 系统启动过程 主要流程 PC 启动主要流程&#xff0c;分为四个阶段&#xff1a; BIOS -> MBR -> GRUB -> KERNEL ->…

6.3.5 修改文件时间或创建新文件: touch

我们在 ls 这个指令的介绍时&#xff0c;有稍微提到每个文件在linux下面都会记录许多的时间参数&#xff0c; 其实是有三个主要的变动时间&#xff0c;那么三个时间的意义是什么呢&#xff1f; modification time &#xff08;mtime&#xff09;&#xff1a; 当该文件的“内容…

1.6、JAVA 分支结构 switch结构 for循环

1 分支结构 1.1 概述 顺序结构的程序虽然能解决计算、输出等问题 但不能做判断再选择。对于要先做判断再选择的问题就要使用分支结构 1.2 形式 1.3.1 练习&#xff1a;商品打折案例 创建包: cn.tedu.basic 创建类: TestDiscount.java 需求: 接收用户输入的原价。满1000打9折…

消息通知模块的设计原理

目录 介绍 一、数据库设计 公告消息记录应该全局唯一&#xff0c;还是为每个用户创建一条公告消息&#xff1f; 用MongoDB存储消息数据 1. 搞冷热数据分离&#xff0c;热数据定期归档 2. 冷数据存储一段时间后就销毁&#xff0c;释放存储空间 二、系统消息的发送与收…

肺癌的成因

中国医师协会 2023 年呼吸医师年会暨第二十二届中国呼吸医师论坛&#xff08;CACP 2023&#xff09;于 2023 年 6 月 15-18 日在大连如期举行。肺癌是我国目前发病率和死亡率最高的癌症&#xff0c;它的早期筛查和诊断十分关键。 丁香园呼吸时间特邀四川大学华西医院院长、呼吸…

软件测试报告办理解决方案分享,为什么要选择CMA认证或CNAS认可测试报告?

在进行软件测试时&#xff0c;合格的测试报告对于软件产品的质量保障至关重要。那么软件测试报告又该如何办理呢?软件企业为什么要选择CMA认证或CNAS认可的测试报告呢?因为CMA认证的测试报告和CNAS认可的测试报告都具有不可忽视的好处。 一、软件测试报告办理解决方案 1. 测…

我蒙了面试官一上来就说:请你介绍一下你测试过的项目

测试人员在找工作中&#xff0c;基本都会碰到让介绍项目的这种面试题&#xff0c;如何正确介绍自己的项目&#xff1f;需要做哪些技术准备&#xff1f; 今天这篇文章&#xff0c;围绕这些问题&#xff0c;跟大家一起聊一聊。 关于介绍自己的项目&#xff1f; 可以从以下几个方面…

赛效:如何将PDF文件分割成单页的PDF文档

1&#xff1a;打开wdashi点击PDF处理菜单里的“PDF分割”。 2&#xff1a;将本地PDF文件添加上去&#xff0c;在下方选择转换页码&#xff0c;在这里我们选择转换每一页。 3&#xff1a;点击右下角“开始转换”。 4&#xff1a;转换好后&#xff0c;点击绿色下载按钮将分割好的…

七、Docker安装MySQL/Tomcat/Redis等

学习参考&#xff1a;尚硅谷Docker实战教程、Docker官网、其他优秀博客(参考过的在文章最后列出) 目录 前言一、安装步骤二、Docker安装Tomcat2.1 搜索镜像2.2 拉取镜像2.3 查看镜像2.4 启动镜像&#xff08;端口映射&#xff09;2.5 停止容器2.6 移除容器 三、Docker安装MySQL…

23年下半年软考软件测评师难考吗?最近考虑要不要考?

软考中级难度是适中的&#xff0c;可以考啊&#xff01; 因为当代随着各种应用技术层出不穷&#xff0c;随着社会发展&#xff0c;需要大量的软件人才支持&#xff0c;同时软件的更新速度越来越快&#xff0c;市场竞争极其激烈。相关国际认证有微软的&#xff0c;Orical&#…

新西兰访问学者签证申请注意事项

新西兰是一个美丽而富有文化多样性的国家&#xff0c;许多学者都梦想着前往这里进行学术交流和研究。如果你计划申请新西兰的访问学者签证&#xff0c;以下是知识人网小编整理的一些你需要注意的事项&#xff1a; 1. 确认申请资格&#xff1a;在开始申请之前&#xff0c;确保你…

旧手机不要轻易扔掉,将其设置为无线网卡,不消耗流量

如果你有一部旧手机正在闲置着&#xff0c;或者正考虑要将其丢弃&#xff0c;那么请暂停一下。因为这个旧手机可以成为你的无线网卡&#xff0c;帮助你在家中或出行时实现更快的网络下载速度&#xff0c;而且毫不费流量。接下来&#xff0c;我将告诉你如何将旧手机变成无线网卡…

安装 Prometheus 指标存储 观测 dubbo /windows_exporter指标 windows 版本 其他系统换个语法思路一样

目录 下载 Prometheus 访问Prometheus Targets 发现服务 对应的 dubbo 指标就出来了 Dubbo脚手架生成个最简单的项目 导入 Prometheus 相关包 或者使用这个包即可 启动后就自动上报指标了 Windows_exporter or node_exporter 端口 9182 Prometheus 配置 windows_exp…

基于SpringBoot+vue的旧物置换网站设计与实现

博主介绍&#xff1a; 大家好&#xff0c;我是一名在Java圈混迹十余年的程序员&#xff0c;精通Java编程语言&#xff0c;同时也熟练掌握微信小程序、Python和Android等技术&#xff0c;能够为大家提供全方位的技术支持和交流。 我擅长在JavaWeb、SSH、SSM、SpringBoot等框架…

【产品应用】一体化步进电机在全自动纸张分切机的应用

全自动纸张分切机是现代印刷业中的重要设备之一&#xff0c;它能够将大的纸张切割成相同大小的小纸张&#xff0c;并具有高精度、高速度和高效率等优点。一体化步进电机作为全自动纸张分切机的重要部件&#xff0c;其应用对于提高设备的性能和稳定性具有重要意义。 01.设备简介…

【Java.SE】数组的练习

作者简介&#xff1a; 辭七七&#xff0c;目前大一&#xff0c;正在学习C/C&#xff0c;Java&#xff0c;Python等 作者主页&#xff1a; 七七的个人主页 文章收录专栏&#xff1a;Java.SE&#xff0c;本专栏主要讲解运算符&#xff0c;程序逻辑控制&#xff0c;方法的使用&…

Servlet从入门到精通

概述&#xff1a; Servlet&#xff08;Server Applet&#xff09;是Java Server的简称&#xff0c;称为小服务程序或服务网连接器&#xff0c;用Java编写的服务器端服务&#xff0c;具有独立于平台和协议的特性&#xff0c;主要功能在于交互式的浏览和生成数据&#xff0c;生成…

【Linux】线程同步(互斥锁和读写锁)

概念 线程同步是指多个线程之间协调和管理彼此的执行顺序&#xff0c;以避免竞态条件和不确定的结果。线程同步的目的是确保共享资源的正确访问和保护临界区的完整性。 作用 避免竞态条件&#xff1a;当多个线程同时访问和修改共享资源时&#xff0c;可能会导致竞态条件的发生…

初创企业办公室租赁现状

概述&#xff1a; 随着创业生态的不断发展&#xff0c;越来越多的初创企业开始涌现。租赁办公室是初创企业成立和运营的必要条件之一&#xff0c;然而&#xff0c;由于各种原因&#xff0c;租赁办公室对于初创企业来说仍然存在一些挑战和难点。本文将探讨初创企业租赁办公室的…