繼法國之後,英國政府宣布2040 年禁售汽柴油車

汽柴油車真的要走入歷史了嗎?繼法國宣布2040 年禁售汽柴油車後,英國政府為解決空氣污染問題,準備從30 億英鎊抗空汙的資金中提撥2.55 億英鎊協助委員會加快地方措施,以應對柴油車輛的污染,終極目標也將在2040 年前禁售汽柴油車。

英國獨立報(The Independent) 報導,空氣污染與英國每年約4 萬人過早死亡有關,運輸也佔溫室氣體排放量的很大一部分。英國政府先前推出的抗空汙計畫版本被環保人士反對,認為力道太弱,無法達到歐盟的排放標準,英國最高法院要求7 月31 日前英國政府必須制定新的計畫,以降低有害二氧化氮的排放量,而就在法院規定的截止日前,英國政府宣布2040 年汽柴油車禁令。

英國追隨法國腳步頒布禁售令,顯示向電動車轉型的速度正在加快,BMW 宣布計畫推出Mini 電動車版,將在英國牛津進行組裝,Volvo 也宣布清潔能源車計畫。

英國政府還將討論柴油車報廢計劃的執行細節。英國環保倡議者認為,這項計劃應包括政府資助且強制性的清潔空氣區,對進入高空氣污染地區的高污染車輛收取費用。對清潔空氣區設立收費制度被視為是最有效打擊二氧化氮污染的政策,而柴油車是排量二氧化氮的禍首。

但是英國政府對此有疑慮,認為這是懲罰柴油車駕駛,畢竟原本認為柴油車的碳排量比汽車少,因此鼓勵消費者購買。英國政府傾向改裝巴士等交通工具,降低排放量,或改變道路佈局,甚至改變速度和重新編排交通號誌等功能,使交通流量更加順暢,減少污染。

英國政府發言人表示,不該責怪柴油車駕駛,為了幫助他們改用清潔車,政府會討論針對性的報廢計劃,支持受本地計劃影響的駕駛。但英國在野黨不滿意這種溫和的作風,呼籲柴油車禁售令應該提前至2025 年,並提出廢止計劃幫駕駛轉換成更環保的車輛。

印度2030 年實現全電動車目標

除歐洲國家之外,受嚴重空汙困擾的印度動作也非常積極,印度政府計畫2030 年實現全電動車目標,而印度政府此舉並不只是為了抗空汙,還可減少燃料進口費用。印度重工業部和印度國家研究院正在製定促進電動汽車發展政策,主要是朝降低成本提高價格誘因做起,現在印度對混合動力車與電動車提供補貼,初期也會補貼業者度過轉型期,為2030 年禁售汽柴油車鋪路。

未來3 年印度將大規模佈建充電基礎設施與電池交換計畫,目前只有印度只有電動車廠  Mahindra Electric 在印度提供全電動車,最近Anand Mahindra 執行長在社群媒體上邀請Tesla 到印度設置商店,Tesla 將在2017 年底之前進入印度市場,開放印度消費者訂購Model 3,明年可能開始展店。

除了電動汽車,混合動力汽車市場處於更好的市場位置,豐田、Volvo 和BMW 等幾家製造商在印度提供混合動力車或插電式混合動力車。特斯拉和日產也宣布進在印度市場堆出Model 3和Leaf。若印度政府的計劃順利進行,印度將成為全球電動車品牌的一級戰場。

(合作媒體:。圖片出處:public domain CC0)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置台北網頁設計,各種案例分享

Freemarker + xml 實現Java導出word

前言

最近做了一個調查問卷導出的功能,需求是將維護的題目,答案,導出成word,參考了幾種方案之後,選擇功能強大的freemarker+固定格式之後的wordxml實現導出功能。導出word的代碼是可以直接復用的,於是在此貼出,並進行總結,方便大家拿走。

實現過程概覽

先在word上,調整好自己想要的樣子。然後存為xml文件。保存為freemarker模板,以ftl後綴結尾。將需要替換的變量使用freemarker的語法進行替換。最終將數據準備好,和模板進行渲染,生成文件並返回給瀏覽器流。

詳細的實現過程

準備好word的樣式

我們新建一個word,我們應該使用Microsoft office,如果使用wps可能會造成樣式有些不兼容。在新建的office中,設置好我們的表格樣式。我們的調查問卷涉及到四種類型,單選,多選,填空,簡答。我們做出四種類型的示例。

樣式沒有問題后,我們選擇另存為word xml 2003版本。將會生成一個xml文件。

格式化xml,並用freemarker語法替換xml

我們可以先下載一個工具 firstobject xml editor,這個可以幫助我們查看xml,同時方便我們定位我們需要改的位置。
複製過去之後,按f8可以將其進行格式化,左側是標籤,右側是內容,我們只需要關注w:body即可。

像右側的調查問卷這個就是個標題,我們實際渲染的時候應該將其進行替換,比如我們的程序數據map中,有title屬性,我們想要這裏展示,我們就使用語法${title}即可。

freemarker的具體語法,可以參考freemarker的問題,在這裏我給出幾個簡單的例子。
比如我們將所有的數據放置在dataList中,所以我們需要判斷,dataList是不是空,是空,我們不應該進行下面的邏輯,不是空,我們應該先循環題目是必須的,答案是需要根據類型進行再次循環的。語法參考文檔,這裏不再贅述。

程序端引入freemarker

<dependency>
    <groupId>org.freemarker</groupId>
    <artifactId>freemarker</artifactId>
</dependency>

將我們的flt文件放在resources下的templates下。

後端代碼實現

此代碼可以復用,在此貼出

public class WordUtils {

