Spring Boot整合redis實(shí)現(xiàn)隊(duì)列存儲(chǔ)微服務(wù)

本文介紹Spring Boot整合Redis實(shí)現(xiàn)隊(duì)列存儲(chǔ)。隊(duì)列存儲(chǔ)通常以Rest微服務(wù)形式提供服務(wù)接口录粱,所以Spring Boot+Redis是一個(gè)理想選型摔桦。

典型的應(yīng)用場(chǎng)景,比如爬蟲系統(tǒng)中任務(wù)列表的存儲(chǔ)疮鲫,各個(gè)爬蟲子進(jìn)(線)程獨(dú)立蘸劈、主動(dòng)訪問該隊(duì)列獲取URLs昏苏,并支持批量獲取。

  • Step1:
    Spring Boot工程的Maven中添加依賴:
<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-redis</artifactId>
</dependency>

本文使用SpringBoot 1.5.2.RELEASE威沫。

  • Step2
    Application.java入口定義必要的Bean:
@SpringBootApplication(scanBasePackages = { 
        "", "" })
public class Application implements CommandLineRunner {

    @Autowired private JedisConnectionFactory jedisConnFactory;
    
    @Bean
    public StringRedisTemplate redisTemplate() {

        StringRedisTemplate redisTemplate = new StringRedisTemplate();
        redisTemplate.setConnectionFactory(jedisConnFactory);
        return redisTemplate;
    }
    
    @Bean
    public QueueService queueService() {
        return new QueueServiceSDRImpl(redisTemplate());
    }
    
    public static void main(String[] args) throws InterruptedException {

        SpringApplication app = new SpringApplication(Application.class);
        app.setBannerMode(Banner.Mode.CONSOLE);
        app.setWebEnvironment(true);
        app.run(args);
    }

    @Override
    public void run(String... args) throws Exception {
        System.out.println("Project: running...");
    }
}

在此只定義一個(gè)StringRedisTemplate贤惯,至于保存對(duì)象的需求可以手動(dòng)轉(zhuǎn)成json存儲(chǔ)。

  • Step3:定義QueueService接口:
public interface QueueService {

    /**
     * 取N條URL隊(duì)列數(shù)據(jù)
     * @param fullTaskName
     * @param numbersOfURL
     * @return
     */
    public List<BasicWebURL> fetchN(String fullTaskName, Long numbersOfURL);
    
    /**
     * URL隊(duì)列入隊(duì)
     * @param webURLList
     * @return
     */
    public Long enQueue(String fullTaskName, String... webURLJSONStringArray);
    
    /**
     * URL隊(duì)列長度
     * @param fullTaskName
     * @return
     */
    public Long queueSize(String fullTaskName);
    
    /**
     * 清空URL隊(duì)列
     * @param fullTaskName
     * @return
     */
    public void queueDump(String fullTaskName);
    
    /**
     * 是否已訪問過
     * @param fullTaskName
     * @param url
     * @return
     */
    public Boolean hasVisit(String fullTaskName, String url);
    
    /**
     * 保存鏈接對(duì)象
     * @param fullTaskName
     * @param url
     */
    public Long saveURL(String fullTaskName, String... visitedLinkArray);
    
}
  • Step4:QueueServiceSDRImpl.java的具體實(shí)現(xiàn):
public class QueueServiceSDRImpl implements QueueService {

    private StringRedisTemplate redisTemplate;

    private static String HEAD_HISTORY = "HIST:";
    private static String HEAD_QUEUE = "QUEUE:";

