這3台不到20萬的中型車,讓開邁騰的年輕人羡慕到想賣車

阿特茲,雖然是一大堆的優點,但都是針對駕駛者而設計的,因此乘客的乘坐體驗就一般了,加上在風噪胎躁方面的控制不是很好,整體的乘坐質感並不高,如果是想給家人更多的舒適體驗,那還是再考慮考慮吧。蒙迪歐,綜合實力很平均,然而在這個價位購車的朋友們多多少少會考慮到油耗這個問題,雖然蒙迪歐的動力數據很好,但在強大動力的加持下,油耗的表現確實是偏高。

今天,跟大家嘮嘮幾款打着運動旗號的中型車,它們分別是:阿特茲,蒙迪歐以及思鉑睿,一看到這幾款車,大家都知道,這些車都有一個通病,就是銷量不高~

其中,蒙迪歐在2017的全年的銷量為:112158台,阿特茲則是:53493台,思鉑睿最少,才19277台,換個說法,這三款車每個月的銷量都是不會破萬的,它們的小眾程度可見一般。

三車外觀如何?

阿特茲自不必說,採用了“魂動”設計語言的它不負“ATENZA”這個寓意為萬眾矚目的稱號。

蒙迪歐,大多朋友戲稱為“小馬丁”的存在,當然,此馬丁非天才DJ Martin Garrix,而是汽車界的紳士-阿斯頓·馬丁,雖然嚴謹的說,它們沒什麼關係,但是蒙迪歐的運動外觀卻是不爭的事實。

思鉑睿,低矮的車身,兇悍的前進氣格柵,這都是它為了區別於傳統舒適性中型車的外觀而設計的。

既然是主打運動,那麼動力是必不可少的較量了

通過三台車的動力系統對比,蒙迪歐在總體上比其它兩款車的動力更加好,畢竟渦輪增壓在這裏佔得便宜不小,但是論起操控,號稱東瀛寶馬的馬自達是三車之中最好的,而屬於買發動機送車殼的本田思鉑睿在這三款車中,發動機與變速箱的匹配程度是最高的。

那麼,運動型車能家用嗎?

在這之前,給大家看看三車的後排空間數據,大家就明白是咋回事了。

阿特茲空間數據

蒙迪歐空間數據

思鉑睿空間數據

通過對比看出,其實三款車型均為中型轎車,在空間方面也不要過於擔心,就算再不濟也是足夠正常使用。

三車的配置上,哪個更優?

給大家帶來的是三款車在相近價位的配置對比,通過對比看出阿特茲的配置更加豐富一點,也更加符合國人買車時“配置豐富”的需求,所以阿特茲在配置對比方面優勢不小。

說了這麼多,這些車都沒有缺點嗎?

當然不是,為你一一道來。

阿特茲,雖然是一大堆的優點,但都是針對駕駛者而設計的,因此乘客的乘坐體驗就一般了,加上在風噪胎躁方面的控制不是很好,整體的乘坐質感並不高,如果是想給家人更多的舒適體驗,那還是再考慮考慮吧。

蒙迪歐,綜合實力很平均,然而在這個價位購車的朋友們多多少少會考慮到油耗這個問題,雖然蒙迪歐的動力數據很好,但在強大動力的加持下,油耗的表現確實是偏高。

思鉑睿,說句老實話,它的裝配水平在這麼多的車之中只能算是一般,而且車內各處的異響也不少,但好在整體的行駛質感做得不錯,在保證一定運動格調的情況下還能兼顧到家庭所需。

對於這三款車型而言,其實取捨也很簡單,假如你是一名極致的操控愛好者,那麼阿特茲絕對可以滿足到你,而針對於蒙迪歐和思鉑睿而言,雖然同樣定位在運動型中級車上,但是它們依然保持着很不錯的舒適性能兼顧到家庭所需,更適合那些以家庭出行為主但又對操控有所要求的朋友。

那麼這幾款車,哪個配置的最值得入手呢?阿特茲,選擇2.5L藍天運動版性價比是最高的,在保證足夠的動力的前提下配置也足夠我們日常所需;而蒙迪歐則可以考慮到1.5T的時尚型,動力能滿足日常所需而且在配置上也能滿足需求;最後輪到思鉑睿,雖然2.4L版本更值得推薦,而且已經自帶了原來的SI運動包圍,但據消息稱2.4L版本已經開始停產,而作為主銷車型的實則為2.0L的低排量版本以及混動版本,而更推薦考慮混動版本。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※超省錢租車方案

※別再煩惱如何寫文案,掌握八大原則!

※回頭車貨運收費標準

※教你寫出一流的銷售文案?

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

java併發編程系列原理篇–JDK中的通信工具類Semaphore

前言

java多線程之間進行通信時,JDK主要提供了以下幾種通信工具類。主要有Semaphore、CountDownLatch、CyclicBarrier、exchanger、Phaser這幾個通訊類。下面我們來詳細介紹每個工具類的作用、原理及用法。

Semaphore介紹

Semaphore翻譯過來是信號的意思。顧名思義,這個工具類提供的功能就是多個線程彼此“打信號”。而這個“信號”是一個int類型的數據,也可以看成是一種“資源”,用來限定線程訪問該資源的數量。
它的構造函數有兩個,一個參數的用來指定線程訪問資源的數量;兩個參數的一個用來指定線程訪問資源的數量,一個用來指定是否為公平鎖。關於公平鎖非公平鎖的概念請參照文章java併發編程系列之原理篇-synchronized與鎖。構造函數代碼如下:


  // 默認情況下使用非公平
  public Semaphore(int permits) {
        sync = new NonfairSync(permits);
  }
  public Semaphore(int permits, boolean fair) {
      sync = fair ? new FairSync(permits) : new NonfairSync(permits);
  }

它的最主要的兩個方法是acquire()和release()。acquire()方法會申請一個permit,而release方法會釋放一個permit。當然,你也可以申請多個acquire(int permits)或者釋放多個release(int permits)。每次acquire,permits就會減少一個或者多個。如果減少到了0,再有其他線程來acquire,那就要阻塞這個線程直到有其它線程release permit為止。

Semaphore的使用

Semaphore主要用來控制線程訪問資源的數量的場景。舉個例子,在併發條件下,我只想讓3個線程來執行某一任務。請看示例代碼:

public class SemaphoreDemo {

    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(3);
        for (int i = 0; i < 10; i++) {
            new Thread(new MyThread(i,semaphore)).start();
        }
    }

     static class  MyThread implements Runnable{

        private int id;//線程的ID號
        private Semaphore semaphore;

        public MyThread(int id, Semaphore semaphore){
            this.id = id;
            this.semaphore = semaphore;
        }

        @Override
        public void run() {
            try {
                //獲取信號量permit許可
                semaphore.acquire();
                //接下來可以用來執行具體的線程任務
                System.out.println(String.format("當前的線程是%d,還剩有%d個線程資源可以使用,有%d個線程處於等待中。",
                        id,semaphore.availablePermits(),semaphore.getQueueLength()));
                Random random = new Random();
                //隨機睡眠時間,打亂釋放順序
                Thread.sleep(random.nextInt(1000));
                System.out.println(String.format("線程%d釋放了資源",id));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                //任務結束,釋放資源
                semaphore.release();
            }
        }
    }
}