    private static Configuration configuration = null;
    private static final String templateFolder = WordUtils.class.getClassLoader().getResource("").getPath()+"/templates/word";
    static {
        configuration = new Configuration();
        configuration.setDefaultEncoding("utf-8");
        try {
            configuration.setDirectoryForTemplateLoading(new File(templateFolder));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     *  @Description:導出word,傳入request,response,map就是值,title是導出問卷名,ftl是你要使用的模板名
     */
    public static void exportWord(HttpServletRequest request, HttpServletResponse response, Map map, String title, String ftlFile) throws Exception {
        Template freemarkerTemplate = configuration.getTemplate(ftlFile);
        File file = null;
        InputStream fin = null;
        ServletOutputStream out = null;
        try {
            file = createDocFile(map,freemarkerTemplate);
            fin = new FileInputStream(file);
            String fileName = title + ".doc";
            response.setCharacterEncoding("utf-8");
            response.setContentType("application/msword");
            response.setHeader("Content-Disposition", "attachment;filename="
             +fileName);
            out = response.getOutputStream();
            byte[] buffer = new byte[512];  
            int bytesToRead = -1;
            while((bytesToRead = fin.read(buffer)) != -1) {
                out.write(buffer, 0, bytesToRead);
            }
        }finally {
            if(fin != null) fin.close();
            if(out != null) out.close();
            if(file != null) file.delete(); 
        }
    }

    /**
     *  @Description:創建doc文件
     */
    private static File createDocFile(Map<?, ?> dataMap, Template template) {
        File file = new File("init.doc");
        try {
            Writer writer = new OutputStreamWriter(new FileOutputStream(file), "utf-8");
            template.process(dataMap, writer);
            writer.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return file;
    }

}

有了工具類后,我們準備好我們的map數據。map裏面的數據大家可以自行定義。然後調用utils中的導出方法即可。

WordUtils.exportWord(request, response, dataMap, "word", "demo.ftl");

結語

至此已經結束了,十分的好用,有疑問的話,可以評論交流。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置台北網頁設計,各種案例分享

利用Python學習線性代數 — 1.1 線性方程組

利用Python學習線性代數 — 1.1 線性方程組

系列,

本節實現的主要功能函數,在源碼文件中,後續章節將作為基本功能調用。

線性方程

線性方程組由一個或多個線性方程組成,如
\[ \begin{array}\\ x_1 – 2 x_2 &= -1\\ -x_1 + 3 x_2 &= 3 \end{array} \]

求包含兩個變量兩個線性方程的方程組的解,等價於求兩條直線的交點。
這裏可以畫出書圖1-1和1-2的線性方程組的圖形。
通過改變線性方程的參數,觀察圖形,體會兩個方程對應直線平行、相交、重合三種可能。

那麼,怎麼畫二元線性方程的直線呢?

方法是這樣的:
假如方程是 \(a x_1 + b x_2 = c\) 的形式,可以寫成 \(x_2 = (c – a x_1) / b\)
在以 \(x_1\)\(x_2\)為兩個軸的直角坐標系中,\(x_1\)取一組值,如 \((-3, -2.9, -2.8, \dots, 2.9, 3.0)\)
計算相應的 \(x_2\),然後把所有點 \((x_1, x_2)\) 連起來成為一條線。
\(b\)\(0\) 時, 則在\(x_1 = c / a\)處畫一條垂直線。

# 引入Numpy和 Matplotlib庫
import numpy as np
from matplotlib import pyplot as plt

Matplotlib 是Python中使用較多的可視化庫,這裏只用到了它的一些基本功能。

def draw_line(a, b, c, start=-4, 
              stop=5, step=0.01):
    """根據線性方程參數繪製一條直線"""
    # 如果b為0,則畫一條垂線
    if np.isclose(b, 0):
        plt.vlines(start, stop, c / a)
    else: # 否則畫 y = (c - a*x) / b
        xs = np.arange(start, stop, step)
        plt.plot(xs, (c - a*xs)/b)
# 1.1 圖1-1
draw_line(1, -2, -1)
draw_line(-1, 3, 3)

def draw_lines(augmented, start=-4, 
              stop=5, step=0.01):
    """給定增廣矩陣,畫兩條線."""
    plt.figure()
    for equation in augmented:
        draw_line(*equation, start, stop, step)
    plt.show()
# Fig. 1-1
# 增廣矩陣用二維數組表示 
# [[1, -2, -1], [-1, 3, 3]]
# 這些数字對應圖1-1對應方程的各項係數
draw_lines([[1, -2, -1], [-1, 3, 3]])

# Fig. 1-2
draw_lines([[1, -2, -2], [-1, 2, 3]])
# Fig. 1-3
draw_lines([[1, -2, -1], [-1, 2, 1]])

  • 建議:改變這些係數,觀察直線,體會兩條直線相交、平行和重合的情況

例如

draw_lines([[1, -2, -2], [-1, 2, 9]])

如果對Numpy比較熟悉,則可以採用更簡潔的方式實現上述繪圖功能。
在計算多條直線方程時,可以利用向量編程的方式,用更少的代碼實現。

def draw_lines(augmented, start=-4, 
               stop=5, step=0.01):
    """Draw lines represented by augmented matrix on 2-d plane."""
    am = np.asarray(augmented)
    xs = np.arange(start, stop, step).reshape([1, -1])
    # 同時計算兩條直線的y值
    ys = (am[:, [-1]] - am[:, [1]]*xs) / am[:, [0]]
    for y in ys:
        plt.plot(xs[0], y)
    plt.show()

矩陣記號

矩陣是一個數表,在程序中通常用二維數組表示,例如

# 嵌套列表表示矩陣
matrix = [[1, -2, 1, 0],
          [0, 2, -8, 8],
          [5, 0, -5, 10]]
matrix
[[1, -2, 1, 0], [0, 2, -8, 8], [5, 0, -5, 10]]

實際工程和研究實踐中,往往會採用一些專門的數值計算庫,簡化和加速計算。
Numpy庫是Python中數值計算的常用庫。
在Numpy中,多維數組類型稱為ndarray,可以理解為n dimensional array。
例如

# Numpy ndarray 表示矩陣
matrix = np.array([[1, -2, 1, 0],
                    [0, 2, -8, 8],
                    [5, 0, -5, 10]])
matrix
array([[ 1, -2,  1,  0],
       [ 0,  2, -8,  8],
       [ 5,  0, -5, 10]])

解線性方程組

本節解線性方程組的方法是 高斯消元法,利用了三種基本行變換。

  1. 把某個方程換成它與另一個方程的倍數的和;
  2. 交換兩個方程的位置;
  3. 某個方程的所有項乘以一個非零項。

假設線性方程的增廣矩陣是\(A\),其第\(i\)\(j\)列的元素是\(a_{ij}\)
消元法的基本步驟是:

  • 增廣矩陣中有 \(n\) 行,該方法的每一步處理一行。
    1. 在第\(i\)步,該方法處理第\(i\)
      • \(a_{ii}\)為0,則在剩餘行 \(\{j| j \in (i, n]\}\)中選擇絕對值最大的行\(a_{ij}\)
        • \(a_{ij}\)為0,返回第1步。
        • 否則利用變換2,交換\(A\)的第\(i\)\(j\)行。
    2. 利用行變換3,第\(i\)行所有元素除以\(a_{ii}\),使第 \(i\) 個方程的第 \(i\)個 係數為1
    3. 利用行變換1,\(i\)之後的行減去第\(i\)行的倍數,使這些行的第 \(i\) 列為0

為了理解這些步驟的實現,這裏先按書中的例1一步步計算和展示,然後再總結成完整的函數。
例1的增廣矩陣是

\[ \left[ \begin{array} &1 & -2 & 1 & 0\\ 0 & 2 & -8 & 8\\ 5 & 0 & -5 & 10 \end{array} \right] \]

# 增廣矩陣
A = np.array([[1, -2, 1, 0],
              [0, 2, -8, 8],
              [5, 0, -5, 10]])
# 行號從0開始,處理第0行
i = 0
# 利用變換3,將第i行的 a_ii 轉成1。這裏a_00已經是1,所不用動
# 然後利用變換1,把第1行第0列,第2行第0列都減成0。
# 這裏僅需考慮i列之後的元素,因為i列之前的元素已經是0
#   即第1行減去第0行的0倍
#   而第2行減去第0行的5倍
A[i+1:, i:] = A[i+1:, i:] - A[i+1:, [i]] * A[i, i:]
A
array([[  1,  -2,   1,   0],
       [  0,   2,  -8,   8],
       [  0,  10, -10,  10]])
i = 1
# 利用變換3,將第i行的 a_ii 轉成1。
A[i] = A[i] / A[i, i]
A
array([[  1,  -2,   1,   0],
       [  0,   1,  -4,   4],
       [  0,  10, -10,  10]])
# 然後利用變換1,把第2行第i列減成0。
A[i+1:, i:] = A[i+1:, i:] - A[i+1:, [i]] * A[i, i:]
A
array([[  1,  -2,   1,   0],
       [  0,   1,  -4,   4],
       [  0,   0,  30, -30]])
i = 2
# 利用變換3,將第i行的 a_ii 轉成1。
A[i] = A[i] / A[i, i]
A
array([[ 1, -2,  1,  0],
       [ 0,  1, -4,  4],
       [ 0,  0,  1, -1]])

消元法的前向過程就結束了,我們可以總結成一個函數

def eliminate_forward(augmented): 
    """
    消元法的前向過程.
    
    返回行階梯形,以及先導元素的坐標(主元位置)
    """
    A = np.asarray(augmented, dtype=np.float64)
    # row number of the last row
    pivots = []
    i, j = 0, 0
    while i < A.shape[0] and j < A.shape[1]:
        A[i] = A[i] / A[i, j]
        if (i + 1) < A.shape[0]: # 除最後一行外
            A[i+1:, j:] = A[i+1:, j:] - A[i+1:, [j]] * A[i, j:]
        pivots.append((i, j))
        i += 1
        j += 1
    return A, pivots

這裡有兩個細節值得注意

  1. 先導元素 \(a_{ij}\),不一定是在主對角線位置,即 \(i\) 不一定等於\(j\).
  2. 最後一行只需要用變換3把先導元素轉為1,沒有剩餘行需要轉換
# 測試一個增廣矩陣,例1
A = np.array([[1, -2, 1, 0],
              [0, 2, -8, 8],
              [5, 0, -5, 10]])
A, pivots = eliminate_forward(A)
print(A)
print(pivots)
[[ 1. -2.  1.  0.]
 [ 0.  1. -4.  4.]
 [ 0.  0.  1. -1.]]
[(0, 0), (1, 1), (2, 2)]

消元法的後向過程則更簡單一些,對於每一個主元(這裏就是前面的\(a_{ii}\)),將其所在的列都用變換1,使其它行對應的列為0.

for i, j in reversed(pivots):
    A[:i, j:] = A[:i, j:] - A[[i], j:] * A[:i, [j]] 
A
array([[ 1.,  0.,  0.,  1.],
       [ 0.,  1.,  0.,  0.],
       [ 0.,  0.,  1., -1.]])
def eliminate_backward(simplified, pivots):
    """消元法的後向過程."""
    A = np.asarray(simplified)
    for i, j in reversed(pivots):
        A[:i, j:] = A[:i, j:] - A[[i], j:] * A[:i, [j]] 
    return A

至此,結合 eliminate_forward 和eliminate_backward函數,可以解形如例1的線性方程。

然而,存在如例3的線性方程,在eliminate_forward算法進行的某一步,主元為0,需要利用變換2交換兩行。
交換行時,可以選擇剩餘行中,選擇當前主元列不為0的任意行,與當前行交換。
這裏每次都採用剩餘行中,當前主元列絕對值最大的行。
補上行交換的前向過程函數如下

def eliminate_forward(augmented): 
    """消元法的前向過程"""
    A = np.asarray(augmented, dtype=np.float64)
    # row number of the last row
    pivots = []
    i, j = 0, 0
    while i < A.shape[0] and j < A.shape[1]:
        # if pivot is zero, exchange rows
        if np.isclose(A[i, j], 0):
            if (i + 1) < A.shape[0]:
                max_k = i + 1 + np.argmax(np.abs(A[i+1:, i]))
            if (i + 1) >= A.shape[0] or np.isclose(A[max_k, i], 0):
                j += 1
                continue
            A[[i, max_k]] = A[[max_k, i]]
        A[i] = A[i] / A[i, j]
        if (i + 1) < A.shape[0]:
            A[i+1:, j:] = A[i+1:, j:] - A[i+1:, [j]] * A[i, j:]
        pivots.append((i, j))
        i += 1
        j += 1
    return A, pivots

行交換時,有一種特殊情況,即剩餘所有行的主元列都沒有非零元素
這種情況下,在當前列的右側尋找不為零的列,作為新的主元列。

# 用例3測試eliminate_forward
aug = [[0, 1, -4, 8],
       [2, -3, 2, 1],
       [4, -8, 12, 1]]
echelon, pivots = eliminate_forward(aug)
print(echelon)
print(pivots)
[[ 1.   -2.    3.    0.25]
 [ 0.    1.   -4.    0.5 ]
 [ 0.    0.    0.    1.  ]]
[(0, 0), (1, 1), (2, 3)]

例3化簡的結果與書上略有不同,由行交換策略不同引起,也說明同一個矩陣可能由多個階梯形。

結合上述的前向和後向過程,即可以給出一個完整的消元法實現

def eliminate(augmented):
    """
    利用消元法前向和後向步驟,化簡線性方程組.
    
    如果是矛盾方程組,則僅輸出前向化簡結果,並打印提示
    否則輸出簡化后的方程組,並輸出最後一列
    """
    print(np.asarray(augmented))
    A, pivots = eliminate_forward(augmented)
    print(" The echelon form is\n", A)
    print(" The pivots are: ", pivots)
    pivot_cols = {p[1] for p in pivots}
    simplified = eliminate_backward(A, pivots)
    if (A.shape[1]-1) in pivot_cols:
        print(" There is controdictory.\n", simplified)
    elif len(pivots) == (A.shape[1] -1):
        print(" Solution: ", simplified[:, -1])
        is_correct = solution_check(np.asarray(augmented), 
                            simplified[:, -1])
        print(" Is the solution correct? ", is_correct)
    else:
        print(" There are free variables.\n", simplified)
    print("-"*30)
eliminate(aug)
[[ 0  1 -4  8]
 [ 2 -3  2  1]
 [ 4 -8 12  1]]
 The echelon form is
 [[ 1.   -2.    3.    0.25]
 [ 0.    1.   -4.    0.5 ]
 [ 0.    0.    0.    1.  ]]
 The pivots are:  [(0, 0), (1, 1), (2, 3)]
 There is controdictory.
 [[ 1.  0. -5.  0.]
 [ 0.  1. -4.  0.]
 [ 0.  0.  0.  1.]]
------------------------------

利用 Sympy 驗證消元法實現的正確性

Python的符號計算庫Sympy,有化簡矩陣為行最簡型的方法,可以用來檢驗本節實現的代碼是否正確。

# 導入 sympy的 Matrix模塊
from sympy import Matrix
Matrix(aug).rref(simplify=True)
# 返回的是行最簡型和主元列的位置
(Matrix([
 [1, 0, -5, 0],
 [0, 1, -4, 0],
 [0, 0,  0, 1]]), (0, 1, 3))
echelon, pivots = eliminate_forward(aug)
simplified = eliminate_backward(echelon, pivots)
print(simplified, pivots)
# 輸出與上述rref一致
[[ 1.  0. -5.  0.]
 [ 0.  1. -4.  0.]
 [ 0.  0.  0.  1.]] [(0, 0), (1, 1), (2, 3)]

綜合前向和後向步驟,並結果的正確性

綜合前向和後向消元,就可以得到完整的消元法過程。
消元結束,如果沒有矛盾(最後一列不是主元列),基本變量數與未知數個數一致,則有唯一解,可以驗證解是否正確。
驗證的方法是將解與係數矩陣相乘,檢查與原方程的b列一致。

def solution_check(augmented, solution):
    # 係數矩陣與解相乘
    b = augmented[:, :-1] @ solution.reshape([-1, 1])
    b = b.reshape([-1])
    # 檢查乘積向量與b列一致
    return all(np.isclose(b - augmented[:, -1], np.zeros(len(b))))
def eliminate(augmented):
    from sympy import Matrix
    print(np.asarray(augmented))
    A, pivots = eliminate_forward(augmented)
    print(" The echelon form is\n", A)
    print(" The pivots are: ", pivots)
    pivot_cols = {p[1] for p in pivots}
    simplified = eliminate_backward(A, pivots)
    if (A.shape[1]-1) in pivot_cols: # 最後一列是主元列
        print(" There is controdictory.\n", simplified)
    elif len(pivots) == (A.shape[1] -1): # 唯一解
        is_correct = solution_check(np.asarray(augmented), 
                            simplified[:, -1])
        print(" Is the solution correct? ", is_correct)
        print(" Solution: \n", simplified)
    else: # 有自由變量
        print(" There are free variables.\n", simplified)
    print("-"*30)
    print("對比Sympy的rref結果")
    print(Matrix(augmented).rref(simplify=True))
    print("-"*30)

測試書中的例子

aug_1_1_1 = [[1, -2, 1, 0], 
             [0, 2, -8, 8], 
             [5, 0, -5, 10]]
eliminate(aug_1_1_1)
# 1.1 example 3
aug_1_1_3 = [[0, 1, -4, 8],
             [2, -3, 2, 1],
             [4, -8, 12, 1]]
eliminate(aug_1_1_3)
eliminate([[1, -6, 4, 0, -1],
           [0, 2, -7, 0, 4],
           [0, 0, 1, 2, -3],
           [0, 0, 3, 1, 6]])
eliminate([[0, -3, -6, 4, 9],
           [-1, -2, -1, 3, 1],
           [-2, -3, 0, 3, -1],
           [1, 4, 5, -9, -7]])

eliminate([[0, 3, -6, 6, 4, -5],
           [3, -7, 8, -5, 8, 9],
           [3, -9, 12, -9, 6, 15]])
[[ 1 -2  1  0]
 [ 0  2 -8  8]
 [ 5  0 -5 10]]
 The echelon form is
 [[ 1. -2.  1.  0.]
 [ 0.  1. -4.  4.]
 [ 0.  0.  1. -1.]]
 The pivots are:  [(0, 0), (1, 1), (2, 2)]
 Is the solution correct?  True
 Solution: 
 [[ 1.  0.  0.  1.]
 [ 0.  1.  0.  0.]
 [ 0.  0.  1. -1.]]
------------------------------
對比Sympy的rref結果
(Matrix([
[1, 0, 0,  1],
[0, 1, 0,  0],
[0, 0, 1, -1]]), (0, 1, 2))
------------------------------
[[ 0  1 -4  8]
 [ 2 -3  2  1]
 [ 4 -8 12  1]]
 The echelon form is
 [[ 1.   -2.    3.    0.25]
 [ 0.    1.   -4.    0.5 ]
 [ 0.    0.    0.    1.  ]]
 The pivots are:  [(0, 0), (1, 1), (2, 3)]
 There is controdictory.
 [[ 1.  0. -5.  0.]
 [ 0.  1. -4.  0.]
 [ 0.  0.  0.  1.]]
------------------------------
對比Sympy的rref結果
(Matrix([
[1, 0, -5, 0],
[0, 1, -4, 0],
[0, 0,  0, 1]]), (0, 1, 3))
------------------------------
[[ 1 -6  4  0 -1]
 [ 0  2 -7  0  4]
 [ 0  0  1  2 -3]
 [ 0  0  3  1  6]]
 The echelon form is
 [[ 1.  -6.   4.   0.  -1. ]
 [ 0.   1.  -3.5  0.   2. ]
 [ 0.   0.   1.   2.  -3. ]
 [-0.  -0.  -0.   1.  -3. ]]
 The pivots are:  [(0, 0), (1, 1), (2, 2), (3, 3)]
 Is the solution correct?  True
 Solution: 
 [[ 1.   0.   0.   0.  62. ]
 [ 0.   1.   0.   0.  12.5]
 [ 0.   0.   1.   0.   3. ]
 [-0.  -0.  -0.   1.  -3. ]]
------------------------------
對比Sympy的rref結果
(Matrix([
[1, 0, 0, 0,   62],
[0, 1, 0, 0, 25/2],
[0, 0, 1, 0,    3],
[0, 0, 0, 1,   -3]]), (0, 1, 2, 3))
------------------------------
[[ 0 -3 -6  4  9]
 [-1 -2 -1  3  1]
 [-2 -3  0  3 -1]
 [ 1  4  5 -9 -7]]
 The echelon form is
 [[ 1.   1.5 -0.  -1.5  0.5]
 [-0.   1.   2.  -3.  -3. ]
 [-0.  -0.  -0.   1.  -0. ]
 [ 0.   0.   0.   0.   0. ]]
 The pivots are:  [(0, 0), (1, 1), (2, 3)]
 There are free variables.
 [[ 1.  0. -3.  0.  5.]
 [-0.  1.  2.  0. -3.]
 [-0. -0. -0.  1. -0.]
 [ 0.  0.  0.  0.  0.]]
------------------------------
對比Sympy的rref結果
(Matrix([
[1, 0, -3, 0,  5],
[0, 1,  2, 0, -3],
[0, 0,  0, 1,  0],
[0, 0,  0, 0,  0]]), (0, 1, 3))
------------------------------
[[ 0  3 -6  6  4 -5]
 [ 3 -7  8 -5  8  9]
 [ 3 -9 12 -9  6 15]]
 The echelon form is
 [[ 1.         -2.33333333  2.66666667 -1.66666667  2.66666667  3.        ]
 [ 0.          1.         -2.          2.          1.33333333 -1.66666667]
 [ 0.          0.          0.          0.          1.          4.        ]]
 The pivots are:  [(0, 0), (1, 1), (2, 4)]
 There are free variables.
 [[  1.   0.  -2.   3.   0. -24.]
 [  0.   1.  -2.   2.   0.  -7.]
 [  0.   0.   0.   0.   1.   4.]]
------------------------------
對比Sympy的rref結果
(Matrix([
[1, 0, -2, 3, 0, -24],
[0, 1, -2, 2, 0,  -7],
[0, 0,  0, 0, 1,   4]]), (0, 1, 4))
------------------------------

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置台北網頁設計,各種案例分享

.NET高級特性-Emit(1)

  在這個大數據/雲計算/人工智能研發普及的時代,Python的崛起以及Javascript的前後端的侵略,程序員與企業似乎越來越青睞動態語言所帶來的便捷性與高效性,即使靜態語言在性能,錯誤檢查等方面的優於靜態語言。對於.NETer來說,.NET做為一門靜態語言,我們不僅要打好.NET的基本功,如基本類型/語法/底層原理/錯誤檢查等知識,也要深入理解.NET的一些高級特性,來為你的工作減輕負擔和提高代碼質量。

  ok,咱們今天開始聊一聊.NET中的Emit。

一、什麼是Emit?

  Emit含義為發出、產生的含義,這是.NET中的一組類庫,命名空間為System.Reflection.Emit,幾乎所有的.NET版本(Framework/Mono/NetCore)都支持Emit,可以實現用C#代碼生成代碼的類庫

二、Emit的本質

  我們知道.NET可以由各種語言進行編寫,比如VB,C++等,當然絕大部分程序員進行.NET開發都是使用C#語言進行的,這些語言都會被各自的語言解釋器解釋為IL語言並執行,而Emit類庫的作用就是用這些語言來編寫生成IL語言,並交給CLR(公共語言運行時)進行執行。

  我們先來看看IL語言長什麼樣子:

  (1) 首先我們創建一個Hello,World程序

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
        }
    }