    public QueueServiceSDRImpl(StringRedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    @Override
    public List<BasicWebURL> fetchN(String fullTaskName, Long numbersOfURL) {

        List<Object> results = redisTemplate.executePipelined(new RedisCallback<String>() {
            @Override
            public String doInRedis(RedisConnection connection) throws DataAccessException {
                StringRedisConnection stringRedisConn = (StringRedisConnection) connection;
                for (int i = 0; i < numbersOfURL; i++) {
                    stringRedisConn.lPop(HEAD_QUEUE.concat(fullTaskName));
                }
                return null; 
            }
        });
        return results.stream().filter(obj -> obj != null).map(obj -> JSONObject.parseObject(obj.toString(), BasicWebURL.class)).collect(Collectors.toList());
    }

    @Override
    public Long enQueue(String fullTaskName, String... webURLJSONStringArray) {
        Long result = -1L;
        BoundListOperations<String, String> opt = redisTemplate.boundListOps(HEAD_QUEUE.concat(fullTaskName));
        try {
            opt.rightPushAll(webURLJSONStringArray);
        } catch (JedisException e) {
            e.printStackTrace();
        }
        return result;
    }

    @Override
    public Long queueSize(String fullTaskName) {
        return redisTemplate.boundListOps(HEAD_QUEUE.concat(fullTaskName)).size();
    }

    @Override
    public void queueDump(String fullTaskName) {
        redisTemplate.boundListOps(HEAD_QUEUE.concat(fullTaskName)).expire(1, TimeUnit.MILLISECONDS);
        redisTemplate.boundSetOps(HEAD_HISTORY.concat(fullTaskName)).expire(1, TimeUnit.MILLISECONDS);
    }

    @Override
    public Boolean hasVisit(String fullTaskName, String url) {
    
        Boolean hasVisit = false;
        try {
            hasVisit = redisTemplate.boundSetOps(HEAD_HISTORY.concat(fullTaskName)).isMember(url);
        } catch (JedisException e) {
            e.printStackTrace();
        }
        return hasVisit;
    }

    @Override
    public Long saveURL(String fullTaskName, String... visitedLinkArray) {
    
        Long result = -1L;
        try {
            result = redisTemplate.boundSetOps(HEAD_HISTORY.concat(fullTaskName)).add(visitedLinkArray);
        } catch (JedisException e) {
            e.printStackTrace();
        }
        return result;
    }

}

在QueueServiceSDRImpl中實(shí)現(xiàn)了兩種隊(duì)列(庫)棒掠,庫的value分別是List和Set孵构,特性對(duì)應(yīng)java中的List(有序)和Set(查重)各自特性。fullTaskName為Spring封裝的Redis存儲(chǔ)中的key對(duì)象烟很。

  • Step5颈墅,最后看一下QueueController如何暴露服務(wù)接口:
@RestController
@RequestMapping()
public class QueueController {

    @Autowired QueueService queueService;
    
    /**
     * Fetch n BasicWebURLs.
     * @param request
     * @param fullTaskName
     * @param numbersOfURL
     * @return
     */
    @GetMapping("/queue/{fullTaskName}")
    public JSONObject webURL(HttpServletRequest request, 
            @PathVariable String fullTaskName, 
            @RequestParam(defaultValue="10", required=false) Long numbersOfURL) {
        
        JSONObject jo = new JSONObject();
        if(numbersOfURL > 0) {
            jo.put("popLength", numbersOfURL);
            jo.put("data", queueService.fetchN(fullTaskName, numbersOfURL));
        }else{
            jo.put("popLength", 0);
            jo.put("data", Lists.newArrayList());
        }
        jo.put("stillHas", queueService.queueSize(fullTaskName));
        return jo;
    }
    
    /**
     * 入隊(duì)
     * @param request
     * @param fullTaskName
     * @param body
     * @return
     */
    @PostMapping("/queue/{fullTaskName}")
    public Long enQueue(HttpServletRequest request, @PathVariable String fullTaskName, @RequestBody String body) {

        JSONObject jo = JSONObject.parseObject(body);
        if(jo != null){
            JSONArray webURLList = jo.getJSONArray("webURLs");
            if(!webURLList.isEmpty()) {
                String [] jsonArray = webURLList.stream().map(item -> item.toString()).toArray(String[]::new);
                return queueService.enQueue(fullTaskName, jsonArray);
            }
        }
        return -1L;
    }
    
    /**
     * 
     * @param request
     * @param fullTaskName
     * @return
     */
    @DeleteMapping("/queue/{fullTaskName}")
    public Integer queueDump(HttpServletRequest request, @PathVariable String fullTaskName) {
        queueService.queueDump(fullTaskName);
        return 1;
    }
    
}

以及在前文所述爬蟲系統(tǒng)場(chǎng)景中,用作查重的接口:

@RestController
@RequestMapping("/link")
@Getter
@Setter
public class VisitedLinkController {

    @Autowired QueueService queueService;
    
