posted @ 2020-04-18 10:12 楊羅羅 閱讀(225) | 評論 (0) | 編輯 收藏
那有沒有什么方式可以方便的管理密碼呢?
為什么要用LastPass?
支持智能手機么?
真的安全嗎?
它收費嗎?
免費獲得一個月的高級賬戶權限
最后也是最重要的:
posted @ 2016-01-13 14:19 楊羅羅 閱讀(356) | 評論 (0) | 編輯 收藏
一. 應用場景
在大型分布式應用中,我們經常碰到在多數據庫之間的數據同步問題,比如說一款游戲,在玩家注冊后,可以馬上登陸進入服務器,即數據在一個IDC更新,其它IDC立即可見。為了簡化思路,我們這里稱玩家注冊的數據庫(數據來源庫)為中心庫,同步目的地的數據庫為分站庫。
在分布式領域有個CAP理論,是說Consistency(一致性), Availability(可用性), Partition tolerance(分區和容錯) 三部分在系統實現只可同時滿足二點,無法三者兼顧。
能做的
· 數據快速搬運到指定的IDC節點
· 數據傳遞過程中失敗時,重新傳遞
· 監控數據傳遞流程
· 故障轉移
· 數據版本控制
· 分配全局唯一的ID
不能做的
· 不參與業務行為,業務操作只能通過注冊的方式集成
· 不保存業務數據,不提供傳遞的業務的查詢
二.系統要求
1.數據快速同步:除去網絡原因,正常情況下從來源庫同步到接收庫的時間不超過300m2.高并發:單個應用每秒同步2000條記錄
3.可伸縮性,在資源達到上限時能通過增加應用分散處理后期增長的壓力
4.數據完整性要求,在數據同步過程中保證數據不丟失和數據安全
5.故障轉移和數據恢復功能
三.設計思路
系統優化,最常用的就是進行業務切割,將總任務切割為許多子任務,分區塊分析系統中可能存在的性能瓶頸并有針對性地進行優化,在本系統中,主要業務包含以下內容:
1.Syncer:外部接口,接收同步數據請求,初始化同步系統的一些必要數據
2.Delivery:將同步數據按照業務或優先級進行分發,并記錄分發結果
3.Batch:分站庫收到同步數據后,根據不同的業務類型調用相應的業務邏輯處理數據
基于以上三塊業務功能,我們可以把整個數據同步流程切割為3個應用,具體如下圖顯示。在Syncer端應用中,我們需要將原始同步數據和分發的分站進行存儲,以備失敗恢復,此時如果采用數據庫進行存儲,勢必會受限于數據庫性能影響,因此我們采用了高效的key-value風格存儲的redis服務來記錄數據,同時在不同應用之間采用隊列(Httpsqs服務)的方式來進行通訊,同時也保證的數據通訊的順序性,為之后的順序同步做好基礎。
Httpsqs提供了http風格的數據操作模式,業務編碼非常簡單,同時也提供了web形式的隊列處理情況查詢,這是選擇它做隊列服務很大一部分原因:方便業務操作和性能監控。
四.數據流轉
綠色-正常流程、紅色-異常流程
隊列處理
根據業務劃分隊列名稱,每個隊列再劃分為三個關聯隊列:正常隊列(Normal)、重試隊列(Retry)、死亡隊列(Death),處理流程為:
1 【進程A】把數據先放入正常隊列,如果放置失敗寫恢復日志
2 【進程B】監聽正常隊列,獲取隊列數據并進行業務處理,處理失敗放入重試隊列
3 【進程C】監聽重試隊列,過幾秒獲取隊列數據并重新進行業務處理,處理失敗放入死亡隊列
4 【進程D】監聽死亡隊列,獲取隊列數據并重新進行業務處理,處理失敗重新放入死亡隊列尾部,等待下一次輪回
業務處理失敗如果無法再次放入隊列,記錄恢復日志
數據同步流程
1發送數據,支持Http POST:curl -d "經過URL編碼的文本消息",如"http://host:port/sync_all/register"
或者Http GET:curl "http://host:port/sync_all/register?data=經過URL編碼的文本消息"
5 sync-syncer接收到同步數據請求,創建sid并分解出需要同步的節點個數,把原始數據和子任務寫入redis中,sid寫入httpsqs中
6 sync-delivery監聽中心httpsqs隊列,根據sid從redis獲取到原始數據和需要同步的節點地址,往其他節點發送數據,流程如按"隊列處理流程"進行
7 sync-batch監聽分節點的httpsqs隊列,調用已經注冊的處理器處理隊列數據,流程如按"隊列處理流程"進行
三. 恢復和監控
恢復數據源
· httpsqs中的死亡隊列 - 業務處理暫時處理不了的數據
· recovery日志文件 - 其它異常情況下的數據,例如網絡無法連接、內部服務不可用
數據恢復
獨立的應用來處理正常流程中沒有完成的任務,主要功能有:
· 監聽死亡隊列,進行業務重做,再次執行失敗時將執行次數+1,最大執行次數為5(默認),超出上限則記錄到恢復日志中
· 讀取恢復日志,重新放入死亡隊列
應用監控
· 使用scribe日志框架服務業務日志的采集和監控
· 收集重要的業務操作日志
· 動態的開啟/關閉某類業務日志
· 對redis進行監控
· 對httpsps,監控隊列個數,每個隊列的狀態
四. 數據結構
{"sid":111,"type":"reg","v":1,"data":"hello world","ctime":65711321800,"exec":1}
· sid(sync id) - 全局唯一id
· v(version) - 版本號
· data - 業務數據
· ctime(create time) - 創建時間(毫秒)
· exec - 可選,執行次數
類別 |
key格式 |
value格式 |
備注 |
redis原始數據 |
sync:<業務類型>:<sid> |
{"ctime":65711321800,"v":1,"data":"hello world"} |
分站沒有此項 |
redis業務附加任務 |
sync:<業務類型>:<sid>:sub |
set類型,保存需要同步的節點id,例如[1,3,5] |
分發確認Set數據結構 |
httpsqs隊列 |
sync:<業務類型> |
{"sid":111,"type":"pp_register","exec":1} |
中心隊列內容,key中<業務類型>是可選項 |
httpsqs隊列 |
sync:<業務類型> |
{"sid":111,"v":1,"data":"hello world","ctime":65711321800,"exec":1} |
分站隊列內容,包含業務數據 |
所有的key都小寫,以 ':' 作為分隔符
五.編碼及測試結果
經過編碼和測試,在內網環境下,在無數據庫限制的情況下,單應用可以傳遞1500條/秒,基本滿足業務需求。如果需進一步擴展,采用集群式布署可使得吞吐量成倍的增長。
posted @ 2011-04-06 15:50 楊羅羅 閱讀(3644) | 評論 (3) | 編輯 收藏
posted @ 2010-12-08 17:40 楊羅羅 閱讀(823) | 評論 (0) | 編輯 收藏
自旋等待適合于比較短的等待,而掛起線程比較適合那些比較耗時的等待。
鎖競爭
影響鎖競爭性的條件有兩個:鎖被請求的頻率和每次持有鎖的時間。顯然當而這二者都很小的時候,鎖競爭不會成為主要的瓶頸。但是如果鎖使用不當,導致二者都比較大,那么很有可能CPU不能有效的處理任務,任務被大量堆積。
所以減少鎖競爭的方式有下面三種:
- 減少鎖持有的時間
- 減少鎖請求的頻率
- 采用共享鎖取代獨占鎖
死鎖
1.一種情況是線程A永遠不釋放鎖,結果B一直拿不到鎖,所以線程B就“死掉”了
2.第二種情況下,線程A擁有線程B需要的鎖Y,同時線程B擁有線程A需要的鎖X,那么這時候線程A/B互相依賴對方釋放鎖,于是二者都“死掉”了。
3.如果一個線程總是不能被調度,那么等待此線程結果的線程可能就死鎖了。這種情況叫做線程饑餓死鎖。比如說非公平鎖中,如果某些線程非常活躍,在高并發情況下這類線程可能總是拿到鎖,那么那些活躍度低的線程可能就一直拿不到鎖,這樣就發生了“饑餓死”。
避免死鎖的解決方案是:
1.盡可能的按照鎖的使用規范請求鎖,另外鎖的請求粒度要小(不要在不需要鎖的地方占用鎖,鎖不用了盡快釋放);
2.在高級鎖里面總是使用tryLock或者定時機制(就是指定獲取鎖超時的時間,如果時間到了還沒有獲取到鎖那么就放棄)。高級鎖(Lock)里面的這兩種方式可以有效的避免死鎖。
posted @ 2010-12-03 10:11 楊羅羅 閱讀(1851) | 評論 (0) | 編輯 收藏
posted @ 2010-11-25 16:27 楊羅羅 閱讀(5041) | 評論 (1) | 編輯 收藏
Spring中提供一些Aware相關接口,像是BeanFactoryAware、 ApplicationContextAware、ResourceLoaderAware、ServletContextAware等等,實現這些 Aware接口的Bean在被初始之后,可以取得一些相對應的資源,例如實現BeanFactoryAware的Bean在初始后,Spring容器將會注入BeanFactory的實例,而實現ApplicationContextAware的Bean,在Bean被初始后,將會被注入 ApplicationContext的實例等等。
Bean取得BeanFactory、ApplicationContextAware的實例目的是什么,一般的目的就是要取得一些檔案資源的存取、相 關訊息資源或是那些被注入的實例所提供的機制,例如ApplicationContextAware提供了publishEvent()方法,可以支持基于Observer模式的事件傳播機制。
ApplicationContextAware接口的定義如下:
ApplicationContextAware.java
public interface ApplicationContextAware {
void setApplicationContext(ApplicationContext context);
}
我們這邊示范如何透過實現ApplicationContextAware注入ApplicationContext來實現事件傳播,首先我們的HelloBean如下:
HelloBean.java
package onlyfun.caterpillar;
import org.springframework.context.*;
public class HelloBean implements ApplicationContextAware {
private ApplicationContext applicationContext;
private String helloWord = "Hello!World!";
public void setApplicationContext(ApplicationContext context) {
this.applicationContext = context;
}
public void setHelloWord(String helloWord) {
this.helloWord = helloWord;
}
public String getHelloWord() {
applicationContext.publishEvent(
new PropertyGettedEvent("[" + helloWord + "] is getted"));
return helloWord;
}
}
ApplicationContext會由Spring容器注入,publishEvent()方法需要一個繼承ApplicationEvent的對象,我們的PropertyGettedEvent繼承了ApplicationEvent,如下:
PropertyGettedEvent.java
package onlyfun.caterpillar;
import org.springframework.context.*;
public class PropertyGettedEvent extends ApplicationEvent {
public PropertyGettedEvent(Object source) {
super(source);
}
}
當ApplicationContext執行publishEvent()后,會自動尋找實現ApplicationListener接口的對象并通知其發生對應事件,我們實現了PropertyGettedListener如下:
PrppertyGettedListener.java
package onlyfun.caterpillar;
import org.springframework.context.*;
public class PropertyGettedListener implements ApplicationListener {
public void onApplicationEvent(ApplicationEvent event) {
System.out.println(event.getSource().toString());
}
}
Listener必須被實例化,這我們可以在Bean定義檔中加以定義:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING/DTD BEAN/EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="propertyGetterListener" class="onlyfun.caterpillar.PropertyGettedListener"/>
<bean id="helloBean" class="onlyfun.caterpillar.HelloBean">
<property name="helloWord"><value>Hello!Justin!</value></property>
</bean>
</beans>
我們寫一個測試程序來測測事件傳播的運行:
Test.java
package onlyfun.caterpillar;
import org.springframework.context.*;
import org.springframework.context.support.*;
public class Test {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
HelloBean hello = (HelloBean) context.getBean("helloBean");
System.out.println(hello.getHelloWord());
}
}
執行結果會如下所示:
log4j:WARN No appenders could be found for logger
(org.springframework.beans.factory.xml.XmlBeanDefinitionReader).
log4j:WARN Please initialize the log4j system properly.
org.springframework.context.support.ClassPathXmlApplicationContext:
displayName=[org.springframework.context.support.ClassPathXmlApplicationContext;
hashCode=33219526]; startup date=[Fri Oct 29 10:56:35 CST 2004];
root of ApplicationContext hierarchy
[Hello!Justin!] is getted
Hello!Justin!
以上是以實現事件傳播來看看實現Aware接口取得對應對象后,可以進行的動作,同樣的,您也可以實現ResourceLoaderAware接口:
ResourceLoaderAware.java
public interface ResourceLoaderAware {
void setResourceLoader(ResourceLoader loader);
}
實現ResourceLoader的Bean就可以取得ResourceLoader的實例,如此就可以使用它的getResource()方法,這對于必須存取檔案資源的Bean相當有用。
基本上,Spring雖然提供了這些Aware相關接口,然而Bean上若實現了這些界面,就算是與Spring發生了依賴,從另一個角度來看,雖然您可以直接在Bean上實現這些接口,但您也可以透過setter來完成依賴注入,例如:
HelloBean.java
package onlyfun.caterpillar;
import org.springframework.context.*;
public class HelloBean {
private ApplicationContext applicationContext;
private String helloWord = "Hello!World!";
public void setApplicationContext(ApplicationContext context) {
this.applicationContext = context;
}
public void setHelloWord(String helloWord) {
this.helloWord = helloWord;
}
public String getHelloWord() {
applicationContext.publishEvent(new PropertyGettedEvent("[" + helloWord + "] is getted"));
return helloWord;
}
}
注意這次我們并沒有實現ApplicationContextAware,我們在程序中可以自行注入ApplicationContext實例:
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
HelloBean hello = (HelloBean) context.getBean("helloBean");
hello.setApplicationContext(context);
System.out.println(hello.getHelloWord());
就Bean而言,降低了對Spring的依賴,可以比較容易從現有的框架中脫離。
posted @ 2010-11-24 11:14 楊羅羅 閱讀(7670) | 評論 (1) | 編輯 收藏
在JDK 5之前Java語言是靠synchronized關鍵字保證同步的,這會導致有鎖(后面的章節還會談到鎖)。
鎖機制存在以下問題:
(1)在多線程競爭下,加鎖、釋放鎖會導致比較多的上下文切換和調度延時,引起性能問題。
(2)一個線程持有鎖會導致其它所有需要此鎖的線程掛起。
(3)如果一個優先級高的線程等待一個優先級低的線程釋放鎖會導致優先級倒置,引起性能風險。
volatile是不錯的機制,但是volatile不能保證原子性。因此對于同步最終還是要回到鎖機制上來。
獨占鎖是一種悲觀鎖,synchronized就是一種獨占鎖,會導致其它所有需要鎖的線程掛起,等待持有鎖的線程釋放鎖。而另一個更加有效的鎖就是樂觀鎖。所謂樂觀鎖就是,每次不加鎖而是假設沒有沖突而去完成某項操作,如果因為沖突失敗就重試,直到成功為止。
CAS 操作
上面的樂觀鎖用到的機制就是CAS,Compare and Swap。
CAS有3個操作數,內存值V,舊的預期值A,要修改的新值B。當且僅當預期值A和內存值V相同時,將內存值V修改為B,否則什么都不做。
非阻塞算法 (nonblocking algorithms)
一個線程的失敗或者掛起不應該影響其他線程的失敗或掛起的算法。
現代的CPU提供了特殊的指令,可以自動更新共享數據,而且能夠檢測到其他線程的干擾,而 compareAndSet() 就用這些代替了鎖定。
拿出AtomicInteger來研究在沒有鎖的情況下是如何做到數據正確性的。
private volatile int value;
首先毫無以為,在沒有鎖的機制下可能需要借助volatile原語,保證線程間的數據是可見的(共享的)。這樣才獲取變量的值的時候才能直接讀取。
public final int get() {
return value;
}
然后來看看++i是怎么做到的。
public final int incrementAndGet() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return next;
}
}
在這里采用了CAS操作,每次從內存中讀取數據然后將此數據和+1后的結果進行CAS操作,如果成功就返回結果,否則重試直到成功為止。
而compareAndSet利用JNI來完成CPU指令的操作。
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
整體的過程就是這樣子的,利用CPU的CAS指令,同時借助JNI來完成Java的非阻塞算法。其它原子操作都是利用類似的特性完成的。
而整個J.U.C都是建立在CAS之上的,因此對于synchronized阻塞算法,J.U.C在性能上有了很大的提升。
CAS看起來很爽,但是會導致“ABA問題”。
CAS算法實現一個重要前提需要取出內存中某時刻的數據,而在下時刻比較并替換,那么在這個時間差類會導致數據的變化。
比如說一個線程one從內存位置V中取出A,這時候另一個線程two也從內存中取出A,并且two進行了一些操作變成了B,然后two又將V位置的數據變成A,這時候線程one進行CAS操作發現內存中仍然是A,然后one操作成功。盡管線程one的CAS操作成功,但是不代表這個過程就是沒有問題的。如果鏈表的頭在變化了兩次后恢復了原值,但是不代表鏈表就沒有變化。因此前面提到的原子操作AtomicStampedReference/AtomicMarkableReference就很有用了。這允許一對變化的元素進行原子操作。
posted @ 2010-11-18 15:16 楊羅羅 閱讀(3133) | 評論 (1) | 編輯 收藏
volatile保證線程間的數據是可見的(共享的),但不保證數據同步
volatile相當于synchronized的弱實現,也就是說volatile實現了類似synchronized的語義,卻又沒有鎖機制。它確保對volatile字段的更新以可預見的方式告知其他的線程。
volatile包含以下語義:
(1)Java 存儲模型不會對valatile指令的操作進行重排序:這個保證對volatile變量的操作時按照指令的出現順序執行的。
(2)volatile變量不會被緩存在寄存器中(只有擁有線程可見)或者其他對CPU不可見的地方,每次總是從主存中讀取volatile變量的結果。也就是說對于volatile變量的修改,其它線程總是可見的,并且不是使用自己線程棧內部的變量。也就是在happens-before法則中,對一個valatile變量的寫操作后,其后的任何讀操作理解可見此寫操作的結果。
volatile變量的特性不錯,但是volatile并不能保證線程安全的,也就是說volatile字段的操作不是原子性的,volatile變量只能保證可見性(一個線程修改后其它線程能夠理解看到此變化后的結果),要想保證原子性,目前為止只能加鎖!
volatile通常在下面的場景:







應用volatile變量的三個原則:
(1)寫入變量不依賴此變量的值,或者只有一個線程修改此變量
(2)變量的狀態不需要與其它變量共同參與不變約束
(3)訪問變量不需要加鎖
posted @ 2010-11-18 14:45 楊羅羅 閱讀(1720) | 評論 (0) | 編輯 收藏
posted @ 2010-11-18 14:42 楊羅羅 閱讀(1094) | 評論 (0) | 編輯 收藏