  (2) 將程序編譯成dll文件,我們可以看到在開發目錄下生成了bin文件夾

  

  (3) 向下尋找,我們可以看到dll文件已經生成,筆者使用netcore3進行開發,故路徑為bin/Debug/netcoreapp3.0

  

  (4) 這時候,我們就要祭出我們的il查看神器了,ildasm工具

  

  如何找到這個工具?打開開始菜單,找到Visual Studio文件夾,打開Developer Command Prompt,在打開的命令行中鍵入ildasm回車即可,筆者使用vs2019進行演示,其它vs版本操作方法均一致

  

 

 

 

 

 

 

   (5) 在dasm菜單欄選擇文件->打開,選擇剛剛生成的dll文件

  

 

 

   (6) 即可查看生成il代碼

  

 

  有了ildasm的輔助,我們就能夠更好的了解IL語言以及如何編寫IL語言,此外,Visual Studio中還有許多插件支持查看il代碼,比如JetBrains出品的Resharper插件等,如果覺得筆者方式較為麻煩可以使用以上插件查看il代碼

三、理解IL代碼

  在上一章節中,我們理解了Emit的本質其實就是用C#來編寫IL代碼,既然要編寫IL代碼,那麼我們首先要理解IL代碼是如何進行工作的,IL代碼是如何完成C#當中的順序/選擇/循環結構的,是如何實現類的定義/字段的定義/屬性的定義/方法的定義的。

  IL代碼是一種近似於指令式的代碼語言,與彙編語言比較相近,所以習慣於寫高級語言的.NETer來說比較難以理解

  讓我們來看看Hello,World程序的IL代碼:

IL_0000:  nop
IL_0001:  ldstr      "Hello World!"
IL_0006:  call       void [System.Console]System.Console::WriteLine(string)
IL_000b:  nop
IL_000c:  ret

  我們可以把IL代碼看成棧的運行

  第一條指令,nop表示不做任何事情,表示代碼不做任何事情

  第二條指令,ldstr表示將字符串放入棧中,字符串的值為“Hello,World!”

  第三條指令,call表示調用方法,參數為調用方法的方法信息,並把返回的結構壓入棧中,使用的參數為之前已經入棧的“Hello World!”,以此類推,如果方法有n個參數,那麼他就會調取棧中n個數據,並返回一個結果放回棧中

  第四條指令,nop表示不做任何事情

  第五條指令,ret表示將棧中頂部的數據返回,如果方法定義為void,則無返回值

  關於Hello,world程序IL的理解就說到這裏,更多的指令含義讀者可以參考微軟官方文檔,筆者之後也會繼續對Emit進行講解和Emit的應用

四、用Emit類庫編寫IL代碼

  既然IL代碼咱們理解的差不多了,咱們就開始嘗試用C#來寫IL代碼了,有了IL代碼的參考,咱們也可以依葫蘆畫瓢的把代碼寫出來了

  (1) 引入Emit命名空間

using System.Reflection.Emit;

  (2) 首先我們定義一個Main方法,入參無,返回類型void

//定義方法名,返回類型,輸入類型
var method = new DynamicMethod("Main", null, Type.EmptyTypes);

  (3) 生成IL代碼

//生成IL代碼
var ilGenerator = method.GetILGenerator();
ilGenerator.Emit(OpCodes.Nop);
ilGenerator.Emit(OpCodes.Ldstr,"Hello World!");
ilGenerator.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) })); //尋找Console的WriteLine方法
ilGenerator.Emit(OpCodes.Nop);
ilGenerator.Emit(OpCodes.Ret);

  (4) 創建委託並調用

//創建委託
var helloWorldMethod = method.CreateDelegate(typeof(Action)) as Action;
helloWorldMethod.Invoke();

  (5)運行,即輸出Hello World!

五、小結

  Emit的本質是使用高級語言生成IL代碼,進而進行調用的的一組類庫,依賴Emit我們可以實現用代碼生成代碼的操作,即編程語言的自舉,可以有效彌補靜態語言的靈活性的缺失。

  Emit的性能非常好,除了第一次構建IL代碼所需要時間外,之後只要將操作緩存在計算機內存中,速度與手寫代碼相差無幾

  有許多著名.NET類庫均依賴於Emit:

  (.NET JSON操作庫)Json.NET/Newtonsoft.Json:

  (輕量ORM)Dapper:

  (ObjectToObjectMapper)EmitMapper:

  (AOP庫)Castle.DynamicProxy:

  學習Emit:

  .NET官方文檔:

  .NET API瀏覽器:

  之後作者將繼續講解.NET Emit的相關內容和應用,感謝閱讀

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置台北網頁設計,各種案例分享

源碼分析RocketMQ消息軌跡

目錄

本文沿着的思路,從如下3個方面對其源碼進行解讀:

  1. 發送消息軌跡
  2. 消息軌跡格式
  3. 存儲消息軌跡數據

@(本節目錄)

1、發送消息軌跡流程

首先我們來看一下在消息發送端如何啟用消息軌跡,示例代碼如下:

public class TraceProducer {
    public static void main(String[] args) throws MQClientException, InterruptedException {
        DefaultMQProducer producer = new DefaultMQProducer("ProducerGroupName",true);      // @1
        producer.setNamesrvAddr("127.0.0.1:9876");
        producer.start();
        for (int i = 0; i < 10; i++)
            try {
                {
                    Message msg = new Message("TopicTest",
                        "TagA",
                        "OrderID188",
                        "Hello world".getBytes(RemotingHelper.DEFAULT_CHARSET));
                    SendResult sendResult = producer.send(msg);
                    System.out.printf("%s%n", sendResult);
                }

            } catch (Exception e) {
                e.printStackTrace();
            }
        producer.shutdown();
    }
}

從上述代碼可以看出其關鍵點是在創建DefaultMQProducer時指定開啟消息軌跡跟蹤。我們不妨瀏覽一下DefaultMQProducer與啟用消息軌跡相關的構造函數:

public DefaultMQProducer(final String producerGroup, boolean enableMsgTrace)
public DefaultMQProducer(final String producerGroup, boolean enableMsgTrace, final String customizedTraceTopic)