輸出結果:

當前的線程是1,還剩有1個線程資源可以使用,有0個線程處於等待中。
當前的線程是0,還剩有2個線程資源可以使用,有0個線程處於等待中。
當前的線程是2,還剩有0個線程資源可以使用,有0個線程處於等待中。
線程2釋放了資源
當前的線程是3,還剩有0個線程資源可以使用,有6個線程處於等待中。
線程1釋放了資源
當前的線程是4,還剩有0個線程資源可以使用,有5個線程處於等待中。
線程0釋放了資源
當前的線程是5,還剩有0個線程資源可以使用,有4個線程處於等待中。
線程3釋放了資源
當前的線程是6,還剩有0個線程資源可以使用,有3個線程處於等待中。
線程4釋放了資源
當前的線程是7,還剩有0個線程資源可以使用,有2個線程處於等待中。
線程5釋放了資源
當前的線程是8,還剩有0個線程資源可以使用,有1個線程處於等待中。
線程8釋放了資源
當前的線程是9,還剩有0個線程資源可以使用,有0個線程處於等待中。
線程7釋放了資源
線程6釋放了資源
線程9釋放了資源

從結果可以看出來,最初搶到這3個資源的線程是1,0,2,而其他線程進入了等待隊列。之後每當有一個線程釋放了該資源,才會有其他在等待隊列的線程搶到資源。Semaphore默認的acquire方法是會讓線程進入等待隊列,且會拋出中斷異常。但它還有一些方法可以忽略中斷或不進入阻塞隊列:

 // 忽略中斷
 public void acquireUninterruptibly()
 public void acquireUninterruptibly(int permits)

 // 不進入等待隊列,底層使用CAS
 public boolean tryAcquire
 public boolean tryAcquire(int permits)
 public boolean tryAcquire(int permits, long timeout, TimeUnit unit) throws InterruptedException
 public boolean tryAcquire(long timeout, TimeUnit unit)

Semaphore的原理

Semaphore內部有一個繼承了AQS的同步器Sync成員變量,重寫了tryAcquireShared方法。在這個方法里,會去嘗試獲取資源。如果獲取失敗(想要的資源數量小於目前已有的資源數量),就會返回一個負數(代表嘗試獲取資源失敗)。然後當前線程就會進入AQS的等待隊列。具體的代碼邏輯請查看JDK1.8中java.util.concurrent包下的Semaphore類。

參考鏈接

在這裏很感謝能夠有幸看到來自各個大廠大神們的開源項目深入淺出Java多線程,讓我對多線程的知識有一個更深層次的了解。文中如有地方寫的不妥當或者有疑問,請大家留言,大家相互學習。感謝!

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※教你寫出一流的銷售文案?

讓人又愛又恨的Lombok,到底該不該用

1 簡介

Lombok,印尼的一個島嶼,龍目島。但在Java的世界里,它是一個方便的類庫,能提供很多便利,因此得到許多人的青睞。但也有不少反對聲音。這是為什麼呢?

之前去龍目島拍的日落。

2 Lombok提供的便利

一般我們在Java中用到POJO時,就很容易想到要用Lombok,如VO、DTO、DO等。使用Lombok需要安裝對應IDE的插件,同時需要引入依賴:

<dependency>
  <groupId>org.projectlombok</groupId>
  <artifactId>lombok</artifactId>
  <version>1.18.10</version>
  <scope>provided</scope>
</dependency>

舉個例子,如果不用Lombok,實現getter/setter、equals、hashCode、toString代碼量非常大,如下所示:

package com.pkslow.basic.lombok;

import java.util.Objects;

public class Book {
    private String name;
    private int id;
    private double price;
    private String author;
    private String desc;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Book book = (Book) o;
        return id == book.id &&
                Double.compare(book.price, price) == 0 &&
                Objects.equals(name, book.name) &&
                Objects.equals(author, book.author) &&
                Objects.equals(desc, book.desc);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, id, price, author, desc);
    }

    @Override
    public String toString() {
        return "Book{" +
                "name='" + name + '\'' +
                ", id=" + id +
                ", price=" + price +
                ", author='" + author + '\'' +
                ", desc='" + desc + '\'' +
                '}';
    }
}

而且大部分是樣板代碼,沒有太多實際邏輯。

如果使用Lombok則非常簡單,一個註解@Data就可以完成以上工作(安裝了Lombok插件后才能显示右邊的方法):

Lombok的常用註解有:

@NonNull:用於做空指針異常檢測;

@Cleanup:自動資源關閉;

@Getter/@Setter:自動生成get/set方法;

@ToString:生成toString方法,方便打印調試;

@EqualsAndHashCode:生成equals和hashCode方法,注意這兩個應該同時去實現;

@NoArgsConstructor, @RequiredArgsConstructor and @AllArgsConstructor:構造方法;

@Data:相當於 @ToString+ @EqualsAndHashCode+@Getter+ @Setter +@RequiredArgsConstructor。

@Builder:生成builder方法;

@Log:日誌相關:

@CommonsLog:org.apache.commons.logging.LogFactory.getLog(LogExample.class);
@Flogger:com.google.common.flogger.FluentLogger.forEnclosingClass();
@JBossLog:org.jboss.logging.Logger.getLogger(LogExample.class);
@Log:java.util.logging.Logger.getLogger(LogExample.class.getName());
@Log4j:org.apache.log4j.Logger.getLogger(LogExample.class);
@Log4j2:org.apache.logging.log4j.LogManager.getLogger(LogExample.class);
@Slf4j:org.slf4j.LoggerFactory.getLogger(LogExample.class);
@XSlf4j:org.slf4j.ext.XLoggerFactory.getXLogger(LogExample.class);
@CustomLog:com.foo.your.LoggerFactory.createYourLogger(LogExample.class);

所以Lombok確實能給我們帶來極大的便利,減少大量重複、無業務邏輯的代碼,代碼顯得比較乾淨;修改了field的名字,不用多處修改;提高開發效率。

3 Lombok所帶來的問題

當然,也有人提出了不同意見:

調試時沒有具體代碼,不方便Debug;

需要強制別人安裝第三方插件,不然會显示報錯;

在檢查測試覆蓋率的時候,無法直觀显示哪些代碼已覆蓋和未覆蓋;

無法按自己意願實現,比如toString方法,有時需要輸出不一樣的String格式,並不一定是按Lombok的實現;

對於一些常用的方法,IDE已經可以自動生成,不需要Lombok一樣可以高效開發。

IDEA沒有提供Builder,但可以通過安裝插件方式使用。

成功安裝后,就能生成Builder代碼了:

4 總結

一開始我是支持使用Lombok的,經過一段時間使用及出現了一些問題后,我還是覺得通過IDE自動生成代碼的方式更適合。畢竟強制要求別人安裝插件這種實在是太野蠻了,而通過IDEA生成代碼,別人不安裝插件也不會有報錯。

