精华内容
下载资源
问答
  • scrapy-redis框架中,reids存储的xxx:requests已经爬取完毕,但程序仍然一直运行,如何自动停止程序,结束空跑。 相信大家都很头疼,尤其是网上一堆搬来搬去的帖子,来看一下 我是如何解决这个问题的吧 课外了解 ...

     

    问题:

    • scrapy-redis框架中,reids存储的xxx:requests已经爬取完毕,但程序仍然一直运行,如何自动停止程序,结束空跑。

    相信大家都很头疼,尤其是网上一堆搬来搬去的帖子,来看一下 我是如何解决这个问题的吧

    课外了解

    分布式扩展:

    我们知道 scrapy 默认是单机运行的,那么scrapy-redis是如何把它变成可以多台机器协作的呢?

    首先解决爬虫等待,不被关闭的问题:

    1、scrapy内部的信号系统会在爬虫耗尽内部队列中的request时,就会触发spider_idle信号。

    2、爬虫的信号管理器收到spider_idle信号后,将调用注册spider_idle信号的处理器进行处理。

    3、当该信号的所有处理器(handler)被调用后,如果spider仍然保持空闲状态, 引擎将会关闭该spider。

    scrapy-redis 中的解决方案 在信号管理器上注册一个对应在spider_idle信号下的spider_idle()方法,当spider_idle触发是,信号管理器就会调用这个爬虫中的spider_idle(), Scrapy_redis 源码如下:

        def spider_idle(self):
            """Schedules a request if available, otherwise waits."""
            # XXX: Handle a sentinel to close the spider.
            self.schedule_next_requests()    # 这里调用schedule_next_requests() 来从redis中生成新的请求
            raise DontCloseSpider              # 抛出不要关闭爬虫的DontCloseSpider异常,保证爬虫活着
    
    

    解决思路:

    • 通过前面的了解,我们知道 爬虫关闭的关键是 spider_idle 信号。
    • spider_idle信号只有在爬虫队列为空时才会被触发, 触发间隔为5s。
    • 那么我们也可以使用同样的方式,在信号管理器上注册一个对应在spider_idle信号下的spider_idle()方法。
    • 在 spider_idle() 方法中,编写结束条件来结束爬虫,这里以 判断redis 中关键key 是否为空,为条件

    解决方案:

    • redis_key 为空后一段时间关闭爬虫

    redis_key 为空后一段时间关闭爬虫 的实现方案:

    这里在 Scrapy 中的 exensions(扩展) 中实现,当然你也可以在pipelines(管道)中实现。

    扩展框架提供一个机制,使得你能将自定义功能绑定到Scrapy。 扩展只是正常的类,它们在Scrapy启动时被实例化、初始化。 关于扩展详细见: scrapy 扩展(Extensions)

    • 在settings.py 文件的目录下,创建一个名为 extensions.py 的文件,
    • 在其中写入以下代码
    # -*- coding: utf-8 -*-
    # Define here the models for your scraped Extensions
    import logging
    import time
    from scrapy import signals
    from scrapy.exceptions import NotConfigured
    logger = logging.getLogger(__name__)
    
    
    class RedisSpiderSmartIdleClosedExensions(object):
    
        def __init__(self, idle_number, crawler):
            self.crawler = crawler
            self.idle_number = idle_number
            self.idle_list = []
            self.idle_count = 0
    
        @classmethod
        def from_crawler(cls, crawler):
            # 首先检查是否应该启用和提高扩展
            # 否则不配置
            if not crawler.settings.getbool('MYEXT_ENABLED'):
                raise NotConfigured
    
            # 获取配置中的时间片个数,默认为360个,30分钟
            idle_number = crawler.settings.getint('IDLE_NUMBER', 360)
    
            # 实例化扩展对象
            ext = cls(idle_number, crawler)
    
            # 将扩展对象连接到信号, 将signals.spider_idle 与 spider_idle() 方法关联起来。
            crawler.signals.connect(ext.spider_opened, signal=signals.spider_opened)
            crawler.signals.connect(ext.spider_closed, signal=signals.spider_closed)
            crawler.signals.connect(ext.spider_idle, signal=signals.spider_idle)
    
            # return the extension object
            return ext
    
        def spider_opened(self, spider):
            logger.info("opened spider %s redis spider Idle, Continuous idle limit: %d", spider.name, self.idle_number)
    
        def spider_closed(self, spider):
            logger.info("closed spider %s, idle count %d , Continuous idle count %d",
                        spider.name, self.idle_count, len(self.idle_list))
    
        def spider_idle(self, spider):
            self.idle_count += 1                        # 空闲计数
            self.idle_list.append(time.time())       # 每次触发 spider_idle时,记录下触发时间戳
            idle_list_len = len(self.idle_list)         # 获取当前已经连续触发的次数
    
            # 判断 当前触发时间与上次触发时间 之间的间隔是否大于5秒,如果大于5秒,说明redis 中还有key 
            if idle_list_len > 2 and self.idle_list[-1] - self.idle_list[-2] > 6:
                self.idle_list = [self.idle_list[-1]]
    
            elif idle_list_len > self.idle_number:
                # 连续触发的次数达到配置次数后关闭爬虫
                logger.info('\n continued idle number exceed {} Times'
                            '\n meet the idle shutdown conditions, will close the reptile operation'
                            '\n idle start time: {},  close spider time: {}'.format(self.idle_number,
                                                                                  self.idle_list[0], self.idle_list[0]))
                # 执行关闭爬虫操作
                self.crawler.engine.close_spider(spider, 'closespider_pagecount')
    • 在settings.py 中添加以下配置, 请将 lianjia_ershoufang 替换为你的项目目录名。
    MYEXT_ENABLED=True      # 开启扩展
    IDLE_NUMBER=360           # 配置空闲持续时间单位为 360个 ,一个时间单位为5s
    
    # 在 EXTENSIONS 配置,激活扩展
    'EXTENSIONS'= {
                'lianjia_ershoufang.extensions.RedisSpiderSmartIdleClosedExensions': 500,
            },
    • 完成空闲关闭扩展,爬虫会在持续空闲 360个时间单位后关闭爬虫

    配置说明:

    MYEXT_ENABLED: 是否启用扩展,启用扩展为 True, 不启用为 False
    IDLE_NUMBER: 关闭爬虫的持续空闲次数,持续空闲次数超过IDLE_NUMBER,爬虫会被关闭。默认为 360 ,也就是30分钟,一分钟12个时间单位

    结语

    此方法只使用于 5秒内跑不完一组链接的情况,如果你的一组链接5秒就能跑完,你可以在此基础上做一些判断。原理一样,大家可以照葫芦画瓢。

    此方法只使用于 5秒内跑不完一组链接的情况,如果你的一组链接5秒就能跑完,你可以在此基础上做一些判断。原理一样,大家可以照葫芦画瓢。

    哈哈,我的方式是不是特别棒呀!

    展开全文
  • Redis莫名其妙自动中断原因排查暨Swap交换分区的创建 Redis莫名其妙自动中断原因排查暨Swap交换分区的创建 Linux Centos 安装了Redis,大概跑2、3天就出现redis失效,网站监控报警,查了redis的日志,竟然没有log...

    Redis莫名其妙自动中断原因排查暨Swap交换分区的创建

    Redis莫名其妙自动中断原因排查暨Swap交换分区的创建

    Linux Centos 安装了Redis,大概跑2、3天就出现redis失效,网站监控报警,查了redis的日志,竟然没有log中断的原因提示,对redis的稳定性产生了怀疑,度娘了大家的评论资料,对redis的稳定性还是肯定的,出问题的原因在于设置没优化。

    1.合理配置 maxmemory 参数

    redis.conf中maxmemory参数意味着redis可使用的最大内存,设置过小效率不高,设置过大会用爆内存,产生崩溃,大多专家的建议,不宜超过物理内存的2/3或3/5,我们保守点选择按物理内存的3/5设置。但运行了2天,仍然无报警自动中断,再查,原来内存用爆了就用到了swap交换文件了,free 一下,系统竟然没有设置swap。

    2.创建Swap交换文件

    创建swap交换文件一般有两种方式:基于分区方式和基于文件方式。基于分区方式一般在系统初建时就分配设置,在系统创建时就应该注意先创建swap,否则只好用基于文件的方式。

    2.1 创建基于分区的swap分区

    用fdisk命令创建新的空白分区,同时修改文件系统为82(Linux swap):

    #fdisk /dev/vda
    #partprobe
    #lsblk -f /dev/vda2
    NAME FSTYPE LABEL UUID                                 MOUNTPOINT
    vda2 swap         fed3d6d167............
    

    用mkswap命令创建swap分区:

    #mkswap /dev/vda2
    mkswap: /dev/vda2: warning: wiping old swap signature.
    Setting up swapspace version 1, size = 1048572 KiB
    no label, UUID=fed3.........
    

    成功了。

    2.2 创建基于文件的swap分区

    用dd命令创建一个指定大小的数据文件,文件大小一般设定为物理内存一样的大小。

    #dd if=/dev/zero of=~/swap bs=1024 count=4096000
    4096000+0 records in
    4096000+0 records out
    4XXXXXXX bytes (4 GB) copied, 13.8478 s, 138 MB/s
    #file ~/swap
    /root/swap: data
    

    可以看到当前文件’swap’的文件类型为’data’。然后再基于此文件使用mkswap命令创建swap分区:

    #mkswap ~/swap
    Setting up swapspace version 1, size = 4096000 KiB
    no label, UUID=XXXX...............
    #file ~/swap
    /root/swap: Linux/i386 swap file (new style), version 1 (4K pages), size ?????? pages, no label, UUID=XXXX......
    

    3.激活已创建的swap分区

    不论是使用空白分区创建的swap分区,还是使用文件创建的Swap分区,均可使用swapon命令激活已创建的Swap交换分区:

    swapon -s
    Filename                                Type            Size    Used    Priority
    
    #swapon /dev/vda2
    #swapon -s
    Filename                                Type            Size    Used    Priority
    /dev/vda2                               partition       1048572 0       -2
    
    #swapon ~/swap
    swapon: /root/swap: insecure permissions 0644, 0600 suggested.
    #swapon -s
    Filename                                Type            Size    Used    Priority
    /dev/vda2                               partition       1048572 0       -2
    /root/swap                              file    2097148 0       -3
    

    可以清楚的看到刚才创建的swap分区,二者的类型分别为’partition’和’file’。另外,我们在使用swapon命令激活swap分区时,可使用-p选项指定优先级。
    现在可使用free -h命令再次查看Swap分区使用状态了:

    4.设定开机自动挂载

    已激活的Swap分区我们还需要设定开机自动挂载,否在在系统重启后,未设定开机挂载的Swap分区将无法使用。 现在需要将以下信息追加写入/etc/fstab文件末尾:

    /dev/vda2                       swap    swap defaults   0 0
    /root/swap                      swap    swap defaults   0 0
    

    调整 maxmemory 参数,并创建 Swap 分区后,跑了半个月测试, Redis 十分稳定!

    展开全文
  • 利用redis实现输入自动补全

    千次阅读 2018-05-28 10:57:14
    模拟场景: 假如要搭建一个公司内部的交流系统,其中有一个功能就是用户可以通过搜索查找公司内部... 我们使用redis作为缓存,然后利用redis中的有序集合数据结构能够根据成员分数自动排序的优点方便快速匹配;...

    模拟场景:

           假如要搭建一个公司内部的交流系统,其中有一个功能就是用户可以通过搜索查找公司内部所有员工,为了方便用户快速方便查找,需要提供快速自动补全姓名的查询,比如用户输入“王”,然后提示以“王”开头的所有用户;

    原理:

           我们使用redis的有序集合数据结构,有序集合有个特性就是当所有成员的分值都相等的时候,有序集合将根据成员的名字来进行排序;当所有成员的分值都为0时,成员将按照字符串的二进制顺利徐进行排序。

           假如:用户的名字都由小写字母组成,那么当输入"abc"的时候,那么以"abc"开头的所有字符将集中在从"abb{"(前驱字符串)到"abc{"(后继字符串)之间,因为在ASCII编码中,排在字母"z"之后的字符是"{"。

           由于redis中不能保存中文字符,那么我们需要提供一个中文和字符互转的方法,方便我们操作redis。

            public static String codeUnicode(String gbString) {
    		if (gbString == null) {
    			return "";
    		}
    		char[] utfChar = gbString.toCharArray();
    		String unicodeString = "";
    		for (int i=0; i<utfChar.length; i++) {
    			String hexB = Integer.toHexString(utfChar[i]);
    			if (hexB.length() <= 2) {
    				hexB = "00" + hexB;
    			}
    			unicodeString = unicodeString + "-" + hexB;
    		}
    		return unicodeString;
    	}
    
            public static String decodeUnicode(String unicodeString) {
    		if (unicodeString == null) {
    			return "";
    		}
    		String[] unicodeArray = unicodeString.split("-");
    		StringBuffer stringBuffer = new StringBuffer();
    		if (unicodeArray != null && unicodeArray.length > 0) {
    			for (int i=0; i<unicodeArray.length; i++) {
    				String arrayString = unicodeArray[i].trim();
    				if (arrayString != null && !"".equals(arrayString)) {
    					char c = (char) Integer.parseInt(arrayString, 16);
    					stringBuffer.append(new Character(c).toString());
    				}
    			}
    		}
    		return stringBuffer.toString();
    	}

    从代码中我们可以看到,我们把每一个字符转换成了16进制字符串,然后每个字符串之间用"-"隔开(方便后期我们把16进制字符串转换回来),所以转换后的字符串由字符"-"、数字和字母"a-f"组成,那么我们可以确定一个字符序列“,-0123456789abcdefg”,这个字符序列方便我们找到一个字符的前驱字符和后继字符。

            /**
    	 * 根据给出的前缀字符串计算出查找发范围
    	 * 原理:
    	 * 在redis的有序集合里面,当所有成员的分值都相等时,有序集合将按照成员的名字进行排序;而当所有成员的分值都是0时,成员按照字符串的二进制顺序排序;
    	 * 因为假系人的姓名先转换为了16进制字符串,那么比-小的字符是',',比'f'大的字符是'g';
    	 * 所以假如prefix='-00ff',那么所有已-00ff开头的字符串都在'-00feg'到'-00ffg'之间
    	 * @param prefix
    	 * @return
    	 */
            private static final String VALID_CHARACTERS = ",-0123456789abcdefg";
    	private String[] findPrefixRange(String prefix) {
    		int posn = VALID_CHARACTERS.indexOf(prefix.charAt(prefix.length() - 1));	//查找出前缀字符串最后一个字符在列表中的位置
                    char suffix = VALID_CHARACTERS.charAt(posn > 0 ? posn - 1 : 0);				//找出前驱字符
                    String start = prefix.substring(0, prefix.length() - 1) + suffix + 'g';		//生成前缀字符串的前驱字符串
                    String end = prefix + 'g';													//生成前缀字符串的后继字符串
                    return new String[]{start, end};
    	}

           到此为止,我们前期的工作已经完成。

             /**
    	 *  根据输入的姓名匹配全名
    	 * @param conn
    	 * @param guild
    	 * @param prefix
    	 * @return
    	 */
            private static final String guildName = "AUTO_COMPLETE";
    	public List<String> autocomplete(Jedis conn, String prefix) {
    		List<String> list = new ArrayList<>();
    		prefix = codeUnicode(prefix);            //把输入的字符转换成16进制字符串,因为redis里面存的是每个字符对应的16进制字符串
                    String[] range = findPrefixRange(prefix);
    		String start = range[0];
    		String end = range[1];			
    		String identifier = UUID.randomUUID().toString();	
    		start += identifier;
    		end += identifier;		//	防止多个群成员可以同时操作有序集合,将相同的前驱字符串和后继字符串插入有序集合
    		conn.zadd(guildName, 0, start);
    		conn.zadd(guildName, 0, end);
    		while (true) {
    			conn.watch(guildName);
    			int sindex = conn.zrank(guildName, start).intValue();
    			int eindex = conn.zrank(guildName, end).intValue();			//找出两个插入元素的位置
    			int erange = Math.min(sindex + 9, eindex-2);				//因为最多展示10个,所以计算出结束为止
    			Transaction transaction = conn.multi();
    			transaction.zrem(guildName, start);
    			transaction.zrem(guildName, end);
    			transaction.zrange(guildName, sindex, erange);
    			List<Object> results = transaction.exec();
    			if (results != null) {
    				Set<String> set = (Set<String>) results.get(results.size() - 1);
    				list.addAll(set);
    				break;
    			}
    		}
    		ListIterator<String> iterator = list.listIterator();
    		// 这里过滤多个成员添加前驱字符串和后继字符串引起的不符合的数据
    		while (iterator.hasNext()) {
    			String string = iterator.next();
                            if (string.indexOf("g") != -1) {
    				iterator.remove();
    			} else {
                                 iterator.set(decodeUnicode(string));        //把16进制字符串转换回来
                            }
    		}
    		return list;
    	}
            /**
    	 * 添加姓名到redis中
    	 */
    	public void joinGuild(Jedis conn, String member) {
    		conn.zadd(codeUnicode(guildName), 0, member);
    	}


    展开全文
  • 通过注解的方式,实现Redis 自动查找缓存,以及未命中时自动更新缓存1、写在前面的话2、你们可以白嫖的代码2.1、需要引入 jar 包2.2、yml 配置文件2.3、Redis 的配置类2.4、定义注解2.5、@Aspect 处理切面2.6 业务层...

    1、写在前面的话

     
           常在项目中使用 Redis 缓存以提高查询效率。遇到查询时一般的套路是,先去查Redis缓存,如果查询未命中缓存就去查数据库,并将数据库查询的数据放在缓存,下次查询时就可以直接查询缓存。
     
           写这篇博客的目的就是直接通过注解的方式,来完成上面的步骤。在编码时在业务层查询方法上,只要添加了注解,就自动处理了优先查缓存,以及未命中缓存时,对数据库的查询结果放入缓存。那么我们在业务层的查询方法,只需要关注未命中缓存时查询数据库的操作,提高编码效率。
     

    2、你们可以白嫖的代码

     

    2.1、需要引入 jar 包

     
    在pom文件中引入以下jar,需要说明的是我用的是 SpringBoot 构建的项目

            <!-- 添加 Redis依赖 -->
    		<dependency>
    			<groupId>org.springframework.boot</groupId>
    			<artifactId>spring-boot-starter-data-redis</artifactId>
    		</dependency>
    		<dependency>
    			<groupId>org.springframework.boot</groupId>
    			<artifactId>spring-boot-starter-aop</artifactId>
    		</dependency>
    		<!---->
    

     

    2.2、yml 配置文件

     
    这是使用 Redis 最基本的配置

    spring:
      redis:
        host: 127.0.0.1
      #Redis服务器连接端口
        port: 6379
      #Redis服务器连接密码(默认为空)
        password:
      #连接超时时间(毫秒)
        timeout: 30000
    

     

    2.3、Redis 的配置类

     

    
    
    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.Jackson2JsonRedisSerializer;
    import org.springframework.data.redis.serializer.StringRedisSerializer;
    import com.fasterxml.jackson.annotation.JsonAutoDetect;
    import com.fasterxml.jackson.annotation.PropertyAccessor;
    import com.fasterxml.jackson.databind.ObjectMapper;
    
    /**
     * redis配置类
     * @author STRANGE-P
     * @date 
     */
    @Configuration
    public class RedisConfig {
    
        @Bean
        @SuppressWarnings("all")
        public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
            RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
            template.setConnectionFactory(factory);
            Jackson2JsonRedisSerializer 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);
            StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
            // key采用String的序列化方式
            template.setKeySerializer(stringRedisSerializer);
            // hash的key也采用String的序列化方式
            template.setHashKeySerializer(stringRedisSerializer);
            // value序列化方式采用jackson
            template.setValueSerializer(jackson2JsonRedisSerializer);
            // hash的value序列化方式采用jackson
            template.setHashValueSerializer(jackson2JsonRedisSerializer);
            template.afterPropertiesSet();
            return template;
        }
    }
    
    
    

     

    2.4、定义注解

     

    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     * 定义注解
     * @author STRANGE-P
     * @date 
     */
    @Target({ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface RedisCacheable {
    
        /** 第一过期时间 **/
        long firstLayerTtl() default 10L;
    
        /** 第一过期时间 **/
        long secondLayerTtl() default 60L;
    
        /** Redis key 值 **/
        String key();
    
    }
    
    

     

    2.5、@Aspect 处理切面

     

    
    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.CodeSignature;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.expression.Expression;
    import org.springframework.expression.ExpressionParser;
    import org.springframework.expression.spel.standard.SpelExpressionParser;
    import org.springframework.expression.spel.support.StandardEvaluationContext;
    import org.springframework.stereotype.Component;
    
    import java.util.concurrent.TimeUnit;
    
    
    /**
     * 
     * @author STRANGE-P
     * @date 
     */
    @Aspect
    @Component
    public class RedisCacheableAspect {
    
        private static final Logger log = LoggerFactory.getLogger(RedisCacheableAspect.class);
        private static final ExpressionParser expressionParser = new SpelExpressionParser();
        @Autowired
        private RedisTemplate<String, Object> redisTemplate;
    
        public RedisCacheableAspect(RedisTemplate<String, Object> redisTemplate) {
            this.redisTemplate = redisTemplate;
        }
    
        @Pointcut("@annotation(redisCacheable)")
        public void RedisCacheablePointcut(RedisCacheable redisCacheable) {
        }
    
        private StandardEvaluationContext getContextContainingArguments(ProceedingJoinPoint joinPoint) {
            StandardEvaluationContext context = new StandardEvaluationContext();
            // 通过Java反射,解析ProceedingJoinPoint的方法参数及参数值
            CodeSignature codeSignature = (CodeSignature)joinPoint.getSignature();
            // 获取入参对象中的所有参数名
            String[] parameterNames = codeSignature.getParameterNames();
            // 获取连接点(joinPoint)的方法运行时的入参列表
            Object[] args = joinPoint.getArgs();
    
            for(int i = 0; i < parameterNames.length; ++i) {
                context.setVariable(parameterNames[i], null == args[i] ? "null" : args[i]);
            }
    
            return context;
        }
    
        private String getCacheKeyFromAnnotationKeyValue(StandardEvaluationContext context, String key) {
            // 表达式解析器,解析注解中的 key 值
            Expression expression = expressionParser.parseExpression(key);
            return (String)expression.getValue(context);
        }
    
        @Around("RedisCacheablePointcut(redisCacheable)")
        public Object cacheTwoLayered(ProceedingJoinPoint joinPoint, RedisCacheable redisCacheable) throws Throwable {
            // 获取注解中的第一过期时间
            long firstLayerTtl = redisCacheable.firstLayerTtl();
            // 获取注解中的第二过期时间
            long secondLayerTtl = redisCacheable.secondLayerTtl();
            // 获取注解中的 key 值
            String key = redisCacheable.key();
            StandardEvaluationContext context = this.getContextContainingArguments(joinPoint);
            String cacheKey = this.getCacheKeyFromAnnotationKeyValue(context, key);
            log.info("### Cache key: {}", cacheKey);
            // 获取系统当前时间
            long start = System.currentTimeMillis();
            Object result;
            // 如果缓存中存在 当前 key 的数据
            if (this.redisTemplate.hasKey(cacheKey)) {
                // 通过 key 获取 redis 缓存值
                result = this.redisTemplate.opsForValue().get(cacheKey);
                log.info("Reading from cache ..." + result.toString());
                // 当缓存中的剩余过期时间,小于第二过期时间时,不取缓存中的数据,查询数据库
                if (this.redisTemplate.getExpire(cacheKey, TimeUnit.MINUTES) < secondLayerTtl) {
                    try {
                        result = joinPoint.proceed();
                        // 将查询结果放入 Redis 缓存,并设置过期时间,过期时间为 第一过期时间+第二过期时间
                        this.redisTemplate.opsForValue().set(cacheKey, result, secondLayerTtl + firstLayerTtl, TimeUnit.MINUTES);
                    } catch (Exception var15) {
                        log.warn("An error occured while trying to refresh the value - extending the existing one", var15);
                        this.redisTemplate.opsForValue().getOperations().expire(cacheKey, secondLayerTtl + firstLayerTtl, TimeUnit.MINUTES);
                    }
                }
            } else {
                result = joinPoint.proceed();
                log.info("Cache miss: Called original method");
                // 将查询结果放入 Redis 缓存,并设置过期时间,过期时间为 第一过期时间+第二过期时间
                this.redisTemplate.opsForValue().set(cacheKey, result, firstLayerTtl + secondLayerTtl, TimeUnit.MINUTES);
            }
            // 获取执行时间
            long executionTime = System.currentTimeMillis() - start;
            log.info("{} executed in {} ms", joinPoint.getSignature(), executionTime);
            log.info("Result: {}", result);
            return result;
        }
    
    }
    
    

     

    2.6 业务层注解使用示例

     

    
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.stereotype.Service;
    
    @Service
    @Slf4j
    public class RedisTestServiceImpl {
    
    	    /**
    	     *  这里的 firstLayerTtl + secondLayerTtl = TTL 过期时间
    	     *  当缓存中的剩余过期时间,小于 secondLayerTtl 时间时,不取缓存中的数据,查询数据库
    	     *  firstLayerTtl 和 secondLayerTtl 时间单位均是 分钟
    	     *  详细逻辑,可以看看 RedisCacheableAspect 类
    	     **/
    	    @RedisCacheable(key = "'gof:com:test'",firstLayerTtl = 20L,secondLayerTtl = 10L)
    	    public User methodTest1(){
    	
    	           log.info("-- 查询开始");
    	           // 这里处理未命中缓存时,查询数据库的逻辑
    	           // todo……
    	           log.info("-- 查询结束 --");
    	           return user;
    	    }
    	
    	    /**
    	     * 这里可以使用通配符,key 作为 Redis 的 key 值,
    	     * key会拼接成为 gof:com:test:入参name的值+入参salt的值
    	     **/
    	    @RedisCacheable(key = "'gof:com:test:'.concat(#name).concat(#salt)",firstLayerTtl = 20L,secondLayerTtl = 10L)
    	    public User methodTest2(String name, String salt){
    	        log.info("-- 查询开始");
    	        // 这里处理未命中缓存时,查询数据库的逻辑
    	        // todo……
    	        log.info("-- 查询结束 --");
    	        return user;
    	    }
        
    
    }
    

     

    3、让你们看看效果

     
    测试代码我随便写的,没用 @Data 相关注解和数据库查询,不是博主菜,你懂得……
     

    3.1、测试的实体类

     

    package com.ttt.gof.entity;
    
    import java.util.Date;
    
    public class User {
        private Integer userId;
    
        private String userName;
    
        private String userPhone;
    
        private String password;
    
        private String salt;
    
        private Date createTime;
    
        private Integer createUser;
    
        private Date modifyTime;
    
        private Integer modifyUser;
    
        public Integer getUserId() {
            return userId;
        }
    
        public void setUserId(Integer userId) {
            this.userId = userId;
        }
    
        public String getUserName() {
            return userName;
        }
    
        public void setUserName(String userName) {
            this.userName = userName == null ? null : userName.trim();
        }
    
        public String getUserPhone() {
            return userPhone;
        }
    
        public void setUserPhone(String userPhone) {
            this.userPhone = userPhone == null ? null : userPhone.trim();
        }
    
        public String getPassword() {
            return password;
        }
    
        public void setPassword(String password) {
            this.password = password == null ? null : password.trim();
        }
    
        public String getSalt() {
            return salt;
        }
    
        public void setSalt(String salt) {
            this.salt = salt == null ? null : salt.trim();
        }
    
        public Date getCreateTime() {
            return createTime;
        }
    
        public void setCreateTime(Date createTime) {
            this.createTime = createTime;
        }
    
        public Integer getCreateUser() {
            return createUser;
        }
    
        public void setCreateUser(Integer createUser) {
            this.createUser = createUser;
        }
    
        public Date getModifyTime() {
            return modifyTime;
        }
    
        public void setModifyTime(Date modifyTime) {
            this.modifyTime = modifyTime;
        }
    
        public Integer getModifyUser() {
            return modifyUser;
        }
    
        public void setModifyUser(Integer modifyUser) {
            this.modifyUser = modifyUser;
        }
    }
    

     

    3.2、业务层测试代码

     

    
    import com.ttt.gof.entity.User;
    import com.qiuwan.gof.rediscache.twolayer.aop.RedisCacheable;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.stereotype.Service;
    
    @Service
    @Slf4j
    public class RedisTestServiceImpl {
    
    
        @RedisCacheable(key = "'gof:com:test'", firstLayerTtl = 20L, secondLayerTtl = 10L)
        public User methodTest1() {
    
            log.info("-- 查询开始");
            User user = new User();
            user.setSalt("param_salt");
            user.setPassword("param_password");
            user.setUserName("param_userName");
            log.info("-- 查询结束 --");
            return user;
        }
    
        @RedisCacheable(key = "'gof:com:test:'.concat(#name).concat(#salt)", firstLayerTtl = 20L, secondLayerTtl = 10L)
        public User methodTest2(String name, String salt) {
    
            log.info("-- 查询开始");
            User user = new User();
            user.setSalt("param_salt");
            user.setPassword("param_password");
            user.setUserName("param_userName");
            log.info("-- 查询结束 --");
            return user;
        }
    
    }
    
    

     

    3.3、控制层测试代码

     

    package com.ttt.gof.controller;
    
    import com.ttt.gof.entity.User;
    import com.ttt.gof.service.impl.RedisTestServiceImpl;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.ResponseBody;
    
    @Controller
    public class RedisTestController {
    
    
        @Autowired
        private RedisTestServiceImpl redisTestService;
    
        @RequestMapping("/testApi1")
        @ResponseBody
        public User testApi() {
            return redisTestService.methodTest1();
        }
    
    
        @RequestMapping("/testApi2")
        @ResponseBody
        public User testApi2() {
            return redisTestService.methodTest2("张三", "碘盐");
        }
    }
    
    

     

    3.4、调用接口

     

    • 调用前先让你们看看 Redis 缓存 空空如也
       
      在这里插入图片描述
       

    3.4.1、testApi1 接口

     

    • 第一次调用 testApi1 接口

     
    在这里插入图片描述
     
    控制台:
     
    在这里插入图片描述
     
    Redis 缓存:
     
    在这里插入图片描述
     

    • 第二次调用 testApi1 接口
       
      在这里插入图片描述
       
      控制台:
       
      在这里插入图片描述
       
      Redis 缓存:
       
      在这里插入图片描述

     

    3.4.1、testApi2 接口

     

    • 第一次调用 testApi2 接口
       
      在这里插入图片描述
       
      控制台:
       
      在这里插入图片描述
       
      Redis 缓存:
       
      在这里插入图片描述
       
    • 第二次调用 testApi2 接口
       
      在这里插入图片描述
       
      控制台:
       
      在这里插入图片描述 
      Redis 缓存:
       
      在这里插入图片描述
       
       
       
       
       
       
       
       
       
       
       
       
      .
    展开全文
  • 上文讲解过自动迁移槽实现集群扩容(传送门) 1.准备新节点 安装redis,参考传送门 节点配置,参考传送门 2.将节点加入集群 redis-cli --cluster add-node {new host}:{new port} {exist host}:{exist port} 加入...
  • db01操作 创建密钥对 ssh-keygen ...结束之前的redis进程 pkill redis 创建目录 mkdir -p /opt/redis_{6380,6381}/{conf,logs,pid} mkdir -p /data/redis_{6380,6381} 编写配置文件 cat >/op...
  • Redis自动安装部署 本文总共分为4个部分: redis自动安装脚本目录结构说明; redis自动安装脚本内容; redis的操作系统服务脚本说明; 本文引用的资料链接地址。 说明:本文未涉及redis内存分配器部分的内容。 第一...
  • 废弃原因:使用redis缓存失效监听会有一定的延时,dev环境下延时已经达到90s左右,线上可能更甚,所以必须更换方案。 (基本上,expired事件是在Redis服务器删除键的时候生成的,而不是在理论上生存时间达到零值时...
  • 生产环境redis为3主3从 4核32G配置,业务量增长要扩容成4主4从的配置,看个网上的教程都是手动移动槽位太麻烦了,这次用cluster rebalance ,生产30多G的内存数据自动重新分槽 30分钟就扩容完了,全程对业务无影响。...
  • 简单定时任务解决方案:使用redis的keyspace notifications(键...1、当一个业务触发以后需要启动一个定时任务,在指定时间内再去执行一个任务(如自动取消订单,自动完成订单等功能) 2、redis的keyspace notifica...
  • 最近学redis,就遇到了各种坑,在这里分享一下 我是将redis做成后台 安装,配置环境变量统统省略掉了。 做成后台服务呢,首先,cd到redis的安装目录下,再cd到util,接着执行 ...好像坑并没有结束,第二天,重新
  • scrapy-redis框架中,reids存储的xxx:requests已经爬取完毕,但程序仍然一直运行,如何自动停止程序,结束空跑。 分布式扩展: 我们知道 scrapy 默认是单机运行的,那么scrapy-redis是如何把它变成可以多台机器...
  • Redis自动安装部署 本文总共分为4个部分: redis自动安装脚本目录结构说明; redis自动安装脚本内容; redis的操作系统服务脚本说明; 本文引用的资料链接地址。
  • Redis安装步骤和怎样自动开启服务

    千次阅读 多人点赞 2019-05-20 17:04:16
    安装redis和启动服务的步骤 1、官网下载:...2、首次需要启动cmd命令界面执行命令进行运行,打开cmd 使用cd 切换到redis目录,输入redis-server.exe redis.windows.conf运行redis! ...
  • 使用过scrapy_redis框架的人一定知道,scrapy redis 在没有requests的时候,会阻塞等待接收start_url...
  • redisredis后端模式启动附录 redis 什么是redis redis是用C语言开发的一个开源的高性能键值对(key-value)数据库。 它通过提供多种键值数据类型来适应不同场景下的存储需求, 目前为止redis支持的键值数据类型...
  • 好的,准备工作已经准备好了,现在要来最终的Nginx+Lua+Redis自动封禁并解封IP了 三、Nginx+Lua+Redis 1、添加访问控制的Lua脚本 (此脚本需要改的只有下面的这一句,把redis的ip和端口替换一下即可 ok, err = ...
  • 简单定时任务解决方案:使用redis的keyspace notifications...1、当一个业务触发以后需要启动一个定时任务,在指定时间内再去执行一个任务(如自动取消订单,自动完成订单等功能) 2、redis的keyspace notifica...
  • 自动补全与输入联想功能已经是大多数网站的标配,给表单加入自动补全功能大大节省了用户输入时间,而输入联想功能则起到了预测用户喜好的作用,两个功能都是提升用户体验的利器。 本实训,我们通过实现搜索历史、...
  • Redis持久化

    千次阅读 多人点赞 2019-10-08 21:34:28
    Redis持久化Redis持久化1 RDB1.1 RDB触发机制1.2 流程说明1.3 RDB文件的处理1.3 RDB优缺点 Redis持久化 ...RDB持久化是把当前进程数据生成快照保存在磁盘的过程,触发RDB持久化的过程分为手动触发和自动触...
  • 文章目录实验背景实验一、安装使用 OpenResty二、安装Redis三、在Nginx中使用Lua脚本访问Redis四、Nginx+Lua+Redis 实验背景 为了防止某恶意用户多次对服务器端口进行攻击,我们需要建立一个动态的 IP 黑名单。对于...
  • redis

    2018-03-12 19:08:08
    Redis 简介Redis 是完全开源免费的,是一个高性能的key-value 的内存数据库。Redis 与其他 key - value 缓存产品有以下三个特点:Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载...
  • 1.场景:  电商系统或者购票系统都必须具备订单功能,生成订单后一段时间不支付订单会自动关闭。...方式可能有很多,在这里介绍一种监听Redis键值对过期时间来实现订单自动关闭。 2.思路:  ...
  • window系统安装redis后,通过命令redis-server.exe redis.windows.conf启动后,关闭cmd窗口服务自动退出问题。 解决:把redis添加到window服务。 执行:redis-server --service-install redis.windows-service.conf ...
  • 本文主要讲解如何配置Canal,以保证某网站的Redis与MySql的数据自动同步。 1.Java开发原则 下面列出一些本项目的开发原则: 1.1.Redis的KEY命名规范 项目名称-模块名称-对象名称-主键id 例如...
  • 你知道的越多,你不知道的越多 点赞再看,养成习惯 ...那提到Redis我相信各位在面试,或者实际开发过程中对缓存雪崩,穿透,击穿也不陌生吧,就算没遇到过但是你肯定听过,那三者到底有什么区别,我们又...
  • Redis

    千次阅读 2019-01-27 11:55:46
    什么是Redis Redis是一个key-value存储系统。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)。与Memcached一样,...
  • redis 面试题

    千次阅读 多人点赞 2019-12-09 22:18:24
    面试还搞不懂redis,快看看这40道面试题 Redis 面试题 1、什么是 Redis?. 2、Redis 的数据类型? 3、使用 Redis 有哪些好处? 4、Redis 相比 Memcached 有哪些优势? 5、Memcache 与 Redis 的区别都有哪些? 6、...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 47,561
精华内容 19,024
关键字:

redis自动结束

redis 订阅