參數如下:

  • String producerGroup
    生產者所屬組名。
  • boolean enableMsgTrace
    是否開啟跟蹤消息軌跡,默認為false。
  • String customizedTraceTopic
    如果開啟消息軌跡跟蹤,用來存儲消息軌跡數據所屬的主題名稱,默認為:RMQ_SYS_TRACE_TOPIC。

1.1 DefaultMQProducer構造函數

public DefaultMQProducer(final String producerGroup, RPCHook rpcHook, boolean enableMsgTrace,final String customizedTraceTopic) {      // @1
    this.producerGroup = producerGroup;
    defaultMQProducerImpl = new DefaultMQProducerImpl(this, rpcHook);
    //if client open the message trace feature
    if (enableMsgTrace) {                                                                                                                                                                                            // @2
        try {
            AsyncTraceDispatcher dispatcher = new AsyncTraceDispatcher(customizedTraceTopic, rpcHook);                                                         
            dispatcher.setHostProducer(this.getDefaultMQProducerImpl());
            traceDispatcher = dispatcher;
            this.getDefaultMQProducerImpl().registerSendMessageHook(
                new SendMessageTraceHookImpl(traceDispatcher));                                                                                                                             // @3
        } catch (Throwable e) {
            log.error("system mqtrace hook init failed ,maybe can't send msg trace data");
        }
    }
}

代碼@1:首先介紹一下其局部變量。

  • String producerGroup
    生產者所屬組。
  • RPCHook rpcHook
    生產者發送鈎子函數。
  • boolean enableMsgTrace
    是否開啟消息軌跡跟蹤。
  • String customizedTraceTopic
    定製用於存儲消息軌跡的數據。

代碼@2:用來構建AsyncTraceDispatcher,看其名:異步轉發消息軌跡數據,稍後重點關注。

代碼@3:構建SendMessageTraceHookImpl對象,並使用AsyncTraceDispatcher用來異步轉發。

1.2 SendMessageTraceHookImpl鈎子函數

1.2.1 SendMessageTraceHookImpl類圖

  1. SendMessageHook
    消息發送鈎子函數,用於在消息發送之前、發送之後執行一定的業務邏輯,是記錄消息軌跡的最佳擴展點。
  2. TraceDispatcher
    消息軌跡轉發處理器,其默認實現類AsyncTraceDispatcher,異步實現消息軌跡數據的發送。下面對其屬性做一個簡單的介紹:
    • int queueSize
      異步轉發,隊列長度,默認為2048,當前版本不能修改。
    • int batchSize
      批量消息條數,消息軌跡一次消息發送請求包含的數據條數,默認為100,當前版本不能修改。
    • int maxMsgSize
      消息軌跡一次發送的最大消息大小,默認為128K,當前版本不能修改。
    • DefaultMQProducer traceProducer
      用來發送消息軌跡的消息發送者。
    • ThreadPoolExecutor traceExecuter
      線程池,用來異步執行消息發送。
    • AtomicLong discardCount
      記錄丟棄的消息個數。
    • Thread worker
      woker線程,主要負責從追加隊列中獲取一批待發送的消息軌跡數據,提交到線程池中執行。
    • ArrayBlockingQueue< TraceContext> traceContextQueue
      消息軌跡TraceContext隊列,用來存放待發送到服務端的消息。
    • ArrayBlockingQueue< Runnable> appenderQueue
      線程池內部隊列,默認長度1024。
    • DefaultMQPushConsumerImpl hostConsumer
      消費者信息,記錄消息消費時的軌跡信息。
    • String traceTopicName
      用於跟蹤消息軌跡的topic名稱。

1.2.2 源碼分析SendMessageTraceHookImpl

1.2.2.1 sendMessageBefore
public void sendMessageBefore(SendMessageContext context) { 
    //if it is message trace data,then it doesn't recorded
    if (context == null || context.getMessage().getTopic().startsWith(((AsyncTraceDispatcher) localDispatcher).getTraceTopicName())) {   // @1
        return;
    }
    //build the context content of TuxeTraceContext
    TraceContext tuxeContext = new TraceContext();
    tuxeContext.setTraceBeans(new ArrayList<TraceBean>(1));
    context.setMqTraceContext(tuxeContext);
    tuxeContext.setTraceType(TraceType.Pub);
    tuxeContext.setGroupName(context.getProducerGroup());                                                                                                                       // @2
    //build the data bean object of message trace
    TraceBean traceBean = new TraceBean();                                                                                                                                                // @3
    traceBean.setTopic(context.getMessage().getTopic());
    traceBean.setTags(context.getMessage().getTags());
    traceBean.setKeys(context.getMessage().getKeys());
    traceBean.setStoreHost(context.getBrokerAddr());
    traceBean.setBodyLength(context.getMessage().getBody().length);
    traceBean.setMsgType(context.getMsgType());
    tuxeContext.getTraceBeans().add(traceBean);
}

代碼@1:如果topic主題為消息軌跡的Topic,直接返回。

代碼@2:在消息發送上下文中,設置用來跟蹤消息軌跡的上下環境,裏面主要包含一個TraceBean集合、追蹤類型(TraceType.Pub)與生產者所屬的組。

代碼@3:構建一條跟蹤消息,用TraceBean來表示,記錄原消息的topic、tags、keys、發送到broker地址、消息體長度等消息。

從上文看出,sendMessageBefore主要的用途就是在消息發送的時候,先準備一部分消息跟蹤日誌,存儲在發送上下文環境中,此時並不會發送消息軌跡數據。

1.2.2.2 sendMessageAfter
public void sendMessageAfter(SendMessageContext context) {
    //if it is message trace data,then it doesn't recorded
    if (context == null || context.getMessage().getTopic().startsWith(((AsyncTraceDispatcher) localDispatcher).getTraceTopicName())     // @1
        || context.getMqTraceContext() == null) {
        return;
    }
    if (context.getSendResult() == null) {
        return;
    }

    if (context.getSendResult().getRegionId() == null
        || !context.getSendResult().isTraceOn()) {
        // if switch is false,skip it
        return;
    }

    TraceContext tuxeContext = (TraceContext) context.getMqTraceContext();
    TraceBean traceBean = tuxeContext.getTraceBeans().get(0);                                                                                                // @2
    int costTime = (int) ((System.currentTimeMillis() - tuxeContext.getTimeStamp()) / tuxeContext.getTraceBeans().size());     // @3
    tuxeContext.setCostTime(costTime);                                                                                                                                      // @4
    if (context.getSendResult().getSendStatus().equals(SendStatus.SEND_OK)) {                                                                    
        tuxeContext.setSuccess(true);
    } else {
        tuxeContext.setSuccess(false);
    }
    tuxeContext.setRegionId(context.getSendResult().getRegionId());                                                                                      
    traceBean.setMsgId(context.getSendResult().getMsgId());
    traceBean.setOffsetMsgId(context.getSendResult().getOffsetMsgId());
    traceBean.setStoreTime(tuxeContext.getTimeStamp() + costTime / 2);
    localDispatcher.append(tuxeContext);                                                                                                                                   // @5
}

代碼@1:如果topic主題為消息軌跡的Topic,直接返回。

代碼@2:從MqTraceContext中獲取跟蹤的TraceBean,雖然設計成List結構體,但在消息發送場景,這裏的數據永遠只有一條,及時是批量發送也不例外。

代碼@3:獲取消息發送到收到響應結果的耗時。

代碼@4:設置costTime(耗時)、success(是否發送成功)、regionId(發送到broker所在的分區)、msgId(消息ID,全局唯一)、offsetMsgId(消息物理偏移量,如果是批量消息,則是最後一條消息的物理偏移量)、storeTime,這裏使用的是(客戶端發送時間 + 二分之一的耗時)來表示消息的存儲時間,這裡是一個估值。

代碼@5:將需要跟蹤的信息通過TraceDispatcher轉發到Broker服務器。其代碼如下:

public boolean append(final Object ctx) {
    boolean result = traceContextQueue.offer((TraceContext) ctx);
    if (!result) {
        log.info("buffer full" + discardCount.incrementAndGet() + " ,context is " + ctx);
    }
    return result;
}

這裏一個非常關鍵的點是offer方法的使用,當隊列無法容納新的元素時會立即返回false,並不會阻塞。

接下來將目光轉向TraceDispatcher的實現。

1.3 TraceDispatcher實現原理

TraceDispatcher,用於客戶端消息軌跡數據轉發到Broker,其默認實現類:AsyncTraceDispatcher。

1.3.1 TraceDispatcher構造函數

public AsyncTraceDispatcher(String traceTopicName, RPCHook rpcHook) throws MQClientException {    
    // queueSize is greater than or equal to the n power of 2 of value
    this.queueSize = 2048;
    this.batchSize = 100;
    this.maxMsgSize = 128000;                                        
    this.discardCount = new AtomicLong(0L);         
    this.traceContextQueue = new ArrayBlockingQueue<TraceContext>(1024);
    this.appenderQueue = new ArrayBlockingQueue<Runnable>(queueSize);
    if (!UtilAll.isBlank(traceTopicName)) {
        this.traceTopicName = traceTopicName;
    } else {
        this.traceTopicName = MixAll.RMQ_SYS_TRACE_TOPIC;
    }                   // @1
    this.traceExecuter = new ThreadPoolExecutor(// :
        10, //
        20, //
        1000 * 60, //
        TimeUnit.MILLISECONDS, //
        this.appenderQueue, //
        new ThreadFactoryImpl("MQTraceSendThread_"));
    traceProducer = getAndCreateTraceProducer(rpcHook);      // @2
}

代碼@1:初始化核心屬性,該版本這些值都是“固化”的,用戶無法修改。

  • queueSize
    隊列長度,默認為2048,異步線程池能夠積壓的消息軌跡數量。
  • batchSize
    一次向Broker批量發送的消息條數,默認為100.
  • maxMsgSize
    向Broker彙報消息軌跡時,消息體的總大小不能超過該值,默認為128k。
  • discardCount
    整個運行過程中,丟棄的消息軌跡數據,這裏要說明一點的是,如果消息TPS發送過大,異步轉發線程處理不過來時,會主動丟棄消息軌跡數據。
  • traceContextQueue
    traceContext積壓隊列,客戶端(消息發送、消息消費者)在收到處理結果后,將消息軌跡提交到噶隊列中,則會立即返回。
  • appenderQueue
    提交到Broker線程池中隊列。
  • traceTopicName
    用於接收消息軌跡的Topic,默認為RMQ_SYS_TRANS_HALF_TOPIC。
  • traceExecuter
    用於發送到Broker服務的異步線程池,核心線程數默認為10,最大線程池為20,隊列堆積長度2048,線程名稱:MQTraceSendThread_。、
  • traceProducer
    發送消息軌跡的Producer。

代碼@2:調用getAndCreateTraceProducer方法創建用於發送消息軌跡的Producer(消息發送者),下面詳細介紹一下其實現。

1.3.2 getAndCreateTraceProducer詳解

private DefaultMQProducer getAndCreateTraceProducer(RPCHook rpcHook) {
        DefaultMQProducer traceProducerInstance = this.traceProducer;
        if (traceProducerInstance == null) {  //@1
            traceProducerInstance = new DefaultMQProducer(rpcHook);
            traceProducerInstance.setProducerGroup(TraceConstants.GROUP_NAME);
            traceProducerInstance.setSendMsgTimeout(5000);
            traceProducerInstance.setVipChannelEnabled(false);
            // The max size of message is 128K
            traceProducerInstance.setMaxMessageSize(maxMsgSize - 10 * 1000);
        }
        return traceProducerInstance;
    }

代碼@1:如果還未建立發送者,則創建用於發送消息軌跡的消息發送者,其GroupName為:_INNER_TRACE_PRODUCER,消息發送超時時間5s,最大允許發送消息大小118K。

1.3.3 start

