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

可以做业务推广的网站有哪些值得浏览的外国网站

可以做业务推广的网站有哪些,值得浏览的外国网站,网页制作软件属于什么软件,wordpress去广告插件Redis 除了做缓存#xff0c;还能干很多很多事情#xff1a;分布式锁、限流、处理请求接口幂等性。。。太多太多了#xff5e;今天想和小伙伴们聊聊用 Redis 处理接口限流。1. 准备工作首先我们创建一个 Spring Boot 工程#xff0c;引入 Web 和 Redis 依赖#xff0c;同时…Redis 除了做缓存还能干很多很多事情分布式锁、限流、处理请求接口幂等性。。。太多太多了今天想和小伙伴们聊聊用 Redis 处理接口限流。1. 准备工作首先我们创建一个 Spring Boot 工程引入 Web 和 Redis 依赖同时考虑到接口限流一般是通过注解来标记而注解是通过 AOP 来解析的所以我们还需要加上 AOP 的依赖最终的依赖如下dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-data-redis/artifactId /dependency dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId /dependency dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-aop/artifactId /dependency然后提前准备好一个 Redis 实例这里我们项目配置好之后直接配置一下 Redis 的基本信息即可如下spring.redis.hostlocalhost spring.redis.port6379 spring.redis.password123好啦准备工作就算是到位了。2. 限流注解接下来我们创建一个限流注解我们将限流分为两种情况针对当前接口的全局性限流例如该接口可以在 1 分钟内访问 100 次。针对某一个 IP 地址的限流例如某个 IP 地址可以在 1 分钟内访问 100 次。针对这两种情况我们创建一个枚举类public enum LimitType {/*** 默认策略全局限流*/DEFAULT,/*** 根据请求者IP进行限流*/IP }接下来我们来创建限流注解Target(ElementType.METHOD) Retention(RetentionPolicy.RUNTIME) Documented public interface RateLimiter {/*** 限流key*/String key() default rate_limit:;/*** 限流时间,单位秒*/int time() default 60;/*** 限流次数*/int count() default 100;/*** 限流类型*/LimitType limitType() default LimitType.DEFAULT; }第一个参数限流的 key这个仅仅是一个前缀将来完整的 key 是这个前缀再加上接口方法的完整路径共同组成限流 key这个 key 将被存入到 Redis 中。另外三个参数好理解我就不多说了。好了将来哪个接口需要限流就在哪个接口上添加 RateLimiter 注解然后配置相关参数即可。3. 定制 RedisTemplate小伙伴们知道在 Spring Boot 中我们其实更习惯使用 Spring Data Redis 来操作 Redis不过默认的 RedisTemplate 有一个小坑就是序列化用的是 JdkSerializationRedisSerializer不知道小伙伴们有没有注意过直接用这个序列化工具将来存到 Redis 上的 key 和 value 都会莫名其妙多一些前缀这就导致你用命令读取的时候可能会出错。例如存储的时候key 是 namevalue 是 javaboy但是当你在命令行操作的时候get name 却获取不到你想要的数据原因就是存到 redis 之后 name 前面多了一些字符此时只能继续使用 RedisTemplate 将之读取出来。我们用 Redis 做限流会用到 Lua 脚本使用 Lua 脚本的时候就会出现上面说的这种情况所以我们需要修改 RedisTemplate 的序列化方案。可能有小伙伴会说为什么不用 StringRedisTemplate 呢StringRedisTemplate 确实不存在上面所说的问题但是它能够存储的数据类型不够丰富所以这里不考虑。修改 RedisTemplate 序列化方案代码如下Configuration public class RedisConfig {Beanpublic RedisTemplateObject, Object redisTemplate(RedisConnectionFactory connectionFactory) {RedisTemplateObject, Object redisTemplate new RedisTemplate();redisTemplate.setConnectionFactory(connectionFactory);// 使用Jackson2JsonRedisSerialize 替换默认序列化(默认采用的是JDK序列化)Jackson2JsonRedisSerializerObject jackson2JsonRedisSerializer new Jackson2JsonRedisSerializer(Object.class);ObjectMapper om new ObjectMapper();om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);jackson2JsonRedisSerializer.setObjectMapper(om);redisTemplate.setKeySerializer(jackson2JsonRedisSerializer);redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);redisTemplate.setHashKeySerializer(jackson2JsonRedisSerializer);redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);return redisTemplate;} }这个其实也没啥好说的key 和 value 我们都使用 Spring Boot 中默认的 jackson 序列化方式来解决。4. 开发 Lua 脚本这个其实我在之前 vhr 那一套视频中讲过Redis 中的一些原子操作我们可以借助 Lua 脚本来实现想要调用 Lua 脚本我们有两种不同的思路在 Redis 服务端定义好 Lua 脚本然后计算出来一个散列值在 Java 代码中通过这个散列值锁定要执行哪个 Lua 脚本。直接在 Java 代码中将 Lua 脚本定义好然后发送到 Redis 服务端去执行。Spring Data Redis 中也提供了操作 Lua 脚本的接口还是比较方便的所以我们这里就采用第二种方案。我们在 resources 目录下新建 lua 文件夹专门用来存放 lua 脚本脚本内容如下local key KEYS[1] local count tonumber(ARGV[1]) local time tonumber(ARGV[2]) local current redis.call(get, key) if current and tonumber(current) count thenreturn tonumber(current) end current redis.call(incr, key) if tonumber(current) 1 thenredis.call(expire, key, time) end return tonumber(current)这个脚本其实不难大概瞅一眼就知道干啥用的。KEYS 和 ARGV 都是一会调用时候传进来的参数tonumber 就是把字符串转为数字redis.call 就是执行具体的 redis 指令具体流程是这样首先获取到传进来的 key 以及 限流的 count 和时间 time。通过 get 获取到这个 key 对应的值这个值就是当前时间窗内这个接口可以访问多少次。如果是第一次访问此时拿到的结果为 nil否则拿到的结果应该是一个数字所以接下来就判断如果拿到的结果是一个数字并且这个数字还大于 count那就说明已经超过流量限制了那么直接返回查询的结果即可。如果拿到的结果为 nil说明是第一次访问此时就给当前 key 自增 1然后设置一个过期时间。最后把自增 1 后的值返回就可以了。其实这段 Lua 脚本很好理解。接下来我们在一个 Bean 中来加载这段 Lua 脚本如下Bean public DefaultRedisScriptLong limitScript() {DefaultRedisScriptLong redisScript new DefaultRedisScript();redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource(lua/limit.lua)));redisScript.setResultType(Long.class);return redisScript; }可以啦我们的 Lua 脚本现在就准备好了。5. 注解解析接下来我们就需要自定义切面来解析这个注解了我们来看看切面的定义Aspect Component public class RateLimiterAspect {private static final Logger log LoggerFactory.getLogger(RateLimiterAspect.class);Autowiredprivate RedisTemplateObject, Object redisTemplate;Autowiredprivate RedisScriptLong limitScript;Before(annotation(rateLimiter))public void doBefore(JoinPoint point, RateLimiter rateLimiter) throws Throwable {String key rateLimiter.key();int time rateLimiter.time();int count rateLimiter.count();String combineKey getCombineKey(rateLimiter, point);ListObject keys Collections.singletonList(combineKey);try {Long number redisTemplate.execute(limitScript, keys, count, time);if (numbernull || number.intValue() count) {throw new ServiceException(访问过于频繁请稍候再试);}log.info(限制请求{},当前请求{},缓存key{}, count, number.intValue(), key);} catch (ServiceException e) {throw e;} catch (Exception e) {throw new RuntimeException(服务器限流异常请稍候再试);}}public String getCombineKey(RateLimiter rateLimiter, JoinPoint point) {StringBuffer stringBuffer new StringBuffer(rateLimiter.key());if (rateLimiter.limitType() LimitType.IP) {stringBuffer.append(IpUtils.getIpAddr(((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest())).append(-);}MethodSignature signature (MethodSignature) point.getSignature();Method method signature.getMethod();Class? targetClass method.getDeclaringClass();stringBuffer.append(targetClass.getName()).append(-).append(method.getName());return stringBuffer.toString();} }Slf4jpublic class IPUtil {public static String getIpAddr() {HttpServletRequest request ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();String ip null;String ipAddresses request.getHeader(X-Forwarded-For);if (ipAddresses null || ipAddresses.length() 0 || unknown.equalsIgnoreCase(ipAddresses)) {ipAddresses request.getHeader(Proxy-Client-IP);}if (ipAddresses null || ipAddresses.length() 0 || unknown.equalsIgnoreCase(ipAddresses)) {ipAddresses request.getHeader(WL-Proxy-Client-IP);}if (ipAddresses null || ipAddresses.length() 0 || unknown.equalsIgnoreCase(ipAddresses)) {ipAddresses request.getHeader(HTTP_CLIENT_IP);}if (ipAddresses null || ipAddresses.length() 0 || unknown.equalsIgnoreCase(ipAddresses)) {ipAddresses request.getHeader(X-Real-IP);}if (ipAddresses ! null ipAddresses.length() ! 0) {ip ipAddresses.split(,)[0];}if (ip null || ip.length() 0 || unknown.equalsIgnoreCase(ipAddresses)) {ip request.getRemoteAddr();}if (ipAddresses null || ipAddresses.length() 0 || unknown.equalsIgnoreCase(ipAddresses)) {ipAddresses request.getRemoteAddr();if (ipAddresses.equals(127.0.0.1) || ipAddresses.equals(0:0:0:0:0:0:0:1)) {//根据网卡取本机配置的IPInetAddress inet null;try {inet InetAddress.getLocalHost();} catch (UnknownHostException e) {log.error(获取ip失败 {}, Arrays.asList(e.getStackTrace()));}if (inet ! null) {ip inet.getHostAddress();} else {ip 127.0.0.1;}}}return ip;} }这个切面就是拦截所有加了 RateLimiter 注解的方法在前置通知中对注解进行处理。首先获取到注解中的 key、time 以及 count 三个参数。获取一个组合的 key所谓的组合的 key就是在注解的 key 属性基础上再加上方法的完整路径如果是 IP 模式的话就再加上 IP 地址。以 IP 模式为例最终生成的 key 类似这样rate_limit:127.0.0.1-org.javaboy.ratelimiter.controller.HelloController-hello如果不是 IP 模式那么生成的 key 中就不包含 IP 地址。将生成的 key 放到集合中。通过 redisTemplate.execute 方法取执行一个 Lua 脚本第一个参数是脚本所封装的对象第二个参数是 key对应了脚本中的 KEYS后面是可变长度的参数对应了脚本中的 ARGV。将 Lua 脚本执行的结果与 count 进行比较如果大于 count就说明过载了抛异常就行了。好了大功告成了。6. 接口测试接下来我们就进行接口的一个简单测试如下RestController public class HelloController {GetMapping(/hello)RateLimiter(time 5,count 3,limitType LimitType.IP)public String hello() {return hellonew Date();} }每一个 IP 地址在 5 秒内只能访问 3 次。这个自己手动刷新浏览器都能测试出来。7. 其他问题RedisTemplate执行lua脚本在Redis集群模式下报错EvalSha is not supported in cluster environme解决。原因spring自带的执行脚本方法中集群模式直接抛出不支持执行脚本的异常所以只能拿到原redis的connection来执行脚本。下面展示一些内联代码片段。 Before(annotation(rateLimiter))public void doBefore(JoinPoint point, RateLimiter rateLimiter) throws Throwable {String key rateLimiter.key();int time rateLimiter.time();int count rateLimiter.count();String combineKey getCombineKey(rateLimiter, point);ListString keys Collections.singletonList(combineKey);ListString paramList Lists.newArrayList(String.valueOf(count), String.valueOf(time),String.valueOf(System.currentTimeMillis()));try {Long number redisTemplate.execute((RedisCallbackLong) connection - {Object nativeConnection connection.getNativeConnection();// 集群if (nativeConnection instanceof JedisCluster) {return (Long) ((JedisCluster) nativeConnection).eval(LIMIT_SCRIPT_SLIDING_WINDOW_3, keys, paramList);}// 单机if (nativeConnection instanceof Jedis) {return (Long) ((Jedis) nativeConnection).eval(LIMIT_SCRIPT_SLIDING_WINDOW_3, keys, paramList);}return null;});if (number null || number.intValue() count) {log.error(访问过于频繁请稍候再试! 提示{}秒内最大并发请求次数为{}次, time, count);throw new BusinessException(访问过于频繁超过限流次数提示 time 秒内最大并发请求次数为 count 次);}log.info(限制请求{},当前请求{},缓存key{}, count, number.intValue(), key);} catch (BusinessException e) {throw e;} catch (Exception e) {throw new RuntimeException(服务器限流异常请稍候再试);}}redis限流算法常用的限流算法有固定窗口滑动窗口令牌桶等。前面给出的算法是固定窗口算法解决不了临界点并发访问超过阈值的问题。下面给出2个版本的滑动窗口lua代码。 /*** 滑动窗口算法-lua脚本字符串2*/private static final String LIMIT_SCRIPT_SLIDING_WINDOW_2 //获取keylocal key KEYS[1] //最大次数 local count tonumber(ARGV[1]) //缓存时间 local time tonumber(ARGV[2]) //当前时间 local currentMs tonumber(ARGV[3]) //窗口开始时间 local windowStartMs currentMs - time * 1000 //清除所有过期成员 redis.call(ZREMRANGEBYSCORE, key, 0, windowStartMs) //添加当前成员 redis.call(zadd, key, tostring(currentMs), currentMs) //获取key的次数 local current redis.call(zcard,key) //如果key的次数存在且大于预设值直接返回当前key的次数 if current and tonumber(current) count then return tonumber(current) end //设置过期时间删除冷数据 redis.call(expire, key, time) //返回key的次数 return tonumber(current);/*** 滑动窗口算法-lua脚本字符串3*/private static final String LIMIT_SCRIPT_SLIDING_WINDOW_3 local key KEYS[1] local count tonumber(ARGV[1]) local time tonumber(ARGV[2]) local currentMs tonumber(ARGV[3]) local windowStartMs currentMs - time * 1000 local current redis.call(zcount, key, windowStartMs, currentMs) if current and tonumber(current) count then return tonumber(current) end redis.call(ZREMRANGEBYSCORE, key, 0, windowStartMs) redis.call(zadd, key, tostring(currentMs), currentMs) redis.call(expire, key, time) return tonumber(current);个人认为LIMIT_SCRIPT_SLIDING_WINDOW_3更加准确一些因为它没有将超过限制的无用请求放入zset中请求数量是相对平滑的。参考文章https://mp.weixin.qq.com/s/ymgwN2w-YxCIug8lgxrwog
http://www.w-s-a.com/news/67484/

