在這里對jedis關于事務、管道和分布式的調用方式做一個簡單的介紹和對比:
一、普通同步方式
最簡單和基礎的調用方式,
@Test public void test1Normal() { Jedis jedis = new Jedis("localhost"); long start = System.currentTimeMillis(); for (int i = 0; i < 100000; i++) { String result = jedis.set("n" + i, "n" + i); } long end = System.currentTimeMillis(); System.out.println("Simple SET: " + ((end - start)/1000.0) + " seconds"); jedis.disconnect(); }
很簡單吧,每次set
之后都可以返回結果,標記是否成功。
二、事務方式(Transactions)
redis的事務很簡單,他主要目的是保障,一個client發起的事務中的命令可以連續的執行,而中間不會插入其他client的命令。
看下面例子:
@Test public void test2Trans() { Jedis jedis = new Jedis("localhost"); long start = System.currentTimeMillis(); Transaction tx = jedis.multi(); for (int i = 0; i < 100000; i++) { tx.set("t" + i, "t" + i); } List<Object> results = tx.exec(); long end = System.currentTimeMillis(); System.out.println("Transaction SET: " + ((end - start)/1000.0) + " seconds"); jedis.disconnect(); }
我們調用jedis.watch(…)
方法來監控key,如果調用后key值發生變化,則整個事務會執行失敗。另外,事務中某個操作失敗,并不會回滾其他操作。這一點需要注意。還有,我們可以使用discard()
方法來取消事務。
三、管道(Pipelining)
有時,我們需要采用異步方式,一次發送多個指令,不同步等待其返回結果。這樣可以取得非常好的執行效率。這就是管道,調用方法如下:
@Test public void test3Pipelined() { Jedis jedis = new Jedis("localhost"); Pipeline pipeline = jedis.pipelined(); long start = System.currentTimeMillis(); for (int i = 0; i < 100000; i++) { pipeline.set("p" + i, "p" + i); } List<Object> results = pipeline.syncAndReturnAll(); long end = System.currentTimeMillis(); System.out.println("Pipelined SET: " + ((end - start)/1000.0) + " seconds"); jedis.disconnect(); }
四、管道中調用事務
就Jedis提供的方法而言,是可以做到在管道中使用事務,其代碼如下:
@Test public void test4combPipelineTrans() { jedis = new Jedis("localhost"); long start = System.currentTimeMillis(); Pipeline pipeline = jedis.pipelined(); pipeline.multi(); for (int i = 0; i < 100000; i++) { pipeline.set("" + i, "" + i); } pipeline.exec(); List<Object> results = pipeline.syncAndReturnAll(); long end = System.currentTimeMillis(); System.out.println("Pipelined transaction: " + ((end - start)/1000.0) + " seconds"); jedis.disconnect(); }
但是經測試(見本文后續部分),發現其效率和單獨使用事務差不多,甚至還略微差點。
五、分布式直連同步調用
@Test public void test5shardNormal() { List<JedisShardInfo> shards = Arrays.asList( new JedisShardInfo("localhost",6379), new JedisShardInfo("localhost",6380)); ShardedJedis sharding = new ShardedJedis(shards); long start = System.currentTimeMillis(); for (int i = 0; i < 100000; i++) { String result = sharding.set("sn" + i, "n" + i); } long end = System.currentTimeMillis(); System.out.println("Simple@Sharing SET: " + ((end - start)/1000.0) + " seconds"); sharding.disconnect(); }
這個是分布式直接連接,并且是同步調用,每步執行都返回執行結果。類似地,還有異步管道調用。
六、分布式直連異步調用
@Test public void test6shardpipelined() { List<JedisShardInfo> shards = Arrays.asList( new JedisShardInfo("localhost",6379), new JedisShardInfo("localhost",6380)); ShardedJedis sharding = new ShardedJedis(shards); ShardedJedisPipeline pipeline = sharding.pipelined(); long start = System.currentTimeMillis(); for (int i = 0; i < 100000; i++) { pipeline.set("sp" + i, "p" + i); } List<Object> results = pipeline.syncAndReturnAll(); long end = System.currentTimeMillis(); System.out.println("Pipelined@Sharing SET: " + ((end - start)/1000.0) + " seconds"); sharding.disconnect(); }
七、分布式連接池同步調用
如果,你的分布式調用代碼是運行在線程中,那么上面兩個直連調用方式就不合適了,因為直連方式是非線程安全的,這個時候,你就必須選擇連接池調用。
@Test public void test7shardSimplePool() { List<JedisShardInfo> shards = Arrays.asList( new JedisShardInfo("localhost",6379), new JedisShardInfo("localhost",6380)); ShardedJedisPool pool = new ShardedJedisPool(new JedisPoolConfig(), shards); ShardedJedis one = pool.getResource(); long start = System.currentTimeMillis(); for (int i = 0; i < 100000; i++) { String result = one.set("spn" + i, "n" + i); } long end = System.currentTimeMillis(); pool.returnResource(one); System.out.println("Simple@Pool SET: " + ((end - start)/1000.0) + " seconds"); pool.destroy(); }
上面是同步方式,當然還有異步方式。
八、分布式連接池異步調用
@Test public void test8shardPipelinedPool() { List<JedisShardInfo> shards = Arrays.asList( new JedisShardInfo("localhost",6379), new JedisShardInfo("localhost",6380)); ShardedJedisPool pool = new ShardedJedisPool(new JedisPoolConfig(), shards); ShardedJedis one = pool.getResource(); ShardedJedisPipeline pipeline = one.pipelined(); long start = System.currentTimeMillis(); for (int i = 0; i < 100000; i++) { pipeline.set("sppn" + i, "n" + i); } List<Object> results = pipeline.syncAndReturnAll(); long end = System.currentTimeMillis(); pool.returnResource(one); System.out.println("Pipelined@Pool SET: " + ((end - start)/1000.0) + " seconds"); pool.destroy(); }
九、需要注意的地方
事務和管道都是異步模式。在事務和管道中不能同步查詢結果。比如下面兩個調用,都是不允許的:
Transaction tx = jedis.multi(); for (int i = 0; i < 100000; i++) { tx.set("t" + i, "t" + i); } System.out.println(tx.get("t1000").get()); //不允許 List<Object> results = tx.exec(); … … Pipeline pipeline = jedis.pipelined(); long start = System.currentTimeMillis(); for (int i = 0; i < 100000; i++) { pipeline.set("p" + i, "p" + i); } System.out.println(pipeline.get("p1000").get()); //不允許 List<Object> results = pipeline.syncAndReturnAll();
事務和管道都是異步的,個人感覺,在管道中再進行事務調用,沒有必要,不如直接進行事務模式。
分布式中,連接池的性能比直連的性能略好(見后續測試部分)。
分布式調用中不支持事務。
因為事務是在服務器端實現,而在分布式中,每批次的調用對象都可能訪問不同的機器,所以,沒法進行事務。
十、測試
運行上面的代碼,進行測試,其結果如下:
Simple SET: 5.227 seconds Transaction SET: 0.5 seconds Pipelined SET: 0.353 seconds Pipelined transaction: 0.509 seconds Simple@Sharing SET: 5.289 seconds Pipelined@Sharing SET: 0.348 seconds Simple@Pool SET: 5.039 seconds Pipelined@Pool SET: 0.401 seconds
另外,經測試分布式中用到的機器越多,調用會越慢。上面是2片,下面是5片:
Simple@Sharing SET: 5.494 seconds Pipelined@Sharing SET: 0.51 seconds Simple@Pool SET: 5.223 seconds Pipelined@Pool SET: 0.518 seconds
下面是10片:
Simple@Sharing SET: 5.9 seconds Pipelined@Sharing SET: 0.794 seconds Simple@Pool SET: 5.624 seconds Pipelined@Pool SET: 0.762 seconds
下面是100片:
Simple@Sharing SET: 14.055 seconds Pipelined@Sharing SET: 8.185 seconds Simple@Pool SET: 13.29 seconds Pipelined@Pool SET: 7.767 seconds
分布式中,連接池方式調用不但線程安全外,根據上面的測試數據,也可以看出連接池比直連的效率更好。
十一、完整的測試代碼
package com.example.nosqlclient; import java.util.Arrays; import java.util.List; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPoolConfig; import redis.clients.jedis.JedisShardInfo; import redis.clients.jedis.Pipeline; import redis.clients.jedis.ShardedJedis; import redis.clients.jedis.ShardedJedisPipeline; import redis.clients.jedis.ShardedJedisPool; import redis.clients.jedis.Transaction; import org.junit.FixMethodOrder; import org.junit.runners.MethodSorters; @FixMethodOrder(MethodSorters.NAME_ASCENDING) public class TestJedis { private static Jedis jedis; private static ShardedJedis sharding; private static ShardedJedisPool pool; @BeforeClass public static void setUpBeforeClass() throws Exception { List<JedisShardInfo> shards = Arrays.asList( new JedisShardInfo("localhost",6379), new JedisShardInfo("localhost",6379)); //使用相同的ip:port,僅作測試 jedis = new Jedis("localhost"); sharding = new ShardedJedis(shards); pool = new ShardedJedisPool(new JedisPoolConfig(), shards); } @AfterClass public static void tearDownAfterClass() throws Exception { jedis.disconnect(); sharding.disconnect(); pool.destroy(); } @Test public void test1Normal() { long start = System.currentTimeMillis(); for (int i = 0; i < 100000; i++) { String result = jedis.set("n" + i, "n" + i); } long end = System.currentTimeMillis(); System.out.println("Simple SET: " + ((end - start)/1000.0) + " seconds"); } @Test public void test2Trans() { long start = System.currentTimeMillis(); Transaction tx = jedis.multi(); for (int i = 0; i < 100000; i++) { tx.set("t" + i, "t" + i); } //System.out.println(tx.get("t1000").get()); List<Object> results = tx.exec(); long end = System.currentTimeMillis(); System.out.println("Transaction SET: " + ((end - start)/1000.0) + " seconds"); } @Test public void test3Pipelined() { Pipeline pipeline = jedis.pipelined(); long start = System.currentTimeMillis(); for (int i = 0; i < 100000; i++) { pipeline.set("p" + i, "p" + i); } //System.out.println(pipeline.get("p1000").get()); List<Object> results = pipeline.syncAndReturnAll(); long end = System.currentTimeMillis(); System.out.println("Pipelined SET: " + ((end - start)/1000.0) + " seconds"); } @Test public void test4combPipelineTrans() { long start = System.currentTimeMillis(); Pipeline pipeline = jedis.pipelined(); pipeline.multi(); for (int i = 0; i < 100000; i++) { pipeline.set("" + i, "" + i); } pipeline.exec(); List<Object> results = pipeline.syncAndReturnAll(); long end = System.currentTimeMillis(); System.out.println("Pipelined transaction: " + ((end - start)/1000.0) + " seconds"); } @Test public void test5shardNormal() { long start = System.currentTimeMillis(); for (int i = 0; i < 100000; i++) { String result = sharding.set("sn" + i, "n" + i); } long end = System.currentTimeMillis(); System.out.println("Simple@Sharing SET: " + ((end - start)/1000.0) + " seconds"); } @Test public void test6shardpipelined() { ShardedJedisPipeline pipeline = sharding.pipelined(); long start = System.currentTimeMillis(); for (int i = 0; i < 100000; i++) { pipeline.set("sp" + i, "p" + i); } List<Object> results = pipeline.syncAndReturnAll(); long end = System.currentTimeMillis(); System.out.println("Pipelined@Sharing SET: " + ((end - start)/1000.0) + " seconds"); } @Test public void test7shardSimplePool() { ShardedJedis one = pool.getResource(); long start = System.currentTimeMillis(); for (int i = 0; i < 100000; i++) { String result = one.set("spn" + i, "n" + i); } long end = System.currentTimeMillis(); pool.returnResource(one); System.out.println("Simple@Pool SET: " + ((end - start)/1000.0) + " seconds"); } @Test public void test8shardPipelinedPool() { ShardedJedis one = pool.getResource(); ShardedJedisPipeline pipeline = one.pipelined(); long start = System.currentTimeMillis(); for (int i = 0; i < 100000; i++) { pipeline.set("sppn" + i, "n" + i); } List<Object> results = pipeline.syncAndReturnAll(); long end = System.currentTimeMillis(); pool.returnResource(one); System.out.println("Pipelined@Pool SET: " + ((end - start)/1000.0) + " seconds"); } }
摘要: Chili ProjectOmnigroupPaymoSpringloopsComindworkWebprojectorSoloOpen AtriumCaseboxRedboothAgilezenProducteevRedmineApollohqCentral DesktopRationalplanTeamworkTeamlabFastdueFreedcampHuddleIssue BurnerN...
閱讀全文
摘要: 用途及用法 網絡請求通常有兩種形式:第一種,請求不是很頻繁,而且每次連接后會保持相當一段時間來讀數據或者寫數據,最后斷開,如文件下載,網絡流媒體等。另一種形式是請求頻繁,但是連接上以后讀/寫很少量的數據就斷開連接。考慮到服務的并發問題,如果每個請求來到以后服務都為它啟動一個線程,那么這對服務的資源可能會造成很大的浪費,特別是第二種情況。因為通常情...
閱讀全文
回想起來,我也算是國內接觸推薦系統較早的人之一了,最近和人聊天,覺得不少人對推薦系統有所誤解,以為需要多么高大上的算法才能搭建起來的,我只想說我經常說的那句話【不是這樣的】,所以有了這篇文章。
第一次接觸【推薦系統】是在兩年前在某高校的互聯網信息處理實驗室的時候,那時候,【機器學習】和【大數據】都是新概念,但是差不多半年后,【大數據】的概念就開始風靡全球了,到現在已經被爆炒得面目全非。
那年還因此買了一本項亮的書《推薦系統實踐》,那本書和現在的很多熱門書籍一樣,都是跟著概念熱起來的。 雖然有一些作者自己的實戰經驗在里面,但是總體上來說并沒有太多值得重復翻開的地方。
幾乎所有宣揚【推薦系統】的人,都要拿【啤酒和尿布】,【亞馬遜推薦占營收20%】之類的經典例子來說力證推薦系統的牛逼之處。到處宣揚【推薦系統】插上【機器學習】等算法的翅膀,就能讓電子商務變得精準無比,能智能的猜出用戶想買的東西。
殊不知,其實這兩個例子和所謂的【算法】其實關系不大。
1. 啤酒和尿布 首先是【啤酒和尿布】,超市的人員發現買啤酒的男人容易順手買尿布。這其實是一種數據分析,是根據數據統計加上人工分析得出,是一種以經驗來改善銷售的行為。和【機器學習】【數據挖掘】等算法的關系不大。 剛接觸【推薦系統】的時候,【協同過濾算法】大熱, 我也曾經迷戀得研究過該算法,以為發現了什么寶貝一樣。但是實際上,在工程中【協同過濾】出來的效果往往慘不忍睹,所謂的【算法工程師】每天能做的就是在那調整【協同過濾】算法的相關參數,然后看看第二天的點擊率有沒有上升。然后調整到最后你會發現,牛逼哄哄的【協同過濾】其實還不如簡簡單單的【看了又看】效果來的好,雖然協同過濾算法本質上也是一種【看了又看】的思想。
2. 亞馬遜的推薦系統 亞馬遜的推薦系統占了營收比,我記得是20%,不知道現在上升了還是下降了。這個說辭會讓很多人誤以為只要你搞好了推薦系統,你的營收就能上升20%以上一樣。其實不然,對于亞馬遜來說,為什么推薦能起到這么高的銷量,一個很重要的原因在于,【亞馬遜的首頁點擊率高的部分位置劃分給了推薦系統的】,從廣告學上講,廣告位置的好壞極大的決定了廣告的銷量。這個很容易理解,假設你的產品的廣告牌能掛上天安門城樓的話,你覺得你還需要擔心該產品的銷量嗎?
當然不可否定的是亞馬遜的推薦系統應該是很牛逼的,但是這并不說明他們采用的【推薦算法】非常牛逼。推薦系統我認為其實和搜索系統并無太大差異,我一直認為推薦系統其實只是一個個性化的搜索引擎。之前在【秘密】上很火的有個爆料是:“360搜索的Rank剛開始就是用【機器學習】的算法去做,屎一樣的效果,是我把百度的基于規則的算法偷過去之后才變好的。” ,這個爆料出來不少人諷刺【基于規則】,覺得這是在黑百度的算法。 其實不是這樣的,記得當時阿里搜索挖了一個谷歌搜索的員工,該人在阿里分享的時候就說過:【谷歌的搜索效果比別人好的原因就是規則庫牛逼,關于算法使用的都是成熟的人盡皆知的算法,并沒什么新奇酷的算法】。 可能也是這個原因,谷歌研究院的科學家幾乎全是【工程師背景】出身的。還記得上次【CCF推薦系統前言講座】,剛開始叫了幾個學院派的講師在那大講特講各種酷炫掉渣天的算法,然后淘寶的大數據負責人車品覺 上臺之后直接來了句【我們實驗出各種算法效果不太好,還不如最基本的 關聯規則效果來的好】直接把前面的學院派專家們打臉打得都腫了。
我心目中的推薦系統 不管是電商,或者是新聞,都有【個性化推薦】和【熱門推薦】的取舍。一個商品熱門或者點擊量高是有其原因的。所以將熱門的東西推薦給用戶是非常合情合理的,因為既然熱門,也側面說明了很大概率上該用戶也會喜歡該商品。而【個性化推薦】本質上是為了解決【長尾】問題,把那些不熱門的東西,但是很可能符合某特定用戶品味的商品【挖掘】出來,推薦給特定的用戶群。
首先,在推薦中,醒目的推薦位應該是【熱門推薦】或者【人工推薦】,【人工推薦】是指比如在體育新聞中,巴薩奪冠之類的大新聞是直接讓編輯來【人工推薦】即可,就是此新聞一出,馬上登上頭條,而不是在那磨磨唧唧的計算特征值,計算相似度,計算是否符合用戶興趣。 對于推薦中的【冷啟動】,最理想的推薦就是【相關推薦】。說到這里,整個推薦系統的 80% 已經搭建完畢,【熱門推薦+人工推薦+相關推薦】,這三者都是【個性化】都沒什么關系,也算法關系也不大,但是這三者效果的好壞就決定了整個系統推薦效果好壞的 80% 。好多人連最基本的這三者都沒有做好,就開始想一步登天,很可惜,這樣的捷徑是不存在的。 接下來是 20% 的【個性化】的做法,如上所說,個性化是為了解決【長尾】問題,正是因為長尾占商品的 20% ,所以在此我認為【個性化】其實也只有 20% 。要解決個性化,首先就是要對用戶分析,最成熟的辦法就是對用戶打標簽(是否讓你想起來社交網絡為什么經常讓你選用合適的標簽描述自己,沒錯,就是為了分析你)。
其實,給用戶打標簽,逼格更高的說法叫【用戶特征提取】或者【用戶行為分析】。說到這兩個詞,那些所謂的算法工程師可能就會開始扯什么高大上的算法,機器學習,自然語言處理,數據挖掘等各種算法。其實在我看來,算法很大情況根本派不上用場,我認為這方面的關鍵在于【數據統計 + 人工分析】。將用戶的瀏覽記錄等記錄下來,統計他最常點擊的東西,最常去的頻道,然后給他打上這些頻道或者商品的標簽。或者收集更詳細的信息,比如年齡,打上【青少年,男人,女人,老人】等標簽,根據這些標簽進行推薦。比如當推薦護膚的商品時,就可以偏向于女人,推薦運動產品時,就可以偏向于男人和青少年,推薦保健品時,就可以偏向于老年人。所以,光看年齡這個標簽的維度,就可以做很多文章。所以標簽庫的設計和積累,是非常廣泛和重要的,而這方面需要大量依賴于【人工分析】,而不是看論文調算法能做到的。 就好比現在的中文分詞,拼到最后大家都在比詞庫的積累,誰的詞庫好,誰的效果就好,【搜狗】的【拼音輸入法】效果好也是因為詞庫比別人好。
最后就是根據標簽的定向推薦,這個推薦概率是有【權重設置】在里面,就比如剛才對年齡這個維度的權重,是需要給予對應的權重值,如何給定呢?其實就是【拍腦袋】,當然,如果有某些公司已經得出經驗值了直接可以拿來用就會更好。但是在拍完腦袋之后需要做的就是觀察點擊率變化,查Bad Case,然后再對權重進行調整,也就是根據評測和反饋來調整,沒有【評測和反饋】,整個系統等于是一個黑盒,談何優化?在我看來,【推薦系統】本質上首先是一個系統,需要不斷的對各種效果進行【評測】,查各種【Bad Case】,而這些都不是看論文可以學到的東西。
總結 1、實力派的【算法工程師】往往都是ABC[always be coding],這樣的算法工程師才能根據實際問題建立模型或者建立規則庫,是真正能解決問題的人。往往是一些有研究背景,經驗豐富的研究員,更加重視工程,因為工程架構上一些恰當合理的設計,效果往往就能遠遠高過于模型算法優化。
2、學院派的【算法工程師】往往是為了算法而算法,而不是為了解決推薦系統的問題去找最適合算法。這也是為什么大公司經常招了一些博士畢業的算法工程師后,不是研究算法而是讓他們整天在那看數據報表?【因為發現算法沒啥好研究,只能讓他們在那看看報表找找規律了。】
3、【幾乎所有所謂的智能推薦算法都是花拳繡腿】
4、當一個做推薦系統的部門開始重視【數據清理,數據標柱,效果評測,數據統計,數據分析】這些所謂的臟活累活,這樣的推薦系統才會有救。
策略模式:
場景:又稱警察模式,假設小明開快車,遇到警察,可能是好警察,只是口頭警告一下,就讓小明走了,也可能是強硬的警察,給小明開了罰單。但小明是不知道到底會遇到哪種警察,要到RUNTIME的時候才知道。
不好的封裝:將好警察的處罰行為封裝為一個類A,將強硬警察的處罰行為封裝為另一個類B,將判斷如何處罰封裝成一個類C,在這個類中判斷類的類型,如果是A類,則執行A方法,如果是B類,則執行B方法。
良好的封裝:將警察的處罰行為統一為一個接口I-A的一個方法,類C的執行方法只傳入接口I-A。