    @GetMapping("/{fullTaskName}")
    public String webURL(HttpServletRequest request, 
            @PathVariable String fullTaskName, 
            @RequestParam(defaultValue="", required = false) String link) {
        return queueService.hasVisit(fullTaskName, link) ? "y" : "n";
    }

    /**
     * 加入訪問歷史
     * @param request
     * @param fullTaskName
     * @param body
     * @return
     */
    @PostMapping("/{fullTaskName}")
    public Boolean visitLinks(HttpServletRequest request, 
            @PathVariable String fullTaskName, @RequestBody String body) {
        
        JSONObject jo = JSONObject.parseObject(body);
        if(jo != null){
            JSONArray webURLList = jo.getJSONArray("visitedLinks");
            if(!webURLList.isEmpty()) {
                String [] jsonArray = webURLList.stream().map(item -> item.toString()).toArray(String[]::new);
                queueService.saveURL(fullTaskName, jsonArray);
            }
        }
        return true;
    }
    
}

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末雾袱,一起剝皮案震驚了整個(gè)濱河市恤筛,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌芹橡,老刑警劉巖毒坛,帶你破解...
    沈念sama閱讀 216,402評(píng)論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異林说,居然都是意外死亡煎殷,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門腿箩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來豪直,“玉大人,你說我怎么就攤上這事度秘。” “怎么了?”我有些...
    開封第一講書人閱讀 162,483評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵剑梳,是天一觀的道長唆貌。 經(jīng)常有香客問我,道長垢乙,這世上最難降的妖魔是什么锨咙? 我笑而不...
    開封第一講書人閱讀 58,165評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮追逮,結(jié)果婚禮上酪刀,老公的妹妹穿的比我還像新娘。我一直安慰自己钮孵,他們只是感情好骂倘,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著巴席,像睡著了一般历涝。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上漾唉,一...
    開封第一講書人閱讀 51,146評(píng)論 1 297
  • 那天荧库,我揣著相機(jī)與錄音,去河邊找鬼赵刑。 笑死分衫,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的般此。 我是一名探鬼主播蚪战,決...
    沈念sama閱讀 40,032評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼恤煞!你這毒婦竟也來了屎勘?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,896評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤居扒,失蹤者是張志新(化名)和其女友劉穎概漱,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體喜喂,經(jīng)...
    沈念sama閱讀 45,311評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡瓤摧,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了玉吁。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片照弥。...
    茶點(diǎn)故事閱讀 39,696評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖进副,靈堂內(nèi)的尸體忽然破棺而出这揣,到底是詐尸還是另有隱情悔常,我是刑警寧澤,帶...
    沈念sama閱讀 35,413評(píng)論 5 343
  • 正文 年R本政府宣布给赞,位于F島的核電站机打,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏片迅。R本人自食惡果不足惜残邀,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望柑蛇。 院中可真熱鬧芥挣,春花似錦、人聲如沸耻台。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽粘我。三九已至鼓蜒,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間征字,已是汗流浹背都弹。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留匙姜,地道東北人畅厢。 一個(gè)月前我還...
    沈念sama閱讀 47,698評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像氮昧,于是被迫代替她去往敵國和親框杜。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理袖肥,服務(wù)發(fā)現(xiàn)咪辱,斷路器,智...
    卡卡羅2017閱讀 134,651評(píng)論 18 139
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,803評(píng)論 6 342
  • 昨晚和今晚把《小女生椎组,職場(chǎng)修行記》看完油狂,昨晚看到1.00。(熊貓眼)寸癌。我這人吧就是看書特死勁(或者說特投入吧)专筷。...
    seven小小柒閱讀 219評(píng)論 0 1
  • 兩個(gè)人若能成為好朋友,那一定是兩個(gè)心靈之間的互相認(rèn)可蒸苇。 1. 小A和小B是室友磷蛹,一起上學(xué)、一起吃飯溪烤、一起逛街味咳、一起...
    一顆小香豬閱讀 360評(píng)論 0 2
  • 然后輸入@"[^"]*[\u4E00-\u9FA5]+[^"\n]*" 就可方便查找的所有字符串.
    一個(gè)開發(fā)者_(dá)閱讀 1,492評(píng)論 1 2