public void start(String nameSrvAddr) throws MQClientException {
    if (isStarted.compareAndSet(false, true)) {     // @1
        traceProducer.setNamesrvAddr(nameSrvAddr);
        traceProducer.setInstanceName(TRACE_INSTANCE_NAME + "_" + nameSrvAddr);
        traceProducer.start();
    }
    this.worker = new Thread(new AsyncRunnable(), "MQ-AsyncTraceDispatcher-Thread-" + dispatcherId);   // @2
    this.worker.setDaemon(true);
    this.worker.start();                                                                                   
    this.registerShutDownHook();
}

開始啟動,其調用的時機為啟動DefaultMQProducer時,如果啟用跟蹤消息軌跡,則調用之。

代碼@1:如果用於發送消息軌跡的發送者沒有啟動,則設置nameserver地址,並啟動着。

代碼@2:啟動一個線程,用於執行AsyncRunnable任務,接下來將重點介紹。

1.3.4 AsyncRunnable

class AsyncRunnable implements Runnable {
         private boolean stopped;
    public void run() {
        while (!stopped) {
            List<TraceContext> contexts = new ArrayList<TraceContext>(batchSize);     // @1
            for (int i = 0; i < batchSize; i++) {
                TraceContext context = null;
                try {
                    //get trace data element from blocking Queue — traceContextQueue
                    context = traceContextQueue.poll(5, TimeUnit.MILLISECONDS);        // @2
                } catch (InterruptedException e) {
                }
                if (context != null) {
                    contexts.add(context);
                } else {
                    break;
                }
            }
            if (contexts.size() > 0) {                                                                               :
                AsyncAppenderRequest request = new AsyncAppenderRequest(contexts);  // @3
                traceExecuter.submit(request);                                                               
            } else if (AsyncTraceDispatcher.this.stopped) {
                this.stopped = true;
            }
        }
    }
}

代碼@1:構建待提交消息跟蹤Bean,每次最多發送batchSize,默認為100條。

代碼@2:從traceContextQueue中取出一個待提交的TraceContext,設置超時時間為5s,即如何該隊列中沒有待提交的TraceContext,則最多等待5s。

代碼@3:向線程池中提交任務AsyncAppenderRequest。

1.3.5 AsyncAppenderRequest#sendTraceData

public void sendTraceData(List<TraceContext> contextList) {
    Map<String, List<TraceTransferBean>> transBeanMap = new HashMap<String, List<TraceTransferBean>>();
    for (TraceContext context : contextList) {        //@1
        if (context.getTraceBeans().isEmpty()) {
            continue;
        }
        // Topic value corresponding to original message entity content
        String topic = context.getTraceBeans().get(0).getTopic();     // @2
        // Use  original message entity's topic as key
        String key = topic;
        List<TraceTransferBean> transBeanList = transBeanMap.get(key);
        if (transBeanList == null) {
            transBeanList = new ArrayList<TraceTransferBean>();
            transBeanMap.put(key, transBeanList);
        }
        TraceTransferBean traceData = TraceDataEncoder.encoderFromContextBean(context);    // @3
        transBeanList.add(traceData);
    }
    for (Map.Entry<String, List<TraceTransferBean>> entry : transBeanMap.entrySet()) {       // @4
        flushData(entry.getValue());
    }
}

代碼@1:遍歷收集的消息軌跡數據。

代碼@2:獲取存儲消息軌跡的Topic。

代碼@3:對TraceContext進行編碼,這裡是消息軌跡的傳輸數據,稍後對其詳細看一下,了解其上傳的格式。

代碼@4:將編碼后的數據發送到Broker服務器。

1.3.6 TraceDataEncoder#encoderFromContextBean

根據消息軌跡跟蹤類型,其格式會有一些不一樣,下面分別來介紹其合適。

1.3.6.1 PUB(消息發送)
case Pub: {
    TraceBean bean = ctx.getTraceBeans().get(0);
    //append the content of context and traceBean to transferBean's TransData
    sb.append(ctx.getTraceType()).append(TraceConstants.CONTENT_SPLITOR)//
      .append(ctx.getTimeStamp()).append(TraceConstants.CONTENT_SPLITOR)//
      .append(ctx.getRegionId()).append(TraceConstants.CONTENT_SPLITOR)//
      .append(ctx.getGroupName()).append(TraceConstants.CONTENT_SPLITOR)//
      .append(bean.getTopic()).append(TraceConstants.CONTENT_SPLITOR)//
      .append(bean.getMsgId()).append(TraceConstants.CONTENT_SPLITOR)//
      .append(bean.getTags()).append(TraceConstants.CONTENT_SPLITOR)//
      .append(bean.getKeys()).append(TraceConstants.CONTENT_SPLITOR)//
      .append(bean.getStoreHost()).append(TraceConstants.CONTENT_SPLITOR)//
      .append(bean.getBodyLength()).append(TraceConstants.CONTENT_SPLITOR)//
      .append(ctx.getCostTime()).append(TraceConstants.CONTENT_SPLITOR)//
      .append(bean.getMsgType().ordinal()).append(TraceConstants.CONTENT_SPLITOR)//
      .append(bean.getOffsetMsgId()).append(TraceConstants.CONTENT_SPLITOR)//
     .append(ctx.isSuccess()).append(TraceConstants.FIELD_SPLITOR);
}

消息軌跡數據的協議使用字符串拼接,字段的分隔符號為1,整個數據以2結尾,感覺這個設計還是有點“不可思議”,為什麼不直接使用json協議呢?

1.3.6.2 SubBefore(消息消費之前)
for (TraceBean bean : ctx.getTraceBeans()) {
    sb.append(ctx.getTraceType()).append(TraceConstants.CONTENT_SPLITOR)//
      .append(ctx.getTimeStamp()).append(TraceConstants.CONTENT_SPLITOR)//
      .append(ctx.getRegionId()).append(TraceConstants.CONTENT_SPLITOR)//
      .append(ctx.getGroupName()).append(TraceConstants.CONTENT_SPLITOR)//
      .append(ctx.getRequestId()).append(TraceConstants.CONTENT_SPLITOR)//
      .append(bean.getMsgId()).append(TraceConstants.CONTENT_SPLITOR)//
      .append(bean.getRetryTimes()).append(TraceConstants.CONTENT_SPLITOR)//
      .append(bean.getKeys()).append(TraceConstants.FIELD_SPLITOR);//
    }
}

軌跡就是按照上述順序拼接而成,各個字段使用1分隔,每一條記錄使用2結尾。

1.3.2.3 SubAfter(消息消費后)
case SubAfter: {
    for (TraceBean bean : ctx.getTraceBeans()) {
        sb.append(ctx.getTraceType()).append(TraceConstants.CONTENT_SPLITOR)//
          .append(ctx.getRequestId()).append(TraceConstants.CONTENT_SPLITOR)//
          .append(bean.getMsgId()).append(TraceConstants.CONTENT_SPLITOR)//
          .append(ctx.getCostTime()).append(TraceConstants.CONTENT_SPLITOR)//
          .append(ctx.isSuccess()).append(TraceConstants.CONTENT_SPLITOR)//
          .append(bean.getKeys()).append(TraceConstants.CONTENT_SPLITOR)//
          .append(ctx.getContextCode()).append(TraceConstants.FIELD_SPLITOR);
        }
    }
}

格式編碼一樣,就不重複多說。

經過上面的源碼跟蹤,消息發送端的消息軌跡跟蹤流程、消息軌跡數據編碼協議就清晰了,接下來我們使用一張序列圖來結束本部分的講解。

其實行文至此,只關注了消息發送的消息軌跡跟蹤,消息消費的軌跡跟蹤又是如何呢?其實現原理其實是一樣的,就是在消息消費前後執行特定的鈎子函數,其實現類為ConsumeMessageTraceHookImpl,由於其實現與消息發送的思路類似,故就不詳細介紹了。

2、 消息軌跡數據如何存儲

其實從上面的分析,我們已經得知,RocketMQ的消息軌跡數據存儲在到Broker上,那消息軌跡的主題名如何指定?其路由信息又怎麼分配才好呢?是每台Broker上都創建還是只在其中某台上創建呢?RocketMQ支持系統默認與自定義消息軌跡的主題。

2.1 使用系統默認的主題名稱

RocketMQ默認的消息軌跡主題為:RMQ_SYS_TRACE_TOPIC,那該Topic需要手工創建嗎?其路由信息呢?

{
    if (this.brokerController.getBrokerConfig().isTraceTopicEnable()) {    // @1
        String topic = this.brokerController.getBrokerConfig().getMsgTraceTopicName();
        TopicConfig topicConfig = new TopicConfig(topic);
        this.systemTopicList.add(topic);
        topicConfig.setReadQueueNums(1);                                              // @2
        topicConfig.setWriteQueueNums(1);
        this.topicConfigTable.put(topicConfig.getTopicName(), topicConfig);
    }
}

上述代碼出自TopicConfigManager的構造函數,在Broker啟動的時候會創建topicConfigManager對象,用來管理topic的路由信息。

代碼@1:如果Broker開啟了消息軌跡跟蹤(traceTopicEnable=true)時,會自動創建默認消息軌跡的topic路由信息,注意其讀寫隊列數為1。

2.2 用戶自定義消息軌跡主題

在創建消息發送者、消息消費者時,可以显示的指定消息軌跡的Topic,例如:

public DefaultMQProducer(final String producerGroup, RPCHook rpcHook, boolean enableMsgTrace,final String customizedTraceTopic)

public DefaultMQPushConsumer(final String consumerGroup, RPCHook rpcHook,
        AllocateMessageQueueStrategy allocateMessageQueueStrategy, boolean enableMsgTrace, final String customizedTraceTopic)

通過customizedTraceTopic來指定消息軌跡Topic。

溫馨提示:通常在生產環境上,將不會開啟自動創建主題,故需要RocketMQ運維管理人員提前創建好Topic。

好了,本文就介紹到這裏了,本文詳細介紹了RocktMQ消息軌跡的實現原理,下一篇,我們將進入到多副本的學習中。

作者介紹:
丁威,《RocketMQ技術內幕》作者,RocketMQ 社區佈道師,公眾號: 維護者,目前已陸續發表源碼分析Java集合、Java 併發包(JUC)、Netty、Mycat、Dubbo、RocketMQ、Mybatis等源碼專欄。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置台北網頁設計,各種案例分享

Nissan新型電動車Leaf續航增4成、可自駕和自動停車

日產汽車(Nissan)6日發表了進行全面改良的電動車「Leaf」新型車款,並預計於10月2日在日本開賣,美國、加拿大、歐洲將在2018年1月開始交車。首款Leaf於2010年末開賣以來、迄今的累計銷售量約28萬台。

新型Leaf採用新研發的鋰離子電池,電池容量為40KWh(舊型車款有24KWh和30KWh兩種),續航距離達400km、較舊型車款提升約四成,進行一般充電時約八小時可充飽電、但用急速充電時充飽80%電力約需40分鐘(舊型車款為30分鐘)。新型Leaf售價約315萬-399萬日圓(舊型車款約280萬至456萬日圓)。

新型Leaf搭載能夠在高速公路單一車道上行駛的自動駕駛技術「ProPILOT」以及自動停車功能「ProPILOT Parking」,另外也採用能減輕駕駛負擔的「e-Pedal」功能。

「e-Pedal」可讓駕駛單靠油門踏板執行前進、加速、減速、停止等動作。只要駕駛採下踏板就會加速,而若鬆開踏板就會減速、完全放開踏板車輛就會停止。

路透社報導,日產為電動車的先驅者,不過近來特斯拉(Tesla)等新興業者抬頭、競爭激化,而日產期望藉由性能提升的新型Leaf展開攻勢。新型Leaf全球年銷售量目標為9萬台,且之後日產計畫在2018年推出電池容量/馬達輸出升級,續航距離更長的高階車款。

以美國基準的續航距離來看,新款Leaf為150英里(約240km),遜於競爭對手特斯拉Model 3的220英里和通用(General Motors)BOLT的238英里。

