精华内容
下载资源
问答
  • redis实现计数器

    2019-09-27 07:36:54
    redis实现计数器 社交产品业务里有很多统计计数的功能,比如: 用户: 总点赞数,关注数,粉丝数 帖子: 点赞数,评论数,热度 消息: 已读,未读,红点消息数 话题: 阅读数,帖子数,收藏数 统计计数的特点 ...

    用redis实现计数器

    社交产品业务里有很多统计计数的功能,比如:

    • 用户: 总点赞数,关注数,粉丝数
    • 帖子: 点赞数,评论数,热度
    • 消息: 已读,未读,红点消息数
    • 话题: 阅读数,帖子数,收藏数

    统计计数的特点

    • 实时性要求高
    • 写的频率很高
    • 写的性能对MySQL是一个挑战

    可以采用redis来优化高频率写入的性能要求。

    redis优化方案一

    对于每一个实体的计数,设计一个hash结构的counter:

    //用户
    counter:user:{userID}
                            ->  praiseCnt: 100      //点赞数
                            ->  hostCnt: 200        //热度
                            ->  followCnt: 332      //关注数
                            ->  fansCnt: 123        //粉丝数
    
    
    //帖子
    counter:topic:{topicID}
                            -> praiseCnt: 100       //点赞数
                            -> commentCnt: 322      //评论数
    
    
    //话题
    counter:subject:{subjectID}
                                -> favoCnt: 312     //收藏数
                                -> viewCnt: 321     //阅读数
                                -> searchCnt: 212   //搜索进入次数
                                -> topicCnt: 312    //话题中帖子数 

    类似这种计数器,随着产品功能的增加,也会越来越多,比如回复数,踩数,转发数什么的。

    redis相关的命令

    //获取指定userID的所有计数器
    HGETALL counter:user:{userID}   
    
    //获取指定userID的指定计数器
    HMGET counter:user:{userID}  praiseCnt hostCnt 
    
    //指定userID点赞数+1
    HINCRBY counter:user:{userID}   praiseCnt 

    缺点:这样设计,如果要批量查询多个用户的数据,就比较麻烦,例如一次要查指定20个userID的计数器?只能循环执行 HGETALL counter:user:{userID}。

    优点:以实体聚合数据,方便数据管理

    redis优化方案二

    方案二是用来解决方案一的缺点的,依然是采用hash,结构设计是这样的:

    counter:user:praiseCnt
                            ->  userID_1001: 100
                            ->  userID_1002: 200
                            ->  userID_1003: 332
                            ->  userID_1004: 123
                                    .......
                            ->  userID_9999: 213
    
    
    
    counter:user:hostCnt
                            ->  userID_1001: 10
                            ->  userID_1002: 290
                            ->  userID_1003: 322
                            ->  userID_1004: 143
                                    .......
                            ->  userID_9999: 213
    
    
    counter:user:followCnt
                            ->  userID_1001: 21
                            ->  userID_1002: 10
                            ->  userID_1003: 32
                            ->  userID_1004: 203
                                    .......
                            ->  userID_9999: 130

    获取多个指定userID的点赞数的命令变成这样了

    HMGET counter:user:praiseCnt userID_1001 userID_1002

    上面命令可以批量获取多个用户的点赞数,时间复杂度为O(n),n为指定userID的数量。

    优点:解决了批量操作的问题

    缺点:当要获取多个计数器,比如同时需要praiseCnt,hostCnt时,要读多次,不过要比第一种方案读的次数要少。一个hash里的字段将会非常宠大,HMGET也许会有性能瓶颈。

    用redis管道(Pipelining)来优化方案一

    对于第一种方案的缺点,可以通过redis管道来优化,一次性发送多个命令给redis执行:

    $userIDArray = array(1001, 1002, 1003, 1009);
    
    $pipe = $redis->multi(Redis::PIPELINE);
    foreach ($userIDArray as $userID) {
        $pipe->hGetAll('counter:user:' . $userID);
    }
    
    $replies = $pipe->exec();
    print_r($replies); 

    还有一种方式是在redis上执行lua脚本,前提是你必须要学会写lua。

    转载于:https://www.cnblogs.com/ShenJunHui6/p/11127737.html

    展开全文
  • 本文主要向大家介绍了redis实现计数器防止刷单的方法和有关代码,具有一定参考价值,需要的朋友可以了解下。
  • redis实现计数器

    千次阅读 2019-04-08 08:57:17
    redis实现计数器 社交产品业务里有很多统计计数的功能,比如: 用户: 总点赞数,关注数,粉丝数 帖子: 点赞数,评论数,热度 消息: 已读,未读,红点消息数 话题: 阅读数,帖子数,收藏数 统计计数的特点...

    用redis实现计数器

    社交产品业务里有很多统计计数的功能,比如:

    • 用户: 总点赞数,关注数,粉丝数
    • 帖子: 点赞数,评论数,热度
    • 消息: 已读,未读,红点消息数
    • 话题: 阅读数,帖子数,收藏数

    统计计数的特点

    • 实时性要求高
    • 写的频率很高
    • 写的性能对MySQL是一个挑战

    可以采用redis来优化高频率写入的性能要求。

    redis优化方案一

    对于每一个实体的计数,设计一个hash结构的counter:

    //用户
    counter:user:{userID}
                            ->  praiseCnt: 100      //点赞数
                            ->  hostCnt: 200        //热度
                            ->  followCnt: 332      //关注数
                            ->  fansCnt: 123        //粉丝数
    
    
    //帖子
    counter:topic:{topicID}
                            -> praiseCnt: 100       //点赞数
                            -> commentCnt: 322      //评论数
    
    
    //话题
    counter:subject:{subjectID}
                                -> favoCnt: 312     //收藏数
                                -> viewCnt: 321     //阅读数
                                -> searchCnt: 212   //搜索进入次数
                                -> topicCnt: 312    //话题中帖子数 
    
    

    类似这种计数器,随着产品功能的增加,也会越来越多,比如回复数,踩数,转发数什么的。

    redis相关的命令

    //获取指定userID的所有计数器
    HGETALL counter:user:{userID}   
    
    //获取指定userID的指定计数器
    HMGET counter:user:{userID}  praiseCnt hostCnt 
    
    //指定userID点赞数+1
    HINCRBY counter:user:{userID}   praiseCnt 

    缺点:

    这样设计,如果要批量查询多个用户的数据,就比较麻烦,例如一次要查指定20个userID的计数器?只能循环执行 HGETALL counter:user:{userID}。

    优点:

    以实体聚合数据,方便数据管理

    redis优化方案二

    方案二是用来解决方案一的缺点的,依然是采用hash,结构设计是这样的:

    counter:user:praiseCnt
                            ->  userID_1001: 100
                            ->  userID_1002: 200
                            ->  userID_1003: 332
                            ->  userID_1004: 123
                                    .......
                            ->  userID_9999: 213
    
    
    
    counter:user:hostCnt
                            ->  userID_1001: 10
                            ->  userID_1002: 290
                            ->  userID_1003: 322
                            ->  userID_1004: 143
                                    .......
                            ->  userID_9999: 213
    
    
    counter:user:followCnt
                            ->  userID_1001: 21
                            ->  userID_1002: 10
                            ->  userID_1003: 32
                            ->  userID_1004: 203
                                    .......
                            ->  userID_9999: 130
    

    获取多个指定userID的点赞数的命令变成这样了:

    HMGET counter:user:praiseCnt userID_1001 userID_1002

    上面命令可以批量获取多个用户的点赞数,时间复杂度为O(n),n为指定userID的数量。

    优点:

    解决了批量操作的问题

    缺点:

    当要获取多个计数器,比如同时需要praiseCnt,hostCnt时,要读多次,不过要比第一种方案读的次数要少。
    一个hash里的字段将会非常宠大,HMGET也许会有性能瓶颈。

    用redis管道(Pipelining)来优化方案一

    对于第一种方案的缺点,可以通过redis管道来优化,一次性发送多个命令给redis执行:

    $userIDArray = array(1001, 1002, 1003, 1009);
    
    $pipe = $redis->multi(Redis::PIPELINE);
    foreach ($userIDArray as $userID) {
        $pipe->hGetAll('counter:user:' . $userID);
    }
    
    $replies = $pipe->exec();
    print_r($replies);  

    还有一种方式是在redis上执行lua脚本,前提是你必须要学会写lua。

    展开全文
  • Redis实现计数器---接口防刷---升级版(Redis+Lua)

    万次阅读 热门讨论 2018-03-18 19:54:39
    Cash Loan(一):Redis实现计数器---接口防刷 中介绍了项目中应用redis来做计数器的实现过程,最近自己看了些关于Redis实现分布式锁的代码后,发现在Redis分布式锁中出现一个问题在这版计数器中同样会出现,于是...

     

    【前言】

             Cash Loan(一):Redis实现计数器---接口防刷  中介绍了项目中应用redis来做计数器的实现过程,最近自己看了些关于Redis实现分布式锁的代码后,发现在Redis分布式锁中出现一个问题在这版计数器中同样会出现,于是融入了Lua脚本进行升级改造有了Redis+Lua版本。

    【实现过程】

                一、问题分析

                     如果set命令设置上,但是在设置失效时间时由于网络抖动等原因导致没有设置成功,这时就会出现死计数器(类似死锁);

                二、解决方案

                      Redis+Lua是一个很好的解决方案,使用脚本使得set命令和expire命令一同达到Redis被执行且不会被干扰,在很大程度上保证了原子操作;

                      为什么说是很大程度上保证原子操作而不是完全保证?因为在Redis内部执行的时候出问题也有可能出现问题不过概率非常小;即使针对小概率事件也有相应的解决方案,比如解决死锁一个思路值得参考:防止死锁会将锁的值存成一个时间戳,即使发生没有将失效时间设置上在判断是否上锁时可以加上看看其中值距现在是否超过一个设定的时间,如果超过则将其删除重新设置锁。       

                三、代码改造

                      1、Redis+Lua锁的实现

    package han.zhang.utils;
    
    import org.springframework.data.redis.core.StringRedisTemplate;
    import org.springframework.data.redis.core.script.DigestUtils;
    import org.springframework.data.redis.core.script.RedisScript;
    
    import java.util.Collections;
    import java.util.UUID;
    
    public class RedisLock {
    
        private static final LogUtils logger = LogUtils.getLogger(RedisLock.class);
        private final StringRedisTemplate stringRedisTemplate;
        private final String lockKey;
        private final String lockValue;
        private boolean locked = false;
        /**
         * 使用脚本在redis服务器执行这个逻辑可以在一定程度上保证此操作的原子性
         * (即不会发生客户端在执行setNX和expire命令之间,发生崩溃或失去与服务器的连接导致expire没有得到执行,发生永久死锁)
         * <p>
         * 除非脚本在redis服务器执行时redis服务器发生崩溃,不过此种情况锁也会失效
         */
        private static final RedisScript<Boolean> SETNX_AND_EXPIRE_SCRIPT;
    
        static {
            StringBuilder sb = new StringBuilder();
            sb.append("if (redis.call('setnx', KEYS[1], ARGV[1]) == 1) then\n");
            sb.append("\tredis.call('expire', KEYS[1], tonumber(ARGV[2]))\n");
            sb.append("\treturn true\n");
            sb.append("else\n");
            sb.append("\treturn false\n");
            sb.append("end");
            SETNX_AND_EXPIRE_SCRIPT = new RedisScriptImpl<>(sb.toString(), Boolean.class);
        }
    
        private static final RedisScript<Boolean> DEL_IF_GET_EQUALS;
    
        static {
            StringBuilder sb = new StringBuilder();
            sb.append("if (redis.call('get', KEYS[1]) == ARGV[1]) then\n");
            sb.append("\tredis.call('del', KEYS[1])\n");
            sb.append("\treturn true\n");
            sb.append("else\n");
            sb.append("\treturn false\n");
            sb.append("end");
            DEL_IF_GET_EQUALS = new RedisScriptImpl<>(sb.toString(), Boolean.class);
        }
    
        public RedisLock(StringRedisTemplate stringRedisTemplate, String lockKey) {
            this.stringRedisTemplate = stringRedisTemplate;
            this.lockKey = lockKey;
            this.lockValue = UUID.randomUUID().toString() + "." + System.currentTimeMillis();
        }
    
        private boolean doTryLock(int lockSeconds) {
            if (locked) {
                throw new IllegalStateException("already locked!");
            }
            locked = stringRedisTemplate.execute(SETNX_AND_EXPIRE_SCRIPT, Collections.singletonList(lockKey), lockValue,
                    String.valueOf(lockSeconds));
            return locked;
        }
    
        /**
         * 尝试获得锁,成功返回true,如果失败立即返回false
         *
         * @param lockSeconds 加锁的时间(秒),超过这个时间后锁会自动释放
         */
        public boolean tryLock(int lockSeconds) {
            try {
                return doTryLock(lockSeconds);
            } catch (Exception e) {
                logger.error("tryLock Error", e);
                return false;
            }
        }
    
        /**
         * 轮询的方式去获得锁,成功返回true,超过轮询次数或异常返回false
         *
         * @param lockSeconds       加锁的时间(秒),超过这个时间后锁会自动释放
         * @param tryIntervalMillis 轮询的时间间隔(毫秒)
         * @param maxTryCount       最大的轮询次数
         */
        public boolean tryLock(final int lockSeconds, final long tryIntervalMillis, final int maxTryCount) {
            int tryCount = 0;
            while (true) {
                if (++tryCount >= maxTryCount) {
                    // 获取锁超时
                    return false;
                }
                try {
                    if (doTryLock(lockSeconds)) {
                        return true;
                    }
                } catch (Exception e) {
                    logger.error("tryLock Error", e);
                    return false;
                }
                try {
                    Thread.sleep(tryIntervalMillis);
                } catch (InterruptedException e) {
                    logger.error("tryLock interrupted", e);
                    return false;
                }
            }
        }
    
        /**
         * 解锁操作
         */
        public void unlock() {
            if (!locked) {
                throw new IllegalStateException("not locked yet!");
            }
            locked = false;
            // 忽略结果
            stringRedisTemplate.execute(DEL_IF_GET_EQUALS, Collections.singletonList(lockKey), lockValue);
        }
    
        private static class RedisScriptImpl<T> implements RedisScript<T> {
            private final String script;
            private final String sha1;
            private final Class<T> resultType;
    
            public RedisScriptImpl(String script, Class<T> resultType) {
                this.script = script;
                this.sha1 = DigestUtils.sha1DigestAsHex(script);
                this.resultType = resultType;
            }
    
            @Override
            public String getSha1() {
                return sha1;
            }
    
            @Override
            public Class<T> getResultType() {
                return resultType;
            }
    
            @Override
            public String getScriptAsString() {
                return script;
            }
        }
    }

                      2、借鉴锁实现Redis+Lua计数器

                       (1)工具类            

    package han.zhang.utils;
    
    import org.springframework.data.redis.core.StringRedisTemplate;
    import org.springframework.data.redis.core.script.DigestUtils;
    import org.springframework.data.redis.core.script.RedisScript;
    
    import java.util.Collections;
    
    public class CountUtil {
    
        private static final LogUtils logger = LogUtils.getLogger(CountUtil.class);
        private final StringRedisTemplate stringRedisTemplate;
    
        /**
         * 使用脚本在redis服务器执行这个逻辑可以在一定程度上保证此操作的原子性
         * (即不会发生客户端在执行setNX和expire命令之间,发生崩溃或失去与服务器的连接导致expire没有得到执行,发生永久死计数器)
         * <p>
         * 除非脚本在redis服务器执行时redis服务器发生崩溃,不过此种情况计数器也会失效
         */
        private static final RedisScript<Boolean> SET_AND_EXPIRE_SCRIPT;
    
        static {
            StringBuilder sb = new StringBuilder();
            sb.append("local visitTimes = redis.call('incr', KEYS[1])\n");
            sb.append("if (visitTimes == 1) then\n");
            sb.append("\tredis.call('expire', KEYS[1], tonumber(ARGV[1]))\n");
            sb.append("\treturn false\n");
            sb.append("elseif(visitTimes > tonumber(ARGV[2])) then\n");
            sb.append("\treturn true\n");
            sb.append("else\n");
            sb.append("\treturn false\n");
            sb.append("end");
            SET_AND_EXPIRE_SCRIPT = new RedisScriptImpl<>(sb.toString(), Boolean.class);
        }
    
    
        public CountUtil(StringRedisTemplate stringRedisTemplate) {
            this.stringRedisTemplate = stringRedisTemplate;
    
        }
    
        public boolean isOverMaxVisitTimes(String key, int seconds, int maxTimes) throws Exception {
            try {
                return stringRedisTemplate.execute(SET_AND_EXPIRE_SCRIPT, Collections.singletonList(key), String.valueOf(seconds), String.valueOf(maxTimes));
    
            } catch (Exception e) {
                logger.error("RedisBusiness>>>isOverMaxVisitTimes; get visit times Exception; key:" + key + "result:" + e.getMessage());
                throw new Exception("already Over MaxVisitTimes");
            }
        }
    
    
        private static class RedisScriptImpl<T> implements RedisScript<T> {
            private final String script;
            private final String sha1;
            private final Class<T> resultType;
    
            public RedisScriptImpl(String script, Class<T> resultType) {
                this.script = script;
                this.sha1 = DigestUtils.sha1DigestAsHex(script);
                this.resultType = resultType;
            }
    
            @Override
            public String getSha1() {
                return sha1;
            }
    
            @Override
            public Class<T> getResultType() {
                return resultType;
            }
    
            @Override
            public String getScriptAsString() {
                return script;
            }
        }
    }
    

                       (2)调用测试代码

        public void run(String... strings) {
            CountUtil countUtil = new CountUtil(SpringUtils.getStringRedisTemplate());
            try {
                for (int i = 0; i < 10; i++) {
                    boolean overMax = countUtil.isOverMaxVisitTimes("zhanghantest", 600, 2);
                    if (overMax) {
                        System.out.println("超过i:" + i + ":" + overMax);
                    } else {
                        System.out.println("没超过i:" + i + ":" + overMax);
                    }
                }
            } catch (Exception e) {
                logger.error("Exception {}", e.getMessage());
            }
        }

                       (3)测试结果

     

     

    【总结】

           1、用心去不断的改造自己的程序;

           2、用代码改变世界。

    展开全文
  • Redis实现计数器功能(接口最大访问次数检查) 1.前言 备注:不要全部粘贴代码,这个是我练习项目的代码(基于spring-boot),主要看2和3的逻辑 1.还是一个简单的redis使用Demo,包含setIfAbsent(不存在则插入),...

    Redis实现计数器功能(接口最大访问次数检查)

    1.前言

    备注:不要全部粘贴代码,这个是我练习项目的代码(基于spring-boot),主要看23的逻辑
    1.还是一个简单的redis使用Demo,包含setIfAbsent(不存在则插入),setIfPresent(存在则更新),getExpire(获取剩余有效时间)
    2.大体业务逻辑如下:检查用户请求系统的最大次数,每日最大请求次数为10.
    3.也可以使用定时任务实现时效性配置(定时任务00:00执行,清空所有用户访问次数)

    2.Redis配置

    pom文件redis配置:

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

    properties文件redis配置:

    #redis 配置
    spring.redis.host=127.0.0.1
    spring.redis.port=6379
    spring.redis.jedis.pool.max-active=8
    spring.redis.timeout=2000
    

    redis序列化配置:
    配置序列化,防止插入redis乱码(默认二进制)

    import com.fasterxml.jackson.annotation.JsonAutoDetect;
    import com.fasterxml.jackson.annotation.PropertyAccessor;
    import com.fasterxml.jackson.databind.ObjectMapper;
    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.RedisTemplate;
    import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
    import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
    import org.springframework.data.redis.serializer.StringRedisSerializer;
    
    @Configuration
    public class RedisUtil  {
        /**
         * 因为序列化使用的jdkSerializeable ,redis存储数据默认为二进制,简单来说key和value是乱码
         * 所以自定义序列化类
         */
        @Bean
        public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
            RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
            redisTemplate.setConnectionFactory(redisConnectionFactory);
    
            // 使用Jackson2JsonRedisSerialize 替换默认序列化
            Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
    
            ObjectMapper objectMapper = new ObjectMapper();
            objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
            objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
    
            jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
    
            // 设置value的序列化规则和 key的序列化规则
            redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
            redisTemplate.setKeySerializer(new StringRedisSerializer());
            redisTemplate.afterPropertiesSet();
            return redisTemplate;
        }
    }
    

    3.redis判断访问次数

    RedisService代码:

    public interface RedisService {
        /**
         * 更新时效性数据--String类型
         **/
        Boolean updateVaildValue(String key, String value);
    
        /**
         * 判断当前用户是否达到访问最大次数
         **/
        Boolean judgeMaxTimesByUserId(String key, String min, String max);
    }
    

    RedisServiceImpl代码:

    import com.example.demo.util.redisUtil.RedisService.RedisService;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.redis.core.StringRedisTemplate;
    import org.springframework.stereotype.Service;
    import org.springframework.util.StringUtils;
    
    import java.math.BigDecimal;
    import java.util.concurrent.TimeUnit;
    
    @Slf4j
    @Service
    public class RedisServiceImpl implements RedisService {
    
        @Autowired
        private StringRedisTemplate stringRedisTemplate;
    
        /**
         * @Author longtao
         * @Date 2020/10/20
         * @Describe 更新失效性数据  保留剩余有效时间
         **/
        @Override
        public synchronized Boolean updateVaildValue(String key, String value) {
            //获取剩余时间 单位秒
            Long balanceSeconds = stringRedisTemplate.opsForValue().getOperations().getExpire(key);
            if (balanceSeconds > 0) {
                return stringRedisTemplate.opsForValue().setIfPresent(key, value, balanceSeconds, TimeUnit.SECONDS);
            }
            return false;
        }
        /**
         * @Author longtao
         * @Date   2020/10/20
         * @Describe 判断 最大访问次数 > userId当前访问次数
         **/
        @Override
        public synchronized Boolean judgeMaxTimesByUserId(String key, String min, String max) {
            //获取key的值,为null时则插入新的
            //不为null时,取出数据进行判断:是否达到最大值
            String value = stringRedisTemplate.opsForValue().get(key);
            if(StringUtils.isEmpty(value)){
            	//这里时间暂用的24小时,也可以计算得到当前时间到24:00点的毫秒时间。
                stringRedisTemplate.opsForValue().setIfAbsent(key,min,24,TimeUnit.HOURS);
                return true;
            }
            //最大次数 <= 当前访问次数
            if(new BigDecimal(max).compareTo(new BigDecimal(value)) <= 0 ){
                return false;
            }
            return updateVaildValue(key,new BigDecimal(value).add(new BigDecimal(1)).toString());
        }
    }
    

    4.切面调用检查逻辑

    切面注解----CheckVisitTimesAroundAnnotation代码:

    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface CheckVisitTimesAroundAnnotation {
    
    }
    

    切面注解----CheckVisitTimesAroundAspection代码:

    //返回异常对象
    return new BaseResponse<>(ResultEnum.CHECK_USER_ID_VISIT_TIMES);
    这个可以换成日志输出,这里直接粘贴了我的Demo项目代码。

    import com.alibaba.excel.util.StringUtils;
    import com.alibaba.fastjson.JSON;
    import com.alibaba.fastjson.JSONObject;
    import com.example.demo.base.Enum.ResultEnum;
    import com.example.demo.base.model.baseModel.BaseModel;
    import com.example.demo.base.model.baseRequest.BaseRequest;
    import com.example.demo.base.model.baseResponse.BaseResponse;
    import com.example.demo.util.redisUtil.RedisService.RedisService;
    import lombok.extern.slf4j.Slf4j;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Pointcut;
    import org.aspectj.lang.reflect.MethodSignature;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.core.annotation.Order;
    import org.springframework.stereotype.Component;
    
    import java.lang.reflect.Method;
    
    @Aspect
    @Component
    @Slf4j
    @Order(2)
    public class CheckVisitTimesAroundAspection {
    
        @Autowired
        private RedisService redisService;
        /**
         * 切入点
         */
        @Pointcut("@annotation(com.example.demo.base.annonation.CheckVisitTimesAroundAnnotation) ")
        public void entryPoint() {
        }
    
        /**
         * @return
         * @Describe 前置切面
         */
        @Around("entryPoint()")
        public Object aroundMethod(ProceedingJoinPoint proceedingJoinPoint) {
            log.info("------检查userId访问次数限制------start");
            Object[] args = proceedingJoinPoint.getArgs();
            //打印入口方法以及入参
            try {
                for (Object arg : args) {
                    Boolean flag = judgeVisitTimesByUserId(arg);
                    if(!flag){
                        //返回异常对象
                        return new BaseResponse<>(ResultEnum.CHECK_USER_ID_VISIT_TIMES);
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
                log.debug("不能打印请求参数");
            }
            //执行请求方法
            Object o = null;
            try {
                o = proceedingJoinPoint.proceed();
            } catch (Throwable throwable) {
                throwable.printStackTrace();
            }
            log.info("------检查userId访问次数限制------end");
            return o;
    
        }
    
        //检查userId请求接口限制
        public Boolean judgeVisitTimesByUserId(Object object){
            BaseModel baseModel = JSONObject.parseObject(JSONObject.toJSONString(object),BaseModel.class);
            String userId = baseModel.getUserId();
            if(!StringUtils.isEmpty(userId)){
                return redisService.judgeMaxTimesByUserId(userId,"1","10");
            }
            return true;
        }
    }
    

    在controller层方法上增加注解代码:
    增加@CheckVisitTimesAroundAnnotation注解

        @BaseBeforeAnnotation
        @CheckVisitTimesAroundAnnotation
        @RequestMapping("selectOne")
        public BaseResponse selectReaderInfo(@RequestBody ReaderInfoModel model) {
            ReaderInfoModel bookInfoModel = readerInfoService.selectOne(model.getId());
            return new BaseResponse(ResultEnum.SUCCESS,bookInfoModel);
        }
    

    4.执行结果

    redis中:userId 的值:
    在这里插入图片描述
    postman收到的返回值:
    在这里插入图片描述
    最后:本次写的比较仓促,我要接我老婆了。有不足的地方望大家指正和包涵!

    展开全文
  • Redis实现计数器---接口防刷

    万次阅读 多人点赞 2017-12-03 22:07:08
     1、Redis是单线程,我接触的很多人对这个概念有误解,我目录理解所谓单线程是指其在执行一个命令时是原子的不会被其他的命令所打断。  2、处女版分图解:    3、重生版图解:  4、大家可以结合...
  • Redis在2.6推出了脚本功能,允许开发者使用Lua语言编写脚本传到Redis中执行。 使用Lua脚本的好处 减少网络开销:通过在脚本中定义多条命令(甚至业务逻辑)可以减少了网络I/O开销。从这一点上看其比管道功能更强大。...
  • 三、计数器实现 背景:控制1分钟内的请求拉取照片次数 1、原代码实现: 1 String key = "gateway_ext_fetchdata_" + model.getExtCode(); 2 try { 3 String accessCount = flowControl.get(key)...
  • Redis Incr 命令将 key 中储存的数字值增一。如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 INCR 操作。且将key的有效时间设置为长期有效。 如果值包含错误的类型,或字符串类型的值不能表示为数字...
  • redis 分布式计数器

    2019-10-02 10:52:29
    使用redis的string类型可以实现分布式计数器 ****************************************************** 使用示例:使用计数器统计对某一路径的访问次数 实现方式:创建计数器,拦截器对指定的路径拦截获取相关...
  • redis 分布式计数器总结

    千次阅读 2019-06-19 14:56:01
    Redis实现分布式环境的计数器限流 http://www.gaowm.com/2018/05/11/Redis实现分布式环境的限流/ 利用Redis实现高并发计数器 https://blog.csdn.net/qq_33556185/article/details/79427271 ...
  • redis计数器,记录某活动使用量 刚刚入行的小白,什么都不太懂,最近某活动要求奖品领取量上限为20万,就想到用redis实现,代码如下: 代码片段 String redisKey= "Activity"; RedisUtil.setString(keyString, "0...
  • 默认引入了redis依赖,这里就不上依赖了。 这里需要的是生成一个huize20171027100140000001这种格式的字符串,可以看到前面大致是字符串+年月日时间分秒毫秒+三位自增num 接单表述一下基本思路: 这里先用时间(年月...
  • 使用redis计数器总结

    万次阅读 2017-03-24 22:34:24
    最近公司系统要求做一个防止刷单的安全拦截,初步拟定的规则是单个用户一天只拿下5单,单个ip一天只能下10单,这个时候自然考虑到了用redis来存储 下单计数,每天当第一个用户下单时,在redis中创建两个map对象,...
  • 解决方案:重复抢单 Redis原子计数器incr 实践 /** * 下单 * * @param time * @param id * @param username */ @Override public void add(String time, Long id, String username) { /** * 解决重复抢...
  • 主要为大家详细介绍了Redis实现高并发计数器,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
  • Redis原子计数器incr,防止并发请求

    万次阅读 2017-10-17 15:14:19
    一些对高并发请求有限制的系统或者功能里,比如说秒杀活动,或者一些网站返回的当前用户过多,请稍后...而在各种限流中,除了系统自身设计的带锁机制的计数器外,利用Redis实现显然是一种既高效安全又便捷方便的方式。
  • Redis原子计数器incr分布式唯一id生成器 为什么分布式系统需要用到ID生成系统 在复杂分布式系统中,往往需要对大量的数据和消息进行唯一标识。如在美团点评的金融、支付、餐饮、酒店、猫眼电影等产品的系统中,数据...
  • 利用Redis实现高并发计数器

    万次阅读 热门讨论 2018-03-02 22:58:57
    业务需求中经常有需要用到...使用Redis的Incr自增命令可以轻松实现以上需求。以一个接口一天限制调用次数为例: /** * 是否拒绝服务 * @return */ private boolean denialOfService(String userId){ long c...
  • java redis实现访问计数器

    千次阅读 2020-08-25 10:47:41
    redis工具类 @SuppressWarnings(value = { "unchecked", "rawtypes" }) @Component public class RedisCache { @Autowired public RedisTemplate redisTemplate; /** * 缓存基本的对象,Integer、String、实体...
  • Redis计数器场景上的应用

    千次阅读 2015-07-31 11:08:46
    对于计数器大家肯定还有或多或少的疑问。  Q1:计数从哪里来?  通常我们发布的社交内容会存储在数据库中,最常见的如MySQL:  更新索引:insert into user_message(uid,messagei

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 24,186
精华内容 9,674
关键字:

redis实现计数器

redis 订阅