另外,多幾行代碼還能體現工作量,哈哈哈哈哈哈……

歡迎訪問南瓜慢說 www.pkslow.com獲取更多精彩文章!

歡迎關注微信公眾號<南瓜慢說>,將持續為你更新…

多讀書,多分享;多寫作,多整理。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

※南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

※超省錢租車方案

使用三台雲服務器搭建真正的Redis集群

三台雲服務器搭建redis集群#

今天花了一天的時間弄集群redis;遇到了很多坑,從頭開始吧

環境講解:

兩台配置:1核2G,另一台:1核1G;
操作系統:Centos 7.6
Redis:3.2.12
Ruby:2.3.4

由於是雲服務器所以默認安裝了JDK1.8和yum以及gcc,如果不是雲服務器的用戶請試試雲服務器吧!還是很方便的

了解什麼是Redis集群(可跳過):

答:集群,即Redis Cluster,是Redis 3.0開始引入的分佈式存儲方案。
集群由多個節點(Node)組成,Redis的數據分佈在這些節點中。集群中的節點分為主節點和從節點:只有主節點負責讀寫請求和集群信息的維護;從節點只進行主節點數據和狀態信息的複製。
集群的作用,可以歸納為兩點:

  1. 數據分區:數據分區(或稱數據分片)是集群最核心的功能。
    集群將數據分散到多個節點,一方面突破了Redis單機內存大小的限制,存儲容量大大增加;另一方面每個主節點都可以對外提供讀服務和寫服務,大大提高了集群的響應能力。
    Redis單機內存大小受限問題,在介紹持久化和主從複製時都有提及;例如,如果單機內存太大,bgsave和bgrewriteaof的fork操作可能導致主進程阻塞,主從環境下主機切換時可能導致從節點長時間無法提供服務,全量複製階段主節點的複製緩衝區可能溢出……。

  2. 高可用:集群支持主從複製和主節點的自動故障轉移(與哨兵類似);當任一節點發生故障時,集群仍然可以對外提供服務。

值得一提的是這幾天的面試遇到一個很有意思的問題(本人親身經歷):

面試官:讓你設計一個搶票或者秒殺或者紅包系統你該如何實現?

我:首先應該加鎖,然後加redis的緩存和預熱。。。

面試官:沒了?

我:我只知道這麼多。

面試官:那如果是在多個集群下呢?

我:分佈式鎖!

面試官欣慰的點頭說:那你知道分佈式鎖的實現方式嗎?

我:zookeeper和redis的redisson。

面試官:redis的分佈式集群玩過嗎?

我:沒有。。。

面試官:有空去玩玩吧,既然你沒玩過那我也不必要和你啰嗦了。

到此我的奇怪的經歷又上漲了!於是今天買了兩個測試服務器來玩玩redis的集群;

這是我的使用場景,但是現實中和面試的例子差不多,都是有需求才去接觸,(可能我比較懶~haha)
當然今天並不是說面試,我們言歸正傳;

搭建,啟動redis集群#

1、安裝Redis(三台服務器都需要安裝)

方式一:

一台全新的服務器首先需要配置密碼和SSH登錄此處我跳過;
接着可以更新yum(服務器的yum都是安裝好的)

yum update

在雲服務器中的安裝則是很簡單的;

yum install redis

安裝需要提示輸入y直接輸入!

接着安裝完成后查看安裝情況:

redis-server -v

或者

redis-server --version

都可以查看當前安裝的redis版本

方式二:

直接從官網下載gz文件

redis舊版本下載地址

一般都是3.2.12版本。所以此處以3.2.12版本為例;

下載好後上傳到雲服務器,並且解壓后,make Install 安裝

redis安裝好后,有五個文件在linux文件系統的/usr/bin中

  • redis-benchmark
  • redis-check-aof
  • redis-check-rdb
  • redis-cli
  • redis-sentinel
  • redis-server

還有一個配置文件在/etc中:

  • redis.conf

現在我們在/usr/local下創建一個文件名叫redis

cd /usr/local
mkdir redis

接着創建兩個文件夾:

cd redis
mkdir redis-01
mkdir redis-02

接着將上述的文件分別複製到這兩個01和02文件內,
這是為了方便我們更好的在本機調用:

2、安裝Ruby(只需要一台安裝即可)

很多小夥伴會有疑問說為什麼需要ruby呢?

因為在redis/src中有一個文件叫做redis-trib.rb,這個文件就是作者用Ruby寫的,用來搭建redis集群(redis3.0版本時才開始支持集群),redis-trib.rb的後綴就是Ruby的簡寫,所以想要搭建redis集群需要有一個能執行.rb這種文件的運行環境,這個環境就是Ruby。

接着又會有小夥伴說:這個安裝簡單!直接 yum install ruby!
你要是這麼想的話,會浪費你將近5-10分鐘的時間(取決你的網速)因為你需要安裝后升級它!