特斯拉Model 3的售價為3.5萬美元,截至7月28日開始在美國市場交車為止,其訂單量超過50萬台。

日產會長Carlos Ghosn於6月28日舉行的股東會上表示,「日產在電動車界居領導位置。日產電動車累計銷售量超過60萬台、為美國特斯拉兩倍」。

(本文內容由授權使用。圖片出處:)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置台北網頁設計,各種案例分享

印度政府年內擬發布新電動車政策,車廠提前動起來

印度政府為對抗空污,矢言2030年全國只賣電動車,促使車廠卯起勁來加速開發電動車。

電動車因電池成本極高而造價昂貴,加上缺乏充電站,普及化面臨重重困難。儘管如此,印度政府仍不畏挑戰,決心走這條困難但正確的路。據路透社報導,某印度政府官員表示正在草擬新政策,當中包含推動電動車的藍圖,預計將在今年底發布。

在政策引導下,印度國內外車廠已經全面動起來。報導指出,韓國現代汽車正與供應商就電動車所需零件進行討論。另外,印度車廠Ashok Leyland去年推出電動巴士後,還與新創企業SUN Mobility結盟,將齊力研發車用電池交換技術。

鄰近印度的中國政府現也意識到電動車時代來臨,據新華社9日報導,中國工信部副部長辛國斌透露已啟動相關研究,正在研擬傳統內燃機汽車退場的時間表。

(本文內容由授權使用。圖片出處:pexels CC0)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