相关文章:

  • 电子商务市场的发展前景seo推广平台服务
  • 乐清网页设计公司哪家好seo推广任务小结
  • 360建筑网是什么pc优化工具
  • 越秀免费网站建设风景区网站建设项目建设可行性
  • 网站建站公司一站式服务学校网站开发招标
  • asp.net mvc 5 网站开发之美电商网站 流程图
  • 室内设计素材网站推荐郑州专业做淘宝网站建设
  • 新建的网站怎么做seo优化模板规格尺寸及价格
  • 平湖网站设计做电子元器件销售什么网站好
  • 可视化网站模板我想建个网站网站怎么建域名
  • 达州网站建设qinsanw南京市建设发展集团有限公司网站
  • django 网站开发实例公司排行榜
  • 韩国做美食网站阳江网站建设 公司价格
  • 网站开发哪里接业务长春高端模板建站
  • 深圳网站制作公司方案dw一个完整网页的代码
  • asp手机网站源码下载做seo推广网站
  • 网站优化建议怎么写网站维护主要有哪些内容和方法
  • 建设网站需要钱吗网络推广加盟
  • 高清素材图片的网站泰安网签备案查询
  • 自助网站建设怎么建设房地产的最新政策
  • 企业网站 生成html网站侵权怎么做公证或证据保存
  • php 手机网站cms系统购物网站制作流程
  • 网络公司网站开发河北省城乡住房和建设厅网站
  • 做网站配置wordpress 中文api
  • 怎样把网站做的好看县蒙文网站建设汇报
  • 网站的优化什么做广西桂林新闻最新消息
  • 做网站准备什么软件搜索引擎广告推广
  • 网站开发地图板块浮动网页设计与制作的模板
  • 中国建设招聘信息网站昆明做网站建设的公司排名
  • 那些网站可以做自媒体wordpress 分类seo