升級的教程我直接用別人的了:[Ruby升級教程](https://www.cnblogs.com/qize/p/11394841.html](https://www.cnblogs.com/qize/p/11394841.html “Ruby升級教程”) 在此也感謝這個博主所寫的經驗教程

一般雲服務器如果安裝的redis是3.2.12,那麼你的ruby使用yum安裝的話一定是2.0版本的!
這個和我們使用的ruby操作redis集群不適應!
所以人生苦短:

wget https://cache.ruby-lang.org/pub/ruby/2.4/ruby-2.4.5.tar.gz

接着解壓下載好的ruby:

tar -xzvf ruby2.4.5

接着cd ruby2.4.5執行下面的命令

./configure –-prefix=/usr/local/ruby -prefix

是將ruby安裝到指定目錄,也可以自定義

make && make install

檢查Ruby安裝情況:

ruby -v

不出意外應該是可以显示版本的,之後安裝這個gem的依賴

yum install rubygems

接着我們需要繼續安裝Redis操作工具

gem install redis

3、檢查配置文件(三台服務器都需要)

上述說到我們有兩個文件:redis-01和redis-02,應該是這樣的

現在需要修改每一個配置文件:

daemonize yes
port 6379(每台機器的端口可以指定為兩個;比如6379和6380)
dbfilename dump-此處添加你的端口.rdb
pidfile /var/run/redis-此處添加你的端口.pid
requirepass 在此輸入你的密碼
masterauth 輸入和上述密碼一樣的
cluster-enabled yes 啟用集群
cluster-config-file nodes-此處添加你的端口.conf
cluster-node-timeout 15000(默認你過期時間15s)

此處網上有很多提示說你的bind屬性為什麼不加呢?
很多小夥伴加了bind屬性反而不能運行redis,剛開始我也犯了這樣的錯誤,後來我發現,雲服務器上的redis
其實不需要加bind屬性綁定端口也可以跑集群。

現在我一共是三台機器:每台上面有兩個redis實例,都分別綁定了6379和6380端口

至此我們的配置文件就結束了。

4、放行端口(三台服務器都需要)

根據不同雲服務器供應商需要進入不同的控制台管理界面;

但是每台服務器的安全組都需要開放對應的端口:

我的三台服務器我就需要為每台服務器開啟入口6379和6380端口;
但是千萬不要以為這就結束了!

redis集群還需要一個總線端口,這個總線端口是你redis實例佔用端口+10000;

比如我一台服務器開了兩個redis實例:6379和6380,那麼他的總線端口救為16379和16380;

需要在安全組同時開放這兩個端口。

啟動集群

回到我們剛剛安裝redis的那台服務器,現在我們來試試啟動redis集群

要啟動redis;需要找到你的 redis-trib.rb 這個文件

一般雲服務器的安裝redis是沒有這個文件的,但是如果你用解壓安裝的redis是有集群的文件

所以需要你自己從官網拷貝比如我的是3.2.12就需要從官網下載。

redis舊版本下載地址

具體安裝見方式二

之後再redis/src下找到 redis-trib.rb 這個文件

把他複製到/usr/local/redis裏面

接下來重中之重的是需要清空/var/lib/redis裏面的所有rdb和conf文件!

之後就可以啟動三個服務器上的redis了

啟動完成可以用 ps -ef|grep redis 查看實例啟動詳情

一般啟動成功是這個樣子

之後回到安裝了ruby的服務器:

cd /usr/local/redis

執行下面命令

./redis-trib.rb create --replicas 1 第一台服務器公網IP地址:6379 第一台服務器公網IP地址:6380 第二台服務器公網IP地址:6379  第二台服務器公網IP地址:6380 第三台服務器公網IP地址:6379 第三台服務器公網IP地址:6380

至此服務器應該是啟動成功了。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※南投搬家公司費用需注意的眉眉角角,別等搬了再說!

※教你寫出一流的銷售文案?

面向對象存儲框架:Obase快速入門

在項目中完成對象建模后,可以使用Obase來進行對象的管理(例如對象持久化),本篇教程將創建一個.NET Core控制台應用,來展示Obase的配置和對象的增刪改查操作。本篇教程旨在指引簡單入門。
本篇教程將以此對象模型展開

class Blog{
    +BlogId:int[文章Id]
    +Url:string[文章地址]
    +Post:sList<Post>[文章評論]
}

class Post{
    +PostId:int[評論Id]
    +Title:string[評論標題]
    +Content:string[評論內容]
    +Blog:Blog[關聯文章]
}

Blog "1"-right-"*"  Post
hide empty member

從NuGet安裝Obase

  • 在VS的NuGet包管理器中添加程序包源:http://nuget.suiyiyun.cn:8081/nuget
  • 在NuGet包管理器中選擇Obase進行安裝

項目搭建

  • 打開 Visual Studio
  • 單擊“創建新項目”
  • 選擇帶有 C# 標記的“控制台應用 (.NET Core)” ,然後單擊“下一步”
  • 輸入“ObaseTutorial” 作為名稱,然後單擊“創建”
  • 添加對freep.Obase.dll的引用

定義領域實體類

	/// <summary>
    /// 文章
    /// </summary>
    public class Blog
    {
        private int blogId;
        private string url;
        private List<Post> posts;

        /// <summary>
        /// 文章Id
        /// </summary>
        public int BlogId { get => blogId; set => blogId = value; }
        /// <summary>
        /// 文章地址
        /// </summary>
        public string Url { get => url; set => url = value; }
       /// <summary>
       /// 文章評論(注意:關聯引用屬性需要定義為virtual)
       /// </summary>
        public virtual List<Post> Posts { get => posts; set => posts = value; }
    }

   /// <summary>
    /// 文章評論
    /// </summary>
    public class Post
    {
        private int postId;
        private string title;
        private string content;
        private int blogId;
        private Blog blog;

        /// <summary>
        /// 評論Id
        /// </summary>
        public int PostId { get => postId; set => postId = value; }
        /// <summary>
        /// 評論標題
        /// </summary>
        public string Title { get => title; set => title = value; }
        /// <summary>
        /// 評論內容
        /// </summary>
        public string Content { get => content; set => content = value; }
        /// <summary>
        /// 文章Id
        /// </summary>
        public int BlogId { get => blogId; set => blogId = value; }
        /// <summary>
        /// 關聯文章(注意:關聯引用屬性需要定義為virtual)
        /// </summary>
        public virtual Blog Blog { get => blog; set => blog = value; }
    }

自定義對象上下文

Obase直接與應用程序進行交互的便是ObectContext(對象上下文),項目中可以根據具體情況定義一個或者多個繼承於ObjectContext的自定義對象上下文。

	using freep.Obase;
	using freep.Obase.ExecuteSql;
	using freep.Obase.Odm.Builder;

	/// <summary>
    /// 自定義對象上下文
    /// </summary>
    public class MyContext : ObjectContext
    {
        /// <summary>
        /// 構造函數
        /// </summary>
        public MyContext() : base("user=root;password=;server=localhost;database=ObaseTutorial;SslMode = none;port=3306;", true)
        {
        }
    }

注意:自定義對象上下文通過繼承父類的構造函數設置數據源連接字符串(此處為了演示方便,直接將連接字符串作為參數進行傳遞,實際項目中可以定義到配置文件中)。

配置對象模型

在對象數據模型生成之前,可以對數據源的類型進行設置,以及對象數據模型的配置,配置的類型包括實體類型,關聯類型,關聯引用,關聯端,屬性等的配置,本篇只展示最基本的實體類型,關聯類型,關聯引用的配置。

    /// <summary>
    /// 自定義對象上下文
    /// </summary>
    public class MyContext : ObjectContext
    {
		/// <summary>
        /// 在即將生成對象數據模型並註冊到對象上下文之前調用此方法
        /// </summary>
        /// <param name="modelBuilder">建模器</param>
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            //設置模型映射目標源的類型(默認不設置未SQLServer)
            modelBuilder.HasTargetSourceType(eDataSource.MySql);
            //配置對象數據模型
            this.ModelConfiguratoin(modelBuilder);
            base.OnModelCreating(modelBuilder);
        }
        
        /// <summary>
        /// 配置對象數據模型
        /// </summary>
        /// <param name="modelBuilder">建模器</param>
        protected virtual void ModelConfiguratoin(ModelBuilder modelBuilder)
        {
        	//配置實體型
            var blogCfg = modelBuilder.Entity<Blog>();
            //設置實體型的映射數據表
            blogCfg.ToTable("Blog");
            //設置實體型的標識屬性
            blogCfg.HasKeyAttribute(p => p.BlogId);
            //設置實體型的標識屬性為自增
            blogCfg.HasKeyIsSelfIncreased(true);

            //配置實體型
            var postCfg = modelBuilder.Entity<Post>();
            //設置實體型的映射數據表
            postCfg.ToTable("Post");
            //設置實體型的標識屬性
            postCfg.HasKeyAttribute(p => p.PostId);
            //設置實體型的標識屬性為自增
            postCfg.HasKeyIsSelfIncreased(true);

            //配置對象間隱式關聯類型
            var blogAssPostCfg = modelBuilder.Association<Blog, Post>();
            //設置關聯類型的映射數據表
            blogAssPostCfg.ToTable("Post");
            //設置關聯映射端1(參照方)的鍵屬性以及在關聯表中映射的字段
            blogAssPostCfg.AssociationEnd<Blog>("End1").HasMapping("BlogId", "BlogId");
            //設置關聯映射端2(被參照方)的鍵屬性以及在關聯表中映射的字段
            //注意:HasDefaultAsNew方法設置一個值,該值指示是否把關聯端對象默認視為新對象。當該屬性為true時,如果關聯端對象未被顯式附加到上下文,該對象將被視為新對象實施持久化。
            blogAssPostCfg.AssociationEnd<Post>("End2").HasMapping("PostId", "PostId").HasDefaultAsNew(true);

            //配置實體類型的關聯引用屬性
            //參數一:關聯引用屬性的名稱 參數二:關聯引用是否具有多重性
            //注:此處在配置Blog實體與Post實體關聯引用屬性Posts
            var blogRefPosts = blogCfg.AssociationReference<Blog, Post>("Posts", true);
            //設置關聯引用的本端
            blogRefPosts.HasLeftEnd("End1");
            //設置關聯引用的對端
            blogRefPosts.HasRightEnd("End2");
            //設置關聯引用屬性延遲加載
            blogRefPosts.HasEnableLazyLoading(true);

            //配置實體類型的關聯引用屬性
            //參數一:關聯引用屬性的名稱 參數二:關聯引用是否具有多重性
            //注:此處在配置Post實體與Blog實體關聯引用屬性Blog
            var postRefBlog = postCfg.AssociationReference<Blog, Post>("Blog", false);
            //設置關聯引用的本端(注意此處Post是作為本端的)
            postRefBlog.HasLeftEnd("End2");
            //設置關聯引用的對端
            postRefBlog.HasRightEnd("End1");
        }
	}