是誰給了你圍剿微信的勇氣?“社交日”,我們採訪了18位投資人

  作者劉洋

  距離上一個刷爆朋友圈的微信公開課過去不到五天,開相聲專場的老羅、產品至上的張一鳴,以及“技術無罪”的王欣都選在今天公布社交產品,圍剿微信的言論一時甚囂塵上。2019 年 1 月 15 日,農曆臘月初十,在這個沒有任何特殊寓意的日子,因為明星創業者撞車發布社交產品而顯得與眾不同。

  今日,王欣的雲歌人工智能宣布公布新的社交產品“馬桶 MT”主打熟人匿名社交;羅永浩站台的快如科技則在子彈短信的基礎上增加了個性化的需求,打造了一款全新 App,名為“聊天寶”;張一鳴創辦的字節跳動則發布了名為“多閃”的短視頻社交產品。

  如果這僅僅是巧合,或許這是互聯網史上難得一見的巧合。八年前,當三大運營商拼的頭破血流時,微信突然出現,運營商們幡然醒悟這個不起眼的 APP 才是真正的大敵。四年前,當阿里和京東覆蓋了電商時,沒人意識到網易考拉和拼多多的出現。兩年前,程維斷言網約車戰爭結束,然後王興突然殺出。互聯網歷史告訴我們一個亘古不變的邏輯——戰爭和對手總是在你沒有意識到的情況下出現。

  在中國,廣義上的社交應用成百上千,社區、社群、音樂軟件都可以納入其中。賈樟柯的電影《江湖兒女》中有句名言:有人的地方就有江湖。套用之亦可得到,有人的地方就有社交。騰訊封殺不了社交品類,微信自然也覆蓋不了全部人群。

  同一個世界,同一個夢想

  分眾傳媒董事長江南春有一句在各大演講台上都說過的話:“行業第一封殺行業,行業第二懟行業第一,行業第三做垂直品類,行業第四開創新賽道。”細細想來,這句話放之四海而皆準。懟微信,是做社交的夢想。

  對於投資社交,投資人們也有自己的看法。

  創世夥伴資本董事聶東辰認為,社交一定會出現新的巨頭。目前的社交產品還不能把所有社交需求都囊括在內,隨着用戶結構的變化、社交場景的變化、社交需求內在的變化,一定會出現新的社交產品成為未來的巨頭。

  探探的投資人,眾為資本北京投資負責人、副總裁周博男則稱,社交產品還會出現新的巨頭。

  名川資本創始合伙人王求樂也有同樣的看法,社交產品肯定有機會,會持續不斷地發展變化。目前的主流社交產品,已經存在好多年了,而且佔據和浪費了用戶太多時間,“喜新厭舊是人性,人們對市場目前的社交產品,有些疲勞。”

  另一方面,壟斷局面不可能持續,這是經濟規律。尤其在意見領袖階層,樂見壟斷被打破,一旦出現有競爭力的新社交產品,會有莫名的力量共同推波助瀾。

  梧桐樹資本創始合伙人童瑋亮表示,社交產品永遠有新的機會,但新公司成為巨頭的概率相對其他創業可能更低。QQ 成立於 1999 年,Facebook 成立於 2004 年,屬於 PC 時代;Instagram,snapchat,陌陌都成立於 2010 年、2011 年,抓住了移動互聯網紅利。當下大的所謂紅利趨勢也許是 5G,但也還沒商用,如果只是玩法有小的差異化而不是藉助行業大的變革,社交產品突破成為巨頭壓力會比較大,當然總是需要有優秀的團隊去嘗試的。

  華蓋資本則用了更給予了更為抽象地解讀,“經濟形勢不好的情況下,文化越會燦爛。”

  長期觀察和投資社交的雲九資本執行董事沈文傑表示,“社交應用的突破性機會在於滿足特定場景特定人群的特定需求,從這類人深度切入,並形成該平台的生態與調性。用小範圍可以跑通的模式去覆蓋更多的人群。”

  熊貓資本合伙人毛聖博認為,社交是人的天性,沒有所謂的痛點或者不痛點。好的社交產品是類似在產品中建立一個“小社會”,小社會裡面有自己的規則、氛圍,有匹配這些規則和氛圍的人群進來。但是,未必會做的很大。

  青銳創投 VP 楊瑜表示,隨着 95、00 後人群長大且逐漸成為互聯網主力軍,5-10 年內是可能出現新的巨頭。

  頂商投資合伙人劉玲認為碎片化時代,社交更多是有目的性的需求,這個過程中或達預期或收穫其他。需求是變量,變化中孕育着新巨頭誕生機會。馬斯洛需求理論分為生理需求、安全需求、社交需求、尊重需求和自我實現需求。為了滿足各種不同類型的單個簡單或複雜的需求,人們在高科技發展的過程中與外界交流溝通時的信息流工具也是在不斷變化的,不斷創新的企業就會應運而生。

  華人地區頗具影響力的公益網站淡藍網創始人耿樂表示:“未來的社交會成為互聯網上最好的模式之一,解決人們的焦慮、人際的迭代、興趣的連接和壓力的釋放。我覺得未來幾年的機會應該來自於垂直社交,視頻化社交和 00 后的社交。”

  他還指出不只是今年集中發布社交產品,今年內都是社交創投的熱點,很多社交產品出來並獲得融資,都說明,大家關注到了微信產品老化,社交固化背後的商機,包括年輕人的成長,需要新的社交產品來切入市場,給用戶更多的選擇。

  在五天前的微信公開課上,微信官方發布了數據:日登錄量超過 10 億,每日 450 億信息發送,4.1 億次音視頻呼叫。作為國民級應用,微信的數據足夠亮眼,沈文傑認為:“微信已經越來越接近一個底層工具的作用。我們可以在此之上嘗試更多的社交玩法。”一如上文所提,微信已然變成了接近短信、郵箱以及語音電話的存在。他是一款社交應用,卻也不僅僅是一款社交應用,而這也是社交應用的新機會。

  耿樂對此也深表贊同,玩法守舊、增長空間已然不多。

  同樣有着投資社交應用經歷的創勢資本董事長、創始合伙人湯旭東社交產品一定存在機會,不一定是巨頭。現在 00 后崛起,包括 10 后,社交產品一定是與時俱進的,每 10 年會一個迭代。

  如果按照投資人們的說法,千禧一代成長起來的年輕人即將接替 90 后成為主流的社交人群,今年是最後一批 80 後進入中年的時間段,同樣,也是第一批 90 后即將步入中年的時間段。

  隨着年齡層的迭代,社交產品的迭代也已經成為必然。那麼誰會成為迭代之後成功的那個?

  如果非要選一個,就選張一鳴吧

  此番,三家公司不約而同選在了同一天發布社交產品,不可避免的存在營銷目的。還有一點,目前已經臨近春節,即將迎來城市人群迴流,而這群人正是互聯網的主流人群,也是掌握話語權的一批人,他們的宣傳能力不可忽視。沈文傑還指出一點:“此前,王欣和老羅都拿到了一輪融資。目前,也是更新產品的時候了。而頭條做社交已經不是一天两天了。”

  楊瑜認為,“同一天發布社交產品更多的是希望第一時間搶佔熱點和關注,因為社交產品第一波的關注能決定首次下載試用用戶有多少,這種敏感性導致這幾家都不願意落後。”

  五天前,微信公開課的刷屏場景已經引發了足夠的關注和討論,此時適時跟進,在熱度退潮前,將社交再次推到輿論頂點。百度的搜索熱點中,關鍵詞“三家 APP 圍剿微信”一躍登頂。

  數位投資人都對這三個產品表示了均不看好。其中,看好頭條的最多,看好雲歌的其次。

  創世夥伴資本董事聶東辰認為:“更看好誰取決於誰更能打中用戶的社交需求痛點,社交產品不是建造空中樓閣,而是基於社交剛需,滿足人性的需求,才能成為更好的社交產品。”

  某 VC 機構投資總監向投中網表示,三家對比來看,字節跳動是實力最強的,雲歌人工智能是產品邏輯最強的,快如科技是目標用戶最明確的。很多人說社交軟件門檻低,實在不然。既然社交軟件用戶流量巨大、每一個互聯網企業都想進入,它的准入門檻就已超出了技術本身。

  沈文傑認為,羅永浩的產品更偏向於用堆砌需求來搭建場景拉高頻次,但是以商務人群高效溝通切入的初衷就顯得模糊和搖擺;王欣的切入點是陌生人社交,非常講究玩法,此前陌生人已經有過很多的嘗試,增長的天花板很難說,用戶留存和內容管控會非常有挑戰。而且簡單的做關係鏈遷移,意義不大。遷移關係鏈並不能算做一個新的產品而是複製品,溝通效率不會比原來的產品更高,且會在未明確新產品屬性的情況下帶入原關係鏈的屬性,產品失去了靈魂,因此必須要構建原生的關係鏈。相反頭條具備大量流量矩陣的產品,並且具備非常強的算法基礎,算法在內容分发上具備非常強的作用,但是此前頭條一直在做社交媒體,而非即時通訊。如果將算法用在即時通訊上,目前還沒有人嘗試過,也就無從得知結果。

  不過相比另外兩家公司,頭條無論是在體量、流量和資本以及戰略高度上,去做社交都不是另外兩家可以比擬的。所以相比之下,沈文傑“會更關注頭條。”

  耿樂對此也持相同意見:“張一鳴的產品應該有機會衝出來,一是他們的強大運營能力和資本能力,二是短視頻方面的資源和布局,三是從玩法上看,他們更像社交,而不是偽社交。”

  毛聖博則從投資的角度表示了對馬桶 MT 的看法:“匿名社交的需求是有的,但是不夠持續。比較隨機,不是天天都很強烈的需求,所以匿名社交很難做大。當然,我們很難說匿名社交產品做的好不好,但做不大的產品對風險投資來說比較難選擇。”

  王求樂也認為:長期而言,真正成功的社交新產品,應該有別於目前的套路。複製騰訊沒有意義,小修小改也沒價值。也許,未來的產品會垂直,更多元,適合更細分的人群。

  黑洞投資合伙人楊蓉表示,更看好今日頭條,張一鳴對年輕人的洞察比較深,之前除了新聞之外,還做了抖音這樣的成功案例,而抖音其實也是一款社交產品,而且他們也有雄厚的技術支撐和實力。

  楊瑜則認為,在產品形態上目前看不出來特別創新的地方,只能後續去看,但如果從流量來看今日頭條毋庸置疑是擁有更低價且更巨大的流量,有可能能成為新產品轉化成社交流量。

  一位看好雲歌的投資人表示:“更看好雲歌,因為快播的成績證明了王欣對於產品體驗、人性以及細分人群的把控和掌握能力。”

  或許是過分了解人性,馬桶 MT 的 IOS 下載鏈接已被關停,一直以來,觸碰監管紅線都使得社交產品如坐針氈。

  耿樂表示,“社交產品是最難監管的互聯網領域之一,在大的監控不斷收緊和趨嚴的形勢下,需要提前做好技術儲備和預警,這對初創企業來說是一個挑戰,所以,在產品功能設計和內容監管上,要做好做到位。”

  – 同題問答 –

  1. 社交產品還會出現新巨頭嗎?

  創勢資本董事長、創始合伙人湯旭東:社交產品一定存在機會,不一定是巨頭。社交產品一定是與時俱進的,每十年會一個迭代。不過做一個大的社交平台比較難,被收購的可能性大一些。

  梧桐樹資本創始合伙人童瑋亮:社交產品永遠有新的機會。用戶總是有喜新厭舊的習慣,但新公司成為巨頭的概率相對其他創業可能更低。當下大的所謂紅利趨勢也許是 5G,但也還沒商用,如果只是玩法有小的差異化而不是藉助行業大的變革,社交產品突破成為巨頭壓力會比較大,當然總是需要有優秀的團隊去嘗試的。

  某文娛投資經理:社交產品都是在不斷迭代的,或出現新的產品去挑戰原有社交產品格局。未來能夠打破格局的新巨頭必然是一個在技術上有進一步突破,快速圈流量的產品。但是目前微信的地位在社交產品中依然是不動如山。

  名川資本創始合伙人王求樂:社交產品肯定有機會,會持續不斷地發展變化。一方面,喜新厭舊是人性;另一方面,壟斷局面不可能持續,這是經濟規律。尤其在意見領袖階層,樂見壟斷被打破,一旦出現有競爭力的新社交產品,會有莫名的力量共同推波助瀾。

  黑洞投資合伙人楊蓉:目前微信一家獨大,其他社交產品想要撼動微信確實很難,但也不是完全沒有機會。這個市場還是有空白的,空白就在於一些相對小眾、垂直的領域,以及一些新的玩法,這些領域還是有誕生小巨頭的可能。

  眾為資本北京投資負責人、副總裁周博男:社交產品還會出現新的巨頭。個人認為在一個周期內社交會出現一個巨頭而不是多個。

  某匿名社交 APP 中期投資人:還會出現新的巨頭。

  創世夥伴資本董事聶東辰:目前的社交產品還不能把所有社交需求都囊括在內,隨着用戶結構的變化、社交場景的變化、社交需求內在的變化,一定會出現新的社交產品成為未來的巨頭。

  頂商投資合伙人劉玲:需求是變量,變化中孕育着新巨頭誕生機會。

  青銳創投 VP 楊瑜:隨着 95、00 後人群長大且逐漸成為互聯網主力軍,5-10 年內是可能出現新的巨頭。

  雲九資本董事沈文傑:社交應用的突破性機會在於滿足特定場景特定人群的特定需求,從這類人深度切入,並形成該平台的生態與調性。用小範圍可以跑通的模式去覆蓋更多的人群。而微信已經越來越接近一個底層工具的作用,我們可以在此之上嘗試更多的社交玩法。

  華蓋資本某 VP:社交產品有機會,經濟越是不好,文明越是燦爛。

  2. 如何看待雲歌、今日頭條、快如科技同一天發布社交產品?更看好哪一家?為什麼?

  某文娛投資經理:三家在同一天發布產品,個人認為是這幾家在聲勢上的一種合縱連橫。其中,字節跳動依託於今日頭條的媒體矩陣,具有技術和流量的天然優勢,所以更看好今日頭條。

  名川資本創始合伙人王求樂:三者同一天發布社交產品,是否巧合,我不清楚。 但這是市場上的呼聲,有人應該聽得到,聽得懂。只是,不大理解,有的人產品都還沒有上線,卻搞這麼大動靜,有些奇怪。 我個人當然對他們還是有點期待的,VC 最喜歡市場出現變化,變化才意味着所有人的新機會。但我覺得,長期而言,真正成功的社交新產品,應該有別於目前的套路。複製騰訊沒有意義,小修小改也沒價值。也許,未來的產品會垂直,更多元,適合更細分的人群。

  某知名天使投資人:都不看好,個人認為他們是目前中國最沒有底線的幾個創業者。挑戰微信並非簡單的投機取巧可以做到,個人在投資過程中還是更看重創始人的大局觀。

  某 VC 機構投資總監:三家對比來看,字節跳動是實力最強的,雲歌人工智能是產品邏輯最強的,快如科技是目標用戶最明確的。很多人說社交軟件門檻低,其實不然。既然社交軟件用戶流量巨大、每一個互聯網企業都想進入,它的准入門檻就已超出了技術本身。

  某知名社交 APP 早期投資人:都不看好。2015 年,我重點關注過垂直聊天社交還有婚戀社交軟件,後來這些平台都死了。一位創業者和我說的話我至今記得:所有社交最後都給微信做了嫁衣。

  黑洞投資合伙人楊蓉:這應該是巧合。因為都還沒有正式發布,我們還看不到他們的產品思路,所以目前很難說更看好誰,應該說三家都有各自的優勢。不過我個人更看好今日頭條,張一鳴對年輕人的洞察比較深,之前除了新聞之外,還做了抖音這樣的成功案例,而抖音其實也是一款社交產品,而且他們也有雄厚的技術支撐和實力。

  眾為資本北京投資負責人、副總裁周博男:三者同一天發布社交產品有營銷的成分在裏面。更看好頭條,頭條之前的多款產品,比如抖音,本身就具備社交屬性,頭條之前在社交領域也做了多次探索,這次產品的發布也必是做足了準備,抱着跟騰訊一站的決心。

  某匿名社交 APP 中期投資人:三款社交產品同一天發布本身就是一個熱點,能夠增加三款社交產品的關注度,造成一種”天下苦微信久矣“的微妙情景,讓更多人有好奇心接觸新的社交產品。更看好雲歌,因為快播的成績證明了王欣對於產品體驗、人性以及細分人群的把控和掌握能力。

  創世夥伴資本董事聶東辰:更看好誰更能打中用戶的社交需求痛點,社交產品不是建造空中樓閣,而是基於社交剛需,滿足人性的需求,才能成為更好的社交產品。

  頂商投資合伙人劉玲:同一天發布是好事情。頭條系打出的是人工智能社交新玩法;王欣的雲歌是“匿名+區塊鏈”,以弱關係為切入點為主展開社交;快如科技“微信群+拼多多+趣頭條”,羅老師是營銷高手。個人比較欣賞技術宅的雲歌。

  青銳創投 VP 楊瑜:同一天發布社交產品更多的是希望第一時間搶佔熱點和關注,因為社交產品第一波的關注能決定首次下載試用用戶有多少,這種敏感性導致這幾家都不願意落後。在產品形態上目前看不出來特別創新的地方,看好誰只能後續去看,但如果從流量來看今日頭條毋庸置疑是擁有更低價且更巨大的流量,有可能能為新產品轉化成社交流量。

  雲九資本董事沈文傑:羅永浩的產品更偏向於用堆砌需求來搭建場景拉高頻次,但是以商務人群高效溝通切入的初衷就顯得模糊和搖擺;王欣的切入點是陌生人社交,非常講究玩法,此前陌生人已經有過很多的嘗試,增長的天花板很難說,用戶留存和內容管控會非常有挑戰。而且簡單的做關係鏈遷移,意義不大。遷移關係鏈並不能算做一個新的產品而是複製品,溝通效率不會比原來的產品更高,且會在未明確新產品屬性的情況下帶入原關係鏈的屬性,產品失去了靈魂,因此必須要構建原生的關係鏈。

  相反頭條具備大量流量矩陣的產品,並且具備非常強的算法基礎,算法在內容分发上具備非常強的作用,但是此前頭條一直在做社交媒體,而非即時通訊。如果將算法用在即時通訊上,目前還沒有人嘗試過,也就無從得知結果。不過相比另外兩家公司,頭條無論是在體量、流量和資本以及戰略高度上,去做社交都不是另外兩家可以比擬的。所以相比之下,會更關注頭條。

  Blued 耿樂:張一鳴的產品應該有機會衝出來,一是他們的強大運營能力和資本能力;二是短視頻方面的資源和布局;三是從玩法上看,他們更像社交,而不是偽社交。

  3. 如果投資,更看好什麼樣的社交模式?

  創勢資本董事長、創始合伙人湯旭東:垂直細分的更有機會。

  黑洞投資合伙人楊蓉:我們相對更看好在某一垂直領域,能和微信形成差異化競爭,並且贏得年輕用戶喜愛的產品。

  眾為資本北京投資負責人、副總裁周博男:我們投資看好基於場景做社交,基於通用型場景的社交,而不是遊戲等垂直場景。縱觀社交產品的發展,過往能跑出來的也就 QQ,微信,陌陌,都是基於基礎設施的迭代。

  某匿名社交 APP 中期投資人:更看好陌生人社交領域及細分人群社交領域。

  創世夥伴資本董事聶東辰:看好兩方面。一方面是針對新群體的社交產品,滿足這類群體的社交調性,產生很強群體活躍和粘性的產品;另一方面是新的交互形式和玩法,用戶在未來更加孤獨焦慮,如果能有社交產品很好地通過功能或者場景滿足用戶,也是值得投資的。

  青銳創投 VP 楊瑜:首先我更看好陌生人社交,我覺得熟人社交如果是個沒有巨頭支撐的新公司做是太難了,而我也更看好能有一個巧妙切入點,能快速破冰且有可能沉澱關係鏈的社交產品,比如狼人殺,概率論等。

  熊貓資本合伙人毛聖博:熟人社交的核心需求是通訊,陌生人社交的核心需求是荷爾蒙。匿名社交是把人性中的好奇、攻擊性、荷爾蒙都從自我控制中徹底釋放出來了。匿名社交不分熟人和陌生人,熟人匿名只是進一步放大了好奇心,也提升了傳播效率。而熟人社交是針對全人群的,溝通需求是人類非常底層的需求,所以熟人社交毋庸置疑是市場最大的。溝通講究的是效率和形式。社交的網絡效應也很大,所以短期內想要打破這個網絡是很難的。除非有款產品徹底顛覆了目前人們溝通交互的形式。

  社交是人的天性,沒有所謂的痛點或者不痛點。好的社交產品是類似在產品中建立一個“小社會”,小社會裡面有自己的規則、氛圍,有匹配這些規則和氛圍的人群進來。但是,未必會做的很大。

  陌生人社交最大的群體是年輕人。這些荷爾蒙最最旺盛的年輕人,主要也就是 16~26 歲,所以我覺得陌生人社交也很難做得很大,做得好也可以做成一家幾十億美金的公司。因為匿名社交的需求是有的,但是不夠持續。比較隨機,不是天天都很強烈的需求。當然,我們很難說匿名社交產品做的好不好,但做不大的產品對風險投資來說比較難選擇。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置台北網頁設計,各種案例分享

