日百万PVのアーキテクチャ実践


最近、携帯アプリのプロジェクトをしています.需要は以下の通りである. 
1:最近ログインしたユーザーのリストと現在のステータスの表示(オンライン/オフライン/ビジー/チャット)
2:ユーザの状態及び音声の時間順に、ユーザ及びそのユーザが発表する最後の投稿リストを表示する.(固定表示上位50名)
如:张三+张三最后帖子-->李四+李四最后帖子.......
3:すべてのユーザ情報とユーザ発表投稿を表示する.時間順に並べる
 
1と3は言わない.解決した2、残りはスラグだ.まず私の実現過程を話します. 
 
ステージ1:
ユーザの状態はリアルタイムで変化するため、音声の時間もリアルタイムで変化する.従って、このようにソート.クエリーのたびに異なる結果が得られます.データベースを保存するのはもちろん担げない.データベースの頻繁な読み書きにかかわる.このため、ユーザ情報及び状態をRedisに格納ことを考慮する. 
 
ビジネスニーズに応じて、認証アイドル>認証ビジー>認証マウント>通常オンライン>認証オフライン>通常オフライン.
 
解決構想:各状態の基礎値を定義し、音声時間を一つの集合とするscore値を加える.このscoreに従ってソートする.(霊感は金の花を揚げる時の各種の札力値の大きさの比較に由来する.)
 
	public static final BigDecimal RENZHENG_KONGXIAN = new BigDecimal("900000000"); //     
	public static final BigDecimal RENZHENG_MANGLU   = new BigDecimal("800000000"); //     
	public static final BigDecimal RENZHENG_GUAJI    = new BigDecimal("700000000"); //     
	public static final BigDecimal PUTONG_ZAIXIAN    = new BigDecimal("600000000"); //     
	public static final BigDecimal RENZHENG_LIXIAN   = new BigDecimal("500000000"); //     
	public static final BigDecimal PUTONG_LIXIAN     = new BigDecimal("400000000"); //     

 
 
	/**
	 * @Notes :       
	 * @Author: songzj
	 * @Date : 2015 5 14    7:53:24
	 * @param uin
	 * @param state
	 */
	private static void updateUserSort(int uin, byte state) {
		JedisCluster jc = JedisPoolUtil.getJedisCluster();

		//          .
		String topics = jc.hget(LOGON_USER_INFO + uin, "topic");
		if (topics != null && Integer.parseInt(topics) > 0) {
			String time = jc.hget(LOGON_USER_INFO + uin, "times"); //          
			time = Utils.isBlank(time) ? "0" : time;
			BigDecimal times = new BigDecimal(time);
			String vip = jc.hget(LOGON_USER_INFO + uin, "vip");//     
			vip = Utils.isBlank(vip) ? "0" : vip;
			if ("0".equals(vip)) {//    
				switch (state) {
				case 0:
				case 1:
				case 2:
				case 3:
					times = PUTONG_ZAIXIAN.add(times);
					break;
				case 4:
					times = PUTONG_LIXIAN.add(times);
					break;
				}
			} else {//   
				switch (state) {
				case 0:
					times = RENZHENG_KONGXIAN.add(times);
					break;
				case 1:
					times = RENZHENG_MANGLU.add(times);
					break;
				case 2:
					times = RENZHENG_GUAJI.add(times);
					break;
				case 4:
					times = RENZHENG_LIXIAN.add(times);
					break;
				}
			}
			//         score
			jc.zadd(USER_SORT_LIST, times.doubleValue(), "" + uin);

			//        .
			String tagId = jc.hget(LOGON_USER_INFO + uin, "tagId");
			if (Utils.isNotBlank(tagId)) {
				jc.zadd(USER_TAG_LIST + tagId, times.doubleValue(), uin + "");
			}
		} else {
			jc.zadd(USER_SORT_LIST, 0, "" + uin);
		}

	}

 
よし、ユーザ状態によるリアルタイムソートOK.
 