定義對象集

最終對對象的操作和訪問是通過對象上下文提供的對象集,此處我們定義文章和文章評論對象集:

    /// <summary>
    /// 自定義對象上下文
    /// </summary>
    public class MyContext : ObjectContext
    {
        /// <summary>
        /// 文章對象集
        /// </summary>
        public ObjectSet<Blog> Blogs { get; set; }

        /// <summary>
        /// 文章評論對象集  
        /// </summary>
        public ObjectSet<Post> Posts { get; set; }
    }

對象的創建、讀取、更新和刪除

實例化對象上下文
var myContext = new MyContext();
創建
//實例化對象
Blog blog = new Blog()
{
    Url = "https://www.yuque.com/geekfish/obase/getting-started",
    Posts = new List<Post>() {
        new Post (){  Title= "請問Obase怎麼安裝?", Content = "暫時只提供dll文件"}
    }
};
//將對象附加到對象上下文
myContext.Blogs.Attach(blog);
//將對象保存到數據源
myContext.SaveChanges();
讀取
using System.Linq;

//從持久化源查詢數據
Blog firstBlog = myContext.Blogs.OrderBy(p => p.Url).First();
//訪問關聯引用屬性
List<Post> posts = firstBlog.Posts;
更新
 //修改屬性
firstBlog.Url = "http://www.test.com/aa.html";
//將對象保存到數據源
myContext.SaveChanges();
刪除
//刪除指定對象
myContext.Blogs.Remove(firstBlog);
//根據條件刪除指定對象
myContext.Blogs.Delete(p => p.BlogId == 1);
//將對象保存到數據源(只有在保存后,數據才真實刪除)
myContext.SaveChanges();

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※別再煩惱如何寫文案,掌握八大原則!

※教你寫出一流的銷售文案?

※超省錢租車方案

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※產品缺大量曝光嗎?你需要的是一流包裝設計!

技術人員該如何站好最後一班崗?

挑槽、入槽、跳槽,堪稱每個技術人員必奏的三部曲,而這三部曲在職場中來回奏,便構成了程序人生。

鐵打的硬盤,流水的碼農,離職時見人品,作為技術人員該如何做交接,到底該如何站好最後一班崗呢?

 

1. 人品不夠,文檔來湊。

 

從上家公司離職已經 5 年多啦,記得離職大概沒多久,前技術同事微信告訴我:你寫的交接文檔,在會議上公開表揚,讓其它組作為參考。

當時個人感覺沒啥,就是寫了一堆文檔罷了,近期看到其它團隊交接的效果,那麼的不盡人意。而且秉着吐露真心,認真分享的原則,不妨把那些年寫過的交接文檔,逐一呈現給你,萬一能助你積攢人品、升職加薪呢?

1.1. 作為技術人員離職前的交接,編寫交接進展表為了誰?

離職前的交接,非常能展現人品,最重要的原則是:交接時一定要儘力而為。

盡量能打造屬於自己的交接計劃,按照計劃一步一步去落實,並把交接進展維護在 excel 中,如圖中的《交接進展表.xlsx》。

編寫交接進展表,一方面讓大家明確知曉交接的過程與進度,另一方面可供後人按此方式進行無腦式交接(前人栽樹後人乘涼)。

1.2. 作為技術人員離職前的交接,編寫XX系統_新手入門文檔為了誰?

離職前的交接,希望都能編寫新手入門之類的傻瓜式文檔,該文檔編寫是個一勞永逸的事情。

倘若後續接手你的是一個新同事,那麼就更有價值,按照入門文檔,一步一步就能上手開發、提測、上線,這樣的文檔誰不喜歡?

僅以上面截圖為例,新手入門文檔中包含了系統的簡要說明、功能說明以及功能模塊劃分,可以讓接手的同事對系統有一個全局的認識。

當然,最重要的是要告訴要接手的同事,如何去幹活?文檔中的應用目錄結構介紹以及如何開發、如何編譯、如何提測、如何上線,這幾大塊就顯得很重要。

另外,站在團隊培養人的成本而言,新手入門文檔,不僅僅是為了做好交接,倘若項目組一直就具備該文檔,能夠讓新手快速上手開發業務需求,大概率會降低團隊溝通、培訓的成本。

1.3. 作為技術人員離職前的交接,編寫XX系統_開發生產部署文檔為了誰?

離職前的交接,當接手的同事能夠按照入門文檔開發需求之後,更一步的就是要了解開發、測試、生產部署環境相關的信息。

 

僅以上面截圖為例,開發及生產部署文檔,也可以理解成環境相關的文檔。其主要目的是匯總開發、生產環境部署的機器、應用部署的位置及應用該如何訪問的關鍵信息,讓接手的同事,能夠清楚當需求開發完成時,應用應該如何部署。

1.4. 作為技術人員離職前的交接,編寫XX系統_業務支撐文檔為了誰?

離職前的交接,當接手的同事了解完如何入門開發、開發生產部署環境,接下來就要花大量的時間,去了解支撐的業務。

鑒於支撐的業務會較多,作為接手的同事梳理起來會比較頭疼,那麼一個清晰的文檔索引就很重要。