多閃喊話微信:你的時代跟我的時代不同

  原標題:你的時代跟我的時代不同?

  文/潘亂

  來源: 亂翻書(ID:luanbooks)

  《南海十三郎》里,想來拜師的唐滌生模仿十三郎的詞句,被十三郎呵斥,說寫得像他沒有屁用,因為“學我者生,像我者死。”

  十三郎對這位弟子&知己的解釋是,“我的時代跟你的時代不同。看我戲的人,十個有九個是文盲,唱詞深一點也聽不明白。目光放長遠一點,觀眾的水準越來越高。”

  多閃這位 93 年的產品經理喊 69 年的張小龍為叔叔,大致意思也是在說,“我的時代跟你的時代不同。”

  你的時代跟我的時代不同?

  多閃定位是幫你增進親密關係的視頻社交產品,其實主要是抄 Snap,所以本文就主要談我對 Snap 的研究和理解。

  開頭先為朋友公司插條硬廣,他們幫忙做了多閃的亮點表情包部分。表情包是年輕一代的語言,多閃實時聊天中的表情匹配,為用戶提供了非常實時和豐富的表達內容,而為多閃提供表情搜索技術的是閃萌,這是一家非常低調但已經服務了全網 90% 以上社交產品的技術公司。

  本文試圖從產品調性,價值主張和瓶頸局限幾個方面展開,有點散和混亂。

  產品調性

  Facebook 之後起來的產品,都是主打潮和酷,Instagram,Snapchat,Musical.ly。

  潮和酷是為了盡可能的攔截低齡段的用戶。

  哪怕是 Facebook 當年對抗 MySpace,主要策略也是在常春藤學校的年輕人群體裏面建立起一種更潮更高級的品牌調性:這是哈佛學生撩妹的地方。後來 Snap 也用同樣的套路嘲諷 Facebook,家長們才喜歡整天呆在 Facebook。

  這是青少年認知的產品調性。潮流附帶着高級的有傳播能力的內容,酷是認同,年輕人喜歡那些最簡單膚淺的快樂。

  微信的問題是,不酷了。

  微信的商務風萬年不變,加上父母都在讓年輕人不能很好放飛自我。微信關係鏈泛化導致朋友圈表達空間受限,不能分組的視頻動態也是屬於有門檻的公開表達,所以微信是很難做起來 story 的。

  原本小孩有父母壓力的解決方案可以是不用微信用 QQ,QQ 本來也是最好的解決方案,但現在 QQ 上垃圾信息實在是太多太讓人倒胃口了。

  且 QQ 空間是為圖文設計的,視頻支持得太差。

  但多閃這種面向青少年的產品上來選擇無差別推廣還是讓人看不懂的。

  本質上產品還是得去服務一個具象人群,只有根子穩了,才有可能往外去散。他應該有一個基石,然後你的東西是應該從基石長出來的。悟空問答和微頭條一開始的發源就是頭條,然後頭條的用戶畫像始終模糊,所以悟空和微頭條也就非常模糊非常混亂,導致產品的核心群體始終形成不了,投入很多人也花了大力氣,效果一直難以讓人滿意。

  問題始終在反覆重演。

  社區產品與信息流產品不同,因為產品需要找到辦法凝聚成一個核。就是那些好的社區,它一開始都是有一個核非常 hardcore 的一個核心人群。hardcore 核心人群能夠逐漸形成他自己的社區文化,然後外延逐漸去外延這個文化和外延人群。知乎、B站、小紅書、最右,每個都是這樣子的。

  這不是你坐擁幾個億的流量就能搞定的,且你的流量跟騰訊比肯定是不夠大,QQ 以同樣無差別導量的方式做興趣部落結果把團隊都給做絕望了。

  即,多閃打的產品需求是對的,但是做法完全無法認同。

  做 IM 冷啟動難度大,缺少直接解決新增和關係鏈的方案,但這種產品開發布會實在是沒什麼必要。且產品設計也不酷。Snap 還有大量卡通和波普文化元素,到多閃就只有濾鏡和表情包了。表情包是日常,日常不叫酷。

  至於世界部分的好友發現,看似高效,實則問題多多。類似設計騰訊多年前搞過一般,叫 QQ 圈子,感興趣的同學可以去搜一下他的歷史。

  這裏引一位前騰訊產品經理的評論:

  “我覺得不管是什麼皮,只要是通訊的核,就必須極其慎重的對待通知的管理。需要精準的踩在,多一點就騷擾,少一點就雞肋的狀態上。這和年不年輕,親不親密沒有關係。多餘的通知對於通訊產品在用戶心智佔領上的傷害是致命的,相當於狼來了的故事。”

  無壓力社交

  跟一位證明過自己的創業者聊,他認為 Snap 的核心是無壓力社交。

  優勢在於:

  · 表達門檻低,你不需要有什麼嚴肅的理由就可以溝通

  · 消費壓力小。你不需要對每次溝通作出回應,甚至都不需要付出思考。Snap 的 Story 的消費方式是:點一下幹掉一條

  · 可以不用對過去做個的任何錶達負有責任

  所以,Snap 的瓶頸在於:

  · 只適合於“沒有目的”的年輕人

  · 只適合於“沒有壓力”的密友之間

  Snap 這種無壓力社交的需求和場景都是有的,在微信群里已經被充分驗證了,密友+高頻交流。當圈子限於密友和高頻聯繫人時,表達門檻是最低的。

  微信過去新推的朋友圈權限設置,朋友圈三天可見,清理不常聯繫的好友,種種舉措都在試圖減輕用戶的社交壓力。

  社交產品有自己的生命周期。16、17 年,Instagram 跟微信差不多在同時期都遇到了要降低用戶表達門檻的問題。

  ins 最初的濾鏡是為審美服務的,太過於形式化,你的照片拍的必須非常精緻、有逼格,屬於炫耀型的產品,這會給人有表達門檻。

  “Instagram 已經成為一個人們必須有什麼值得曬才能發照片的地方,我們並不想這樣”。

  從 16 年夏天 ins 的發版記錄,可以看出他們是如何來解決這個問題的:

  · 拋出產品障礙:取消正方形圖片的默認形式。

  · Instagram Stories:用戶可以和他們的關注者分享編輯過的照片和視頻,這些內容會在 24 小時后消失。

  · feed 排序算法:一個新的計算機模型會根據你會喜歡的事物而非最新發布的內容展示在 Instagram 界面上。

  · 現場視頻播放:可以直接把現場錄的視頻分享給你的關注者,他們在觀看時可以進行評論。視頻會在播放結束時消失。

  · 轉瞬即逝的信息:發信息有了一個新功能,你所發的照片和視頻可以在它們被打開后自動被抹除。

  “是用戶和他們的朋友、他們的家人之間的聯繫讓 Instagram 得以生存。所有的數據表明,如果用戶關注越多的朋友,越與朋友們進行互動,他們的活動越頻繁;如果用戶只是關注一些明星和那些他們感興趣的內容,就不會有什麼分享。”

  Instagram 在 2016 年鼓勵用戶在傳統的 feed 之外與朋友進行分享,Facebook 在 2018 年初跟進調整 newsfeed 的算法,將朋友和家人放在體驗的核心。

  小札當時公開信的解釋是:

  “我們正在對構建 Facebook 的方式做出重大調整。我正在改變我給我們的產品團隊的目標,讓他們專註於幫助你找到相關的內容,幫助你有更多有意義的社交互動。從去年開始,我們就在朝着這個方向做出改變,但這種新變化需要幾個月的時間才能部署到我們所有的產品中。你將看到的第一個變化將出現在 News Feed 中,你會看到更多來自朋友、家人的信息。在我們推出這個項目后,你會看到更少的公共內容,比如來自企業、品牌和媒體的帖子。你所看到的公共內容將會以同樣的標準來衡量——它應該鼓勵人們之間進行有意義的互動。”

  抖音應該是面臨了類似的發表壓力問題,所以想把這部分的解題方案扔給多閃。

  多閃打的產品需求是對的,但起手式的打法是看不懂的。

  小龍沒因為老而不會做產品,Spiegel 也沒因為年輕而更有競爭力。

  你的時代,跟我的時代,真的不同嘛?

  Instagram 那段是我從兩年前筆記裏面翻出來的,忘記是跟誰聊的還是從哪篇文章里看到的,我搜了一下沒發現原文。如有重合,見諒。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置台北網頁設計,各種案例分享

任正非:絕不會做有損任何國家的事,繼續減少自己在華為持股

  “沒有法律要求任何中國公司強制安裝後門。”1 月 14 日,任正非在深圳的一個媒體座談會上向 6 位外媒記者表示,“我個人絕對不會損害自己和客戶的利益,我的公司也不會答應這樣的要求。”他補充稱,華為“從未發生嚴重的安全事件”。

  華為創始人兼 CEO 任正非。東方 IC 圖

  據華爾街日報 1 月 15 日報道,隨着華為這家電信巨頭在多個方面面臨挑戰,任正非打破了多年來的沉默,為華為公開置評。

  在此次同 6 位外媒記者進行的兩個多小時的座談會中,任正非表示非常想念女兒孟晚舟。他讚揚了美國總統特朗普,並稱希望向美國傳達合作和共享成功。另外,雖然華為遭遇了美國及其盟國的嚴格審查,但在其他國家依舊繼續發展,並已在全球拿到了 30 份 5G 合同。

  在美國及其盟國的情報官員對華為參與建立全球 5G 網絡的規模表示擔憂之後,華為正處在危機之中。

  目前,華為已經在世界的幾個市場被封鎖。美國、澳大利亞和新西蘭已經禁止華為用於本國的下一代移動互聯網。

  任正非此前只進行過兩次公開採訪,每次都是為了消除外界對華為的擔憂。他最近一次出現是 3 年前的達沃斯論壇,當時他再度試圖將業務與政治分開,告訴記者稱:“商業的原則是顧客至上。”

  “我依然愛我的祖國,我擁護中國共產黨,我絕不會做任何有損世界上任何國家的事情。”任正非對外媒記者說。

  他還補充道,華為在保護隱私方面參照了蘋果的模型,並表示自己計劃減少公司的股份。“我擁有華為 1.14% 的股份,史蒂夫·喬布斯擁有蘋果 0.8% 的股份,這表明我的股權被進一步稀釋是合理的,我將向史蒂夫·喬布斯學習。”

  任正非還讚揚了美國現任總統唐納德·特朗普。

  “從特朗普總統個人來講,我仍然相信他是一位偉大的總統。”任正非說,“從某種意義上說,他大膽地削減稅收,我認為這有利於美國的工業發展。”

  任正非說:“我想傳達給美國的信息是:合作和共享成功。在我們高科技的世界中,一個單一的公司或國家越來越不可能維持和支撐整個世界的需求。”

  他補充道:“現在再也不是工業時代了,在工業時代,一個國家可以製造一整台紡織機、一整輛火車、一整艘船。現在是信息時代,我們的相互依賴性非常高。”

  任正非也承認,5G 相關的問題有可能為華為今年的出口業務造成“困難”但他還表示,“一直都是如此,你不能和每個人都合作,我們將把我們的重點轉移到更好地服務歡迎華為的國家。”他補充稱,全球而言,華為已經拿到了 30 份 5G 建設合同。

  英國金融時報報道評論稱,雖然華為受到美國及其盟友的嚴格審查,但它仍繼續在南歐、東歐及整個發展中世界簽署 5G 實驗協議。並且,華為是世界上研發預算最高的公司之一,它承諾每年花費超過 200 億美元用於研發,以領先競爭對手。

  據美國媒體 CNBC1 月 15 日報道,任正非說,2019 對華為來說是困難的一年,公司的收入增長會低於 20%。他還表示,公司 2019 年全年的目標收入是 1250 億美元。

  “2019 年,我們在國際市場會面臨挑戰與困難,我這麼說的原因是,我們接下來一年的增長率會低於 20%。”任正非說。

  華為目前還沒有發布 2018 年的總收入,但華為的一位輪值主席徐直軍(Eric Xu)在 2018 年 11 月對 CNBC 表示,華為 2018 年的銷售額超過 1000 億美元。

  英國金融時報報道還提到,對於誰會繼任他成為華為的接班人,任正非沒有透露。“我不知道究竟誰會成為我的接班人,接班人會自然出現……”他還表示了對接班人並不着急的態度,“至於我的退休,這取決於人們什麼時候能發明出長生不老葯。我在等待這種長生不老葯的出現。”

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置台北網頁設計,各種案例分享