しかし、ユーザーリストと投稿リストを取るときは少し穴があいています.クラスタを使用するため、異なるユーザデータ、投稿データは異なる機器に格納される.またJedisを使用する場合はパイプを使用することができない.大神様に良い案があれば、教えてください.ユーザー一人一人の投稿を読み取る必要がある.中間ネットワークの伝送は多くの資源と時間を占めている.従って効率が低下. 
 
簡単にabで測って、単台tomcat.10,000回のリクエスト100個同時で300~500 tps.ひどく遅い 
もう2つ前にnginxでエージェントをします.複数のtomcatで担ぐ 
 
nginxの配置は以下の通りである. 
 
 
upstream mfs_server{
   server 1.251.192.37;
   server 1.251.192.47:8080;
   server 1.251.192.47:8085;
   keepalive 128;
}
 
 
 
location /mfs{
        proxy_set_header Host  $host;
        proxy_set_header X-Forwarded-For  $remote_addr;
        proxy_pass http://mfs_server;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
 }
 
 
nginxを再起動します.同じ条件abで1回走ると、性能が3つアップする.しかし、伝送率はまだ足りない.ネットカードが最大10 Mを満たしていない.tomcatとnginxを最適化した.昇進量はまだ大きくない. 
 
業務の観点から考えるユーザの基本情報や投稿情報はリアルタイムで更新表示する必要はない.15 s~30 sの遅延はユーザが許容できる. 
したがって、nginxをキャッシュとすることを考慮する.このようにして直接nginx処理を要求する. 
 
JAvaコードの処理は以下の通りである.ページに戻るときに有効期限を設定します.nginxがこの時間内にtomcatに迷惑をかけないようにします.
 
 
		long cur = System.currentTimeMillis() + (8 * 60 * 60 * 1000);
		response.setHeader("ETag", Long.valueOf(cur).toString());
		response.setHeader("Cache-Control", "public,max-age=60");
		response.setHeader("Cache-Control", "max-age=60");
		long adddaysM = 60 * 1000;
		response.addDateHeader("Last-Modified", cur);
		response.addDateHeader("Expires", cur + adddaysM);
		String requestUrl = request.getRequestURI();
 
 
nginxの配置は以下の通りである.
 
 
  client_body_buffer_size  512k;
  proxy_connect_timeout    60;
  proxy_read_timeout       60;
  proxy_send_timeout       60;
  proxy_buffer_size        16k;
  proxy_buffers            8 128k;
  proxy_busy_buffers_size 128k;
  proxy_temp_file_write_size 128k;
  gzip on;
 #nginx    .        10m   ,   Z.
  proxy_cache_path /home/songzj/cache levels=1:2 keys_zone=Z:10m inactive=1 max_size=1g;


 
 
 
location /mfs/topic/topicList{
      proxy_pass http://mfs_server; //     tomcat.   round_robin
      proxy_set_header X-Forwarded-For $remote_addr;

      proxy_cache Z;  // Z     
      proxy_cache_min_uses 1;
      proxy_cache_valid 200 302 1m;
      proxy_cache_valid 404 1m;
 
}
 
プロジェクトを再配置するnginxを再起動します.OK. 所期の効果を達成するネットカードがいっぱい走る12000 tpsに達することができます.
もう一つの問題は、キャッシュページが無効になったとき、大量のリクエストがバックグラウンドtomcatに届いたことです.tomcatはプレッシャーが大きい.これが伝説のdogpileです.解決策は簡単だ. 
 location /mfs/topic/topicList{
        proxy_pass http://mfs_server;
        proxy_set_header X-Forwarded-For $remote_addr;

        proxy_cache Z;
        proxy_cache_min_uses 1;
        proxy_cache_valid 200 302 1m;
        proxy_cache_valid 404 1m;
        proxy_cache_use_stale updating invalid_header error timeout http_500;
}
 
これにより、updateがキャッシュされる.の場合、新しいデータをバックグラウンドで更新するスレッドは1つしかありません.残りのユーザは古いキャッシュを読み出す.もちろん、データ量が大きい場合は、Gzip圧縮をオンにすることができる.
よし百万pvはほとんど問題ありません.Redisメモリのユーザ情報については、個々に取る問題が最適化される.请高手指点