業務支撐文檔就是把 SVN 或者 Git 上的產品相關的資料,分門別類把路徑整理到文檔中,以便接手的同事查閱,以便進行快速深入。

1.5. 作為技術人員離職前的交接,編寫XX系統_經驗匯總文檔為了誰?

離職前的交接,最重要的是分享前車之鑒,對於要接手的同事而言少走彎路,避免再掉坑,絕對是一筆財富。

僅以上面截圖為例,系統的經驗匯總文檔,主要記錄平時該注意的事項、項目團隊中以往遇到那些坑,以及如何把坑填平的。

有了經驗匯總文檔,無論是接手的同事,還是新招的同事,再去做需求開發,相信同樣問題出錯的概率應該會大幅降低。

不過該經驗文檔離不開一個長期積累的過程,所以程序員要養成一個善於記錄的習慣。

 

2. 人品不夠,分享來湊。

 

離職前的交接,梳理文檔是一方面,隔三差五的組織分享也是必不可少的環節。

如上面截圖所示,主要包含生產部署相關以及業務支撐相關,目的就是把重要的信息,以培訓會議的形式再次同步給大家,讓團隊中的每個人都做到心中有數。

當然,鑒於分享會耗費大家的時間,所以要提前準備好要分享的重要信息,合理安排時間去完成分享。

 

3. 離職之後,保持藕斷絲連。

 

如果你之前負責的是重要項目,即使交接做的很成功,但是之前的老同事,偶爾還會給你打電話諮詢項目的事情,至少會持續一個月甚至更長。

那麼請不要悲傷、憤怒,換個角度去思考,前同事有問題能想到你,說明你在他們心中還是有分量的,或許他們認為你了解的比較透徹,知道問題的解決方案,能夠快速幫其解決問題。

 

今日留情面,他日好相見。

 

互聯網的圈子真的很小,說不定哪一天又在下一家公司相見啦,所以一定要留有情面,把事情交接好,把最後一班崗站好。

最後一班崗站好,大家心中都有你,有機會就會向你拋橄欖枝。

 

曾經的那些人兒,那些事兒。

 

橄欖枝一:發生在 3 年前,上家公司的技術總監去了知名網購平台,電話問我能不能把簡歷發來,是否願意來承擔一些事兒?當我接到電話時,瞬間詫異,技術總監都拿到我手機號啦。

橄欖枝二:發生在去年,上家公司的某位高級經理被挖去了新的公司,擔任 CTO 職位,由於業務擴展,多次問我是不是可以一起搞一搞?

 

估計很多人都接過橄欖枝,聊橄欖枝不是為了裝 B,只是想反思一下橄欖枝背後,是不是和之前交接的過程有點關係呢?

 

4. 鐵打的硬盤,流水的碼農。

 

作為技術人員請不要:這個我交接給他啦,你直接去找他吧!

作為技術人員請不要:惡意製造交接困難,讓交接難上加難!

作為技術人員請做到:站好最後一班崗,今日留情面,他日好相見。

 

本次主要分享了之前交接時的思路以及寫過的一些文檔,如果感覺有一絲參考價值,那請拿去在團隊中實踐,沉澱下來的都是財富。

好了,分享就到這裏,希望對你有幫助。一起聊技術、談業務、噴架構,少走彎路,不踩大坑。歡迎關注「一猿小講」,會持續輸出原創精彩分享,敬請期待!

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※別再煩惱如何寫文案,掌握八大原則!

※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※超省錢租車方案

※教你寫出一流的銷售文案?

※網頁設計最專業,超強功能平台可客製化

※產品缺大量曝光嗎?你需要的是一流包裝設計!

Vue數據雙向綁定原理

Vue數據雙向綁定

Vue是通過數據劫持的方式來實現數據雙向數據綁定的,其中最核心的方法便是通過Object.defineProperty()來實現對屬性的劫持,該方法允許精確地添加或修改對象的屬性,對數據添加屬性描述符中的getter與setter實現劫持。

描述

運行一個Vue實例並將data打印,可以看到對象中對於msg有了get與set,通過他們就可以實現數據的劫持,從而進行數據的更新,在Vue中get與set是通過ES5的Object.defineProperty()方法定義的,該方法的具體功能可以查閱https://github.com/WindrunnerMax/EveryDay/blob/master/JavaScript/defineProperty.md。

<!DOCTYPE html>
<html>
<head>
    <title>數據綁定</title>
</head>
<body>
    <div id="app">
        <div>{{msg}}</div>
    </div> 
</body>
<script src="https://cdn.bootcss.com/vue/2.4.2/vue.js"></script>
<script type="text/javascript">
    var vm = new Vue({
        el: '#app',
        data: {
            msg: 'Data'
        },
        created: function() {
            console.log(this.$data); //{__ob__: Observer} 
        }
    })
</script>
</html>
/*
{__ob__: Observer}
    msg: "Data"
    __ob__: Observer {value: {…}, dep: Dep, vmCount: 1}
    get msg: ƒ reactiveGetter()
    set msg: ƒ reactiveSetter(newVal)
    __proto__: Object
*/

分析實現

Vue的雙向數據綁定,簡單點來說分為以下三個部分:

  • Observer: 這裏的主要工作是遞歸地監聽對象上的所有屬性,在屬性值改變的時候,觸發相應的Watcher。
  • Watcher: 觀察者,當監聽的數據值修改時,執行響應的回調函數,在Vue裏面的更新模板內容。
  • Dep: 鏈接Observer和Watcher的橋樑,每一個Observer對應一個Dep,它內部維護一個數組,保存與該Observer相關的Watcher。

根據上面的三部分實現一個功能非常簡單的Demo,實際Vue中的數據在頁面的更新是異步的,且存在大量優化,實際非常複雜。
首先實現Dep方法,這是鏈接Observer和Watcher的橋樑,簡單來說,就是一個監聽者模式的事件總線,負責接收watcher並保存。其中subscribers數組用以保存將要觸發的事件,addSub方法用以添加事件,notify方法用以觸發事件。

function __dep(){
    this.subscribers = [];
    this.addSub = function(watcher){
        if(__dep.target && !this.subscribers.includes(__dep.target) ) this.subscribers.push(watcher);
    }
    this.notifyAll = function(){
        this.subscribers.forEach( watcher => watcher.update());
    }
}

Observer方法就是將數據進行劫持,使用Object.defineProperty對屬性進行重定義,注意一個屬性描述符只能是數據描述符和存取描述符這兩者其中之一,不能同時是兩者,所以在這個小Demo中使用getter與setter操作的的是定義的value局部變量,主要是利用了let的塊級作用域定義value局部變量並利用閉包的原理實現了getter與setter操作value,對於每個數據綁定時都有一個自己的dep實例,利用這個總線來保存關於這個屬性的Watcher,並在set更新數據的時候觸發。

function __observe(obj){
    for(let item in obj){
        let dep = new __dep();
        let value = obj[item];
        if (Object.prototype.toString.call(value) === "[object Object]") __observe(value);
        Object.defineProperty(obj, item, {
            configurable: true,
            enumerable: true,
            get: function reactiveGetter() {
                if(__dep.target) dep.addSub(__dep.target);
                return value;
            },
            set: function reactiveSetter(newVal) {
                if (value === newVal) return value;
                value = newVal;
                dep.notifyAll();
            }
        });
    }
    return obj;
}

Watcher方法傳入一個回調函數,用以執行數據變更后的操作,一般是用來進行模板的渲染,update方法就是在數據變更后執行的方法,activeRun是首次進行綁定時執行的操作,關於這個操作中的__dep.target,他的主要目的是將執行回調函數相關的數據進行sub,例如在回調函數中用到了msg,那麼在執行這個activeRun的時候__dep.target就會指向this,然後執行fn()的時候會取得msg,此時就會觸發msg的get(),而get中會判斷這個__dep.target是不是空,此時這個__dep.target不為空,上文提到了每個屬性都會有一個自己的dep實例,此時這個__dep.target便加入自身實例的subscribers,在執行完之後,便將__dep.target設置為null,重複這個過程將所有的相關屬性與watcher進行了綁定,在相關屬性進行set時,就會觸發各個watcher的update然後執行渲染等操作。

function __watcher(fn){
    this.update = function(){
        fn();
    }
    
    this.activeRun = function(){
        __dep.target = this;
        fn();
        __dep.target = null;
    }
    this.activeRun();
}

代碼示例

這是上述的小Demo的代碼示例,其中上文沒有提到的__proxy函數主要是為了將vm.$data中的屬性直接代理到vm對象上,兩個watcher中第一個是為了打印並查看數據,第二個是之前做的一個非常簡單的模板引擎的渲染,為了演示數據變更使得頁面數據重新渲染,在這個Demo下打開控制台,輸入vm.msg = 11;即可觸發頁面的數據更改,也可以通過在40行添加一行console.log(dep);來查看每個屬性的dep綁定的watcher。

<!DOCTYPE html>
<html>
<head>
    <title>數據綁定</title>
</head>
<body>
    <div id="app">
        <div>{{msg}}</div>
        <div>{{date}}</div>
    </div> 
</body>
<script type="text/javascript">

    var Mvvm = function(config) {
        this.$el = config.el;
        this.__root = document.querySelector(this.$el);
        this.__originHTML = this.__root.innerHTML;

        function __dep(){
            this.subscribers = [];
            this.addSub = function(watcher){
                if(__dep.target && !this.subscribers.includes(__dep.target) ) this.subscribers.push(watcher);
            }
            this.notifyAll = function(){
                this.subscribers.forEach( watcher => watcher.update());
            }
        }


        function __observe(obj){
            for(let item in obj){
                let dep = new __dep();
                let value = obj[item];
                if (Object.prototype.toString.call(value) === "[object Object]") __observe(value);
                Object.defineProperty(obj, item, {
                    configurable: true,
                    enumerable: true,
                    get: function reactiveGetter() {
                        if(__dep.target) dep.addSub(__dep.target);
                        return value;
                    },
                    set: function reactiveSetter(newVal) {
                        if (value === newVal) return value;
                        value = newVal;
                        dep.notifyAll();
                    }
                });
            }
            return obj;
        }

        this.$data = __observe(config.data);

        function __proxy (target) {
            for(let item in target){
                Object.defineProperty(this, item, {
                    configurable: true,
                    enumerable: true,
                    get: function proxyGetter() {
                        return this.$data[item];
                    },
                    set: function proxySetter(newVal) {
                        this.$data[item] = newVal;
                    }
                });
            }
        }

        __proxy.call(this, config.data);

        function __watcher(fn){
            this.update = function(){
                fn();
            }
            
            this.activeRun = function(){
                __dep.target = this;
                fn();
                __dep.target = null;
            }
            this.activeRun();
        }

        new __watcher(() => {
            console.log(this.msg, this.date);
        })

        new __watcher(() => {
            var html = String(this.__originHTML||'').replace(/"/g,'\\"').replace(/\s+|\r|\t|\n/g, ' ')
            .replace(/\{\{(.)*?\}\}/g, function(value){ 
                return  value.replace("{{",'"+(').replace("}}",')+"');
            })
            html = `var targetHTML = "${html}";return targetHTML;`;
            var parsedHTML = new Function(...Object.keys(this.$data), html)(...Object.values(this.$data));
            this.__root.innerHTML = parsedHTML;
        })

    }

    var vm = new Mvvm({
        el: "#app",
        data: {
            msg: "1",
            date: new Date(),
            obj: {
                a: 1,
                b: 11
            }
        }
    })

</script>
</html>

每日一題

https://github.com/WindrunnerMax/EveryDay

參考

https://www.jianshu.com/p/255d4dec710a
https://www.jianshu.com/p/c8186e9e027b
https://www.cnblogs.com/wangjiachen666/p/9883916.html
https://blog.csdn.net/wangshu696/article/details/84570886
https://blog.csdn.net/qq_43051529/article/details/82877673
https://github.com/liutao/vue2.0-source/blob/master/%E5%8F%8C%E5%90%91%E6%95%B0%E6%8D%AE%E7%BB%91%E5%AE%9A.md

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※教你寫出一流的銷售文案?

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

※超省錢租車方案

※產品缺大量曝光嗎?你需要的是一流包裝設計!

曹工改bug:centos下,mongodb開機不能自啟動,systemctl、rc.local都試了,還是不行,要不要放棄?

問題背景

最近裝個centos 7.6的環境,其中,基礎環境包括,redis、nginx、mongodb、fastdfs、mysql等,其中,自啟動使用的是systemctl,其他幾個組件,都沒啥問題,唯獨,這個mongodb,是死活啟動不了。

但是,我這裏說的,不是啟動不了,如果直接在shell里敲:

systemctl start  mongod.service

是沒啥問題的,是可以啟動的。

mongod.service大致如下,各文件夾的權限也已經仔細檢查過,應該是沒毛病:

[Unit]
Description=High-performance, schema-free document-oriented database
After=network.target
Documentation=https://docs.mongodb.org/manual

[Service]
User=mongod
Group=mongod
Environment="OPTIONS=-f /etc/mongod.conf"
ExecStart=/usr/bin/mongod $OPTIONS
ExecStartPre=/usr/bin/mkdir -p /var/run/mongodb
ExecStartPre=/usr/bin/chown mongod:mongod /var/run/mongodb
ExecStartPre=/usr/bin/chmod 0755 /var/run/mongodb
PermissionsStartOnly=true
PIDFile=/var/run/mongodb/mongod.pid
Type=forking
# file size
LimitFSIZE=infinity
# cpu time
LimitCPU=infinity
# virtual memory size
LimitAS=infinity
# open files
LimitNOFILE=64000
# processes/threads
LimitNPROC=64000
# locked memory
LimitMEMLOCK=infinity
# total threads (user+kernel)
TasksMax=infinity
TasksAccounting=false
# Recommended limits for for mongod as specified in
# http://docs.mongodb.org/manual/reference/ulimit/#recommended-settings

[Install]
WantedBy=multi-user.target

後邊換成了rc.local方式:

/etc/rc.d/rc.local  

#!/bin/bash
/usr/bin/mongod --fork -f /etc/mongod.conf &

但是,依然不行。

這两天,同事也在斷斷續續在弄,大家手裡有其他事,這塊暫時放下了。

轉機:strace命令

我一般瀏覽器開的tab比較多,有個幾天前的tab,是關於strace的,我當時主要是想:找一個命令,可以監控某個進程的網絡請求。

本來可以用tcpdump,但是,這個不是針對某個進程的,只能通過端口過濾,一般情況下,用端口過濾也足夠了,但是,總是覺得不爽。

然後找到了這個鏈接:

https://askubuntu.com/questions/11709/how-can-i-capture-network-traffic-of-a-single-process

裏面提到了strace可以做到。試了下,確實完美解決了我的問題。

比如說,我可以attach到某個進程,然後調用進程的某個接口,讓該進程調用某個微服務,然後看看我們能不能抓到:

(看不清可在單獨tab查看,這個是java應用,向註冊中心eureka發送的心跳,可以發現,完美抓到了)

strace的使用

可參考我的這篇簡單介紹:

Linux下,如何監控某個進程到底向哪個地址發起了網絡調用

大家也可以直接在服務器上直接執行以下shell,查看幫助文檔:

yum install strace
man strace

其主要支持兩種方式,一種是直接使用strace來包裝某個命令,使用strace來啟動該命令;另一種,就是上面說的,attach到某個已經在運行中的進程。

然後,我想到,既然strace可以監控系統調用,那麼,監控下mongodb吧,這裏可以用strace來包裝命令,我試着把命令換成了如下的樣子:

strace -tt -s 10000 -o  mongo.txt /usr/bin/mongod --fork -f /etc/mongod.conf

這塊命令,分兩塊看。

strace -tt -s 10000  -o  mongo.txt 

這部分是strace的參數,

-tt 打印時間

-s 設置字符串的長度,否則具體內容显示不出來

-o 將結果輸出到文件

另外部分,就是mongo的命令了:

/usr/bin/mongod --fork  /etc/mongod.conf

比較正常情況下,和異常情況下的strace輸出日誌

在同事建議下,我們先直接在shell中執行了以下命令:

strace -tt -s 10000  -o  mongo.txt /usr/bin/mongod --fork -f /etc/mongod.conf

然後,得到了mongo.txt。這個是正常的文件。

然後,我們reboot了服務器,然後不清楚文件生成到哪裡了,直接find查找了一把,然後找到后,把這兩個文件,都存到了pc上,用beyond compare進行對比。

下面是對比結果:

建議大圖查看。

可以發現,有問題的文件里,在打開以下文件時,報了錯,提示沒有權限:

/sys/fs/cgroup/memory/memory.limit_in_bytes   EACCES (Permission denied)

然後,我們只是知道了,有這個現象,但不知道為啥,然後就開始了一頓漫無邊際的面向搜索引擎找問題。找了半天,沒啥收穫。

後邊我們就決定再去看看官網,看看官網有沒有說,怎麼才是正統的服務自啟動方式(已經快放棄了。。。)

柳暗花明

結果在官網的如下鏈接:

https://docs.mongodb.com/manual/tutorial/install-mongodb-on-red-hat/#install-mongodb-community-edition

看到如下一段話,說SELinux,默認不讓mongo訪問/sys/fs/cgroup:

然後我一看,這個目錄有點熟悉啊,上面報沒有權限的文件,不就這個目錄下嗎?

/sys/fs/cgroup/memory/memory.limit_in_bytes   EACCES (Permission denied)

知道原因就好說了,原來是SELinux,我們這邊比較暴力,直接把這個關閉了。

關閉方式:https://www.cnblogs.com/activiti/p/7552677.html

總結

在華為的時候,組裡的大佬們有一句話:辦法總比困難多。

那時感覺,這也太雞湯了。。。

我現在也有這個感覺,每次在快要放棄時,問題結果被解決了,有點意思。

另外一個感悟:方法比結果重要

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※超省錢租車方案

※別再煩惱如何寫文案,掌握八大原則!

※回頭車貨運收費標準

※教你寫出一流的銷售文案?

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

斯里蘭卡逾百頭鯨魚擱淺 漁民徹夜忙搶救

摘錄自2020年11月3日公視報導

斯里蘭卡首都可倫坡附近,發生有120頭鯨魚擱淺事件,當地民眾無法顧及防疫宵禁措施,徹夜協助要把鯨魚推回大海,但其中還是有兩頭鯨魚因為擱淺、受傷死亡。

警方說,當時不斷有鯨魚沖上岸,民眾根本搶救不及,後來海岸警衛隊及海軍部隊也加入搶救行列,利用小型巡邏艇將鯨魚送回深水區。斯里蘭卡海洋環境保護署則表示,這是斯里蘭卡首度有這麼多的鯨魚擱淺,情況很不尋常,懷疑可能跟澳洲日前發生大規模鯨魚擱淺事件有相似之處。

澳洲的塔斯馬尼亞在九月間,發生470頭領航鯨集體擱淺的意外。當時救援隊伍歷經五天時間,成功將其中110頭送回大海,其餘鯨魚則不幸死亡。為了避免造成二度海洋生態浩劫,當時清運鯨魚屍體也成了棘手問題。至於鯨魚為何一再被沖上岸始終成謎,科學家研判,領航鯨是高度社會化動物,擱淺原因可能跟牠們喜歡群聚生活、彼此互動密切,跟隨著一兩隻偏離航道的領航鯨而集體擱淺。

海洋
國際新聞
斯里蘭卡
鯨豚擱淺

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※教你寫出一流的銷售文案?

非洲大城空污直逼北京 禍首是二手車

摘錄自2020年11月4日科技新報報導

非洲國家蒙受空污之苦,在 PM2.5 方面最大禍首是從先進國家進口的二手車,隨著人民對汽車需求愈來愈大,城市空污問題成為難解之題。

QUARTZ 報導,在 2015~2018 年間,從歐盟、日本和美國出口的 1400 萬輛二手車中,約有 80% 流向中低收入國家。非洲是全球每年二手車進口量最多的國家,隨著這種缺乏檢驗標準的老舊汽車進入非洲,加劇城市空氣污染問題。譬如 2017 年與 2018 年荷蘭出口 3.5 萬台到西非的汽車當中,大部分都是 16~20 年的車,由於缺乏監管標準來衡量進口汽車的安全性和品質,這些車很多都低於歐洲安全和排放要求,造成汽車排放物成為非洲最大城市空污的重要來源。

接下來的 30 年中,非洲的人口增長將比其他任何地方都快,污染情況幾乎無法逆轉。最新研究估計,到 2030 年非洲大陸僅發電廠和車輛排放的化石燃料每年將導致近 5 萬人死亡。預計到 2030 年,二氧化硫和氮氧化物的年排放量將比 2012 年的水平多一倍。

污染治理
國際新聞
非洲
空氣污染
二手車

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

※南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

※超省錢租車方案