<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="/rss.xsl" type="text/xsl"?>
<rss version="2.0">
  <channel>
    <title>IT瘾分析推荐</title>
    <link>https://itindex.net/categories/分析</link>
    <description>IT社区推荐资讯 - ITIndex.net</description>
    <language>zh</language>
    <copyright>https://itindex.net/</copyright>
    <generator>https://itindex.net/</generator>
    <docs>http://backend.userland.com/rss</docs>
    <image>
      <url>https://itindex.net/images/logo.gif</url>
      <title>IT社区推荐资讯 - ITIndex.net</title>
      <link>https://itindex.net/categories/分析</link>
    </image>
    <item>
      <title>Python地理数据分析工具MovingPandas</title>
      <link>https://itindex.net/detail/62944-python-%E5%9C%B0%E7%90%86-%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90</link>
      <description>&lt;p&gt;MovingPandas 是一个用于分析轨迹数据的 Python 库。它在处理和分析移动对象的时空数据方面非常强大，适用于地理信息系统（GIS）、时空数据分析和可视化等领域。它是在热门的地理数据处理库 GeoPandas 的基础上构建的，GeoPandas 本身是建立在Pandas数据处理库之上的。MovingPandas 旨在提供高效、易于使用的工具，以便分析和处理包含位置信息的时间序列数据。MovingPandas使得研究移动模式、路径分析、时空聚类等任务变得更加高效和直观。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="719" src="https://www.biaodianfu.com/wp-content/uploads/2024/10/MovingPandas.png" width="977"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;核心功能：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;轨迹数据表示。MovingPandas 使用 GeoPandas GeoDataFrames 来表示轨迹数据。每条轨迹由一系列带有时间戳的点组成，形成一条时空路径。&lt;/li&gt;
  &lt;li&gt;轨迹分割。可以根据时间间隔、距离阈值等条件将轨迹分割成多个子轨迹。这对于处理长轨迹或者在某些关键事件发生前后进行分析非常有用。&lt;/li&gt;
  &lt;li&gt;轨迹特征提取。提供了多种方法来计算轨迹的特征，比如速度、加速度、方向变化等。这些特征在进行模式识别和行为分析时非常有用。&lt;/li&gt;
  &lt;li&gt;轨迹聚类。支持基于轨迹的聚类分析，可以识别出类似移动模式的轨迹群体。常用的聚类方法包括基于密度的聚类（DBSCAN）、分层聚类等。&lt;/li&gt;
  &lt;li&gt;轨迹可视化。通过与 Matplotlib 和 Folium 等库的集成，MovingPandas 能够提供强大的轨迹数据可视化功能，包括静态和交互式地图。&lt;/li&gt;
  &lt;li&gt;时空聚合。支持时空聚合分析，比如计算某个区域在特定时间段内的平均速度、轨迹数量等。&lt;/li&gt;
&lt;/ul&gt;
 &lt;h2&gt;MovingPandas的使用&lt;/h2&gt;
 &lt;h3&gt;MovingPandas的安装&lt;/h3&gt;
 &lt;p&gt;MovingPandas作者推荐在Python 3.7及以上环境下安装。请确保你的Python版本符合这一要求。如果你已经安装了Anaconda，可以使用conda命令来安装MovingPandas及其依赖包。&lt;/p&gt;
 &lt;pre&gt;conda install -c conda-forge movingpandas&lt;/pre&gt;
 &lt;p&gt;MovingPandas同样可以使用pip进行安装，但是不推荐，主要原因是其依赖环境较为复杂，使用pip安装可能会出现依赖项缺失或版本冲突的问题。因此，推荐使用conda进行安装。&lt;/p&gt;
 &lt;h3&gt;MovingPandas接口详解&lt;/h3&gt;
 &lt;h4&gt;MovingPandas.Trajectory对象&lt;/h4&gt;
 &lt;p&gt;在 MovingPandas 中，Trajectory 类是核心组件之一，主要用于表示和处理单个轨迹。Trajectory 对象是一个时间序列的集合，其中每个数据点代表轨迹上的一个位置，包含了位置信息（经纬度或其他地理空间参考）、时间戳和其他可能的属性（如速度、方向等）。因此，一个 Trajectory 对象是连续移动的点组成的线，这些点按照时间顺序排列。&lt;/p&gt;
 &lt;p&gt;Trajectory 对象的主要特性：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;时间索引：Trajectory 对象的索引通常是时间戳，这使得基于时间的查询和操作变得直观和高效。&lt;/li&gt;
  &lt;li&gt;空间位置：每个时间点对应一个空间位置，这通常是通过经纬度坐标表示的。&lt;/li&gt;
  &lt;li&gt;其他属性：除了时间和位置，还可以包含其他相关的数据列，如速度、加速度、方向等，这些信息对于分析移动行为至关重要。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;创建 Trajectory 对象通常涉及几个步骤，首先你可能需要有一个包含时空数据的pandas DataFrame。这个DataFrame应该至少包含三列：表示时间戳的列（通常会被设置为索引）、表示X坐标的列（如经度）、表示Y坐标的列（如纬度）。然后，你可以使用 MovingPandas 提供的函数或方法（如TrajectoryCollection.from_geodataframe()）来创建一个或多个 Trajectory 对象。&lt;/p&gt;
 &lt;p&gt;class movingpandas.Trajectory(df, traj_id, traj_id_col=None, obj_id=None, t=None, x=None, y=None, crs=’epsg:4326′, parent=None)&lt;/p&gt;
 &lt;p&gt;参数说明：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;df：具有GeoPandas的geometry坐标列和时间戳索引的GeoDataFrame，或Pandas的DataFrame。必填参数。&lt;/li&gt;
  &lt;li&gt;traj_id：任意类型，表示轨迹的唯一标识符。必填参数。&lt;/li&gt;
  &lt;li&gt;obj_id：任意类型，表示移动物体的唯一标识符。默认为 None。&lt;/li&gt;
  &lt;li&gt;t：表示包含时间戳的列名，默认为 None。&lt;/li&gt;
  &lt;li&gt;x：表示包含x坐标的列名，使用Pandas的DataFrame需指定。默认为 None。&lt;/li&gt;
  &lt;li&gt;y：表示包含y坐标的列名，使用Pandas的DataFrame需指定。默认为 None。&lt;/li&gt;
  &lt;li&gt;crs：表示 x/y 坐标的坐标参考系统。默认为 “epsg:4326″，即 WGS84。&lt;/li&gt;
  &lt;li&gt;parent：一个Trajectory 对象，表示父轨迹。默认为 None。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;strong&gt;基本信息与操作&lt;/strong&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;copy(): 返回轨迹对象的一个副本。&lt;/li&gt;
  &lt;li&gt;drop(**kwargs) 方法用于从数据集中删除满足特定条件的行或列。&lt;/li&gt;
  &lt;li&gt;plot(self, *args, **kwargs): 绘制轨迹。&lt;/li&gt;
  &lt;li&gt;explore(*args, **kwargs) 方法用于以交互方式可视化和分析数据，支持多种参数和选项以定制显示。&lt;/li&gt;
  &lt;li&gt;is_latlon() 方法用于判断轨迹数据是否采用经纬度坐标系。&lt;/li&gt;
  &lt;li&gt;is_valid() 方法用于检查轨迹数据是否有效，例如是否包含必要的字段和合理的坐标。&lt;/li&gt;
  &lt;li&gt;size() 方法用于返回轨迹中包含的定位点数量。&lt;/li&gt;
  &lt;li&gt;get_crs() 方法用于获取当前地理数据集的坐标参考系统（CRS），返回一个描述该坐标系的对象或信息。&lt;/li&gt;
  &lt;li&gt;to_crs(self, crs): 转换轨迹的坐标参考系统。&lt;/li&gt;
  &lt;li&gt;get_column_names() 方法用于获取数据集中的所有列名，返回一个包含列名的列表。这个方法通常用于快速查看数据集的结构或在进行数据处理时动态获取列名。&lt;/li&gt;
  &lt;li&gt;get_direction_col() 方法用于获取表示方向数据的列，这些数据通常以角度或方位形式存储。&lt;/li&gt;
  &lt;li&gt;get_distance_col() 方法用于获取表示距离数据的列，这些数据通常用于计算或分析两点之间的距离。&lt;/li&gt;
  &lt;li&gt;get_speed_col() 方法用于获取表示对象速度的列名。&lt;/li&gt;
  &lt;li&gt;get_timedelta_col() 方法用于获取表示时间增量的列名。&lt;/li&gt;
  &lt;li&gt;get_traj_id_col() 方法用于获取表示轨迹标识符的列名。&lt;/li&gt;
  &lt;li&gt;get_geom_col() 方法用于获取表示几何数据的列，该列通常包含地理空间信息，如点、线或多边形。&lt;/li&gt;
  &lt;li&gt;get_angular_difference_col() 方法用于获取包含角度差异的列，这些差异通常用于分析方向或角度变化。&lt;/li&gt;
  &lt;li&gt;to_point_gdf(self): 返回包含轨迹点的GeoDataFrame。&lt;/li&gt;
  &lt;li&gt;to_line_gdf(columns=None) 方法用于将轨迹数据转换为 GeoDataFrame 格式的线条几何数据，可以选择包含特定的列。&lt;/li&gt;
  &lt;li&gt;to_linestring() 方法用于将轨迹数据转换为 LineString 对象，表示轨迹的线条几何形状。&lt;/li&gt;
  &lt;li&gt;to_linestringm_wkt() 方法用于将轨迹数据转换为包含 ZM（高程和度量）信息的 WKT（Well-Known Text）格式的 LineStringM 字符串。&lt;/li&gt;
  &lt;li&gt;to_mf_json(datetime_to_str=True, temporal_columns=None) 方法用于将轨迹数据转换为 Moving Features JSON 格式，可以选择将日期时间转换为字符串，并指定时间相关的列。&lt;/li&gt;
  &lt;li&gt;to_point_gdf(return_orig_tz=False) 方法将轨迹数据转换为 GeoDataFrame 格式的点几何数据，可以选择返回原始时区的时间。&lt;/li&gt;
  &lt;li&gt;to_traj_gdf(wkt=False, agg=False) 方法将轨迹数据转换为 GeoDataFrame 格式，可以选择生成 WKT 格式的几何数据或进行聚合处理。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;strong&gt;轨迹分析与聚合统计&lt;/strong&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;get_bbox(self): 返回轨迹的范围 (bounding box)。&lt;/li&gt;
  &lt;li&gt;get_start_location(self): 返回轨迹的起始位置。&lt;/li&gt;
  &lt;li&gt;get_end_location(self): 返回轨迹的结束位置。&lt;/li&gt;
  &lt;li&gt;get_start_time() 方法用于获取时间序列数据或对象轨迹的起始时间。&lt;/li&gt;
  &lt;li&gt;get_end_time() 方法用于获取某个事件或过程的结束时间，通常返回一个时间戳或日期时间对象。&lt;/li&gt;
  &lt;li&gt;get_max(column) 方法用于获取指定列 column 中的最大值。&lt;/li&gt;
  &lt;li&gt;get_min(column) 方法用于获取指定列 column 中的最小值。&lt;/li&gt;
  &lt;li&gt;get_position_at(t, method=’interpolated’) 方法用于获取在时间点 t 处的对象位置，默认使用插值方法来计算位置。&lt;/li&gt;
  &lt;li&gt;get_row_at(t, method=’nearest’) 方法用于获取在时间点 t 附近的对象所在的行，默认使用最近邻方法来选择行。&lt;/li&gt;
  &lt;li&gt;get_length(units=(None, None, None, None)) 方法用于计算并获取几何对象的长度，可以接受多个单位参数来指定长度的测量单位。&lt;/li&gt;
  &lt;li&gt;get_mcp() 方法用于获取某个对象的最小凸包 (Minimum Convex Polygon, MCP)，通常用于地理空间分析中确定一组点的最小包围区域。&lt;/li&gt;
  &lt;li&gt;add_direction(self, overwrite=False): 计算并添加方向信息到轨迹数据中。&lt;/li&gt;
  &lt;li&gt;get_direction() 方法用于计算和获取两个地理点之间的方向或方位角，通常以度数表示。&lt;/li&gt;
  &lt;li&gt;get_duration(self): 返回轨迹的总时长。&lt;/li&gt;
  &lt;li&gt;add_distance(overwrite=False, name=’distance’, units=None)：计算并添加轨迹数据中相邻点之间的距离信息。&lt;/li&gt;
  &lt;li&gt;add_acceleration(self, overwrite=False, name=’acceleration’): 计算并添加加速度信息到轨迹数据中。&lt;/li&gt;
  &lt;li&gt;add_speed(self, overwrite=False): 计算并添加速度信息到轨迹数据中。&lt;/li&gt;
  &lt;li&gt;add_angular_difference(overwrite=False, name=’angular_difference’)：计算并添加轨迹中相邻点之间的角度差异信息到轨迹数据中。&lt;/li&gt;
  &lt;li&gt;add_timedelta(overwrite=False, name=’timedelta’) ：计算并添加轨迹数据中相邻点之间的时间差信息。&lt;/li&gt;
  &lt;li&gt;add_traj_id(overwrite=False) 方法用于为轨迹数据添加或覆盖轨迹ID列，以标识相同轨迹中的所有点。&lt;/li&gt;
  &lt;li&gt;get_segment_between(t1, t2) 方法用于获取在时间点 t1 和 t2 之间的对象轨迹或数据段。&lt;/li&gt;
  &lt;li&gt;get_linestring_between(t1, t2, method=’interpolated’) 方法用于生成并获取在时间点 t1 和 t2 之间的一条线串，默认使用插值方法。&lt;/li&gt;
  &lt;li&gt;get_sampling_interval() 方法用于获取时间序列数据中的采样时间间隔。&lt;/li&gt;
  &lt;li&gt;hausdorff_distance(other, units=(None, None, None, None)) 方法用于计算当前轨迹与另一个轨迹之间的Hausdorff距离，并允许指定单位。&lt;/li&gt;
  &lt;li&gt;hvplot(*args, **kwargs) 方法用于使用hvPlot库创建高度可定制的图形和可视化。&lt;/li&gt;
  &lt;li&gt;hvplot_pts(*args, **kwargs) 方法用于使用hvPlot库对地理点数据进行可视化并创建交互式图形。&lt;/li&gt;
  &lt;li&gt;interpolate_position_at(t) 方法用于在给定时间 t 处插值并返回轨迹的位置。&lt;/li&gt;
  &lt;li&gt;intersection(feature, point_based=False) 方法用于计算轨迹与给定地理特征的交集，并可以选择基于点的方式进行计算。&lt;/li&gt;
  &lt;li&gt;intersects(polygon) 方法用于判断轨迹是否与指定的多边形区域相交。&lt;/li&gt;
  &lt;li&gt;clip(self, polygon): 按多边形裁剪轨迹。&lt;/li&gt;
  &lt;li&gt;apply_offset_minutes(column, offset) 方法用于将指定列的时间值按给定的分钟数进行偏移调整。&lt;/li&gt;
  &lt;li&gt;apply_offset_seconds(column, offset) 方法用于将指定列的时间值按给定的秒数进行偏移调整。&lt;/li&gt;
&lt;/ul&gt;
 &lt;h4&gt;MovingPandas.TrajectoryCollection对象&lt;/h4&gt;
 &lt;p&gt;TrajectoryCollection 类是 MovingPandas 中用于表示多条轨迹的集合。它允许用户以集合的形式操作多条轨迹，支持对这些轨迹的批量处理和分析。&lt;/p&gt;
 &lt;p&gt;可以通过传递一系列 Trajectory 对象来创建一个 TrajectoryCollection。每个 Trajectory 对象代表一条轨迹，包含了时间和位置的信息。&lt;/p&gt;
 &lt;p&gt;class movingpandas.TrajectoryCollection(data, traj_id_col=None, obj_id_col=None, t=None, x=None, y=None, crs=’epsg:4326′, min_length=0, min_duration=None)&lt;/p&gt;
 &lt;p&gt;参数说明：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;data (list[Trajectory] 或 GeoDataFrame 或 DataFrame) – 包含 Trajectory 对象的列表，或一个包含轨迹 ID、点几何列和时间戳索引的 GeoDataFrame。&lt;/li&gt;
  &lt;li&gt;traj_id_col (string) – 包含轨迹 ID 的 GeoDataFrame 列名。&lt;/li&gt;
  &lt;li&gt;obj_id_col (string) – 包含移动对象 ID 的 GeoDataFrame 列名。&lt;/li&gt;
  &lt;li&gt;t (string) – 包含时间戳的 DataFrame 列名。&lt;/li&gt;
  &lt;li&gt;x (string) – 包含 x 坐标的 DataFrame 列名。&lt;/li&gt;
  &lt;li&gt;y (string) – 包含 y 坐标的 DataFrame 列名。&lt;/li&gt;
  &lt;li&gt;crs (string) – x/y 坐标的坐标参考系 (CRS)。&lt;/li&gt;
  &lt;li&gt;min_length (numeric) – 期望的轨迹最小长度。长度使用 CRS 单位计算，若 CRS 是地理坐标系（例如 EPSG:4326 WGS84），则长度以米为单位计算。（较短的轨迹将被丢弃。）&lt;/li&gt;
  &lt;li&gt;min_duration (timedelta) – 期望的轨迹最短持续时间。（较短的轨迹将被丢弃。）&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;相比MovingPandas.Trajectory多了一些方法：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;filter(predicate): 根据给定条件过滤轨迹集合。&lt;/li&gt;
&lt;/ul&gt;
 &lt;h4&gt;MovingPandas.TrajectoryCollectionAggregator对象&lt;/h4&gt;
 &lt;p&gt;MovingPandas.TrajectoryCollectionAggregator 是 MovingPandas 库中的一个类，主要用于对轨迹集合进行聚合操作。通过对轨迹数据进行空间和时间上的聚合，可以帮助用户有效地分析和总结移动模式。&lt;/p&gt;
 &lt;p&gt;class movingpandas.TrajectoryCollectionAggregator(traj_collection, max_distance, min_distance, min_stop_duration, min_angle=45)&lt;/p&gt;
 &lt;p&gt;参数说明&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;traj_collection (TrajectoryCollection) – 要进行聚合的 TrajectoryCollection 对象。&lt;/li&gt;
  &lt;li&gt;max_distance (float) – 重要点之间的最大距离（距离使用 CRS 单位计算，若 CRS 是地理坐标系，例如 EPSG:4326 WGS84，则距离以米为单位计算）。&lt;/li&gt;
  &lt;li&gt;min_distance (float) – 重要点之间的最小距离。&lt;/li&gt;
  &lt;li&gt;min_stop_duration (datetime.timedelta) – 停止检测所需的最短持续时间。&lt;/li&gt;
  &lt;li&gt;min_angle (float) – 提取重要点的最小角度。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;相关方法：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;get_clusters_gdf() 方法返回一个 GeoDataFrame，其中包含聚合后的轨迹数据的簇（clusters）。&lt;/li&gt;
  &lt;li&gt;get_flows_gdf() 方法返回一个 GeoDataFrame，其中包含聚合后的轨迹数据的流动（flows）信息。&lt;/li&gt;
  &lt;li&gt;get_significant_points_gdf() 方法返回一个 GeoDataFrame，其中包含从轨迹数据中提取的显著点（significant points）。&lt;/li&gt;
&lt;/ul&gt;
 &lt;h4&gt;MovingPandas.TrajectoryCleaner对象&lt;/h4&gt;
 &lt;p&gt;MovingPandas.TrajectoryCleaner 是 MovingPandas 库中的一个类，专门用于清理轨迹数据。清理操作可以帮助去除数据中的噪声、填补缺失值以及进行其他预处理步骤，确保轨迹数据的质量和一致性。&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;IqrCleaner(traj) 是一个类，用于基于四分位数范围 (IQR) 方法来清理轨迹数据中的异常值。&lt;/li&gt;
  &lt;li&gt;OutlierCleaner(traj) 是一个类，用于通过多种方法识别和清理轨迹数据中的离群点（异常值）。&lt;/li&gt;
&lt;/ul&gt;
 &lt;h4&gt;MovingPandas.TrajectoryGeneralizer对象&lt;/h4&gt;
 &lt;p&gt;MovingPandas.TrajectoryGeneralizer 是 MovingPandas 库中的一个类，用于对轨迹数据进行简化和概括。通过轨迹数据的概括，可以减少数据量，提高处理效率，并且在某些应用场景下有助于更清晰地展示轨迹特征。&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;TrajectoryGeneralizer(traj) 是一个类，用于通过多种算法对轨迹数据进行简化和概括，以减少数据量并提高处理效率。&lt;/li&gt;
  &lt;li&gt;DouglasPeuckerGeneralizer(traj) 是一个类，专门使用 Douglas-Peucker 算法对轨迹数据进行简化，保留主要特征点以减少数据量。&lt;/li&gt;
  &lt;li&gt;MinDistanceGeneralizer(traj) 是一个类，用于根据最小距离间隔对轨迹数据进行简化，移除距离变化小于指定阈值的点。&lt;/li&gt;
  &lt;li&gt;MinTimeDeltaGeneralizer(traj) 是一个类，用于根据最小时间间隔对轨迹数据进行简化，移除时间间隔小于指定阈值的点。&lt;/li&gt;
  &lt;li&gt;TopDownTimeRatioGeneralizer(traj) 是一个类，用于通过时间比率算法对轨迹数据进行简化，保留关键时间点以减少数据量。&lt;/li&gt;
&lt;/ul&gt;
 &lt;h4&gt;MovingPandas.TrajectorySmoother对象&lt;/h4&gt;
 &lt;p&gt;MovingPandas.TrajectorySmoother 是一个类，用于对轨迹数据进行平滑处理。轨迹平滑通常是为了减少由于数据采集误差和噪声导致的轨迹抖动和异常点，从而得到更加平滑和准确的轨迹线条。&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;KalmanSmootherCV(traj) 是一个类，用于使用常速模型（Constant Velocity Model）的卡尔曼滤波算法对轨迹数据进行平滑处理，以减少噪声和抖动。&lt;/li&gt;
&lt;/ul&gt;
 &lt;h4&gt;MovingPandas.TrajectorySplitter对象&lt;/h4&gt;
 &lt;p&gt;MovingPandas.TrajectorySplitter 是一个类，用于将轨迹数据根据特定条件进行分割。这在处理长时间、多段的轨迹数据时特别有用，比如在分析车辆行驶路径、运动员运动轨迹或动物迁徙路径时，可以根据特定的规则将连续的轨迹分割成多个部分，以便进行更细致的分析。&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;TrajectorySplitter(traj) 是一个类，用于根据指定的条件（如距离、时间或速度）对轨迹数据进行分割，生成多个段以便更细致的分析。&lt;/li&gt;
  &lt;li&gt;TemporalSplitter(traj) 是一个类，用于根据时间间隔对轨迹数据进行分割，将轨迹分成多个时间段以便更细致的时间序列分析。&lt;/li&gt;
  &lt;li&gt;ObservationGapSplitter(traj) 是一个类，用于根据观测数据中的时间间隙对轨迹进行分割，当连续观测点之间的时间间隔超过指定阈值时，将轨迹分割成多个部分。&lt;/li&gt;
  &lt;li&gt;SpeedSplitter(traj) 是一个类，用于根据速度阈值对轨迹数据进行分割，当轨迹点的速度超过指定阈值时，将轨迹分割成多个部分。&lt;/li&gt;
  &lt;li&gt;StopSplitter(traj) 是一个类，用于根据停留点（长时间停留的点）对轨迹数据进行分割，将轨迹分成移动段和停留段以便更细致的分析。&lt;/li&gt;
&lt;/ul&gt;
 &lt;h4&gt;MovingPandas.TrajectoryStopDetector对象&lt;/h4&gt;
 &lt;p&gt;TrajectoryStopDetector 通过分析轨迹点的时空属性来识别停留点。它会检查一个轨迹对象中的每个点，并根据设定的阈值参数（如最小速度、最小停留时间和最小停留距离等）来鉴定轨迹中是否存在停留段。&lt;/p&gt;
 &lt;p&gt;class movingpandas.TrajectoryStopDetector(traj, n_threads=1)&lt;/p&gt;
 &lt;p&gt;方法介绍：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;get_stop_points(max_diameter, min_duration) 是 TrajectoryStopDetector 类中的方法，用于根据最大停留直径和最小持续时间来识别和提取轨迹数据中的停留点，并返回包含这些停留点的 GeoDataFrame。&lt;/li&gt;
  &lt;li&gt;get_stop_segments(max_diameter, min_duration) 是 TrajectoryStopDetector 类中的方法，用于根据最大停留直径和最小持续时间来识别和提取轨迹中的停留段，并返回包含这些停留段的列表。&lt;/li&gt;
  &lt;li&gt;get_stop_time_ranges(max_diameter, min_duration) 是 TrajectoryStopDetector 类中的方法，用于根据最大停留直径和最小持续时间来识别停留时间范围，并返回停留时间段的列表。&lt;/li&gt;
&lt;/ul&gt;
 &lt;h2&gt;MovingPandas使用实例&lt;/h2&gt;
 &lt;h3&gt;准备工作&lt;/h3&gt;
 &lt;p&gt;  &lt;strong&gt;加载需要的库&lt;/strong&gt;&lt;/p&gt;
 &lt;pre&gt;import pandas as pd
import geopandas as gpd
import movingpandas as mpd
from datetime import datetime, timedelta
import matplotlib.pyplot as plt
import folium
import bokeh.io
bokeh.io.output_notebook()
from holoviews import opts
opts.defaults(opts.Overlay(active_tools=[&amp;quot;wheel_zoom&amp;quot;], frame_width=500, frame_height=400))
&lt;/pre&gt;
 &lt;p&gt;  &lt;strong&gt;加载数据&lt;/strong&gt;&lt;/p&gt;
 &lt;pre&gt;df = pd.read_excel(&amp;quot;driver_log.xlsx&amp;quot;)

# 将DataFrame 转换为 GeoDataFrame
gdf = gpd.GeoDataFrame(df, geometry=gpd.points_from_xy(df.lon, df.lat), crs=&amp;apos;EPSG:4326&amp;apos;)

# 将GeoDataFrame转化为TrajectoryCollection对象
tc = mpd.TrajectoryCollection(gdf, traj_id_col=&amp;apos;session_id&amp;apos;, obj_id_col = &amp;apos;driver_no&amp;apos;, t=&amp;apos;log_time&amp;apos;)
# 过滤某个司机的轨迹
df[&amp;apos;driver_no&amp;apos;].value_counts()
df[&amp;apos;driver_no&amp;apos;].value_counts().plot(kind=&amp;apos;bar&amp;apos;, figsize=(15,3))
driver_tc = tc.filter(&amp;apos;driver_no&amp;apos;, &amp;apos;DR202407021504081000000&amp;apos;)

# 展示司机轨迹
driver_tc.plot()
&lt;/pre&gt;
 &lt;p&gt;  &lt;img alt="" height="413" src="https://www.biaodianfu.com/wp-content/uploads/2024/10/driver_traj.png" width="450"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;pre&gt;# 获取单个轨迹
my_traj = driver_tc.trajectories[0]

# 展示单个轨迹
traj_plot = my_traj.hvplot(title=&amp;quot;Trajectory {}&amp;quot;.format(my_traj.id),line_width=7.0, tiles=&amp;quot;CartoLight&amp;quot;, color=&amp;quot;slategray&amp;quot;)
traj_plot
&lt;/pre&gt;
 &lt;p&gt;  &lt;img alt="" height="636" src="https://www.biaodianfu.com/wp-content/uploads/2024/10/my_traj.png" width="815"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h3&gt;停留点检测&lt;/h3&gt;
 &lt;p&gt;  &lt;strong&gt;针对单轨迹停留点检测&lt;/strong&gt;&lt;/p&gt;
 &lt;pre&gt;detector = mpd.TrajectoryStopDetector(my_traj)
## 检测停留的时间（这里检测5分钟位移100米以内）
stop_time_ranges = detector.get_stop_time_ranges(min_duration=timedelta(seconds=300), max_diameter=100)
## 检测停留的时间
for stop_time in stop_time_ranges:
    print(stop_time)
## 检测停留点
stop_points = detector.get_stop_points(min_duration=timedelta(seconds=300), max_diameter=100)
stop_points
## 展示停留点
stop_point_plot = traj_plot * stop_points.hvplot(geo=True, size=&amp;quot;duration_s&amp;quot;, color=&amp;quot;deeppink&amp;quot;)
stop_point_plot
&lt;/pre&gt;
 &lt;p&gt;  &lt;img alt="" height="631" src="https://www.biaodianfu.com/wp-content/uploads/2024/10/stop-point.png" width="817"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;pre&gt;## 停留点信息
stop_points_gdf = gpd.GeoDataFrame(stop_points, geometry=&amp;quot;geometry&amp;quot;, crs=&amp;quot;EPSG:4326&amp;quot;)
stop_points_gdf
## 使用folium展示停留点
# m = my_traj.explore(color=&amp;quot;blue&amp;quot;,style_kwds={&amp;quot;weight&amp;quot;: 4},name=&amp;quot;Trajectory&amp;quot;)
# stop_points_gdf.explore(m=m,color=&amp;quot;red&amp;quot;,style_kwds={&amp;quot;style_function&amp;quot;: lambda x: {&amp;quot;radius&amp;quot;: x[&amp;quot;properties&amp;quot;][&amp;quot;duration_s&amp;quot;] / 10 }},name=&amp;quot;Stop points&amp;quot;)
# folium.TileLayer(&amp;quot;OpenStreetMap&amp;quot;).add_to(m)
# folium.LayerControl().add_to(m)
# m
## 停留轨迹
stop_segments = detector.get_stop_segments(min_duration=timedelta(seconds=60), max_diameter=100)
stop_segments.to_traj_gdf()
## 停留轨迹
stop_segment_plot = stop_point_plot * stop_segments.hvplot(line_width=7.0, tiles=None, color=&amp;quot;orange&amp;quot;)
stop_segment_plot
&lt;/pre&gt;
 &lt;p&gt;  &lt;img alt="" height="629" src="https://www.biaodianfu.com/wp-content/uploads/2024/10/stop_segment.png" width="811"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;pre&gt;## 使用folium绘图
# m = my_traj.explore(
#     color=&amp;quot;blue&amp;quot;,
#     popup=True,
#     style_kwds={&amp;quot;weight&amp;quot;: 4},
#     name=&amp;quot;Trajectory&amp;quot;,
# )

# stop_segments.explore(
#     m=m,
#     color=&amp;quot;orange&amp;quot;,
#     popup=True,
#     style_kwds={&amp;quot;weight&amp;quot;: 4},
#     name=&amp;quot;Stop segments&amp;quot;,
# )

# stop_points_gdf.explore(
#     m=m,
#     color=&amp;quot;red&amp;quot;,
#     tooltip=&amp;quot;stop_id&amp;quot;,
#     popup=True,
#     marker_kwds={&amp;quot;radius&amp;quot;: 3},
#     name=&amp;quot;Stop points&amp;quot;,
# )

# folium.TileLayer(&amp;quot;CartoDB positron&amp;quot;).add_to(m)
# folium.LayerControl().add_to(m)

# m

## 行驶线路
split = mpd.StopSplitter(my_traj).split(min_duration=timedelta(seconds=300), max_diameter=100)
split.to_traj_gdf()
## 可视化行驶线路
split.explore(column=&amp;quot;session_id&amp;quot;, tiles=&amp;quot;CartoDB positron&amp;quot;, style_kwds={&amp;quot;weight&amp;quot;: 4})

## 整体可视化
stop_segment_plot + split.hvplot(title=&amp;quot;Trajectory {} split at stops&amp;quot;.format(my_traj.id),line_width=7.0,tiles=&amp;quot;CartoLight&amp;quot;)
&lt;/pre&gt;
 &lt;p&gt;  &lt;img alt="" height="457" src="https://www.biaodianfu.com/wp-content/uploads/2024/10/stop_segment_plot.png" width="1058"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;轨迹合集的经停点检测&lt;/strong&gt;&lt;/p&gt;
 &lt;pre&gt;## 停留点检测
detector = mpd.TrajectoryStopDetector(driver_tc)
stop_points = detector.get_stop_points(min_duration=timedelta(seconds=300), max_diameter=100)
stop_points
## 停留点可视化
ax = driver_tc.plot(figsize=(7, 7))
stop_points.plot(ax=ax, color=&amp;quot;red&amp;quot;)
&lt;/pre&gt;
 &lt;p&gt;  &lt;img alt="" height="486" src="https://www.biaodianfu.com/wp-content/uploads/2024/10/TrajectoryStopDetector.png" width="603"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;pre&gt;## 使用folium可视化
## 使用方folium可视化
# m = driver_tc.explore(
#     column=&amp;quot;session_id&amp;quot;,
#     popup=True,
#     style_kwds={&amp;quot;weight&amp;quot;: 4},
#     name=&amp;quot;Trajectories&amp;quot;,
# )

# stop_points.explore(
#     m=m,
#     color=&amp;quot;red&amp;quot;,
#     tooltip=&amp;quot;stop_id&amp;quot;,
#     popup=True,
#     marker_kwds={&amp;quot;radius&amp;quot;: 5},
#     name=&amp;quot;Stop points&amp;quot;,
# )

# folium.TileLayer(&amp;quot;CartoDB positron&amp;quot;).add_to(m)
# folium.LayerControl().add_to(m)

# m
&lt;/pre&gt;
 &lt;h3&gt;速度计算&lt;/h3&gt;
 &lt;pre&gt;## 单轨迹增加速度
my_traj.add_speed(overwrite=True,units=(&amp;quot;km&amp;quot;, &amp;quot;h&amp;quot;))
my_traj.df.head()

## 展示速度
my_traj.plot(column=&amp;quot;speed&amp;quot;, linewidth=5, capstyle=&amp;apos;round&amp;apos;, legend=True)
# my_traj.hvplot(c=&amp;apos;speed&amp;apos;, clim=(0,20), line_width=7.0, tiles=&amp;apos;CartoLight&amp;apos;, cmap=&amp;apos;Viridis&amp;apos;, colorbar=True)
&lt;/pre&gt;
 &lt;p&gt;  &lt;img alt="" height="404" src="https://www.biaodianfu.com/wp-content/uploads/2024/10/speed.png" width="546"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;pre&gt;## 添加方向
my_traj.add_direction(overwrite=True)
my_traj.df.head()

## 添加时差
my_traj.add_timedelta(overwrite=True)
my_traj.df.head()

## 添加距离
my_traj.add_distance(overwrite=True, name=&amp;quot;distance (km)&amp;quot;, units=&amp;quot;m&amp;quot;)
my_traj.df.head()

## 添加加速度
my_traj.add_acceleration(overwrite=True, name=&amp;quot;acceleration (mph/s)&amp;quot;, units=(&amp;quot;mi&amp;quot;, &amp;quot;h&amp;quot;, &amp;quot;s&amp;quot;))
my_traj.df.head()

## 轨迹集增加速度
driver_tc.add_speed(overwrite=True,units=(&amp;quot;km&amp;quot;, &amp;quot;h&amp;quot;))
driver_tc.plot(column=&amp;apos;speed&amp;apos;, linewidth=5, capstyle=&amp;apos;round&amp;apos;, legend=True, vmax=20)
&lt;/pre&gt;
 &lt;p&gt;  &lt;img alt="" height="418" src="https://www.biaodianfu.com/wp-content/uploads/2024/10/speed2.png" width="534"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h3&gt;提取位置&lt;/h3&gt;
 &lt;pre&gt;## 获取起点与终点
ax = my_traj.plot()
gpd.GeoSeries(my_traj.get_start_location()).plot(ax=ax, color=&amp;apos;blue&amp;apos;)
gpd.GeoSeries(my_traj.get_end_location()).plot(ax=ax, color=&amp;apos;red&amp;apos;)
&lt;/pre&gt;
 &lt;p&gt;  &lt;img alt="" height="417" src="https://www.biaodianfu.com/wp-content/uploads/2024/10/location.png" width="574"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;pre&gt;## 获取特定时间点的位置
t = datetime(2024,7,3,9,30,0)
print(my_traj.get_position_at(t, method=&amp;quot;nearest&amp;quot;))
print(my_traj.get_position_at(t, method=&amp;quot;interpolated&amp;quot;))
print(my_traj.get_position_at(t, method=&amp;quot;ffill&amp;quot;)) # from the previous row
print(my_traj.get_position_at(t, method=&amp;quot;bfill&amp;quot;)) # from the following row

point = my_traj.get_position_at(t, method=&amp;quot;interpolated&amp;quot;)
ax = my_traj.plot()
gpd.GeoSeries(point).plot(ax=ax, color=&amp;apos;red&amp;apos;, markersize=100)
&lt;/pre&gt;
 &lt;p&gt;  &lt;img alt="" height="417" src="https://www.biaodianfu.com/wp-content/uploads/2024/10/time-point.png" width="574"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;pre&gt;## 获取特定时间区间的位置
segment = my_traj.get_segment_between(datetime(2024,7,3,9,10,0), datetime(2024,7,3,9,30,0))
print(segment)
ax = my_traj.plot()
segment.plot(ax=ax, color=&amp;apos;red&amp;apos;, linewidth=5)
&lt;/pre&gt;
 &lt;p&gt;  &lt;img alt="" height="446" src="https://www.biaodianfu.com/wp-content/uploads/2024/10/segment.png" width="542"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;pre&gt;## 获取特定区域内的轨迹
from shapely.geometry import Polygon

xmin, xmax, ymin, ymax = 104.135, 104.137, 30.642, 30.643
polygon = Polygon([(xmin, ymin), (xmin, ymax), (xmax, ymax), (xmax, ymin), (xmin, ymin)])
intersections = my_traj.clip(polygon)
ax = my_traj.plot()
gpd.GeoSeries(polygon).plot(ax=ax, color=&amp;apos;lightgray&amp;apos;)
intersections.plot(ax=ax, color=&amp;apos;red&amp;apos;, linewidth=5, capstyle=&amp;apos;round&amp;apos;)
&lt;/pre&gt;
 &lt;p&gt;  &lt;img alt="" height="446" src="https://www.biaodianfu.com/wp-content/uploads/2024/10/intersections.png" width="542"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h3&gt;导出轨迹&lt;/h3&gt;
 &lt;pre&gt;## 返回 GeoDataFrame
driver_tc.to_point_gdf()
driver_tc.to_line_gdf()
driver_tc.to_traj_gdf(wkt=True) # 生成wkt格式的聚合

# 聚合数据
driver_tc.add_speed(overwrite=True,units=(&amp;quot;km&amp;quot;, &amp;quot;h&amp;quot;))
driver_tc.to_traj_gdf(agg={&amp;apos;speed&amp;apos;:[&amp;apos;min&amp;apos;, &amp;apos;max&amp;apos;,&amp;apos;mode&amp;apos;]})

# 导出数据
export_gdf = driver_tc.to_traj_gdf(agg={&amp;apos;speed&amp;apos;:[&amp;apos;min&amp;apos;, &amp;apos;max&amp;apos;,&amp;apos;mode&amp;apos;]})
export_gdf.to_file(&amp;quot;temp.gpkg&amp;quot;, layer=&amp;apos;trajectories&amp;apos;, driver=&amp;quot;GPKG&amp;quot;)
gpd.read_file(&amp;apos;temp.gpkg&amp;apos;).plot()
&lt;/pre&gt;
 &lt;p&gt;  &lt;img alt="" height="413" src="https://www.biaodianfu.com/wp-content/uploads/2024/10/export_gdf.png" width="450"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h3&gt;轨迹分割&lt;/h3&gt;
 &lt;pre&gt;## 数据准备
my_traj.add_speed(overwrite=True,units=(&amp;quot;km&amp;quot;, &amp;quot;h&amp;quot;))
my_traj.plot(column=&amp;apos;speed&amp;apos;, vmax=20, linewidth=5, capstyle=&amp;apos;round&amp;apos;, figsize=(9,3), legend=True )
&lt;/pre&gt;
 &lt;p&gt;  &lt;img alt="" height="308" src="https://www.biaodianfu.com/wp-content/uploads/2024/10/my_traj.plot_.png" width="455"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;pre&gt;## 根据观测数据中的时间间隙对轨迹进行分割
split = mpd.ObservationGapSplitter(my_traj).split(gap=timedelta(minutes=1))
split.to_traj_gdf()
fig, axes = plt.subplots(nrows=1, ncols=len(split), figsize=(19,4))
for i, traj in enumerate(split):
    traj.plot(ax=axes[i], linewidth=5.0, capstyle=&amp;apos;round&amp;apos;, column=&amp;apos;speed&amp;apos;, vmax=20)
&lt;/pre&gt;
 &lt;p&gt;  &lt;img alt="" height="288" src="https://www.biaodianfu.com/wp-content/uploads/2024/10/split.png" width="1047"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;pre&gt;## 根据停留点（长时间停留的点）对轨迹数据进行分割
split = mpd.StopSplitter(my_traj).split(max_diameter=10, min_duration=timedelta(minutes=1), min_length=20)
split.to_traj_gdf()
fig, axes = plt.subplots(nrows=1, ncols=len(split), figsize=(19,4))
for i, traj in enumerate(split):
    traj.plot(ax=axes[i], linewidth=5.0, capstyle=&amp;apos;round&amp;apos;, column=&amp;apos;speed&amp;apos;, vmax=20)
&lt;/pre&gt;
 &lt;p&gt;  &lt;img alt="" height="269" src="https://www.biaodianfu.com/wp-content/uploads/2024/10/split2.png" width="1055"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;pre&gt;## 根据速度阈值对轨迹数据进行分割
split = mpd.SpeedSplitter(my_traj).split(speed=0, duration=timedelta(minutes=1))
split.to_traj_gdf()
fig, axes = plt.subplots(nrows=1, ncols=len(split), figsize=(19,4))
for i, traj in enumerate(split):
    traj.plot(ax=axes[i], linewidth=5.0, capstyle=&amp;apos;round&amp;apos;, column=&amp;apos;speed&amp;apos;, vmax=20)
&lt;/pre&gt;
 &lt;p&gt;  &lt;img alt="" height="284" src="https://www.biaodianfu.com/wp-content/uploads/2024/10/split3.png" width="1031"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h3&gt;轨迹抽稀&lt;/h3&gt;
 &lt;pre&gt;## 展示原始轨迹
plot_defaults = {&amp;apos;linewidth&amp;apos;:5, &amp;apos;capstyle&amp;apos;:&amp;apos;round&amp;apos;, &amp;apos;figsize&amp;apos;:(9,3), &amp;apos;legend&amp;apos;:True}
my_traj.add_speed(overwrite=True,units=(&amp;quot;km&amp;quot;, &amp;quot;h&amp;quot;))
my_traj.plot(column=&amp;apos;speed&amp;apos;, vmax=20, **plot_defaults)
&lt;/pre&gt;
 &lt;p&gt;  &lt;img alt="" height="308" src="https://www.biaodianfu.com/wp-content/uploads/2024/10/plot_defaults.png" width="455"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;pre&gt;## 使用 Douglas-Peucker 算法对轨迹数据进行简化
dp_generalized  = mpd.DouglasPeuckerGeneralizer(my_traj).generalize(tolerance=0.0001)
dp_generalized.plot(column=&amp;apos;speed&amp;apos;, vmax=20, **plot_defaults)
&lt;/pre&gt;
 &lt;p&gt;  &lt;img alt="" height="308" src="https://www.biaodianfu.com/wp-content/uploads/2024/10/dp_generalized.png" width="448"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;pre&gt;print(&amp;apos;Original length: %s&amp;apos;%(my_traj.get_length()))
print(&amp;apos;Generalized length: %s&amp;apos;%(dp_generalized.get_length()))

## 根据最小时间间隔对轨迹数据进行简化
time_generalized = mpd.MinTimeDeltaGeneralizer(my_traj).generalize(tolerance=timedelta(minutes=3))
time_generalized.plot(column=&amp;apos;speed&amp;apos;, vmax=20, **plot_defaults)
&lt;/pre&gt;
 &lt;p&gt;  &lt;img alt="" height="308" src="https://www.biaodianfu.com/wp-content/uploads/2024/10/time_generalized.png" width="448"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;pre&gt;## 通过时间比率算法对轨迹数据进行简化
tdtr_generalized = mpd.TopDownTimeRatioGeneralizer(my_traj).generalize(tolerance=0.001)
fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(19,4))
tdtr_generalized.plot(ax=axes[0], column=&amp;apos;speed&amp;apos;, vmax=20, **plot_defaults)
dp_generalized.plot(ax=axes[1], column=&amp;apos;speed&amp;apos;, vmax=20, **plot_defaults)
&lt;/pre&gt;
 &lt;p&gt;  &lt;img alt="" height="344" src="https://www.biaodianfu.com/wp-content/uploads/2024/10/tdtr_generalized.png" width="1198"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;pre&gt;fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(19,4))
tdtr_generalized.plot(ax=axes[0], column=&amp;apos;speed&amp;apos;, vmax=20, **plot_defaults)
time_generalized.plot(ax=axes[1], column=&amp;apos;speed&amp;apos;, vmax=20, **plot_defaults)
&lt;/pre&gt;
 &lt;p&gt;  &lt;img alt="" height="344" src="https://www.biaodianfu.com/wp-content/uploads/2024/10/tdtr_generalized.plot_.png" width="1199"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h3&gt;平滑轨迹&lt;/h3&gt;
 &lt;pre&gt;split = mpd.ObservationGapSplitter(my_traj).split(gap=timedelta(minutes=1))
smooth = mpd.KalmanSmootherCV(split).smooth(process_noise_std=0.1, measurement_noise_std=10)
hvplot_defaults = {&amp;apos;tiles&amp;apos;:&amp;apos;CartoLight&amp;apos;, &amp;apos;frame_height&amp;apos;:320, &amp;apos;frame_width&amp;apos;:320, &amp;apos;cmap&amp;apos;:&amp;apos;Viridis&amp;apos;, &amp;apos;colorbar&amp;apos;:True}
kwargs = {**hvplot_defaults, &amp;apos;line_width&amp;apos;:4}
(split.hvplot(title=&amp;apos;Original Trajectories&amp;apos;, **kwargs) +  smooth.hvplot(title=&amp;apos;Smooth Trajectories&amp;apos;, **kwargs))
&lt;/pre&gt;
 &lt;p&gt;  &lt;img alt="" height="378" src="https://www.biaodianfu.com/wp-content/uploads/2024/10/smooth.png" width="733"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;pre&gt;kwargs = {**hvplot_defaults, &amp;apos;c&amp;apos;:&amp;apos;speed&amp;apos;, &amp;apos;line_width&amp;apos;:7, &amp;apos;clim&amp;apos;:(0,20)}
(split.trajectories[1].hvplot(title=&amp;apos;Original Trajectory&amp;apos;, **kwargs) + smooth.trajectories[1].hvplot(title=&amp;apos;Smooth Trajectory&amp;apos;, **kwargs))
&lt;/pre&gt;
 &lt;p&gt;  &lt;img alt="" height="380" src="https://www.biaodianfu.com/wp-content/uploads/2024/10/split.trajectories.png" width="846"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;pre&gt;traj = split.trajectories[1]

cleaned = traj.copy()
cleaned = mpd.OutlierCleaner(cleaned).clean(alpha=2)

smoothed = mpd.KalmanSmootherCV(cleaned).smooth(process_noise_std=0.1, measurement_noise_std=10)
    
(traj.hvplot(title=&amp;apos;Original Trajectory&amp;apos;, **kwargs) + 
 cleaned.hvplot(title=&amp;apos;Cleaned Trajectory&amp;apos;, **kwargs) + 
 smoothed.hvplot(title=&amp;apos;Cleaned &amp;amp; Smoothed Trajectory&amp;apos;, **kwargs))
&lt;/pre&gt;
 &lt;p&gt;  &lt;img alt="" height="386" src="https://www.biaodianfu.com/wp-content/uploads/2024/10/smoothed.png" width="1301"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h3&gt;轨迹聚类和分类&lt;/h3&gt;
 &lt;pre&gt;## 查看数据
driver_tc.explore(column=&amp;quot;session_id&amp;quot;, cmap=&amp;quot;plasma&amp;quot;, style_kwds={&amp;quot;weight&amp;quot;: 4})
## 根据最小距离间隔对轨迹数据进行简化
generalized = mpd.MinDistanceGeneralizer(driver_tc).generalize(tolerance=100)
generalized.to_traj_gdf()

## 对轨迹进行聚合操作
aggregator = mpd.TrajectoryCollectionAggregator(
    generalized,
    max_distance=1000,
    min_distance=100,
    min_stop_duration=timedelta(minutes=10),
)

## 提取显著点
pts = aggregator.get_significant_points_gdf()
pts.hvplot(geo=True, tiles=&amp;quot;OSM&amp;quot;)
&lt;/pre&gt;
 &lt;p&gt;  &lt;img alt="" height="634" src="https://www.biaodianfu.com/wp-content/uploads/2024/10/pts.png" width="819"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;pre&gt;## 获取聚合轨迹的簇
clusters = aggregator.get_clusters_gdf()
(pts.hvplot(geo=True, tiles=&amp;quot;OSM&amp;quot;) * clusters.hvplot(geo=True, color=&amp;quot;red&amp;quot;))
&lt;/pre&gt;
 &lt;p&gt;  &lt;img alt="" height="638" src="https://www.biaodianfu.com/wp-content/uploads/2024/10/clusters.png" width="815"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;pre&gt;## 使用folium绘制
# m = pts.explore(marker_kwds={&amp;quot;radius&amp;quot;: 3}, name=&amp;quot;Significant points&amp;quot;)
# clusters.explore(m=m, color=&amp;quot;red&amp;quot;, marker_kwds={&amp;quot;radius&amp;quot;: 3}, name=&amp;quot;Cluster centroids&amp;quot;)
# folium.TileLayer(&amp;quot;CartoDB positron&amp;quot;).add_to(m)
# folium.LayerControl().add_to(m)
# m

## 获取聚合后的轨迹数据的流动
flows = aggregator.get_flows_gdf()
(flows.hvplot(geo=True, hover_cols=[&amp;quot;weight&amp;quot;], line_width=dim(&amp;quot;weight&amp;quot;) * 7, color=&amp;quot;#1f77b3&amp;quot;,tiles=&amp;quot;CartoLight&amp;quot;) * clusters.hvplot(geo=True, color=&amp;quot;red&amp;quot;, size=dim(&amp;quot;n&amp;quot;)))
&lt;/pre&gt;
 &lt;p&gt;  &lt;img alt="" height="626" src="https://www.biaodianfu.com/wp-content/uploads/2024/10/flows.png" width="829"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;pre&gt;## 使用Folium绘制
# m = flows.explore(style_kwds={&amp;quot;weight&amp;quot;: 5},name=&amp;quot;Flows&amp;quot;)
# clusters.explore( m=m,color=&amp;quot;red&amp;quot;,style_kwds={&amp;quot;style_function&amp;quot;: lambda x: {&amp;quot;radius&amp;quot;: x[&amp;quot;properties&amp;quot;][&amp;quot;n&amp;quot;]}}, name=&amp;quot;Clusters&amp;quot;)
# folium.TileLayer(&amp;quot;OpenStreetMap&amp;quot;).add_to(m)
# folium.LayerControl().add_to(m)
# m
&lt;/pre&gt;
 &lt;h3&gt;距离计算&lt;/h3&gt;
 &lt;pre&gt;## 选择2个轨迹
my_traj = driver_tc.trajectories[3]
toy_traj = driver_tc.trajectories[1]
## 呈现数据
ax = my_traj.plot()
toy_traj.plot(ax=ax, color=&amp;apos;red&amp;apos;)
&lt;/pre&gt;
 &lt;p&gt;  &lt;img alt="" height="343" src="https://www.biaodianfu.com/wp-content/uploads/2024/10/toy_traj.plot_.png" width="565"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;pre&gt;## 计算记录
print(f&amp;apos;Distance: {toy_traj.distance(my_traj)} meters&amp;apos;) # 返回最短距离
print(f&amp;apos;Hausdorff distance: {toy_traj.hausdorff_distance(my_traj):.2f} meters&amp;apos;) # 返回Hausdorff距离
&lt;/pre&gt;
 &lt;p&gt;Hausdorff距离可以理解为：对于集合A 中的每个点，计算它到集合B的最近距离，然后在这些距离中找到最大值；反过来对于集合 B 中的每个点，计算它到集合A 的最近距离，然后在这些距离中找到最大值。Hausdorff距离是这两个最大值中的较大者。&lt;/p&gt;
 &lt;p&gt;参考链接：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;a href="https://github.com/movingpandas/movingpandas"&gt;movingpandas/movingpandas: Movement trajectory classes and functions built on top of GeoPandas (github.com)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://movingpandas.org/"&gt;MovingPandas | A Python library for movement data exploration and analysis&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://movingpandas.org/examples.html"&gt;Tutorials | MovingPandas&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://movingpandas.readthedocs.io/en/main/"&gt;MovingPandas Documentation — MovingPandas main documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
 &lt;div&gt;

  &lt;h3&gt;相关文章:&lt;/h3&gt;  &lt;ol&gt;
   &lt;li&gt;    &lt;a href="https://www.biaodianfu.com/seaborn.html" rel="bookmark" title="Python&amp;#25968;&amp;#25454;&amp;#21487;&amp;#35270;&amp;#21270;&amp;#20043;Seaborn"&gt;Python数据可视化之Seaborn&lt;/a&gt;&lt;/li&gt;
   &lt;li&gt;    &lt;a href="https://www.biaodianfu.com/learn-css.html" rel="bookmark" title="CSS&amp;#20307;&amp;#31995;&amp;#21270;&amp;#23398;&amp;#20064;"&gt;CSS体系化学习&lt;/a&gt;&lt;/li&gt;
   &lt;li&gt;    &lt;a href="https://www.biaodianfu.com/arima.html" rel="bookmark" title="&amp;#26102;&amp;#38388;&amp;#24207;&amp;#21015;&amp;#39044;&amp;#27979;&amp;#20043;ARIMA"&gt;时间序列预测之ARIMA&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>器→工具 工具软件 开源项目 GIS</category>
      <guid isPermaLink="true">https://itindex.net/detail/62944-python-%E5%9C%B0%E7%90%86-%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90</guid>
      <pubDate>Wed, 09 Oct 2024 19:54:20 CST</pubDate>
    </item>
    <item>
      <title>数据分析模式的演进</title>
      <link>https://itindex.net/detail/62883-%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90-%E6%A8%A1%E5%BC%8F</link>
      <description>&lt;p&gt;数据分析是一个不断变化的领域。稍微了解一下历史将帮助你欣赏到这一领域取得的进展，以及数据架构模式如何演进以满足不断变化的分析需求。&lt;/p&gt;
 &lt;p&gt;首先，让我们从一些定义开始：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;什么是分析？分析被定义为将数据转化为洞察的任何行为。&lt;/li&gt;
  &lt;li&gt;什么是数据架构？数据架构是使数据的存储、转换、利用和治理成为可能的结构。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;分析及支持分析的数据架构已经走过了很长的路。现在让我们来探讨一些在过去几十年中逐渐演变的模式。&lt;/p&gt;
 &lt;p&gt;本章探讨了数据增长的起源，并解释了对数据架构新范式的需求。本章首先检视了主导的范式——企业数据仓库，在20世纪90年代和2000年代很受欢迎。它探讨了与这一范式相关的挑战，然后介绍了导致数据激增的驱动因素。接着，它进一步探讨了新范式——数据湖的崛起及其面临的挑战。此外，本章最后提倡了对一个新的范式——数据湖仓的需求，并阐明了一个经过良好设计的数据湖仓所提供的关键优势。&lt;/p&gt;
 &lt;p&gt;我们将在以下主题中涵盖所有这些内容：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;发现企业数据仓库时代&lt;/li&gt;
  &lt;li&gt;探索变革的五个因素&lt;/li&gt;
  &lt;li&gt;调查数据湖时代&lt;/li&gt;
  &lt;li&gt;介绍数据湖仓范式&lt;/li&gt;
&lt;/ol&gt;
 &lt;h1&gt;探索企业数据仓库时代&lt;/h1&gt;
 &lt;p&gt;由Ralph Kimball和Bill Inmon推广的企业数据仓库（EDW）模式在20世纪90年代和2000年代占主导地位。这一时代的需求相对较为简单（至少与当前背景相比是如此）。焦点主要集中在优化数据库结构以满足报告需求上。在这个时期，分析与报告是同义的。机器学习是一个专业领域，尚未在企业中普及。&lt;/p&gt;
 &lt;p&gt;下图展示了一个典型的企业数据仓库模式：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image.png" src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7ccfc7b32a69420d8b812e325ab7504a~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=1198&amp;h=730&amp;s=156470&amp;e=png&amp;b=fefefe"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;如图1.1所示，该模式涉及由数据库或平面文件结构组成的源系统。数据源主要是结构化的，即行和列。一个称为提取-转换-加载（ETL）的过程首先从源系统中提取数据。然后，该过程将数据转化为有利于分析的形状和形式。一旦数据被转换，就被加载到企业数据仓库（EDW）中。然后，数据的子集被填充到下游的数据集市中。数据集市可以被看作是满足特定部门业务需求的小型数据仓库。&lt;/p&gt;
 &lt;p&gt;正如你可以想象的，这一模式主要关注以下方面：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;创建一个对存储进行优化并为报告建模的数据结构&lt;/li&gt;
  &lt;li&gt;关注业务的报告需求&lt;/li&gt;
  &lt;li&gt;将结构化数据转化为可操作的洞察&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;每个硬币都有两面。EDW模式也不例外。它有其优势和劣势。这一模式经受住了时间的考验。由于以下关键优势，它被广泛采用：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;由于大多数分析需求与报告相关，这一模式有效地满足了许多组织的报告需求。&lt;/li&gt;
  &lt;li&gt;大型企业数据模型能够将组织的数据结构化为逻辑和物理模型。这一模式以模块化和高效的方式管理组织的数据。&lt;/li&gt;
  &lt;li&gt;由于这一模式只服务于结构化数据，因此用于利用结构化数据的技术得以发展，并且随时可用。关系数据库管理系统（RDBMS）得以发展，并被适当地用于报告的特性。&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;然而，它也面临一系列随着数据量增长和新的数据格式出现而浮出水面的挑战。与EDW模式相关的一些挑战包括：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;这一模式不如变化迅速的业务需求希望它变得敏捷。对报告需求的任何更改都必须经过漫长的数据模型更改、ETL代码更改和报告系统的相应更改过程。通常，ETL过程是一种专业技能，并成为减少数据到洞察转化时间的瓶颈。分析的性质是独特的。你看到的产出越多，你就越要求。许多EDW项目被认为是失败的。失败并非来自技术角度，而是来自业务角度。在操作上，为了满足这些快速发展的需求而需要进行的设计更改太难处理了。&lt;/li&gt;
  &lt;li&gt;随着数据量的增长，这一模式变得成本过高。大规模的并行处理数据库技术开始演变，专门用于数据仓库工作负载。维护这些数据库的成本也是禁锢的。它涉及昂贵的软件价格、频繁的硬件更新和可观的人工成本。投资回报不再合理。&lt;/li&gt;
  &lt;li&gt;随着数据格式的演变，与EDW相关的挑战变得更为明显。数据库技术已经发展到可以满足半结构化数据（JSON）的需求。然而，基本概念仍然是基于关系数据库管理系统（RDBMS）。底层技术无法有效地满足这些新类型数据的需求。分析非结构化数据的价值更大。数据的多样性对EDW来说过于复杂，难以处理。&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;EDW主要专注于商业智能（BI）。它促进了定期报告的创建、自由数据分析和自助式BI。尽管它适应了大多数执行分析的角色，但不利于AI/ML用例。EDW中的数据已经被清理并以对报告的高度专注的方式进行了结构化。这留下了很少的空间供数据科学家（当时的统计建模者）探索数据并提出新的假设。简而言之，EDW主要关注于BI。&lt;/p&gt;
 &lt;p&gt;而EDW模式变得主流的同时，一场完美的风暴正在兴起，改变了数据架构模式。接下来的部分将专注于五个不同的因素，它们共同作用，以改变数据架构模式为善。&lt;/p&gt;
 &lt;h1&gt;探索变革的五个因素&lt;/h1&gt;
 &lt;p&gt;2007年改变了我们所知的世界；史蒂夫·乔布斯走上舞台宣布iPhone发布的那一天是数据时代的转折点。那一天酝酿出了完美的“数据”风暴。&lt;/p&gt;
 &lt;p&gt;完美风暴是由罕见的多种因素组合而成的气象事件。在数据演进的世界中，在过去的十年里发生了这样一场完美的风暴，它将数据推上了战略性企业资产的顶峰。五个因素引发了这场完美的“数据”风暴。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image.png" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/87dfbb4db8ea4c879c366bc2bc4710f8~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=1430&amp;h=881&amp;s=325428&amp;e=png&amp;b=ffffff"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;正如图1.2所示，形成完美风暴的有五个因素。数据的指数增长和计算能力的增加是前两个因素。这两个因素与存储成本的降低同时发生。人工智能的崛起和云计算的进步同时汇聚在一起，形成了这场完美的风暴。&lt;/p&gt;
 &lt;p&gt;这些因素独立发展并共同趋势，改变和塑造了产业。让我们简要地了解一下每个因素。&lt;/p&gt;
 &lt;h2&gt;数据的指数增长&lt;/h2&gt;
 &lt;p&gt;数据的指数增长是完美风暴的第一个因素。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image.png" src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2b9099332fb646c88b4f7bdb827d7376~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=899&amp;h=586&amp;s=75823&amp;e=png&amp;b=ffffff"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;根据国际数据公司（IDC）的数据，到2025年，生成的总数据量将达到约163 ZB（字节），即一万亿吉字节。而2010年，这个数字大约是0.5 ZB。这种数据的指数增长归因于互联网技术的巨大改进，推动了许多行业的增长。电信行业是发生转变的主要行业。这反过来又改变了许多其他行业。数据变得无处不在，每个企业都渴望更多的数据带宽。社交媒体平台也开始被广泛使用。像Facebook、Twitter和Instagram这样的平台在互联网空间里涌入了更多的数据。流媒体服务和电子商务也产生了大量的数据。这些生成的数据被用来塑造和影响消费者行为。最后但同样重要的是，物联网（IoT）领域的技术飞跃产生了大量的数据。&lt;/p&gt;
 &lt;p&gt;传统的企业数据仓库（EDW）模式无法应对这种数据增长。它们被设计用于处理结构化数据。大数据已经改变了可用数据的定义。现在的数据是庞大的（体积），其中一些是持续流动的（速度），以不同的形状和形式生成的（多样性），来自各种来源且带有噪声（真实性）。&lt;/p&gt;
 &lt;h2&gt;计算能力的增加&lt;/h2&gt;
 &lt;p&gt;计算能力的指数增长是完美风暴的第二个因素。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image.png" src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b4aade5f3b1f4877afc0b87fd553e1cd~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=811&amp;h=480&amp;s=86921&amp;e=png&amp;b=fefefe"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;摩尔定律是美国工程师戈登·摩尔在1965年提出的一个预测，即每年硅芯片上的晶体管数量翻倍一次。迄今为止，这一定律一直忠实于其预测。2010年，微处理器上的晶体管数量约为20亿。到2020年，这一数字已经达到了540亿。计算能力的这种指数增长与提供无限计算能力的云计算技术的崛起密切相关，而且价格合理。&lt;/p&gt;
 &lt;p&gt;计算能力在一个合理的价格点上的增加为大数据提供了迫切需要的推动力。组织现在可以以更低的价格点采购越来越多的计算能力。云计算中可用的计算能力现在可以用于按需处理和分析数据。&lt;/p&gt;
 &lt;h2&gt;存储成本的降低&lt;/h2&gt;
 &lt;p&gt;存储成本的迅速降低是完美风暴的第三个因素。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image.png" src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/fbe360390ff4426d90ec0a81e64ff2c1~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=776&amp;h=476&amp;s=58912&amp;e=png&amp;b=ffffff"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;存储成本也呈指数级下降。在2010年，将一GB数据存储到硬盘驱动器（HDD）中的平均成本约为0.1美元。而在10年后，这一数字降至大约0.01美元。在传统的企业数据仓库（EDW）模式中，组织必须谨慎选择哪些数据需要存储用于分析，哪些数据可以丢弃。保留数据是一项昂贵的提议。然而，存储成本的指数级下降意味着现在可以以之前成本的一小部分存储所有数据。现在无需挑选哪些数据应该被存储，哪些应该被丢弃。以任何形式的数据都可以以较低的价格进行存储。现在可以实施先存储，后分析的策略。&lt;/p&gt;
 &lt;h2&gt;人工智能的崛起&lt;/h2&gt;
 &lt;p&gt;人工智能（AI）系统并非新事物。实际上，它们的起源可以追溯到1950年代，当时使用统计模型基于过去的数据估计数据点的值。由于当时缺乏运行这些模型所需的计算能力和大量数据，这一领域在很长一段时间内未受到关注。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image.png" src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/513b29afe8d04d4b98c47774111be102~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=1398&amp;h=641&amp;s=238194&amp;e=png&amp;b=fefefe"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;然而，在长时间的休眠之后，人工智能技术在2010年代初期迎来了复苏。这种复苏部分原因在于强大的计算资源的丰富和数据的充分可用。现在，人工智能模型可以更快地进行训练，并且结果惊人地准确。&lt;/p&gt;
 &lt;p&gt;存储成本降低和计算资源更加充足的因素对人工智能来说是一种福音。现在可以训练更加复杂的模型。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image.png" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e22e6b626f3d45d48d1bf6fe143a83e2~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=1336&amp;h=630&amp;s=131012&amp;e=png&amp;b=ffffff"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;这对深度学习算法尤其是真实。例如，一种称为卷积神经网络（CNNs）的深度学习技术在图像检测方面变得非常流行。随着时间的推移，越来越深层次的神经网络被创建出来。现在，人工智能系统在检测对象方面已经超过了人类。&lt;/p&gt;
 &lt;p&gt;随着人工智能系统变得更加准确，它们变得越来越受欢迎。这推动了循环行为，越来越多的企业将人工智能应用于数字化转型议程中。&lt;/p&gt;
 &lt;h2&gt;云计算的进步&lt;/h2&gt;
 &lt;p&gt;完美“数据”风暴的第五个因素是云计算的崛起。云计算是计算和存储资源按需提供的服务。典型的公共云服务提供商包括亚马逊（AWS）、微软（Azure）和谷歌（GCP）等大型科技公司。云计算消除了在组织数据中心托管大型服务器的需求。根据在云中订阅的服务，组织还可以减少对软件和硬件维护的依赖。云以非常经济的价格提供了大量的按需服务。自2010年以来，云计算领域不断攀升。2010年，全球公共云的支出约为770亿美元，而到2020年已经达到约4410亿美元。云计算还促使了数字原生业务（DNB）的崛起。它推动了Uber、Deliveroo、TikTok和Instagram等组织的兴起，仅举几例。&lt;/p&gt;
 &lt;p&gt;云计算对数据而言是一种福音。随着云计算的兴起，数据现在可以以较低的成本存储。云提供的相对无限的计算能力意味着能够迅速转换数据。云计算还提供创新的数据平台，可以轻松点击利用。&lt;/p&gt;
 &lt;p&gt;这五个因素在一个适当的时刻相互交汇，挑战了现有的数据架构模式。完美“数据”风暴促使了一个以大数据为重点的新数据架构范式的崛起，即数据湖。&lt;/p&gt;
 &lt;h1&gt;探讨数据湖时代&lt;/h1&gt;
 &lt;p&gt;数据湖的起源可以追溯到2004年。在2004年，Google的研究人员Jeffery Dean和Sanjay Ghemawat发表了一篇题为《MapReduce: Simplified Data Processing on Large Clusters》的论文。这篇论文奠定了一项新技术的基础，演变成了Hadoop，最初的作者是Doug Cutting和Mike Cafarella。 Hadoop后来被整合到了Apache Software Foundation，这是一个分散的开源开发者社区。Hadoop一直是Apache生态系统中最重要的开源项目之一。 Hadoop基于一个简单的概念——分而治之。这个想法包括三个步骤：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;将数据分发到多个文件，并将它们分布在集群中的各个节点上。&lt;/li&gt;
  &lt;li&gt;使用计算节点在每个集群节点上本地处理数据。&lt;/li&gt;
  &lt;li&gt;使用与每个节点通信并聚合数据以得到最终输出的编排器。 多年来，这个概念逐渐获得了推广，为分析出现了一种新的范式。这种架构范式就是数据湖范式。一个典型的数据湖模式可以在下图中描述：&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;  &lt;img alt="image.png" src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/32554f280e404551b5474fa6371d61a3~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=1315&amp;h=575&amp;s=152193&amp;e=png&amp;b=fefefe"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;这种模式解决了企业数据仓库（EDW）模式中普遍存在的挑战。数据湖架构模式能够提供的优势是显而易见的。其主要优势包括：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;数据湖适用于结构化和非结构化数据。Hadoop生态系统最初是为存储和处理JSON、文本和图像等数据格式而开发的。而EDW模式并未设计用于存储或分析这些数据类型。&lt;/li&gt;
  &lt;li&gt;数据湖模式可以以相对较低的成本处理大量数据。数据湖可以存储和处理的数据量可达高TB或PB级。而EDW模式发现难以高效地存储和处理这么大量的数据。&lt;/li&gt;
  &lt;li&gt;数据湖能更好地应对快速变化的业务需求。不断发展的人工智能技术可以更好地利用数据湖。&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;尽管这种模式得到了广泛采用，但它也有自己的挑战。与这种模式相关的一些挑战包括：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;数据湖很容易变成数据沼泽。数据湖接收各种形式的数据，并以其原始形式存储。其理念是首先摄取数据，然后再弄清楚如何处理它。这导致治理方面容易失控，对数据湖的治理变得具有挑战性。没有适当的数据治理，数据就会在数据湖的各个地方迅速增多，很快就变成了数据沼泽。&lt;/li&gt;
  &lt;li&gt;数据湖在技术快速发展方面也面临挑战。数据湖范式主要依赖于开源软件。开源软件迅速演变成难以管理的庞然大物。该软件主要由社区驱动，缺乏适当的企业支持。这导致了大量的维护开销和实施复杂性。企业需要的许多功能在开源软件中缺失，例如健壮的安全框架。&lt;/li&gt;
  &lt;li&gt;数据湖更注重于AI的支持，而非BI。随着开源软件演变，更多的关注点集中在支持人工智能上。人工智能正在经历自己的发展过程，并随着Hadoop的潮涌而起伏。而BI被视为过时，因为它已经成熟了。&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;很快就变得明显，单独采用数据湖模式在长期内是不可持续的。需要一种新的范式将这两种模式融合起来。&lt;/p&gt;
 &lt;h2&gt;介绍数据湖仓范式&lt;/h2&gt;
 &lt;p&gt;在2006年，英国数学家Clive Humbly创造了如今著名的短语：“数据是新石油。” 这就像透过水晶球，窥探未来。数据是组织的生命线。竞争优势取决于组织如何使用数据。在这个数字化转型的时代，数据管理至关重要。越来越多的组织正在拥抱数字化转型计划，而数据是这些转型的核心。&lt;/p&gt;
 &lt;p&gt;正如前面讨论的，企业数据仓库（EDW）和数据湖的范式在它们的时代是合适的。它们有它们的优势和挑战。需要出现一种新的范式，它在核心上是有纪律的，而在边缘上是灵活的。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image.png" src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5c7075048c364b8ca8782ca51e6464ad~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=1341&amp;h=869&amp;s=306240&amp;e=png&amp;b=fefefe"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;新的数据架构范式被称为数据湖仓。它努力将数据湖和企业数据仓库（EDW）这两种范式的优势结合起来，同时尽量减少它们的挑战。&lt;/p&gt;
 &lt;p&gt;一个经过适当架构的数据湖仓提供了四个关键优势。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image.png" src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8d07679a47104f3ea2d01948d3f10c5f~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=683&amp;h=561&amp;s=103106&amp;e=png&amp;b=ffffff"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;它从结构化和非结构化数据中获取洞察：数据湖仓架构应能够存储、转换和整合结构化和非结构化数据。它应能够将它们融合在一起，实现从数据中提取有价值洞察。&lt;/p&gt;
 &lt;p&gt;它满足组织的不同角色需求：数据是一道供不同角色品尝的美食。数据湖仓应能够满足这些角色的需求。数据湖仓满足一系列组织角色的需求，满足他们对洞察的要求。数据科学家应该有一个测试他们假设的场所。分析师应该能够使用他们选择的工具分析数据，业务用户应该能够准确及时地获得他们的报告。它使数据分析民主化。&lt;/p&gt;
 &lt;p&gt;它促进了强大治理框架的采用：数据湖架构模式的主要挑战是缺乏强大的治理框架。数据湖很容易变成数据沼泽。相比之下，企业数据仓库架构由于内容太少而受到过多治理的限制。数据湖仓架构力求在治理方面取得平衡。它力求为正确的数据类型实现适当的治理，并使正确的利益相关方能够访问。&lt;/p&gt;
 &lt;p&gt;它利用云计算：数据湖仓架构需要具备灵活性和创新性。该模式需要适应不断变化的组织需求，缩短数据转化为洞察的时间。为了实现这种灵活性，采用云计算技术是必不可少的。云计算平台提供所需的创新性。它提供了具有可伸缩性和灵活性的适当技术堆栈，并满足现代数据分析平台的需求。&lt;/p&gt;
 &lt;p&gt;数据湖仓范式解决了企业数据仓库和数据湖范式所面临的挑战。然而，它确实有自己一套需要管理的挑战。其中一些挑战包括：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;   &lt;strong&gt;架构复杂性：&lt;/strong&gt; 鉴于数据湖仓模式融合了企业数据仓库和数据湖模式，它必然会具有相当一部分的架构复杂性。这种复杂性体现在实现该模式所需的多个组件中。架构模式是一种交换，因此在架构复杂性与潜在业务收益之间进行仔细权衡至关重要。数据湖仓架构需要小心谨慎地走这条路。&lt;/li&gt;
  &lt;li&gt;   &lt;strong&gt;全面数据治理的需要：&lt;/strong&gt; 与数据湖范式相关的挑战并没有随着数据湖仓范式的出现而神奇地消失。数据湖最大的问题之一是它容易变成数据沼泽。随着数据湖仓在范围和复杂性上的增长，缺乏全面治理框架是将数据湖仓变成沼泽的一种确切方式。&lt;/li&gt;
  &lt;li&gt;   &lt;strong&gt;在灵活性与纪律之间平衡：&lt;/strong&gt; 数据湖仓范式力求在核心上具有灵活性，以及在边缘上具有适应不断变化的业务需求的灵活性。它运作的原则是在核心上保持纪律，在边缘上保持灵活性。实现这一目标是一种谨慎的平衡行为，清晰地定义灵活性的界限和纪律的严格性。数据湖仓的管理者在确保这种平衡方面发挥着关键作用。&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;让我们回顾一下我们在本章讨论的内容。&lt;/p&gt;
 &lt;h1&gt;总结&lt;/h1&gt;
 &lt;p&gt;这一章是关于新范式的起源。了解起源是重要的，这样我们就能理解前任的不足之处以及新框架如何发展以解决这些不足之处。了解导致这种演变的驱动因素也很重要。技术领域的其他发展，如存储、云计算和人工智能，对数据架构产生了涟漪效应。在这一章中，我们首先探讨了长时间占主导地位的EDW架构模式。然后，我们探讨了导致完美的“数据”风暴的因素。随后，本章深入探讨了数据湖架构模式。接着讨论了新的架构范式——数据湖仓的需求。最后，本章总结了新架构范式的关键优势。&lt;/p&gt;
 &lt;p&gt;下一章旨在深入研究数据湖仓架构的组件。&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/62883-%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90-%E6%A8%A1%E5%BC%8F</guid>
      <pubDate>Sun, 19 Nov 2023 16:44:39 CST</pubDate>
    </item>
    <item>
      <title>使用 LAL 收集并分析 Nginx access log</title>
      <link>https://itindex.net/detail/62875-lal-%E5%88%86%E6%9E%90-nginx</link>
      <description>&lt;p&gt;本篇文章演示如何将 Nginx access log 收集到 SkyWalking 中，并通过 LAL 进行指标分析&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;本文由社区贡献者    &lt;strong&gt;魏翔&lt;/strong&gt; 撰写, SkyWalking 社区帐号发表&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;h2&gt;背景介绍&lt;/h2&gt;
 &lt;p&gt;Nginx access log 中包含了丰富的信息，例如：日志时间、状态码、响应时间、body 大小等。通过收集并分析 access log，我们可以实现对 Nginx 中接口状态的监控。&lt;/p&gt;
 &lt;p&gt;在本案例中，将由   &lt;a href="https://fluentbit.io/"&gt;fluent-bit&lt;/a&gt; 收集 access log，并通过 HTTP 将日志信息发送给 SkyWalking OAP Server 进行进一步的分析。&lt;/p&gt;
 &lt;h2&gt;环境准备&lt;/h2&gt;
 &lt;p&gt;实验需要的 Nginx 及 Fluent-bit 相关配置文件都被上传到了  &lt;a href="https://github.com/weixiang1862/nginx-fluent-bit"&gt;Github&lt;/a&gt;，有需要的读者可以自行 git clone 并通过 docker compose 启动，本文中将介绍配置文件中几个关键点。&lt;/p&gt;
 &lt;h3&gt;Nginx日志格式配置&lt;/h3&gt;
 &lt;p&gt;LAL 目前支持 JSON、YAML 及 REGEX 日志解析，为了方便获取到日志中的指标字段，我们将 Nginx 的日志格式定义为 JSON.&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;    http {
        ...
        ...

        log_format  main  &amp;apos;{&amp;quot;remote_addr&amp;quot;: &amp;quot;$remote_addr&amp;quot;,&amp;apos;
                &amp;apos;&amp;quot;remote_user&amp;quot;: &amp;quot;$remote_user&amp;quot;,&amp;apos;
                &amp;apos;&amp;quot;request&amp;quot;: &amp;quot;$request&amp;quot;,&amp;apos;
                &amp;apos;&amp;quot;time&amp;quot;: &amp;quot;$time_iso8601&amp;quot;,&amp;apos;
                &amp;apos;&amp;quot;status&amp;quot;: &amp;quot;$status&amp;quot;,&amp;apos;
                &amp;apos;&amp;quot;request_time&amp;quot;:&amp;quot;$request_time&amp;quot;,&amp;apos;
                &amp;apos;&amp;quot;body_bytes_sent&amp;quot;: &amp;quot;$body_bytes_sent&amp;quot;,&amp;apos;
                &amp;apos;&amp;quot;http_referer&amp;quot;: &amp;quot;$http_referer&amp;quot;,&amp;apos;
                &amp;apos;&amp;quot;http_user_agent&amp;quot;: &amp;quot;$http_user_agent&amp;quot;,&amp;apos;
                &amp;apos;&amp;quot;http_x_forwarded_for&amp;quot;: &amp;quot;$http_x_forwarded_for&amp;quot;}&amp;apos;;

        access_log  /var/log/nginx/access.log  main;
        
        ...
        ...
    }
&lt;/code&gt;&lt;/pre&gt;
 &lt;h3&gt;Fluent bit Filter&lt;/h3&gt;
 &lt;p&gt;我们通过 Fluent bit 的 lua filter 进行日志格式的改写，将其调整为 SkyWalking 所需要的格式，record的各个字段含义如下：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;body：日志内容体&lt;/li&gt;
  &lt;li&gt;service：服务名称&lt;/li&gt;
  &lt;li&gt;serviceInstance：实例名称&lt;/li&gt;
&lt;/ul&gt;

 &lt;pre&gt;  &lt;code&gt;function rewrite_body(tag, timestamp, record)
    local newRecord = {}
    newRecord[&amp;quot;body&amp;quot;] = { json = { json = record.log } }
    newRecord[&amp;quot;service&amp;quot;] = &amp;quot;nginx::nginx&amp;quot;
    newRecord[&amp;quot;serviceInstance&amp;quot;] = &amp;quot;localhost&amp;quot;
    return 1, timestamp, newRecord
end
&lt;/code&gt;&lt;/pre&gt;
 &lt;h2&gt;OAP 日志分析&lt;/h2&gt;
 &lt;h3&gt;LAL定义&lt;/h3&gt;
 &lt;p&gt;在 filter 中，我们通过条件判断，只处理   &lt;code&gt;service=nginx::nginx&lt;/code&gt; 的服务，其他服务依旧走默认逻辑：&lt;/p&gt;
 &lt;p&gt;第一步，使用 json 指令对日志进行解析，解析的结果会被存放到 parsed 字段中，通过 parsed 字段我们可以获取 json 日志中的字段信息。&lt;/p&gt;
 &lt;p&gt;第二步，使用 timestamp 指令解析 parsed.time 并将其赋值给日志的 timestamp 字段，这里的 time 就是access log json 中的 time。&lt;/p&gt;
 &lt;p&gt;第三步，使用 tag 指令给日志打上对应的标签，标签的值依然可以通过 parsed 字段获取。&lt;/p&gt;
 &lt;p&gt;第四步，使用 metrics 指令从日志中提取出指标信息，我们共提取了四个指标：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;code&gt;nginx_log_count&lt;/code&gt;：Nginx 每次请求都会生成一条 access log，该指标可以帮助我们统计 Nginx 当前的请求数。&lt;/li&gt;
  &lt;li&gt;   &lt;code&gt;nginx_request_time&lt;/code&gt;：access log 中会记录请求时间，该指标可以帮助我们统计上游接口的响应时长。&lt;/li&gt;
  &lt;li&gt;   &lt;code&gt;nginx_body_bytes_sent&lt;/code&gt;：body 大小指标可以帮助我们了解网关上的流量情况。&lt;/li&gt;
  &lt;li&gt;   &lt;code&gt;nginx_status_code&lt;/code&gt;：状态码指标可以实现对状态码的监控，如果出现异常上涨可以结合 alarm 进行告警。&lt;/li&gt;
&lt;/ul&gt;
 &lt;pre&gt;  &lt;code&gt;rules:
  - name: default
    layer: GENERAL
    dsl: |
      filter {
        if (log.service == &amp;quot;nginx::nginx&amp;quot;) {
          json {
            abortOnFailure true
          }
    
          extractor {
            timestamp parsed.time as String, &amp;quot;yyyy-MM-dd&amp;apos;T&amp;apos;HH:mm:ssXXX&amp;quot;
            tag status: parsed.status
            tag remote_addr: parsed.remote_addr
      
            metrics {
              timestamp log.timestamp as Long
              labels service: log.service, instance: log.serviceInstance
              name &amp;quot;nginx_log_count&amp;quot;
              value 1
            }
            metrics {
              timestamp log.timestamp as Long
              labels service: log.service, instance: log.serviceInstance
              name &amp;quot;nginx_request_time&amp;quot;
              value parsed.request_time as Double
            }
            metrics {
              timestamp log.timestamp as Long
              labels service: log.service, instance: log.serviceInstance
              name &amp;quot;nginx_body_bytes_sent&amp;quot;
              value parsed.body_bytes_sent as Long
            }
            metrics {
              timestamp log.timestamp as Long
              labels service: log.service, instance: log.serviceInstance, status: parsed.status
              name &amp;quot;nginx_status_code&amp;quot;
              value 1
            }
          }
        }
      
        sink {
        }
      }
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;经过 LAL 处理后，我们已经可以在日志面板看到日志信息了，接下来我们将对 LAL 中提取的指标进行进一步分析：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image.png" src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7aa8e0193717483bb4d413919517baed~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=1904&amp;h=915&amp;s=543312&amp;e=png&amp;b=ffffff"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h3&gt;MAL定义&lt;/h3&gt;
 &lt;p&gt;在 MAL 中，我们可以对上一步 LAL 中提取的指标进行进一步的分析聚合，下面的例子里：&lt;/p&gt;
 &lt;p&gt;nginx_log_count、nginx_request_time、nginx_status_code 使用 sum 聚合函数处理，并使用 SUM 方式 downsampling，&lt;/p&gt;
 &lt;p&gt;nginx_request_time 使用 avg 聚合函数求平均值，默认使用 AVG 方式 downsampling。&lt;/p&gt;
 &lt;p&gt;完成聚合分析后，SkyWalking Meter System 会完成对上述指标的持久化。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;expSuffix: service([&amp;apos;service&amp;apos;], Layer.GENERAL)
metricPrefix: nginx
metricsRules:
  - name: cpm
    exp: nginx_log_count.sum([&amp;apos;service&amp;apos;]).downsampling(SUM)
  - name: avg_request_time
    exp: nginx_request_time.avg([&amp;apos;service&amp;apos;])
  - name: body_bytes_sent_count
    exp: nginx_body_bytes_sent.sum([&amp;apos;service&amp;apos;]).downsampling(SUM)
  - name: status_code_count
    exp: nginx_status_code.sum([&amp;apos;service&amp;apos;,&amp;apos;status&amp;apos;]).downsampling(SUM)
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;最后，我们便可以来到 SkyWalking UI 页面新建 Nginx 仪表板，使用刚刚 MAL 中定义的指标信息创建 Nginx Dashboard（也可以通过上文提到  &lt;a href="https://github.com/weixiang1862/nginx-fluent-bit"&gt;仓库&lt;/a&gt;中的 dashboard.json 直接导入测试）：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image.png" src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ab6eb416b5d3416fa91a6edc38a426c4~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=1913&amp;h=792&amp;s=380103&amp;e=png&amp;b=ffffff"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h2&gt;参考文档&lt;/h2&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;a href="https://docs.fluentbit.io/manual/pipeline/filters/lua"&gt;Fluent Bit lua Filter&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://skywalking.apache.org/docs/main/next/en/concepts-and-designs/lal/"&gt;Log Analysis Language&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://skywalking.apache.org/docs/main/next/en/concepts-and-designs/mal/"&gt;Meter Analysis Language&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/62875-lal-%E5%88%86%E6%9E%90-nginx</guid>
      <pubDate>Fri, 03 Nov 2023 16:58:59 CST</pubDate>
    </item>
    <item>
      <title>【线上故障分析】深入理解缓存预热</title>
      <link>https://itindex.net/detail/62848-%E7%BA%BF%E4%B8%8A-%E6%95%85%E9%9A%9C%E5%88%86%E6%9E%90-%E7%90%86%E8%A7%A3</link>
      <description>&lt;hr&gt;&lt;/hr&gt;
 &lt;h2&gt;theme: condensed-night-purple&lt;/h2&gt;
 &lt;p&gt;缓存不预热会怎么样？我帮大家淌了路。缓存不预热会导致系统接口性能下降，数据库压力增加，更重要的是导致我写了两天的复盘文档，在复盘会上被骂出了翔。&lt;/p&gt;
 &lt;h1&gt;悲惨的上线时刻&lt;/h1&gt;
 &lt;p&gt;事情发生在几年前，我刚毕业时，第一次使用缓存内心很激动。需求场景是虚拟商品页面需要向用户透出库存状态，提单时也需要校验库存状态是否可售卖。但是由于库存状态的计算包含较复杂的业务逻辑，耗时比较高，在500ms以上。如果要在商品页面透出库存状态那么商品页面耗时增加500ms，这几乎是无法忍受的事情。&lt;/p&gt;
 &lt;p&gt;如何实现呢？最合适的方案当然是缓存了，我当时设计的方案是如果缓存有库存状态直接读缓存，如果缓存查不到，则计算库存状态，然后加载进缓存，同时设定过期时间。何时写库存呢？ 答案是过期后，cache miss时重新加载进缓存。 由于计算逻辑较复杂，库存扣减等用户写操作没有同步更新缓存，但是产品认可库存状态可以有几分钟的状态不一致。为什么呢？&lt;/p&gt;
 &lt;p&gt;因为仓库有冗余库存，就算库存状态不一致导致超卖，也能容忍。同时库存不足以后，需要运营补充库存，而补充库存的时间是肯定比较长的。虽然补充库存完成几分钟后，才变为可售卖的，产品也能接受。 梳理完缓存的读写方案，我就沉浸于学习Redis的过程。&lt;/p&gt;
 &lt;p&gt;第一次使用缓存，我把时间和精力都放在Redis存储结构，Redis命令，Redis为什么那么快等方面的关注。如饥似渴的学习Redis知识。&lt;/p&gt;
 &lt;p&gt;直到上线阶段我也没有意识到系统设计的缺陷。&lt;/p&gt;
 &lt;p&gt;代码写的很快，测试验证也没有问题。然而上线过程中，就开始噼里啪啦的报警，开始我并没有想到报警这事和我有关。直到有人问我，“XXX，你是不是在上线库存状态的需求？”。&lt;/p&gt;
 &lt;p&gt;我人麻了，”怎么了，啥事”，我颤抖的问&lt;/p&gt;
 &lt;p&gt;“商品页面耗时暴涨，赶紧回滚”。一个声音传来&lt;/p&gt;
 &lt;p&gt;“我草”，那一瞬间，我的血压上涌，手心发痒，心跳加速，头皮发麻，颤抖的手不知道怎么在发布系统点回滚，“我没回滚过啊，咋回滚啊？”&lt;/p&gt;
 &lt;p&gt;“有降级开关吗”? 一个声音传来。&lt;/p&gt;
 &lt;p&gt;&amp;quot;没写...&amp;quot;。我回答的时候觉得自己真是二笔，为啥没加降级啊。（这也是复盘被骂的重要原因）&lt;/p&gt;
 &lt;p&gt;那么如何对缓存进行预热呢？&lt;/p&gt;
 &lt;h1&gt;如何预热缓存&lt;/h1&gt;
 &lt;h2&gt;灰度放量&lt;/h2&gt;
 &lt;p&gt;灰度放量实际上并不是缓存预热的办法，但是确实能避免缓存雪崩的问题。例如这个需求场景中，如果我没有放开全量数据，而是选择放量1%的流量。这样系统的性能不会有较大的下降，并且逐步放量到100%。&lt;/p&gt;
 &lt;p&gt;虽然这个过程中，没有主动同步数据到缓存，但是通过控制放量的节奏，保证了初始化缓存过程中，不会出现较大的耗时波动。&lt;/p&gt;
 &lt;p&gt;例如新上线的缓存逻辑，可以考虑逐渐灰度放量。&lt;/p&gt;
 &lt;h2&gt;扫描数据库刷缓存&lt;/h2&gt;
 &lt;p&gt;如果缓存维度是商品维度或者用户维度，可以考虑扫描数据库，提前预热部分数据到缓存中。&lt;/p&gt;
 &lt;p&gt;开发成本较高。除了开发缓存部分的代码，还需要开发扫描全表的任务。为了控制缓存刷新的进度，还需要使用线程池增加并发，使用限流器限制并发。这个方案的开发成本较高。&lt;/p&gt;
 &lt;h2&gt;通过数据平台刷缓存&lt;/h2&gt;
 &lt;p&gt;这是比较好的方式，具体怎么实现呢？&lt;/p&gt;
 &lt;p&gt;数据平台如果支持将数据库离线数据同步到Hive，Hive数据同步到Kafka，我们就可以编写Hive SQL，建立ETL任务。把业务需要被刷新的数据同步到Kafka中，再消费Kafka，把数据写入到缓存中。在这个过程中通过数据平台控制并发度，通过Kafka 分片和消费线程并发度控制 缓存写入的速率。&lt;/p&gt;
 &lt;p&gt;这个方案开发逻辑包括ETL 任务，消费Kafka写入缓存。这两部分的开发工作量不大。并且相比扫描全表任务，ETL可以编写更加复杂的SQL，修改后立即上线，无需自己控制并发、控制限流。在多个方面ETL刷缓存效率更高。&lt;/p&gt;
 &lt;p&gt;但是这个方案需要公司级别支持 多个存储系统之间可以进行数据同步。例如mysql、kafka、hive等。&lt;/p&gt;
 &lt;p&gt;除了首次上线，是否还有其他场景需要预热缓存呢？&lt;/p&gt;
 &lt;h1&gt;需要预热缓存的其他场景&lt;/h1&gt;
 &lt;h2&gt;如果Redis挂了，数据怎么办&lt;/h2&gt;
 &lt;p&gt;刚才提到上线前，一定要进行缓存预热。还有一个场景：假设Redis挂了，怎么办？全量的缓存数据都没有了，全部请求同时打到数据库，怎么办。&lt;/p&gt;
 &lt;p&gt;除了首次上线需要预热缓存，实际上如果缓存数据丢失后，也需要预热缓存。所以预热缓存的任务一定要开发的，一方面是上线前预热缓存，同时也是为了保证缓存挂掉后，也能重新预热缓存。&lt;/p&gt;
 &lt;h2&gt;假如有大量数据冷启动怎么办&lt;/h2&gt;
 &lt;p&gt;假如促销场景，例如春节抢红包，平时非活跃用户会在某个时间点大量打开App，这也会导致大量cache miss，进而导致雪崩。 此时就需要提前预热缓存了。具体的办法，可以考虑使用ETL任务。离线加载大量数据到Mafka，然后再同步到缓存。&lt;/p&gt;
 &lt;h1&gt;总结&lt;/h1&gt;
 &lt;ol&gt;
  &lt;li&gt;一定要预热缓存，不然线上接口性能和数据库真的扛不住。&lt;/li&gt;
  &lt;li&gt;可以通过灰度放量，扫描全表、ETL数据同步等方式预热缓存&lt;/li&gt;
  &lt;li&gt;Redis挂了，大量用户冷启动的促销场景等场景都需要提前预热缓存。&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/62848-%E7%BA%BF%E4%B8%8A-%E6%95%85%E9%9A%9C%E5%88%86%E6%9E%90-%E7%90%86%E8%A7%A3</guid>
      <pubDate>Tue, 12 Sep 2023 10:01:07 CST</pubDate>
    </item>
    <item>
      <title>Node.js 内存溢出OOM分析</title>
      <link>https://itindex.net/detail/62835-node-js-%E5%86%85%E5%AD%98</link>
      <description>Node.js 内存飙涨以及 OOM 的问题，只要业务流量稍微复杂，一般都会遇到。如果是堆内内存，在 OOM 之前可以打一个 Heap Profiling 进行分析，如果是 OOM 之后，可以利用 llnode 对 corefile 进行分析，但如果是堆外内存飙涨呢？这一块内存通过 Chrome Devtool 工具是分析不出来的。
     
     &lt;div&gt;  &lt;br /&gt;&lt;/div&gt; &lt;div&gt;一年前看到内网有个团队做了个工具，叫做 andb，目前已经开源了，github.com/noslate-project/andb，使用它可以帮助我们找到堆外内存的泄漏源，分析起来尽管也不是很轻松，但在频繁 OOM 的场景下，还是有着不可缺失的重要价值的。它的内存分析思路大概是这样的：&lt;/div&gt; &lt;div&gt;  &lt;br /&gt;&lt;/div&gt; &lt;div&gt;1）找到 RSS 区的内存，通过 list 指令将每个内存 block 都打印出来，可以看到对应的地址区间以及大小&lt;/div&gt; &lt;div&gt;2）找到大内存地址块，使用 walk 指令打印详细的对象占用情况&lt;/div&gt; &lt;div&gt;3）通过 find ref 相关操作，一步步将大对象的父对象引用给挖掘出来，最后大概率可以找到业务代码的异常位置&lt;/div&gt; &lt;div&gt;  &lt;br /&gt;&lt;/div&gt; &lt;div&gt;这个操作是耗耐心，需要一个个地去向上排查内存引用堆栈，直到溯源到业务逻辑代码，在工具的体验上，我觉得还有机会做到可视化+自动化分析的，跟他们提过这个问题，只不过当下 Node.js 在业务的地位这几年肉眼可见地坍缩，人力投入也得不到保障，再加上这个工具场景偏小，真的要把体验做起来，还是有难度的。&lt;/div&gt; &lt;div&gt;  &lt;br /&gt;&lt;/div&gt; &lt;div&gt;如果你的业务是 Node.js 支撑，而且流量比较大，经常看到部分机器的内存在抖动，可以尝试下 andb，它有 lldb 和 gdb 两种模式，无论是堆内内存还是堆外内存的分析体验，相比市面上开源的其他工具，还是要好上不少的。#Web技术#&lt;/div&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/62835-node-js-%E5%86%85%E5%AD%98</guid>
      <pubDate>Wed, 23 Aug 2023 19:35:30 CST</pubDate>
    </item>
    <item>
      <title>【Redis故障排查】「连接失败问题排查和解决」带你深入分析一下Redis阻塞原因以及问题排查方案指南</title>
      <link>https://itindex.net/detail/62799-redis-%E9%97%AE%E9%A2%98-%E5%92%8C%E8%A7%A3</link>
      <description>&lt;h1&gt;Redis阻塞原因以及问题排查&lt;/h1&gt;
 &lt;p&gt;尽管我们在日常工作中经常使用Redis作为数据库的缓存，以大大减轻数据库压力并提升用户体验，但Redis也可能出现阻塞情况，导致整个系统变慢，进而影响用户体验。&lt;/p&gt;
 &lt;p&gt;因此，在面对Redis阻塞的情况下，我们可以从以下七个方面进行全面的分析，以确定造成Redis阻塞的具体原因。
  &lt;img alt="&amp;#22312;&amp;#36825;&amp;#37324;&amp;#25554;&amp;#20837;&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/cf73f59188694120880a5563dab722cf~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h2&gt;慢查询&lt;/h2&gt;
 &lt;p&gt;因为Redis是单线程的，所以如果出现大量的慢查询，可能会导致redis-server阻塞，可以通过slowlog get n 获取慢日志查看详细情况，如下所示。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;&amp;gt; slowlog get 3
34
1688630099
14659
LPOP
Automatic:Plan:wait:RestoreList
192.168.0.168:17777

33
1688608199
12247
LPOP
Automatic:Plan:process:RestoreList
192.168.0.168:61399

32
1688562824
15059
LPOP
Automatic:Plan:process:RestoreList
192.168.0.168:56006
&lt;/code&gt;&lt;/pre&gt;
 &lt;h3&gt;查看慢查询配置&lt;/h3&gt;
 &lt;p&gt;在Redis中，慢查询日志是用来记录执行时间超过阈值的命令的。可以通过配置慢查询相关的参数来控制记录的条件和日志的保存位置。&lt;/p&gt;
 &lt;p&gt;可以使用  &lt;code&gt;CONFIG GET slowlog-*&lt;/code&gt;命令来查看现有的Redis配置。这个命令用于获取Redis慢查询日志的相关配置。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;CONFIG GET slowlog-*
slowlog-log-slower-than
10000
slowlog-max-len
128
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;使用  &lt;code&gt;CONFIG GET slowlog-*&lt;/code&gt;命令可以获取与慢查询日志有关的配置项及其对应的值。该命令会返回一个列表，列表中包含了以  &lt;code&gt;slowlog-*&lt;/code&gt;开头的配置项的名称和对应的值。&lt;/p&gt;
 &lt;h4&gt;参数介绍分析&lt;/h4&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;code&gt;slowlog-log-slower-than&lt;/code&gt;是Redis的一个配置项，用于设置慢查询日志的阈值。它表示执行时间超过该阈值的命令会被记录到慢查询日志中。在您提供的示例中，   &lt;code&gt;slowlog-log-slower-than&lt;/code&gt;被配置为10000，单位是微秒（μs），即10毫秒。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;这意味着当一个命令的执行时间超过10毫秒时，它将被Redis记录到慢查询日志中。&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;code&gt;slowlog-max-len&lt;/code&gt;是Redis的另一个配置项，表示慢查询日志的最大长度。它决定了慢查询日志中可以保存的记录数量。在您提供的示例中，   &lt;code&gt;slowlog-max-len&lt;/code&gt;被配置为128，即最多保存128条慢查询日志记录。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;当慢查询日志中的记录达到最大长度后，新的慢查询会替换掉最旧的记录。&lt;/p&gt;
 &lt;p&gt;通过配置这些参数，您可以根据实际需求，灵活地设置Redis的慢查询日志的阈值和最大长度。这样可以帮助您及时发现影响性能的慢查询操作，并对其进行优化。&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;注意，执行   &lt;code&gt;CONFIG GET slowlog-*&lt;/code&gt;命令需要具备访问Redis配置的权限。如果您没有对应的权限，将无法执行该命令。&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;h2&gt;bigkey大对象&lt;/h2&gt;
 &lt;p&gt;大对象（bigkey）可能导致以下问题：
  &lt;img alt="&amp;#22312;&amp;#36825;&amp;#37324;&amp;#25554;&amp;#20837;&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6ed9ade79f544741a7c5ce004e09579d~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;
   &lt;p&gt;    &lt;strong&gt;内存空间不均衡&lt;/strong&gt;：    &lt;strong&gt;在Redis Cluster中，大对象会导致节点的内存使用不均衡。一些节点可能会存储更多的大对象，而另一些节点可能只保存较小的键值对，从而导致内存分配不平衡。&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;    &lt;strong&gt;超时阻塞&lt;/strong&gt;：    &lt;strong&gt;由于Redis是单线程的，处理大对象会占用更多的处理时间，可能导致其他操作的阻塞。如果一个大对象的存取操作耗时较长，那么有可能会造成其他请求的延迟或阻塞&lt;/strong&gt;。&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;    &lt;strong&gt;网络阻塞&lt;/strong&gt;：    &lt;strong&gt;获取大对象时会产生大量网络流量，尤其在分布式环境中。如果每次获取大对象的操作都会涉及传输大量数据，可能会导致网络拥塞或延迟&lt;/strong&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
 &lt;h3&gt;性能问题影响&lt;/h3&gt;
 &lt;blockquote&gt;
  &lt;p&gt;   &lt;strong&gt;如果一个大对象几乎不会被访问到，那么对性能的影响相对较小，主要存在内存空间不均衡的问题。但是，如果一个大对象是一个热点key（频繁访问），它将对系统的性能产生重大的影响。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;h4&gt;拆分键值&lt;/h4&gt;
 &lt;p&gt;需要根据实际情况来评估和管理大对象，如果一个大对象经常被访问，可能需要将其分解成多个小的键值对，或者使用其他数据结构来存储和处理该对象，以减轻Redis的负担。&lt;/p&gt;
 &lt;h4&gt;集群分片&lt;/h4&gt;
 &lt;p&gt;同样，可以考虑使用Redis的分片、集群等功能来均衡内存使用，并增加系统的扩展性和容错性。&lt;/p&gt;
 &lt;p&gt;可以通过  &lt;code&gt;redis-cli -h {ip} -p {port} bigkeys&lt;/code&gt;发现大对象。&lt;/p&gt;
 &lt;hr&gt;&lt;/hr&gt;
 &lt;h2&gt;swap热交换&lt;/h2&gt;
 &lt;h3&gt;前提介绍&lt;/h3&gt;
 &lt;p&gt;首先，Redis在版本3.0之后，从内部代码中删除了对交换空间（  &lt;strong&gt;swap space&lt;/strong&gt;）的支持和依赖。因此，Redis 3.0及更高版本不再使用交换空间进行内存交换。&lt;/p&gt;
 &lt;p&gt;在早期版本的Redis中，如果Redis实例的内存超过可用最大内存限制，操作系统会使用交换空间进行内存交换。然而，这种行为被发现会导致严重的性能下降，并且将Redis带入不稳定的状态，因此在Redis 3.0中已经放弃了对交换空间的依赖。&lt;/p&gt;
 &lt;h4&gt;识别Redis进程号&lt;/h4&gt;
 &lt;pre&gt;  &lt;code&gt;redis-cli info server | grep process_id
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;根据进程号查询内存交换信息&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;cat /proc/{process_id}/smaps | grep Swap
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;如果交换量都是0kb或者个别的4kb都是正常现象&lt;/p&gt;
 &lt;h3&gt;官方建议&lt;/h3&gt;
 &lt;p&gt;Redis官方建议用户合理配置最大可用内存，并确保Redis实例具有足够的可用内存来容纳数据和处理操作，以避免内存交换问题。这样做可以提高Redis的性能和稳定性，确保良好的的用户体验。&lt;/p&gt;
 &lt;p&gt;我们可以使用指标&amp;quot;used_memory&amp;quot;来了解Redis当前使用的内存情况。通过监测used_memory的值，可以确定Redis是否接近或超过了最大可用内存的阈值，从而及时做出相应的调整。&lt;/p&gt;
 &lt;p&gt;为了避免内存交换问题，需要合理配置Redis的内存限制，并确保Redis实例有足够的可用内存来容纳数据和处理操作。这可以通过监控内存使用情况、调整Redis的缓存策略、设置合理的最大内存限制等手段来实现。&lt;/p&gt;
 &lt;h4&gt;分布式Redis集群&lt;/h4&gt;
 &lt;p&gt;为了提高性能和避免内存交换问题，建议单独部署Redis实例或使用分布式Redis集群，并且配置足够的内存，以便满足实际需求和预防潜在的性能问题。&lt;/p&gt;
 &lt;hr&gt;&lt;/hr&gt;
 &lt;h2&gt;Fork子进程&lt;/h2&gt;
 &lt;p&gt;当Redis执行持久化操作（RDB生成或AOF重写）时，会使用fork系统调用创建一个子进程来完成工作。在执行fork操作时，子进程会复制父进程的内存空间，包括所有数据集的内存表，这会导致fork操作的耗时与内存量（数据集）相关。&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;根据经验，每GB内存的fork操作耗时大约在20毫秒左右。   &lt;strong&gt;因此，为了避免长时间的阻塞，需要严格控制每个Redis实例可使用的最大内存在10GB以内&lt;/strong&gt;，以减少fork操作的执行时间。&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;h3&gt;查看对应INFO STATS命令分析&lt;/h3&gt;
 &lt;p&gt;可以通过使用Redis的INFO STATS命令来获取  &lt;strong&gt;lastest_fork_usec&lt;/strong&gt;指标，它表示Redis最近一次fork操作的耗时。通过监控这个指标，可以了解每次fork操作的耗时情况，并根据需要采取适当的措施，如降低fork操作的频率或调整内存配置，以减少阻塞情况的发生。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt; INFO STATS
# Stats
total_connections_received:513883995
total_commands_processed:4448904052
instantaneous_ops_per_sec:43
total_net_input_bytes:524046943608
total_net_output_bytes:562840058709
instantaneous_input_kbps:2.20
instantaneous_output_kbps:0.24
rejected_connections:0
sync_full:0
sync_partial_ok:0
sync_partial_err:0
expired_keys:7725231
expired_stale_perc:0.00
expired_time_cap_reached_count:0
evicted_keys:0
keyspace_hits:1361084505
keyspace_misses:311319261
pubsub_channels:28
pubsub_patterns:6
latest_fork_usec:15086
migrate_cached_sockets:0
slave_expires_tracked_keys:0
active_defrag_hits:0
active_defrag_misses:0
active_defrag_key_hits:0
active_defrag_key_misses:0
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;总之，了解fork操作对性能的影响以及控制内存使用是重要的，以确保Redis实例的稳定性和良好的性能。&lt;/p&gt;
 &lt;hr&gt;&lt;/hr&gt;
 &lt;h2&gt;AOF刷盘阻塞&lt;/h2&gt;
 &lt;p&gt;当Redis开启AOF（Append-Only File）持久化的同时，文件刷盘操作通常每秒执行一次。但是，当硬盘压力过大时，执行  &lt;code&gt;fsync&lt;/code&gt;操作需要等待写入完成，这可能会导致延迟。&lt;/p&gt;
 &lt;h3&gt;INFO PERSISTENCE&lt;/h3&gt;
 &lt;p&gt;要查看Redis日志或使用  &lt;code&gt;INFO PERSISTENCE&lt;/code&gt;命令可以获取到  &lt;code&gt;aof_delayed_fsync&lt;/code&gt;指标，它表示延迟执行的  &lt;code&gt;fsync&lt;/code&gt;操作的次数。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;&amp;gt; INFO PERSISTENCE
# Persistence
loading:0
rdb_changes_since_last_save:1062773605
rdb_bgsave_in_progress:0
rdb_last_save_time:1672120744
rdb_last_bgsave_status:ok
rdb_last_bgsave_time_sec:-1
rdb_current_bgsave_time_sec:-1
rdb_last_cow_size:0
aof_enabled:1
aof_rewrite_in_progress:0
aof_rewrite_scheduled:0
aof_last_rewrite_time_sec:5
aof_current_rewrite_time_sec:-1
aof_last_bgrewrite_status:ok
aof_last_write_status:ok
aof_last_cow_size:4104192
aof_current_size:669972573
aof_base_size:366781665
aof_pending_rewrite:0
aof_buffer_length:0
aof_rewrite_buffer_length:0
aof_pending_bio_fsync:0
aof_delayed_fsync:0
&lt;/code&gt;&lt;/pre&gt;
 &lt;hr&gt;&lt;/hr&gt;
 &lt;h2&gt;Redis的输入/输出缓冲区也可能导致阻塞&lt;/h2&gt;
 &lt;p&gt;当Redis无法及时处理写入数据或无法将数据及时发送到磁盘，输入/输出缓冲区可能会增长，并导致阻塞。为了避免这种情况，可以根据实际情况调整Redis的相关配置参数，如  &lt;code&gt;client-output-buffer-limit&lt;/code&gt;和  &lt;code&gt;aof-rewrite-incremental-fsync&lt;/code&gt;等，以控制输入/输出缓冲区的大小和行为。&lt;/p&gt;
 &lt;p&gt;综上所述，理解和监控  &lt;code&gt;aof_delayed_fsync&lt;/code&gt;指标以及适时调整Redis的相关配置参数，可以帮助应对可能导致阻塞的硬盘压力和输入/输出缓冲区的问题。&lt;/p&gt;
 &lt;h3&gt;输入缓冲区&lt;/h3&gt;
 &lt;p&gt;要查看输入缓冲区的总容量(  &lt;code&gt;qbuf&lt;/code&gt;)和剩余容量(  &lt;code&gt;qbuf-free&lt;/code&gt;)，你可以使用Redis的  &lt;code&gt;INFO MEMORY&lt;/code&gt;命令。&lt;/p&gt;
 &lt;p&gt;在Redis客户端中，执行以下步骤：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;
   &lt;p&gt;使用    &lt;code&gt;INFO MEMORY&lt;/code&gt;命令获取内存使用情况的相关信息。&lt;/p&gt;
   &lt;pre&gt;    &lt;code&gt;INFO MEMORY
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;在命令输出中，你会看到关于内存使用的详细信息，包括输入缓冲区的总容量和剩余容量。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
 &lt;pre&gt;  &lt;code&gt;   # Memory
   used_memory: 123456
   used_memory_human: 120.57K
   used_memory_rss: 456789
   used_memory_rss_human: 445.68K
   used_memory_peak: 789012
   used_memory_peak_human: 770.67K
&lt;/code&gt;&lt;/pre&gt;
 &lt;ul&gt;
  &lt;li&gt;参数介绍
   &lt;ul&gt;
    &lt;li&gt;     &lt;code&gt;used_memory&lt;/code&gt;字段表示Redis使用的总内存。&lt;/li&gt;
    &lt;li&gt;     &lt;code&gt;used_memory_human&lt;/code&gt;字段表示Redis使用的总内存的人类可读形式。&lt;/li&gt;
    &lt;li&gt;     &lt;code&gt;used_memory_rss&lt;/code&gt;字段表示Redis的物理内存占用。&lt;/li&gt;
    &lt;li&gt;     &lt;code&gt;used_memory_rss_human&lt;/code&gt;字段表示Redis的物理内存占用的人类可读形式。&lt;/li&gt;
    &lt;li&gt;     &lt;code&gt;used_memory_peak&lt;/code&gt;字段表示Redis使用的内存峰值。&lt;/li&gt;
    &lt;li&gt;     &lt;code&gt;used_memory_peak_human&lt;/code&gt;字段表示Redis使用的内存峰值的人类可读形式。
注意：     &lt;code&gt;qbuf&lt;/code&gt;和     &lt;code&gt;qbuf-free&lt;/code&gt;字段在这个输出中可能不显式地列出，但是你可以根据上面提到的字段来推断Redis的输入缓冲区容量。如果     &lt;code&gt;used_memory_peak&lt;/code&gt;字段和     &lt;code&gt;used_memory_peak_human&lt;/code&gt;字段的值为0，那么说明没有分配查询缓冲区，每次只能处理一个命令。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;redis为每个客户端分配了输入缓冲区，会将客户端发送命令临时保存，然后取出来执行。&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;qbuf 表示总容量（0 表示没有分配查询缓冲区）&lt;/li&gt;
  &lt;li&gt;qbuf-free 表示剩余容量（0 表示没有剩余空间）；&lt;/li&gt;
&lt;/ul&gt;
 &lt;blockquote&gt;
  &lt;p&gt;   &lt;strong&gt;大小不能超过 1G，当大小超过1G时会将客户端自动关闭，输入缓冲区不受 maxmemory 限制&lt;/strong&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;h4&gt;Blocked_Clients&lt;/h4&gt;
 &lt;p&gt;当大量的 key 进入输入缓冲区且无法被消费时，即可造成 redis 阻塞；通过 client list 命令可定位发生阻塞的客户端；通过 info clients 命令的 blocked_clients 参数可以查看到当前阻塞的命令。&lt;/p&gt;
 &lt;h3&gt;输出缓冲区&lt;/h3&gt;
 &lt;p&gt;redis-server端实现的一个读取缓冲区，redis-server 在接收到客户端的请求后，把获取结果写入到 client buffer 中，而不是直接发送给客户端。从而可以继续处理客户端的其他请求，这样异步处理方式使 redis-server 不会因为网络原因阻塞其他请求的处理。&lt;/p&gt;
 &lt;hr&gt;&lt;/hr&gt;
 &lt;h2&gt;网络问题&lt;/h2&gt;
 &lt;h3&gt;连接拒绝&lt;/h3&gt;
 &lt;p&gt;当出现网络闪断或Redis连接拒绝时，你可以使用以下方法来解决问题：&lt;/p&gt;
 &lt;h4&gt;网络闪断：&lt;/h4&gt;
 &lt;ol&gt;
  &lt;li&gt;检查网络连接是否正常。确保网络连接稳定，没有断开或中断。&lt;/li&gt;
  &lt;li&gt;检查网络带宽是否耗尽。如果网络带宽达到极限，可能会导致闪断。你可以联系网络管理员或提供商以解决带宽问题。&lt;/li&gt;
&lt;/ol&gt;
 &lt;h4&gt;Redis连接拒绝：&lt;/h4&gt;
 &lt;ol&gt;
  &lt;li&gt;确认maxclients设置。在Redis配置文件(redis.conf)中，找到   &lt;code&gt;maxclients&lt;/code&gt;设置项，确保其值足够大以容纳你的并发连接数。你可以通过修改配置文件来增加maxclients的值。&lt;/li&gt;
  &lt;li&gt;检查已建立的连接数。使用Redis的   &lt;code&gt;INFO STATS&lt;/code&gt;命令可以获取有关连接的统计信息。关注   &lt;code&gt;rejected_connections&lt;/code&gt;字段的值，如果该值增加，说明已经达到了maxclients的限制。你可以通过增加maxclients或优化现有连接以减少连接数来解决此问题。&lt;/li&gt;
&lt;/ol&gt;
 &lt;h3&gt;连接溢出&lt;/h3&gt;
 &lt;h4&gt;进程限制&lt;/h4&gt;
 &lt;p&gt;进程可打开的最大文件数控制(  &lt;code&gt;ulimit -n&lt;/code&gt;)是限制系统中同时存在的文件描述符数量的设置。对于Redis来说，高并发情况下需要处理大量的连接，因此需要增大该值。&lt;/p&gt;
 &lt;h5&gt;增大Redis连接&lt;/h5&gt;
 &lt;p&gt;建议增大  &lt;code&gt;ulimit -n&lt;/code&gt;的值来满足Redis的连接需求。你可以通过修改系统的配置文件来设置该值。例如，在Linux系统中，可以编辑  &lt;code&gt;/etc/security/limits.conf&lt;/code&gt;文件或  &lt;code&gt;/etc/sysctl.conf&lt;/code&gt;文件，并添加如下配置来增大该值：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;# /etc/security/limits.conf
*    soft    nofile    65535
*    hard    nofile    65535

# /etc/sysctl.conf
fs.file-max = 65535
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;然后重启系统或使用  &lt;code&gt;sysctl -p&lt;/code&gt;命令加载配置。这样可以增大系统对打开文件数量的限制，为Redis提供足够的文件描述符。&lt;/p&gt;
 &lt;h4&gt;backlog队列溢出&lt;/h4&gt;
 &lt;p&gt;backlog队列溢出是指由于系统对于特定端口的TCP连接使用的backlog队列溢出，导致连接无法进入队列，造成连接丢失。&lt;/p&gt;
 &lt;p&gt;你可以通过定时执行  &lt;code&gt;netstat -s | grep overflowed&lt;/code&gt;命令来统计backlog队列溢出情况。如果溢出的数量较多，可以考虑增加backlog队列的大小。&lt;/p&gt;
 &lt;h5&gt;参数来增大backlog队列&lt;/h5&gt;
 &lt;p&gt;在Redis中，默认backlog队列大小为511，而系统默认的backlog队列大小为128。你可以通过修改Redis的配置文件(redis.conf)中的  &lt;code&gt;tcp-backlog&lt;/code&gt;参数来增大backlog队列的大小。例如，将其设置为1024：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;tcp-backlog 1024
&lt;/code&gt;&lt;/pre&gt;
 &lt;h3&gt;网络延迟&lt;/h3&gt;
 &lt;p&gt;网络延迟是指数据在网络中的传输所需要的时间。若要优化网络延迟，你可以考虑以下几个方面：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;
   &lt;p&gt;确保网络连接的稳定性。检查网络设备和链路，确保没有断开、抖动或故障。可以联系网络管理员或服务提供商来解决网络问题。&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;优化Redis的网络配置。可以调整Redis的    &lt;code&gt;timeout&lt;/code&gt;参数，增加客户端与Redis服务器之间的超时时间，以适应网络延迟的情况。默认超时时间为0，即永不超时。你可以根据实际情况适度增加这个时间。&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;使用合适的网络协议及相关优化技术。选择较低的网络协议延迟、使用TCP/IP的Nagle算法或开启TCP_NODELAY选项来减少延迟等方式，都可以帮助优化网络延迟。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
 &lt;h4&gt;开启TCP_NODELAY&lt;/h4&gt;
 &lt;p&gt;  &lt;code&gt;TCP_NODELAY&lt;/code&gt;是一个TCP套接字选项，用于控制是否启用Nagle算法。Nagle算法通过将数据缓存一小段时间，合并多个小数据包一起发送，以减少网络传输中的报文数量，提升网络吞吐量和效率。但是这种优化策略会增加网络传输的延迟，特别是对于需要实时响应的应用来说，Nagle算法可能会导致延迟较高的现象。&lt;/p&gt;
 &lt;p&gt;当启用  &lt;code&gt;TCP_NODELAY&lt;/code&gt;选项时，套接字禁用了Nagle算法，数据会立即发送，并且不会缓冲等待合并。这可以降低延迟，但会增加网络传输的负载。&lt;/p&gt;
 &lt;p&gt;在Redis中，默认情况下  &lt;code&gt;TCP_NODELAY&lt;/code&gt;是被禁用的，这意味着Nagle算法是启用的，适用于大部分情况。然而，如果你的应用对实时性要求较高，可以考虑启用  &lt;code&gt;TCP_NODELAY&lt;/code&gt;来减少延迟。&lt;/p&gt;
 &lt;p&gt;可以通过Redis的配置文件(redis.conf)中的  &lt;code&gt;tcp-keepalive&lt;/code&gt;参数来启用  &lt;code&gt;TCP_NODELAY&lt;/code&gt;。将其设置为1以启用该选项：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;tcp-keepalive 1
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;需要注意的是，启用  &lt;code&gt;TCP_NODELAY&lt;/code&gt;可能会增加网络带宽的负载，特别是在高并发环境中使用时，可能会影响性能。因此，在启用该选项之前，应该仔细评估你的应用的实际需求，并进行相应的性能测试。&lt;/p&gt;
 &lt;h1&gt;彩蛋介绍&lt;/h1&gt;
 &lt;blockquote&gt;
  &lt;p&gt;   &lt;strong&gt;在这里，我向大家推荐一本关于JVM优化和调优的实战系列书籍，《深入浅出Java虚拟机 — JVM原理与实战》。这本书是最新出版的，内容涵盖了与我们当前工作和开发实例密切相关的技术和实战案例。通过学习这本书，我们可以深入了解Java虚拟机的原理，并通过实践掌握优化和调优的技巧。我诚挚地推荐这本书给大家，相信它将为我们的工作和技术发展带来巨大的收益。希望大家能够抽出时间多多学习一下这本宝贵的资料&lt;/strong&gt;。
   &lt;img alt="&amp;#22312;&amp;#36825;&amp;#37324;&amp;#25554;&amp;#20837;&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/56cc96a026974482a7d38d1575500994~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;
【   &lt;a href="http://product.dangdang.com/29583403.html"&gt;当当-点击链接&lt;/a&gt;】【   &lt;a href="https://item.jd.com/13762401.html"&gt;京东-点击链接&lt;/a&gt;】&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/62799-redis-%E9%97%AE%E9%A2%98-%E5%92%8C%E8%A7%A3</guid>
      <pubDate>Sat, 08 Jul 2023 15:30:13 CST</pubDate>
    </item>
    <item>
      <title>分析 Kubernetes Nodes  ‘Not Ready’ 状态</title>
      <link>https://itindex.net/detail/62742-%E5%88%86%E6%9E%90-kubernetes-nodes</link>
      <description>&lt;div&gt;    &lt;p&gt;   &lt;a href="https://www.airplane.dev/blog/kubernetes-node?ref=airplane.ghost.io"&gt;节点是&lt;/a&gt;   &lt;a href="https://www.airplane.dev/blog/kubernetes-cluster?ref=airplane.ghost.io"&gt;Kubernetes 集群&lt;/a&gt;的重要组成部分，负责运行   &lt;a href="https://kubernetes.io/docs/concepts/workloads/pods/?ref=airplane.ghost.io"&gt;pod&lt;/a&gt;。根据您的集群设置，节点可以是物理机或虚拟机。   &lt;a href="https://www.airplane.dev/blog/kubernetes-control-plane?ref=airplane.ghost.io"&gt;一个集群通常有一个或多个节点，这些节点由控制平面&lt;/a&gt;管理。&lt;/p&gt;  &lt;p&gt;由于节点负责管理工作负载，因此您需要确保所有节点都正常运行。该   &lt;code&gt;kubectl get nodes&lt;/code&gt;命令可用于检查节点的状态。&lt;/p&gt;  &lt;img alt="kubectl get nodes &amp;#30340;&amp;#36755;&amp;#20986;" src="https://airplane.ghost.io/content/images/804e529a-aba4-47ed-986f-102881e80645_img.png"&gt;&lt;/img&gt;kubectl get nodes 的输出  &lt;p&gt;具有状态的节点   &lt;code&gt;NotReady&lt;/code&gt;意味着由于潜在问题，它不能用于运行 pod。它本质上用于调试   &lt;code&gt;NotReady&lt;/code&gt;状态中的节点，以便它不会闲置。&lt;/p&gt;  &lt;p&gt;在本文中，您将了解节点可能进入该状态的一些可能原因   &lt;code&gt;NotReady&lt;/code&gt;以及如何对其进行调试。&lt;/p&gt;  &lt;h2&gt;未就绪状态&lt;/h2&gt;  &lt;p&gt;如前所述，集群中的每个节点都用于运行 pod。在节点上调度 Pod 之前，Kubernetes 会检查该节点是否能够运行 Pod。   &lt;code&gt;STATUS&lt;/code&gt;输出中的列表示   &lt;code&gt;kubectl get nodes&lt;/code&gt;状态。此列中的可能值为：&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;    &lt;code&gt;Ready&lt;/code&gt;：节点健康并准备好接受 pod。&lt;/li&gt;   &lt;li&gt;    &lt;code&gt;NotReady&lt;/code&gt;: 节点遇到了一些问题，无法在其上调度 pod。&lt;/li&gt;   &lt;li&gt;    &lt;code&gt;SchedulingDisabled&lt;/code&gt;: 节点被标记为不可调度。    &lt;a href="https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands?ref=airplane.ghost.io#cordon"&gt;这可以使用kubectl cordon&lt;/a&gt;命令来完成。&lt;/li&gt;   &lt;li&gt;    &lt;code&gt;Unknown&lt;/code&gt;：控制平面无法访问该节点。&lt;/li&gt;&lt;/ol&gt;  &lt;p&gt;有一个节点处于   &lt;code&gt;NotReady&lt;/code&gt;状态意味着该节点实际上未被使用，并且会在不参与运行 pod 的情况下累积成本。此外，丢失节点会对您的生产工作负载产生负面影响。&lt;/p&gt;  &lt;p&gt;为了让您的应用程序顺利运行，   &lt;a href="https://www.airplane.dev/blog/debugging-your-kubernetes-cluster-pods-and-containers?ref=airplane.ghost.io"&gt;您必须快速调试它们&lt;/a&gt;。&lt;/p&gt;  &lt;h2&gt;未就绪状态的可能原因&lt;/h2&gt;  &lt;p&gt;节点进入该状态可能有多种原因   &lt;code&gt;NotReady&lt;/code&gt;。本节将回顾导致此错误的一些最常见原因。&lt;/p&gt;  &lt;h3&gt;资源稀缺&lt;/h3&gt;  &lt;p&gt;   &lt;a href="https://www.airplane.dev/blog/setting-and-rightsizing-kubernetes-resource-limits?ref=airplane.ghost.io"&gt;要正常运行&lt;/a&gt;，节点必须有足够的磁盘空间、内存和足够的处理能力。如果一个   &lt;a href="https://www.airplane.dev/blog/kubernetes-disk-pressure?ref=airplane.ghost.io"&gt;节点的磁盘空间不足&lt;/a&gt;或可用内存不足，它将进入该   &lt;code&gt;NotReady&lt;/code&gt;状态。如果进程有压力，   &lt;em&gt;比如&lt;/em&gt;节点上运行的进程太多，也会改变状态   &lt;code&gt;NotReady&lt;/code&gt;。&lt;/p&gt;  &lt;h3&gt;网络配置错误&lt;/h3&gt;  &lt;p&gt;如果节点上没有正确配置网络或无法连接到互联网，则节点将无法与主节点通信，并被列为   &lt;code&gt;NotReady&lt;/code&gt;。&lt;/p&gt;  &lt;h3&gt;kubelet 进程的问题&lt;/h3&gt;  &lt;p&gt;   &lt;a href="https://kubernetes.io/docs/reference/command-line-tools-reference/kubelet/?ref=airplane.ghost.io"&gt;kubelet&lt;/a&gt;是在每个节点上运行的代理。它负责与   &lt;a href="https://www.airplane.dev/blog/kubernetes-api?ref=airplane.ghost.io"&gt;Kubernetes API&lt;/a&gt;服务器通信并注册节点。如果 kubelet 在节点上崩溃或停止，将无法与 API Server 通信并处于状态   &lt;code&gt;NotReady&lt;/code&gt;。&lt;/p&gt;  &lt;h3&gt;kube-proxy 的问题&lt;/h3&gt;  &lt;p&gt;   &lt;a href="https://kubernetes.io/docs/reference/command-line-tools-reference/kube-proxy/?ref=airplane.ghost.io"&gt;kube-proxy&lt;/a&gt;是运行在每个节点上并维护网络规则的网络代理。这些规则允许从集群内部或外部与您的 pod 进行网络通信。如果 kube-proxy 崩溃或停止，节点将处于状态   &lt;code&gt;NotReady&lt;/code&gt;。&lt;/p&gt;  &lt;h3&gt;供应商特定问题&lt;/h3&gt;  &lt;p&gt;   &lt;a href="https://www.airplane.dev/blog/running-kubernetes-on-gcp-with-gke?ref=airplane.ghost.io"&gt;假设您正在使用GKE&lt;/a&gt;或   &lt;a href="https://www.airplane.dev/blog/running-kubernetes-on-aws-with-eks?ref=airplane.ghost.io"&gt;EKS&lt;/a&gt;等云托管解决方案。在这种情况下，一些特定于供应商的问题可能会阻止您的节点正常运行并与控制平面通信。这些问题可能是 IAM 配置错误、网络规则配置错误等。&lt;/p&gt;  &lt;h2&gt;调试notready状态&lt;/h2&gt;  &lt;p&gt;如您所见，   &lt;code&gt;NotReady&lt;/code&gt;状态可能由多种问题引起。本节将帮助您   &lt;em&gt;确定&lt;/em&gt;问题的根本原因。但是，必须了解如何解决这些问题取决于确切原因和您的集群设置。没有放之四海而皆准的解决方案。但是，一旦您确定了根本原因，解决它应该会更容易。&lt;/p&gt;  &lt;h3&gt;检查 kube-proxy pod&lt;/h3&gt;  &lt;p&gt;首先，确保每个节点只有一个   &lt;code&gt;kube-proxy&lt;/code&gt;pod 并且处于   &lt;code&gt;Running&lt;/code&gt;state。&lt;/p&gt;  &lt;div&gt;   &lt;div&gt;去&lt;/div&gt;   &lt;pre&gt;    &lt;code&gt;kubectl get pods -n kube-system -o wide
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;p&gt;输出可能如下所示：‍&lt;/p&gt;  &lt;table&gt;   &lt;tr&gt;    &lt;th&gt;姓名&lt;/th&gt;    &lt;th&gt;准备好&lt;/th&gt;    &lt;th&gt;地位&lt;/th&gt;    &lt;th&gt;年龄&lt;/th&gt;    &lt;th&gt;知识产权&lt;/th&gt;    &lt;th&gt;节点&lt;/th&gt;    &lt;th&gt;提名节点&lt;/th&gt;    &lt;th&gt;就绪门&lt;/th&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;kube-代理-nhbtp&lt;/td&gt;    &lt;td&gt;1/1&lt;/td&gt;    &lt;td&gt;跑步&lt;/td&gt;    &lt;td&gt;2 (11 小时前)&lt;/td&gt;    &lt;td&gt;2d16h&lt;/td&gt;    &lt;td&gt;192.168.99.10&lt;/td&gt;    &lt;td&gt;1 我的集群&lt;/td&gt;    &lt;td&gt;&amp;lt;无&amp;gt;&lt;/td&gt;    &lt;td&gt;&amp;lt;无&amp;gt;&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;kube-proxy-tkmsk&lt;/td&gt;    &lt;td&gt;1/1&lt;/td&gt;    &lt;td&gt;跑步&lt;/td&gt;    &lt;td&gt;2 (11 小时前)&lt;/td&gt;    &lt;td&gt;2d16h&lt;/td&gt;    &lt;td&gt;192.168.99.10&lt;/td&gt;    &lt;td&gt;3 我的集群-m03&lt;/td&gt;    &lt;td&gt;&amp;lt;无&amp;gt;&lt;/td&gt;    &lt;td&gt;&amp;lt;无&amp;gt;&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;kube-proxy-vk4ch&lt;/td&gt;    &lt;td&gt;1/1&lt;/td&gt;    &lt;td&gt;跑步&lt;/td&gt;    &lt;td&gt;2 (11 小时前)&lt;/td&gt;    &lt;td&gt;2d16h&lt;/td&gt;    &lt;td&gt;192.168.99.10&lt;/td&gt;    &lt;td&gt;2 我的集群-m02&lt;/td&gt;    &lt;td&gt;&amp;lt;无&amp;gt;&lt;/td&gt;    &lt;td&gt;&amp;lt;无&amp;gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;  &lt;p&gt;   &lt;br /&gt;如果任何一个 pod 处于除 之外的某种状态   &lt;code&gt;Running&lt;/code&gt;，请使用以下命令获取更多信息：&lt;/p&gt;  &lt;div&gt;   &lt;div&gt;去&lt;/div&gt;   &lt;pre&gt;    &lt;code&gt;kubectl describe pod yourPodName -n kube-system
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;p&gt;Events部分记录了 pod 上的各种事件，它可能是开始寻找任何错误的好地方   &lt;a href="https://www.airplane.dev/blog/kubernetes-events?ref=airplane.ghost.io"&gt;。&lt;/a&gt;&lt;/p&gt;  &lt;img alt="&amp;#36755;&amp;#20986;&amp;#20013;&amp;#30340;&amp;#20107;&amp;#20214;&amp;#37096;&amp;#20998;" src="https://airplane.ghost.io/content/images/194418b7-57ea-4ae0-9ce6-21a6704dbc27_img.png"&gt;&lt;/img&gt;输出中的事件部分  &lt;p&gt;   &lt;br /&gt;您可以通过运行以下命令来访问 pod 日志：&lt;/p&gt;  &lt;div&gt;   &lt;div&gt;去&lt;/div&gt;   &lt;pre&gt;    &lt;code&gt;kubectl logs yourPodName -n kube-system
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;p&gt;日志和事件列表是开始查找任何问题的好地方   &lt;a href="https://www.airplane.dev/blog/kubernetes-logging?ref=airplane.ghost.io"&gt;。&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;如果您的节点没有   &lt;code&gt;kube-proxy&lt;/code&gt;pod，那么您需要检查   &lt;code&gt;kube-proxy&lt;/code&gt;daemonset，它负责在每个节点上运行一个 kube-proxy pod。&lt;/p&gt;  &lt;div&gt;   &lt;div&gt;去&lt;/div&gt;   &lt;pre&gt;    &lt;code&gt;kubectl describe daemonset kube-proxy -n kube-system
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;p&gt;此命令的输出可能会揭示 daemonset 的任何可能问题。&lt;/p&gt;  &lt;h3&gt;验证资源是否可用&lt;/h3&gt;  &lt;p&gt;运行以下命令以获取有关未就绪节点的详细信息：&lt;/p&gt;  &lt;div&gt;   &lt;div&gt;去&lt;/div&gt;   &lt;pre&gt;    &lt;code&gt;kubectl describe node nodeName
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;p&gt;在输出中，该   &lt;code&gt;Conditions&lt;/code&gt;部分显示节点是否耗尽资源。&lt;/p&gt;  &lt;img alt="&amp;#36755;&amp;#20986;&amp;#20013;&amp;#30340;&amp;#26465;&amp;#20214;&amp;#37096;&amp;#20998;" src="https://airplane.ghost.io/content/images/76cc79f2-06f2-4c01-922d-ef49b543e7dc_img.png"&gt;&lt;/img&gt;输出中的条件部分  &lt;p&gt;   &lt;br /&gt;以下条件可用：&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;    &lt;code&gt;MemoryPressure&lt;/code&gt;：如果是    &lt;code&gt;True&lt;/code&gt;，说明节点    &lt;a href="https://www.airplane.dev/blog/oomkilled-troubleshooting-kubernetes-memory-requests-and-limits?ref=airplane.ghost.io"&gt;内存不足&lt;/a&gt;。&lt;/li&gt;   &lt;li&gt;    &lt;code&gt;DiskPressure&lt;/code&gt;：    &lt;code&gt;True&lt;/code&gt;此字段中的值表示该节点缺少足够的空间。&lt;/li&gt;   &lt;li&gt;    &lt;code&gt;PIDPressure&lt;/code&gt;：如果节点上运行的进程过多，则此字段将为    &lt;code&gt;True&lt;/code&gt;。&lt;/li&gt;   &lt;li&gt;    &lt;code&gt;NetworkUnavailable&lt;/code&gt;：如果节点的网络配置不正确，这将是    &lt;code&gt;True&lt;/code&gt;。&lt;/li&gt;   &lt;li&gt;    &lt;code&gt;Ready&lt;/code&gt;：如果节点健康并准备好接受 pod，这将是    &lt;code&gt;True&lt;/code&gt;。在该字段中，a    &lt;code&gt;False&lt;/code&gt;相当于输出    &lt;code&gt;NotReady&lt;/code&gt;中的状态    &lt;code&gt;get nodes&lt;/code&gt;。它也可以有    &lt;code&gt;Unknown&lt;/code&gt;值，这意味着节点控制器最近没有收到节点的消息    &lt;code&gt;node-monitor-grace-period&lt;/code&gt;（默认为 40 秒）。&lt;/li&gt;&lt;/ol&gt;  &lt;p&gt;如果前四个条件中的任何一个是   &lt;code&gt;True&lt;/code&gt;，则您已确定问题所在。&lt;/p&gt;  &lt;h3&gt;验证 kubelet 是否正在运行&lt;/h3&gt;  &lt;p&gt;如果所有   &lt;code&gt;Conditions&lt;/code&gt;字段都显示   &lt;code&gt;Unknown&lt;/code&gt;，则可能暗示节点上的 kubelet 进程遇到了一些问题。&lt;/p&gt;  &lt;img alt="&amp;#26465;&amp;#20214;&amp;#23383;&amp;#27573;&amp;#26174;&amp;#31034;&amp;#26410;&amp;#30693;" src="https://airplane.ghost.io/content/images/36675162-59d6-474c-8ca0-7368faaad9ed_img.png"&gt;&lt;/img&gt;条件字段显示未知  &lt;p&gt;要对此进行调试，首先通过 SSH 进入节点并检查 kubelet 进程的状态。如果它作为 systemd 服务运行，请使用以下命令：&lt;/p&gt;  &lt;div&gt;   &lt;div&gt;去&lt;/div&gt;   &lt;pre&gt;    &lt;code&gt;systemctl status kubelet
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;p&gt;如果该   &lt;code&gt;Active&lt;/code&gt;字段显示   &lt;code&gt;inactive (dead)&lt;/code&gt;，则表示 kubelet 进程已停止。&lt;/p&gt;  &lt;img alt="&amp;#36755;&amp;#20986;&amp;#30340;&amp;#27963;&amp;#21160;&amp;#23383;&amp;#27573;" src="https://airplane.ghost.io/content/images/301538cb-f36f-4ac9-955e-c005f8329269_img.png"&gt;&lt;/img&gt;输出的活跃字段‍  &lt;p&gt;要揭示崩溃的可能原因，请使用以下命令检查日志：&lt;/p&gt;  &lt;div&gt;   &lt;div&gt;去&lt;/div&gt;   &lt;pre&gt;    &lt;code&gt;journalctl -u kubelet
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;p&gt;问题解决后，重新启动 kubelet：&lt;/p&gt;  &lt;div&gt;   &lt;div&gt;去&lt;/div&gt;   &lt;pre&gt;    &lt;code&gt;systemctl restart kubelet
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;h3&gt;验证与控制平面的网络通信&lt;/h3&gt;  &lt;p&gt;如果该   &lt;code&gt;Conditions&lt;/code&gt;字段显示   &lt;code&gt;NetworkUnavailable&lt;/code&gt;，则表明节点与控制平面之间的网络通信存在问题。&lt;/p&gt;  &lt;p&gt;一些可能的修复：&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;如果节点配置为使用代理，请验证代理是否允许访问 API 服务器端点。&lt;/li&gt;   &lt;li&gt;确保正确配置路由表以避免阻塞与 API 服务器的通信。&lt;/li&gt;   &lt;li&gt;如果您使用的是 AWS 等云提供商，请确认没有 VPC 网络规则阻止控制平面和节点之间的通信。&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;您可以从节点内运行以下命令以验证它是否可以访问 API 服务器。&lt;/p&gt;  &lt;div&gt;   &lt;div&gt;去&lt;/div&gt;   &lt;pre&gt;    &lt;code&gt;nc -vz &amp;lt;your-api-server-endpoint&amp;gt; 443
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;p&gt;如果输出显示   &lt;code&gt;succeeded&lt;/code&gt;，则网络通信工作正常。&lt;/p&gt;  &lt;h3&gt;供应商特定调试&lt;/h3&gt;  &lt;p&gt;如果您使用的是 EKS 或 GKE 等云提供商，如果您已经用尽了所有其他调试技术，有时值得研究供应商特定的问题。EKS 有一个非常详细的   &lt;a href="https://aws.amazon.com/premiumsupport/knowledge-center/eks-node-status-ready/?ref=airplane.ghost.io"&gt;指南&lt;/a&gt;，您可以遵循。&lt;/p&gt;  &lt;p&gt;GKE 提供   &lt;a href="https://cloud.google.com/kubernetes-engine/docs/how-to/node-auto-repair?ref=airplane.ghost.io"&gt;自动修复&lt;/a&gt;功能，可以尝试修复处于该   &lt;code&gt;NotReady&lt;/code&gt;状态达给定时间量的节点。如果一切都失败了，您可以随时与您的云提供商联系以获得更多帮助。&lt;/p&gt;  &lt;h2&gt;最后的想法&lt;/h2&gt;  &lt;p&gt;有一个节点处于   &lt;code&gt;NotReady&lt;/code&gt;状态是不可取的，需要立即修复。但是，发生这种情况的原因有多种，要查明确切原因可能具有挑战性。本文讨论了您可能会遇到该   &lt;code&gt;NotReady&lt;/code&gt;命令的一些常见原因及其解决方案。&lt;/p&gt;  &lt;p&gt;您越早捕捉到进入该   &lt;code&gt;NotReady&lt;/code&gt;状态的节点，快速调试它的机会就越高。您还可以查看导致此问题发生的所有事件，从而快速识别并解决问题。&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;      &lt;a href="https://www.airplane.dev/blog/kubernetes-node?ref=airplane.ghost.io"&gt;Nodes&lt;/a&gt; are a vital component of a      &lt;a href="https://www.airplane.dev/blog/kubernetes-cluster?ref=airplane.ghost.io"&gt;Kubernetes cluster&lt;/a&gt;and are responsible for running the      &lt;a href="https://kubernetes.io/docs/concepts/workloads/pods/?ref=airplane.ghost.io"&gt;pods&lt;/a&gt;. Depending on your cluster setup, a node can be a physical or a virtual machine. A cluster typically has one or multiple nodes, which are managed by the      &lt;a href="https://www.airplane.dev/blog/kubernetes-control-plane?ref=airplane.ghost.io"&gt;control plane&lt;/a&gt;.&lt;/p&gt;    &lt;p&gt;Because nodes do the heavy lifting of managing the workload, you want to make sure all your nodes are running correctly. The      &lt;code&gt;kubectl get nodes&lt;/code&gt;command can be used to check the state of your nodes.&lt;/p&gt;    &lt;img alt="Output of kubectl get nodes" src="https://airplane.ghost.io/content/images/804e529a-aba4-47ed-986f-102881e80645_img.png"&gt;&lt;/img&gt;Output of kubectl get nodes    &lt;p&gt;A node with a      &lt;code&gt;NotReady&lt;/code&gt;status means it can’t be used to run a pod because of an underlying issue. It’s essentially used to debug a node in the      &lt;code&gt;NotReady&lt;/code&gt;state so that it doesn’t lie unused.&lt;/p&gt;    &lt;p&gt;In this article, you’ll learn a few possible reasons why a node might enter the      &lt;code&gt;NotReady&lt;/code&gt;state and how you can debug it.&lt;/p&gt;    &lt;h2&gt;The notready state&lt;/h2&gt;    &lt;p&gt;As mentioned earlier, each node in a cluster is used to run pods. Before a pod is scheduled on a node, Kubernetes checks whether the node is capable of running the pod or not. The      &lt;code&gt;STATUS&lt;/code&gt;column in the output of      &lt;code&gt;kubectl get nodes&lt;/code&gt;represents the status. The possible values in this column are:&lt;/p&gt;    &lt;ol&gt;      &lt;li&gt;        &lt;code&gt;Ready&lt;/code&gt;: The node is healthy and ready to accept pods.&lt;/li&gt;      &lt;li&gt;        &lt;code&gt;NotReady&lt;/code&gt;: The node has encountered some issue and a pod cannot be scheduled on it.&lt;/li&gt;      &lt;li&gt;        &lt;code&gt;SchedulingDisabled&lt;/code&gt;: The node is marked as unschedulable. This can be done using the        &lt;a href="https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands?ref=airplane.ghost.io#cordon"&gt;kubectl cordon&lt;/a&gt;command.&lt;/li&gt;      &lt;li&gt;        &lt;code&gt;Unknown&lt;/code&gt;: The node is unreachable by the control plane.&lt;/li&gt;&lt;/ol&gt;    &lt;p&gt;Having a node in the      &lt;code&gt;NotReady&lt;/code&gt;state means that the node is effectively unused and will accumulate costs without participating in running pods. Furthermore, losing a node can negatively impact your production workload.&lt;/p&gt;    &lt;p&gt;In order for your application to run smoothly,      &lt;a href="https://www.airplane.dev/blog/debugging-your-kubernetes-cluster-pods-and-containers?ref=airplane.ghost.io"&gt;you must debug them quickly&lt;/a&gt;.&lt;/p&gt;    &lt;h2&gt;Possible causes of the notready state&lt;/h2&gt;    &lt;p&gt;There can be various reasons why a node might enter the      &lt;code&gt;NotReady&lt;/code&gt;state. This section will review some of the most common reasons for this error.&lt;/p&gt;    &lt;h3&gt;Scarcity of resources&lt;/h3&gt;    &lt;p&gt;      &lt;a href="https://www.airplane.dev/blog/setting-and-rightsizing-kubernetes-resource-limits?ref=airplane.ghost.io"&gt;To operate normally&lt;/a&gt;, a node must have sufficient disk space, memory, and sufficient processing ability. If a      &lt;a href="https://www.airplane.dev/blog/kubernetes-disk-pressure?ref=airplane.ghost.io"&gt;node is running low on disk space&lt;/a&gt;or the available memory is low, it will go into the      &lt;code&gt;NotReady&lt;/code&gt;state. If pressure exists on the processes,      &lt;em&gt;eg&lt;/em&gt;too many processes are running on the node, it will also change to the      &lt;code&gt;NotReady&lt;/code&gt;state.&lt;/p&gt;    &lt;h3&gt;Network misconfiguration&lt;/h3&gt;    &lt;p&gt;If the network has not been correctly configured on the node or it can’t reach the internet, the node will be unable to communicate with the master node and will be listed as      &lt;code&gt;NotReady&lt;/code&gt;.&lt;/p&gt;    &lt;h3&gt;Issue with kubelet process&lt;/h3&gt;    &lt;p&gt;      &lt;a href="https://kubernetes.io/docs/reference/command-line-tools-reference/kubelet/?ref=airplane.ghost.io"&gt;kubelet&lt;/a&gt;is an agent that runs on each node. It is responsible for communicating with the      &lt;a href="https://www.airplane.dev/blog/kubernetes-api?ref=airplane.ghost.io"&gt;Kubernetes API&lt;/a&gt;server and registering the nodes. If kubelet crashes or stops on the node, it will not be able to communicate with the API Server and will be in the      &lt;code&gt;NotReady&lt;/code&gt;state.&lt;/p&gt;    &lt;h3&gt;Issue with kube-proxy&lt;/h3&gt;    &lt;p&gt;      &lt;a href="https://kubernetes.io/docs/reference/command-line-tools-reference/kube-proxy/?ref=airplane.ghost.io"&gt;kube-proxy&lt;/a&gt;is a network proxy that runs on each node and maintains the network rules. These rules allow network communication to your pods from inside or outside your cluster. If kube-proxy crashes or stops, the node will be in the      &lt;code&gt;NotReady&lt;/code&gt;state.&lt;/p&gt;    &lt;h3&gt;Vendor specific issues&lt;/h3&gt;    &lt;p&gt;Suppose you’re using a cloud-hosted solution like      &lt;a href="https://www.airplane.dev/blog/running-kubernetes-on-gcp-with-gke?ref=airplane.ghost.io"&gt;GKE&lt;/a&gt;or      &lt;a href="https://www.airplane.dev/blog/running-kubernetes-on-aws-with-eks?ref=airplane.ghost.io"&gt;EKS&lt;/a&gt;. In that case, some vendor-specific issues may be preventing your nodes from operating normally and communicating with the control plane. These issues could be IAM misconfiguration, misconfigured network rules, etc.&lt;/p&gt;    &lt;h2&gt;Debugging the notready state&lt;/h2&gt;    &lt;p&gt;As you can see, the      &lt;code&gt;NotReady&lt;/code&gt;status can be caused by a multitude of issues. This section will help you      &lt;em&gt;identify&lt;/em&gt;the root cause of the problem. However, it’s essential to understand that how you go about fixing these issues depends on the exact cause and your cluster setup. There are no one-size-fits-all solutions. But, once you identify the root cause, it should be easier to resolve it.&lt;/p&gt;    &lt;h3&gt;Check the kube-proxy pod&lt;/h3&gt;    &lt;p&gt;First, ensure that each node has exactly one      &lt;code&gt;kube-proxy&lt;/code&gt;pod and is in the      &lt;code&gt;Running&lt;/code&gt;state.&lt;/p&gt;    &lt;div&gt;      &lt;div&gt;go&lt;/div&gt;&lt;/div&gt;    &lt;p&gt;The output might look like this:‍&lt;/p&gt;    &lt;table&gt;      &lt;tr&gt;        &lt;th&gt;NAME&lt;/th&gt;        &lt;th&gt;READY&lt;/th&gt;        &lt;th&gt;STATUS&lt;/th&gt;        &lt;th&gt;AGE&lt;/th&gt;        &lt;th&gt;IP&lt;/th&gt;        &lt;th&gt;NODE&lt;/th&gt;        &lt;th&gt;NOMINATED NODE&lt;/th&gt;        &lt;th&gt;READINESS GATES&lt;/th&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;kube-proxy-nhbtp&lt;/td&gt;        &lt;td&gt;1/1&lt;/td&gt;        &lt;td&gt;Running&lt;/td&gt;        &lt;td&gt;2 (11h ago)&lt;/td&gt;        &lt;td&gt;2d16h&lt;/td&gt;        &lt;td&gt;192.168.99.10&lt;/td&gt;        &lt;td&gt;1 my-cluster&lt;/td&gt;        &lt;td&gt;&amp;lt;none&amp;gt;&lt;/td&gt;        &lt;td&gt;&amp;lt;none&amp;gt;&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;kube-proxy-tkmsk&lt;/td&gt;        &lt;td&gt;1/1&lt;/td&gt;        &lt;td&gt;Running&lt;/td&gt;        &lt;td&gt;2 (11h ago)&lt;/td&gt;        &lt;td&gt;2d16h&lt;/td&gt;        &lt;td&gt;192.168.99.10&lt;/td&gt;        &lt;td&gt;3 my-cluster-m03&lt;/td&gt;        &lt;td&gt;&amp;lt;none&amp;gt;&lt;/td&gt;        &lt;td&gt;&amp;lt;none&amp;gt;&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;kube-proxy-vk4ch&lt;/td&gt;        &lt;td&gt;1/1&lt;/td&gt;        &lt;td&gt;Running&lt;/td&gt;        &lt;td&gt;2 (11h ago)&lt;/td&gt;        &lt;td&gt;2d16h&lt;/td&gt;        &lt;td&gt;192.168.99.10&lt;/td&gt;        &lt;td&gt;2 my-cluster-m02&lt;/td&gt;        &lt;td&gt;&amp;lt;none&amp;gt;&lt;/td&gt;        &lt;td&gt;&amp;lt;none&amp;gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;    &lt;p&gt;      &lt;br /&gt;If any one pod is in some state other than      &lt;code&gt;Running&lt;/code&gt;, use the following command to get more information:&lt;/p&gt;    &lt;div&gt;      &lt;div&gt;go&lt;/div&gt;&lt;/div&gt;    &lt;p&gt;The      &lt;a href="https://www.airplane.dev/blog/kubernetes-events?ref=airplane.ghost.io"&gt;Events&lt;/a&gt;section logs the various events on the pod, and it could be an excellent place to start looking for any mishaps.&lt;/p&gt;    &lt;img alt="The events section in the output" src="https://airplane.ghost.io/content/images/194418b7-57ea-4ae0-9ce6-21a6704dbc27_img.png"&gt;&lt;/img&gt;The events section in the output    &lt;p&gt;      &lt;br /&gt;You can get access to the pod logs by running the following command:&lt;/p&gt;    &lt;div&gt;      &lt;div&gt;go&lt;/div&gt;&lt;/div&gt;    &lt;p&gt;The      &lt;a href="https://www.airplane.dev/blog/kubernetes-logging?ref=airplane.ghost.io"&gt;logs&lt;/a&gt;and the events list is a good place to start looking for any issues.&lt;/p&gt;    &lt;p&gt;If your node does not have a      &lt;code&gt;kube-proxy&lt;/code&gt;pod, then you need to inspect the      &lt;code&gt;kube-proxy&lt;/code&gt;daemonset, which is responsible for running one kube-proxy pod on each node.&lt;/p&gt;    &lt;div&gt;      &lt;div&gt;go&lt;/div&gt;&lt;/div&gt;    &lt;p&gt;The output of this command might reveal any possible issue with the daemonset.&lt;/p&gt;    &lt;h3&gt;Verify resources are available&lt;/h3&gt;    &lt;p&gt;Run the following command to get detailed information about a node that is not ready:&lt;/p&gt;    &lt;div&gt;      &lt;div&gt;go&lt;/div&gt;&lt;/div&gt;    &lt;p&gt;In the output, the      &lt;code&gt;Conditions&lt;/code&gt;section shows if the node is running out of resources or not.&lt;/p&gt;    &lt;img alt="The conditions section in the output" src="https://airplane.ghost.io/content/images/76cc79f2-06f2-4c01-922d-ef49b543e7dc_img.png"&gt;&lt;/img&gt;The conditions section in the output    &lt;p&gt;      &lt;br /&gt;The following conditions are available:&lt;/p&gt;    &lt;ol&gt;      &lt;li&gt;        &lt;code&gt;MemoryPressure&lt;/code&gt;: If        &lt;code&gt;True&lt;/code&gt;, it indicates that the node is        &lt;a href="https://www.airplane.dev/blog/oomkilled-troubleshooting-kubernetes-memory-requests-and-limits?ref=airplane.ghost.io"&gt;running out of memory&lt;/a&gt;.&lt;/li&gt;      &lt;li&gt;        &lt;code&gt;DiskPressure&lt;/code&gt;: A        &lt;code&gt;True&lt;/code&gt;value in this field indicates that the node lacks enough space.&lt;/li&gt;      &lt;li&gt;        &lt;code&gt;PIDPressure&lt;/code&gt;: If too many processes are running on the node, this field will be        &lt;code&gt;True&lt;/code&gt;.&lt;/li&gt;      &lt;li&gt;        &lt;code&gt;NetworkUnavailable&lt;/code&gt;: If the network for the node is not correctly configured, this will be        &lt;code&gt;True&lt;/code&gt;.&lt;/li&gt;      &lt;li&gt;        &lt;code&gt;Ready&lt;/code&gt;: If the node is healthy and ready to accept pods, this will be        &lt;code&gt;True&lt;/code&gt;. In this field, a        &lt;code&gt;False&lt;/code&gt;is equivalent to the        &lt;code&gt;NotReady&lt;/code&gt;status in the        &lt;code&gt;get nodes&lt;/code&gt;output. It can also have the        &lt;code&gt;Unknown&lt;/code&gt;value, which means the node controller has not heard from the node in the last        &lt;code&gt;node-monitor-grace-period&lt;/code&gt;(defaults to 40 seconds).&lt;/li&gt;&lt;/ol&gt;    &lt;p&gt;If any one of the first four conditions is      &lt;code&gt;True&lt;/code&gt;, you have identified the problem.&lt;/p&gt;    &lt;h3&gt;Verify kubelet is running&lt;/h3&gt;    &lt;p&gt;If all the      &lt;code&gt;Conditions&lt;/code&gt;fields show      &lt;code&gt;Unknown&lt;/code&gt;, it might hint that the kubelet process on the node has run into some issues.&lt;/p&gt;    &lt;img alt="The conditions field shows unknown" src="https://airplane.ghost.io/content/images/36675162-59d6-474c-8ca0-7368faaad9ed_img.png"&gt;&lt;/img&gt;The conditions field shows unknown    &lt;p&gt;To debug this, first SSH into the node and check the status of the kubelet process. If it’s running as a systemd service, use the following command:&lt;/p&gt;    &lt;div&gt;      &lt;div&gt;go&lt;/div&gt;&lt;/div&gt;    &lt;p&gt;If the      &lt;code&gt;Active&lt;/code&gt;field shows      &lt;code&gt;inactive (dead)&lt;/code&gt;, it means the kubelet process has stopped.&lt;/p&gt;    &lt;img alt="The active field of the output" src="https://airplane.ghost.io/content/images/301538cb-f36f-4ac9-955e-c005f8329269_img.png"&gt;&lt;/img&gt;The active field of the output ‍    &lt;p&gt;To reveal the possible reason for the crash, check the logs with the following command:&lt;/p&gt;    &lt;div&gt;      &lt;div&gt;go&lt;/div&gt;&lt;/div&gt;    &lt;p&gt;Once the issue is fixed, restart kubelet with:&lt;/p&gt;    &lt;div&gt;      &lt;div&gt;go&lt;/div&gt;&lt;/div&gt;    &lt;h3&gt;Verify network communication with the control plane&lt;/h3&gt;    &lt;p&gt;If the      &lt;code&gt;Conditions&lt;/code&gt;field shows      &lt;code&gt;NetworkUnavailable&lt;/code&gt;, it indicates an issue in the network communication between the node and the control plane.&lt;/p&gt;    &lt;p&gt;A few possible fixes:&lt;/p&gt;    &lt;ul&gt;      &lt;li&gt;If the node is configured to use a proxy, verify that the proxy allows access to the API server endpoints.&lt;/li&gt;      &lt;li&gt;Ensure that the route tables are appropriately configured to avoid blocking communication with the API server.&lt;/li&gt;      &lt;li&gt;If you’re using a cloud provider like AWS, verify that no VPC network rules block communication between the control plane and the node.&lt;/li&gt;&lt;/ul&gt;    &lt;p&gt;You can run the following command from within the node to verify that it can reach the API server.&lt;/p&gt;    &lt;div&gt;      &lt;div&gt;go&lt;/div&gt;&lt;/div&gt;    &lt;p&gt;If the output shows      &lt;code&gt;succeeded&lt;/code&gt;, then network communication is working correctly.&lt;/p&gt;    &lt;h3&gt;Vendor specific debugging&lt;/h3&gt;    &lt;p&gt;If you’re using a cloud provider like EKS, or GKE, sometimes it’s worth looking into vendor-specific issues if you’ve exhausted all other debugging techniques. EKS has an extremely detailed      &lt;a href="https://aws.amazon.com/premiumsupport/knowledge-center/eks-node-status-ready/?ref=airplane.ghost.io"&gt;guide&lt;/a&gt;that you can follow.&lt;/p&gt;    &lt;p&gt;GKE provides an      &lt;a href="https://cloud.google.com/kubernetes-engine/docs/how-to/node-auto-repair?ref=airplane.ghost.io"&gt;auto repair&lt;/a&gt;feature that can attempt to repair a node that has been in the      &lt;code&gt;NotReady&lt;/code&gt;state for a given amount of time. If all else fails, you can always get in touch with your cloud provider for more assistance.&lt;/p&gt;    &lt;h2&gt;Final thoughts&lt;/h2&gt;    &lt;p&gt;Having a node in the      &lt;code&gt;NotReady&lt;/code&gt;state is undesirable and needs to be fixed immediately. However, there are multiple reasons this might occur, and it can be challenging to pinpoint the exact cause. This article discussed some common reasons you may encounter the      &lt;code&gt;NotReady&lt;/code&gt;command and solutions for it.&lt;/p&gt;    &lt;p&gt;The earlier you can catch nodes entering the      &lt;code&gt;NotReady&lt;/code&gt;state, the higher your chances of quickly debugging it. You can also see all the events leading up to this, allowing you to quickly identify and solve the issue.&lt;/p&gt;&lt;/div&gt;
    &lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/62742-%E5%88%86%E6%9E%90-kubernetes-nodes</guid>
      <pubDate>Sat, 22 Apr 2023 11:09:12 CST</pubDate>
    </item>
    <item>
      <title>java获取到heapdump文件后，如何快速分析？</title>
      <link>https://itindex.net/detail/62740-java-heapdump-%E6%96%87%E4%BB%B6</link>
      <description>&lt;blockquote&gt;
  &lt;p&gt;原创：扣钉日记（微信公众号ID：codelogs），欢迎分享，非公众号转载保留此声明。&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;h3&gt;简介&lt;/h3&gt;
 &lt;p&gt;在之前的OOM问题复盘之后，本周，又一Java服务出现了内存问题，这次问题不严重，只会触发堆内存占用高报警，没有触发OOM，但好在之前的复盘中总结了dump脚本，会在堆占用高时自动执行jstack与jmap，使得我们成功保留了问题现场。&lt;/p&gt;
 &lt;h4&gt;查看堆占用分布&lt;/h4&gt;
 &lt;p&gt;发现有heapdump文件后，我立马拷贝到本机，并使用MAT分析，如下：  &lt;br /&gt;
  &lt;img alt="mat" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d829a335afb141c1af02a51776d92dfb~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;  &lt;br /&gt;
很显然，好像是什么接口分配了非常大的String对象，一个String对象约200MB，那它是哪分配的呢？&lt;/p&gt;
 &lt;h4&gt;查找大对象分配线程&lt;/h4&gt;
 &lt;p&gt;这个分配行为肯定是某个线程做的，而线程是最常见的GC Root，因此只要查找对象的GC Root即可，如下：  &lt;br /&gt;
  &lt;img alt="gc_root" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a65ff32263634db7a5cae582c13056a9~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;  &lt;br /&gt;
找到了大对象对应的分配线程是http-nio-8088-exec-6，如下：  &lt;br /&gt;
  &lt;img alt="image_2023-04-21_20230421200748" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/845ce4e9a2254650b91aee052bf3404a~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h4&gt;查看线程栈&lt;/h4&gt;
 &lt;p&gt;如何查看这个线程在干什么呢？在MAT中摸索了一会，没找到相关内容，回想起我们的dump脚本中记录了jstack，打开看看，如下：  &lt;br /&gt;
  &lt;img alt="jstack" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3d534fc575f6454dbaad525d5f03426a~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;  &lt;br /&gt;
可以发现，这个线程正在做json序列化，但我仔细找了好一会，也没有找到相关接口的Controller，这是因为线程已经执行完了Controller里面的逻辑，之后返回接口响应数据时分配的大对象。&lt;/p&gt;
 &lt;p&gt;可是，线程栈中没有业务代码，就没法定位是哪个接口有问题了。。。&lt;/p&gt;
 &lt;h4&gt;检查accesslog日志&lt;/h4&gt;
 &lt;p&gt;考虑到分配大对象的接口肯定会很慢，于是我转向查看tomcat的accesslog日志，如下：  &lt;br /&gt;
  &lt;img alt="accesslog" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8f4ebf90fb164ad39728de79ab803180~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;  &lt;br /&gt;
终于，找到了问题接口，这个接口是用来查询商品数据的，当输入3时会查询出所有3开头的商品，而这有20w+数据，解决问题很简单，加个limit完事。&lt;/p&gt;
 &lt;h3&gt;排查过程复盘&lt;/h3&gt;
 &lt;p&gt;然而，我一直有个习惯，就是解决一个问题后，我会反思一下问题解决过程中有多少运气成分。&lt;/p&gt;
 &lt;p&gt;如果你经常阅读排查问题类的技术文章，就会发现不少文章，中间突然有一步定位到了问题根因，可能是突然发现了一个线索，或是硬看代码看出来的，或是猜测某处有问题，我觉得这种排查过程都有不少运气成分，我希望问题是通过多年理论基础的积累和对诊断工具的熟练使用，而有章法的一步步查出来的。&lt;/p&gt;
 &lt;p&gt;而上面通过accesslog能够定位到问题，有一定的运气成分，因为本次内存问题不极端，如果此接口请求量大，那就会瞬间触发多次FGC，进而会影响其它接口也变慢，进而无法分辨出哪个是导致问题的接口！&lt;/p&gt;
 &lt;p&gt;我想，从理论上来说，Java堆文件里面，应该有线程栈以及线程栈上的参数，因为线程是对象，参数也是对象，它们理应都在堆里，于是我找了个空闲时间，又摸索起MAT这个工具了。&lt;/p&gt;
 &lt;h4&gt;MAT查看线程栈&lt;/h4&gt;
 &lt;p&gt;摸索了一会，我就发现有这样一个按钮，可以查看线程信息，如下：  &lt;br /&gt;
  &lt;img alt="mat_thread" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7f9965d83ed04d5eb59c0a1dc9f8b845~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;找到前面说的线程http-nio-8088-exec-6，展开后，就可以发现线程栈以及栈上的参数，如下：  &lt;br /&gt;
  &lt;img alt="mat_thread_stack" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6409603df1d5475fa657b0871c960f3e~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;这就找到了请求的Request参数对象，再将Request对象多次展开后，就可以找到接口url信息，如下：  &lt;br /&gt;
  &lt;img alt="mat_request" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4f7b58d149ce470d8d7792adf7a8444b~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;  &lt;br /&gt;
嗯，这样分析heapdump文件真tm的高效啊&lt;/p&gt;
 &lt;p&gt;MAT下载地址：https://www.eclipse.org/mat/downloads.php&lt;/p&gt;
 &lt;h4&gt;VisualVM查看线程栈&lt;/h4&gt;
 &lt;p&gt;考虑到不少同学习惯用VisualVM分析heapdump，这里也放一下VisualVM的使用方法。&lt;/p&gt;
 &lt;p&gt;首先，加载heapdump文件，如下：  &lt;br /&gt;
  &lt;img alt="VisualVM_open" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6794f4169c634ecda05fffa059fe1536~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;然后选择相应对象，右键选择Select in Threads，如下：  &lt;br /&gt;
  &lt;img alt="image_2023-04-21_20230421211033" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c475eb903b574ec1bbee851cf758fe85~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;定位到线程栈后，找到要查看的Request对象，点击进入，如下：
  &lt;img alt="image_2023-04-21_20230421211738" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/fe63f2bcafd8428abab381c6c143b250~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;同样，展开Request对象后，可找到url信息，如下：  &lt;br /&gt;
  &lt;img alt="image_2023-04-21_20230421211858" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/79d4ed0022354078b3022b5b5d24090c~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;VisualVM下载地址：https://visualvm.github.io/download.html&lt;/p&gt;
 &lt;h3&gt;总结&lt;/h3&gt;
 &lt;p&gt;虽然我也用MAT很多次了，但每次问题都太简单，以至于没有深入使用过MAT，导致到现在才知道有如此便捷的分析路径。&lt;/p&gt;
 &lt;p&gt;如果你对我们的自动dump脚本感兴趣，可看看我之前写的这两篇文章。  &lt;br /&gt;
  &lt;a href="https://mp.weixin.qq.com/s/Ta_BgTNo67Li9RQsd24nBw"&gt;一次线上OOM问题的个人复盘&lt;/a&gt;  &lt;br /&gt;
  &lt;a href="https://mp.weixin.qq.com/s/pwDY2xvYAXj3ra-5Ep-0og"&gt;jmap执行失败了，怎么获取heapdump？&lt;/a&gt;&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/62740-java-heapdump-%E6%96%87%E4%BB%B6</guid>
      <pubDate>Fri, 21 Apr 2023 22:08:49 CST</pubDate>
    </item>
    <item>
      <title>探索性数据分析详解</title>
      <link>https://itindex.net/detail/62737-%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90</link>
      <description>&lt;h2&gt;什么是探索性数据分析？&lt;/h2&gt;
 &lt;p&gt;探索性数据分析（Exploratory Data Analysis，简称EDA） 是指对已有的数据（特别是调查或观察得来的原始数据）在尽量少的先验假定下进行探索，通过作图、制表、方程拟合、计算特征量等手段探索数据的结构和规律的一种数据分析方法。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="181" src="https://www.biaodianfu.com/wp-content/uploads/2023/04/eda.png" width="480"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;探索性数据分析（EDA）与传统统计分析（Classical Analysis）的区别：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;传统的统计分析方法通常是先假设样本服从某种分布，然后把数据套入假设模型再做分析。但由于多数数据并不能满足假设的分布，因此，传统统计分析结果常常不能让人满意。&lt;/li&gt;
  &lt;li&gt;探索性数据分析方法注重数据的真实分布，强调数据的可视化，使分析者能一目了然看出数据中隐含的规律，从而得到启发，以此帮助分析者找到适合数据的模型。“探索性”是指分析者对待解问题的理解会随着研究的深入不断变化。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;img alt="" height="446" src="https://www.biaodianfu.com/wp-content/uploads/2023/04/eda-diff.png" width="480"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;探索性数据分析除了日常的数据分析外，也是算法模型搭建过程中的必要环节。特别适合数据比较杂乱，不知所措的场景。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="66" src="https://www.biaodianfu.com/wp-content/uploads/2023/04/eda-1.png" width="780"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;探索性分析用一句话概况就是：折磨数据，它会坦白任何事情。&lt;/p&gt;
 &lt;h2&gt;探索性数据分析的一般流程&lt;/h2&gt;
 &lt;p&gt;探索性分析的一般流程：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;数据总览&lt;/li&gt;
  &lt;li&gt;探索性分析每个变量&lt;/li&gt;
  &lt;li&gt;探索性分析变量与target标签的关系&lt;/li&gt;
  &lt;li&gt;探索性分析变量之间的关系&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;img alt="" height="647" src="https://www.biaodianfu.com/wp-content/uploads/2023/04/eda-flow.png" width="480"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h3&gt;数据总览&lt;/h3&gt;
 &lt;p&gt;在数据处理前首先要充分了解数据，了解数据包含以下两部分：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;了解数据的外部信息。即数据的现实意义。可通过业务知识与流量计获取采集方式进行了解。&lt;/li&gt;
  &lt;li&gt;了解数据的内部信息。即数据的自身情况。可通过统计学的相关知识，如计算均值，标准差，峰度，偏度等。另外，也可以通过绘图，来深入了解数据，为创建有效特征提供思路。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;对于数据总览一般可借助Pandas的一些函数对数据有些大概了解：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;describe() # 查看所有数据平均值，四分位数等信息&lt;/li&gt;
  &lt;li&gt;info() # 查看所有数据的数据类型和非空值个数。&lt;/li&gt;
  &lt;li&gt;shape # 查看数据行列数&lt;/li&gt;
  &lt;li&gt;isnull().sum() # 查看数据各个特征为空值的个数&lt;/li&gt;
&lt;/ul&gt;
 &lt;h3&gt;探索性分析每个变量&lt;/h3&gt;
 &lt;p&gt;需要了解的内容包括：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;变量是什么类型&lt;/li&gt;
  &lt;li&gt;变量是否有缺失值&lt;/li&gt;
  &lt;li&gt;变量是否有异常值&lt;/li&gt;
  &lt;li&gt;变量是否有重复值&lt;/li&gt;
  &lt;li&gt;变量是否均匀&lt;/li&gt;
  &lt;li&gt;变量是否需要转换&lt;/li&gt;
  &lt;li&gt;…&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;在分析每个变量可通过描述统计量和图表进行描述。数据类型分为数值型，类别型，文本型，时间序列等。这里主要指的是数值型（定量数据）和类别型（定性数据），其中数值型又可以分为连续型和离散型。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="197" src="https://www.biaodianfu.com/wp-content/uploads/2023/04/data.png" width="480"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h4&gt;1）连续数据分析&lt;/h4&gt;
 &lt;p&gt;数据分析分为两个方面，一是统计汇总，二是可视化。离散也是这样。&lt;/p&gt;
 &lt;p&gt;统计计算：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;在统计学中，想要描述一个数据，要从三个方面进行说明：
   &lt;ul&gt;
    &lt;li&gt;集中趋势：均值，中位数，众数。对于正太分布的数据，均值的效果比较好，而对于有偏数据，因为存在极值，所有会对均值产生影响，此时，用中位数去进行集中趋势的描述。&lt;/li&gt;
    &lt;li&gt;离散程度：方差和标准差。这两个用哪个都可，不过标准差是具有实际意义的。另外，还可以用极差，平均差，四分位差，离散系数（针对多组数据离散程度的对比）。&lt;/li&gt;
    &lt;li&gt;分布形状：偏度skew()，衡量数据偏斜情况。峰度kurt()，衡量数据分布的平坦度。检验数据正态性。一般可绘制P-P图，Q-Q图来进行判断。或者通过计算偏度，峰度进行判断，也有其他别的方法，但了解的较少。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
  &lt;li&gt;数据转化。这步一般在特征工程中，这里提一下，通过box-cox可以将非正态数据转为正态数据。&lt;/li&gt;
  &lt;li&gt;游程检验。非参数统计的一种方法，判断数据是否是随机出现的。连续，离散都可以用。&lt;/li&gt;
  &lt;li&gt;通过describe()，可观察数据的大致情况。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;可视化：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;对连续数据可视化主要有以下几个图形：
   &lt;ul&gt;
    &lt;li&gt;直方图。可以大致看出数据的分布情况，但会受限于bins的取值并且图形不光滑。可在直方图上再画出核密度图(KDE)，进行更详细的查看。&lt;/li&gt;
    &lt;li&gt;核密度估计&lt;/li&gt;
    &lt;li&gt;核密度图&lt;/li&gt;
    &lt;li&gt;箱线图。反映原始数据的分布特征，还能进行多组数据的比较。可看出数据的离群点。&lt;/li&gt;
    &lt;li&gt;散点图。利用索引和连续数据作散点图，直观看数据是否随机。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
  &lt;li&gt;类型转换
   &lt;ul&gt;
    &lt;li&gt;将连续型数据转为离散型数据。比如，年龄，可以将其分组为少年，青年，壮年，老年等。这种处理方式的关键是如何分组，在数据噪声处理中有过描述，介绍了人为区分，等深等宽分组，无监督算法分组，聚类等方法。&lt;/li&gt;
    &lt;li&gt;关于为什么要把连续型数据转为离散型数据，大概的好处有：去噪声，易理解，算法需要。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;strong&gt;针对连续变量&lt;/strong&gt;常见的描述统计量：平均值，中位数，众数，最小值，最大值，四分位数，标准差等&lt;/p&gt;
 &lt;p&gt;图表：频数分布表（需进行分箱操作），直方图，箱形图（查看分布情况）&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="139" src="https://www.biaodianfu.com/wp-content/uploads/2023/04/data-1.png" width="780"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h4&gt;2）离散数据分析&lt;/h4&gt;
 &lt;p&gt;统计计算&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;主要查看数据的结构。用众数看哪类数据出现的最多。利用value_counts()函数，查看各个类别出现的次数。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;可视化&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;饼图。对于查看数据结构比较直观，所占百分比。&lt;/li&gt;
  &lt;li&gt;柱形图。对各类别出现次数进行可视化。可排序，这样观察数据更直观。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;针对  &lt;strong&gt;无序型离散变量&lt;/strong&gt;常见的描述统计量：各个变量出现的频数和占比&lt;/p&gt;
 &lt;p&gt;图表：频数分布表（绝对频数，相对频数，百分数频数），柱形图，条形图，茎叶图，饼图&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="229" src="https://www.biaodianfu.com/wp-content/uploads/2023/04/data-2.png" width="480"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;针对  &lt;strong&gt;有序型离散变量&lt;/strong&gt;常见的描述统计量：各个变量出现的频数和占比&lt;/p&gt;
 &lt;p&gt;图表：频数分布表，堆积柱形图，堆积条形图（比较大小）&lt;/p&gt;
 &lt;h3&gt;变量间关系分析&lt;/h3&gt;
 &lt;p&gt;当对单个数据分析完后，还要看各个数据与目标特征的关系，和除目标特征外，其他数据间的关系。&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;探索性分析变量与target标签的关系&lt;/li&gt;
  &lt;li&gt;探索性分析变量之间的关系&lt;/li&gt;
&lt;/ul&gt;
 &lt;h4&gt;1）连续型变量与连续型变量关系&lt;/h4&gt;
 &lt;p&gt;统计计算：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;协方差，可以得到两个变量间的相关性。但协方差越大，并不表明越相关。因为协方差的定义中没有考虑属性值本身大小的影响。&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://www.biaodianfu.com/pearson-kendall-spearman.html"&gt;相关系数&lt;/a&gt;考虑了属性值本身大小的影响，因此是一个更合适的统计量。取值在[-1,1]上，-1表示负相关，即变换相反，1表示正相关，0则表示不相关。相关系数是序数型的，只能比较相关程度大小(绝对值比较),并不能做四则运算。而相关系数一般常用的有三种：
   &lt;ul&gt;
    &lt;li&gt;Pearson相关系数：这个比较常用，主要用于正态的连续型数据间的比较。但在使用时，限制的条件比较多，对于偏态数据，效果不是很好。&lt;/li&gt;
    &lt;li&gt;Spearman相关系数：相比于Pearson，这个的限制条件比较少，不受异常值影响。可以应用在多种场合。但若对正太正态数据使用，则效果一般。&lt;/li&gt;
    &lt;li&gt;Kendall相关系数：限制条件同Spearman。一般用在分类数据的相关性上。&lt;/li&gt;
    &lt;li&gt;注：Pearson和协方差，主要看数据间的关系是不是线性的，如不是线性，但有其他联系，这两个系数是判断不出来的。比如指数函数这种。而Spearman和Kendall则可以进行一定的判断，主要是单调增函数。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;可视化&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;散点图。可看出两个特征间的关系大致是什么样的。如果要具体探究数据间的关系，需要进行一定的计算。&lt;/li&gt;
  &lt;li&gt;相关性热力图。如果是一个数据与另一个时间序列进行搭配，则这个图可以很好地看出变化趋势。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;img alt="" height="364" src="https://www.biaodianfu.com/wp-content/uploads/2023/04/cross.jpg" width="480"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;对于连续变量与连续变量之间的关系，可以通过散点图进行查看。对于多个连续变量，可使用散点图矩阵，相关系数矩阵，热图。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="140" src="https://www.biaodianfu.com/wp-content/uploads/2023/04/data-3.png" width="700"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;量化指标：皮尔逊相关系数（线性关系），互信息（非线性关系）&lt;/p&gt;
 &lt;h4&gt;2）离散变量和离散变量关系&lt;/h4&gt;
 &lt;p&gt;对于离散变量与离散变量之间的关系，可以通过交叉分组表（crosstab），复合柱形图，堆积柱形图，饼图进行查看。对于多个离散变量，可以使用网状图，通过各个要素之间是否有线条，以及线条的粗线来显示是否有关系以及关系的强弱。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="179" src="https://www.biaodianfu.com/wp-content/uploads/2023/04/data-4.png" width="1000"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;量化指标：卡方独立性检验—&amp;gt;Cramer’s φ (Phi) or Cramer’s V&lt;/p&gt;
 &lt;h4&gt;3）离散变量和连续变量关系&lt;/h4&gt;
 &lt;p&gt;对于离散变量和连续变量之间的关系，可以使用直方图，箱线图，小提琴图进行查看，将离散变量在图形中用不同的颜色显示，来直观地观察变量之间的关系。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="254" src="https://www.biaodianfu.com/wp-content/uploads/2023/04/data-5.png" width="780"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;量化指标：独立样本t检验中的t统计量和相应的p值（两个变量），单因素方差分析中的η²（三个变量及以上）&lt;/p&gt;
 &lt;h4&gt;4） 其他&lt;/h4&gt;
 &lt;p&gt;检查数据的正态性：直方图，箱线图，Q-Q图（Quantile-Quantile Plot ）&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;直方图，箱线图：看图形是否对称&lt;/li&gt;
  &lt;li&gt;Q-Q图：比较数据的分位数与某个理论分布的分位数是否匹配&lt;/li&gt;
&lt;/ul&gt;
 &lt;h2&gt;探索性数据分析的的产出&lt;/h2&gt;
 &lt;p&gt;根据EDA我们可以得出以下结论：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;变量是否需要筛选、替换和清洗&lt;/li&gt;
  &lt;li&gt;变量是否需要转换&lt;/li&gt;
  &lt;li&gt;变量之间是否需要交叉&lt;/li&gt;
  &lt;li&gt;变量是否需要采样&lt;/li&gt;
&lt;/ul&gt;
 &lt;h2&gt;探索性数据分析的辅助工具&lt;/h2&gt;
 &lt;ul&gt;
  &lt;li&gt;DataPrep&lt;/li&gt;
  &lt;li&gt;Pandas Profiling&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://github.com/fbdesignpro/sweetviz"&gt;Sweetviz&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://github.com/AutoViML/AutoViz"&gt;AutoViz&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://klib.readthedocs.io/en/latest/"&gt;Klib&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://amueller.github.io/dabl/dev/"&gt;Dabl&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://github.com/Speedml/speedml"&gt;SpeedML&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;参考链接：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;a href="https://towardsdatascience.com/exploratory-data-analysis-in-python-a-step-by-step-process-d0dfa6bf94ee"&gt;Exploratory Data Analysis in Python — A Step-by-Step Process&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://medium.com/epfl-extension-school/advanced-exploratory-data-analysis-eda-with-python-536fa83c578a"&gt;Advanced exploratory data analysis (EDA) with Python&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://medium.com/geekculture/10-automated-eda-libraries-at-one-place-ea5d4c162bbb"&gt;10 automated EDA libraries in one place | by Satyam Kumar | Geek Culture | Medium&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;其他参考：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="579" src="https://www.biaodianfu.com/wp-content/uploads/2023/04/data-plot.jpg" width="894"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;div&gt;
  &lt;h3&gt;相关文章:&lt;/h3&gt;  &lt;ol&gt;
   &lt;li&gt;    &lt;a href="https://www.biaodianfu.com/dataprep-eda.html" rel="bookmark" title="&amp;#25968;&amp;#25454;&amp;#25506;&amp;#32034;&amp;#24037;&amp;#20855;Dataprep.eda"&gt;数据探索工具Dataprep.eda &lt;/a&gt;&lt;/li&gt;
   &lt;li&gt;    &lt;a href="https://www.biaodianfu.com/elasticsearch-install-chinese-segmenter-plugin.html" rel="bookmark" title="Elasticsearch&amp;#20013;&amp;#25991;&amp;#20998;&amp;#35789;&amp;#25554;&amp;#20214;&amp;#23433;&amp;#35013;"&gt;Elasticsearch中文分词插件安装 &lt;/a&gt;&lt;/li&gt;
   &lt;li&gt;    &lt;a href="https://www.biaodianfu.com/survival-analysis.html" rel="bookmark" title="&amp;#29983;&amp;#23384;&amp;#20998;&amp;#26512;&amp;#20174;&amp;#27010;&amp;#24565;&amp;#21040;&amp;#23454;&amp;#25112;"&gt;生存分析从概念到实战 &lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>数据 术→技巧 数据分析</category>
      <guid isPermaLink="true">https://itindex.net/detail/62737-%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90</guid>
      <pubDate>Tue, 18 Apr 2023 22:54:59 CST</pubDate>
    </item>
    <item>
      <title>微服务架构中的链路超时分析</title>
      <link>https://itindex.net/detail/62713-%E5%BE%AE%E6%9C%8D%E5%8A%A1-%E6%9E%B6%E6%9E%84-%E5%88%86%E6%9E%90</link>
      <description>&lt;h3&gt;1、前言&lt;/h3&gt;
 &lt;h4&gt;1.1 现象（问题）&lt;/h4&gt;
 &lt;p&gt;​微服务架构项目落地过程中，开发人员一般都遇到过调用超时问题，大部分时候会出现在feign接口调用上，这是微服务与单体服务最大的区别，单体从来不用考虑服务之间调用因为网络、序列化等因素导致的额外时间损耗问题。很多开发人员在微服务开发中通常会随手设置一个较长超时，原则就是别在feign接口调用超时，这个随手的超时时间可能是5分钟、10分钟，甚至1个小时不等，看似解决超时导致的问题，实际如果没有从整体微服务架构来考虑超时背后的因素，这样会导致给整个链路调用埋下隐患，可能会随机或者在高并发等情况下爆发。&lt;/p&gt;
 &lt;p&gt;​超时设置不正确会导致以下现象：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;应用不稳定，时不时出现小问题，问题复现困难，用户感知差。&lt;/li&gt;
  &lt;li&gt;前端请求卡住，也不知道是网络问题，还是应用问题或者是数据库问题，排查问题费时费力。&lt;/li&gt;
  &lt;li&gt;数据库资源浪费，出现空算。&lt;/li&gt;
  &lt;li&gt;应用空算，引发资源浪费或内存溢出。&lt;/li&gt;
  &lt;li&gt;高并发下，TPS和QPS同时出现异常下降。&lt;/li&gt;
&lt;/ul&gt;
 &lt;h4&gt;1.2 原则（结论）&lt;/h4&gt;
 &lt;p&gt;微服务架构（基于  &lt;code&gt;Spring Cloud&lt;/code&gt;）中，在行业应用中，超时设置都应满足以下条件：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;不应超过客户等待最大容忍度时间，这是一个弹性指标，通常在这个指标可以考虑在5s到30s之间；&lt;/li&gt;
  &lt;li&gt;超时设置应大于API计算最大时间，如果和上一条冲突，API计算应转入异步计算；&lt;/li&gt;
  &lt;li&gt;微服务链路超时各环节应保持一致性，并且从前端到后端到数据库（定义为从做左往右），越靠右超时设置应越短，链路超时应该是向右收敛。&lt;/li&gt;
  &lt;li&gt;快速失败，节省核心资源，特别是数据库。&lt;/li&gt;
&lt;/ul&gt;
 &lt;h3&gt;2、链路超时（细节）&lt;/h3&gt;
 &lt;ul&gt;
  &lt;li&gt;
   &lt;p&gt;链路超时应满足向右收敛原则&lt;/p&gt;
   &lt;p&gt;假设网关超时或服务超时，数据库还依然在执行客户端提交的的慢查询，等结果计算出来后，中间链路已经超时，这个时候数据是无法响应的，相当于数据库的计算被浪费。而数据库又是极为珍贵的资源，这种调用一旦过多很快就会导致与数据相关的API出现响应故障。&lt;/p&gt;
   &lt;blockquote&gt;
    &lt;p&gt;默认情况下，整个微服务的调用链路是不符合这个要求的，所以一旦发生慢调用，很多时候会产生无效的计算，浪费资源或者直接影响服务的使用。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;快速失败，满足向右收敛原则后，发生慢调用的时候，靠右侧的核心资源会先超时，通过调用链传递，快速失败响应，这个时候服务无论是进行降级还是熔断都可以快速降低系统的压力，并且还能及时向开发团队或者运维团队反馈问题。&lt;/p&gt;
   &lt;blockquote&gt;
    &lt;p&gt;通常情况下，资源越靠右侧，说明资源越珍贵，调用的代价也越大。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;缺省值，如果使用组件缺省值，一定要显式的设置参数。&lt;/p&gt;
   &lt;blockquote&gt;
    &lt;p&gt;注意：不同版本，默认值可能是不同的。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;通常对链路入口（网关），服务入口（web中间件），服务调用（Feign），数据库调用（sql）等环节调整超时参数，遵守向右收敛原则。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
 &lt;h4&gt;2.1 网关关键配置&lt;/h4&gt;
 &lt;ul&gt;
  &lt;li&gt;全局超时配置&lt;/li&gt;
&lt;/ul&gt;
 &lt;pre&gt;  &lt;code&gt;spring:
  cloud:
    gateway:     
      httpclient:
        connect-timeout: 45000 #毫秒
        response-timeout: 10000
        pool:
          type: elastic
&lt;/code&gt;&lt;/pre&gt;
 &lt;ul&gt;
  &lt;li&gt;单路由配置&lt;/li&gt;
&lt;/ul&gt;
 &lt;pre&gt;  &lt;code&gt;      - id: per_route_timeouts
        uri: https://example.org
        predicates:
          - name: Path
            args:
              pattern: /delay/{timeout}
        metadata:
          response-timeout: 200
          connect-timeout: 200
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;connect-timeout是指网关到目标路由的连接超时时间（缺省45秒）。&lt;/p&gt;
 &lt;p&gt;response-timeout是指服务给网关返回响应的时间（默认应该是无限时间，暂时没分析源码）。&lt;/p&gt;
 &lt;p&gt;网关默认使用弹性连接连接池，默认的连接数是  &lt;code&gt;Integer.MAX_VALUE&lt;/code&gt;。&lt;/p&gt;
 &lt;p&gt;网关使用netty组件并且采用了响应式设计，大部分时候，网关不是整个链路的瓶颈。&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;官网：https://cloud.spring.io/spring-cloud-gateway/reference/html/#http-timeouts-configuration&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;ul&gt;
  &lt;li&gt;服务端超时&lt;/li&gt;
&lt;/ul&gt;
 &lt;pre&gt;  &lt;code&gt;server:
  netty:
    connection-timeout: 60000
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;这个参数是外部连接与gateway建立连接的超时时间（应该是指tcp连接三次握手超时时间），目前该参数有争议。&lt;/p&gt;
 &lt;p&gt;在某些版本应该是固定是10s，配置参数无效。&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;https://stackoverflow.com/questions/53587611/how-to-configure-netty-connection-timeout-for-spring-webflux&lt;/p&gt;
  &lt;p&gt;https://github.com/spring-projects/spring-boot/issues/15368&lt;/p&gt;
  &lt;p&gt;https://github.com/spring-projects/spring-boot/issues/18473&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;h4&gt;2.2 Tomcat关键配置&lt;/h4&gt;
 &lt;ul&gt;
  &lt;li&gt;以内嵌Tomcat为例：&lt;/li&gt;
&lt;/ul&gt;
 &lt;pre&gt;  &lt;code&gt;server:
  tomcat:   
    accept-count: 100
    threads:
      max: 200
    max-connections: 8192
    connection-timeout: 60000
    keep-alive-timeout: 60000
    max-keep-alive-requests: 100
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;**threads.max：**表示服务器最大有多少个线程处理请求，默认200，实际上这个参数超过大多数服务器核心数，实际会降低服务器cpu处理速度，所以在业务处理中，应让tomcat应该让业务快速响应。&lt;/p&gt;
 &lt;p&gt;**max-connections：**表示服务器与客户端可以建立多少个连接数，即持有的连接数。tomcat缺省是8192个连接数，cpu未必有时间给你处理，但是可以保持连接。这个参数是客户感知型参数。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;accept-count:&lt;/strong&gt; 与服务器内核相关，是客户端传入给服务器内核，请求的backlog值，该值与服务器内核参数  &lt;code&gt;net.core.somaxconn&lt;/code&gt;取小后的值为最终起效的TCP内核全队列值。它表示在max-connections值达到预设的值后，服务器内核还能建立的连接数，这个连接保存在内核，还未被上层应用（tomcat）取走。该值在tomcat中默认是100，在Centos7.x版本中内核  &lt;code&gt;net.core.somaxconn&lt;/code&gt;是128。如果超过max-connections和accept-count总和，新的连接会被拒绝，即直接拒绝服务（直接返回connection refused）。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;connection-timeout：&lt;/strong&gt; 连接超时，URI所请求的内容被呈现出来前的超时时间。在SpringBoot2.x中缺省是60秒，注意：如果是使用标准server.xml的tomcat，缺省是20秒，不同版本的SpringBoot，其内嵌tomcat的连接超时可能不同，所以，建议直接指定该值。&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;The number of milliseconds this Connector will wait, after accepting a connection, for the request URI line to be presented. Use a value of -1 to indicate no (i.e. infinite) timeout. The default value is 60000 (i.e. 60 seconds) but note that the standard server.xml that ships with Tomcat sets this to 20000 (i.e. 20 seconds). Unless disableUploadTimeout is set to false, this timeout will also be used when reading the request body (if any).&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;  &lt;strong&gt;keep-alive-timeout：&lt;/strong&gt; keepalive的超时时间，缺省与connection-timeout相同。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;max-keep-alive-requests:&lt;/strong&gt; 最大的保持keepalive的请求数量。&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;缺省情况下：tomcat可以保持8192个socket连接，系统内核帮忙保持100个连接。直至connection-timeout的时间。&lt;/p&gt;
  &lt;p&gt;同一个连接在保活期间可以多次请求和响应。&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;h4&gt;2.3 feign接口配置&lt;/h4&gt;
 &lt;p&gt;feign接口配置影响的是链路中服务之间的调用。&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;feign全局服务超时&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;default配置项影响全局配置（是否只是影响缺省客户端待查）。在使用第三方客户端的时候，应是以第三个客户端为基准，例如httpclient或okhttp。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;feign:  
  client:
    config:
      # 全局配置
      default:
        loggerLevel: basic # NONE(默认)、BASIC、HEADERS、FULL
        connectTimeout: 30000 #毫秒
        readTimeout: 30000 #毫秒
  # 开启httpClient客户端作为http连接池
  httpclient:
    enabled: true
    max-connections: 200
    max-connections-per-route: 50 # feign单个路径的最大连接数
    connection-timeout: 30000
    connection-timer-repeat: 3000
&lt;/code&gt;&lt;/pre&gt;
 &lt;ul&gt;
  &lt;li&gt;feign独立服务超时&lt;/li&gt;
&lt;/ul&gt;
 &lt;pre&gt;  &lt;code&gt;feign:
  client:
    config:
      # 设置FooClient的超时时间
      FooClient:
        connectTimeout: 5000
        readTimeout: 3000
&lt;/code&gt;&lt;/pre&gt;
 &lt;ul&gt;
  &lt;li&gt;单独给某接口设置超时时间&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;在feign接口里加入这个参数就可以单独为接口单独设置超时时间了&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;@FeignClient(name = &amp;quot;wood-system&amp;quot;,contextId = &amp;quot;wood-system-holiday-feign&amp;quot;)
public interface HolidayFeign {
    @GetMapping(&amp;quot;/api/holiday/{id}&amp;quot;)
    Result&amp;lt;SysHoliday&amp;gt; selectOne(Request.Options options,@PathVariable Long id);

    @PostMapping(&amp;quot;/api/holiday/page/{pageNum}/{pageSize}&amp;quot;)
    Result&amp;lt;Object&amp;gt; queryAllByPage(Request.Options options, @RequestBody SysHoliday holiday, @PathVariable int pageNum, @PathVariable int pageSize);

    ... ...
}
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;调用的时候new 一下Options对象&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;   @Resource
    private HolidayFeign holidayFeign;

@GetMapping(&amp;quot;{id}&amp;quot;)
    public Result&amp;lt;SysHoliday&amp;gt; selectOne(@PathVariable Long id) {
        Request.Options options = new Request.Options(10, TimeUnit.SECONDS, 10, TimeUnit.SECONDS, true);
        return holidayFeign.selectOne(options, id);
    }

    @PostMapping(&amp;quot;/page/{pageNum}/{pageSize}&amp;quot;)
    public Result&amp;lt;Object&amp;gt; queryAllByPage(@RequestBody SysHoliday holiday, @PathVariable int pageNum, @PathVariable int pageSize) {
        Request.Options options = new Request.Options(1, TimeUnit.SECONDS, 1, TimeUnit.SECONDS, true);
        Result&amp;lt;Object&amp;gt; result = holidayFeign.queryAllByPage(options, holiday, pageNum, pageSize);
        return result;
    }
&lt;/code&gt;&lt;/pre&gt;
 &lt;ul&gt;
  &lt;li&gt;关于ribbon的超时&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;因为 Feign 是基于 ribbon 来实现的，所以通过 ribbon 的超时时间设置也能达到目的。类似配置：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt; ribbon:  
    ReadTimeout: 5000 #单位毫秒
    ConnectTimeout: 2000 #单位毫秒

&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;实际上，在使用OpenFeign之后，ribbon已经无法直接配置超时，通常就是使用Feign来配置超时。&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;注意：ribbon 的默认 ConnectTimeout 和 ReadTimeout 都是 1000 ms。这里有两处默认值，见源码。&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;blockquote&gt;
  &lt;p&gt;注意：@FeignClient 注解的 url 参数进行服务调用时是不走ribbon的。&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;h4&gt;2.4 数据库配置&lt;/h4&gt;
 &lt;ul&gt;
  &lt;li&gt;数据库连接池&lt;/li&gt;
&lt;/ul&gt;
 &lt;pre&gt;  &lt;code&gt;  datasource:
    druid:    
      # 连接池属性
      initial-size: 15
      max-active: 100
      min-idle: 15
      # 配置从连接池获取连接等待超时的时间
      max-wait: 30000
      login-timeout: 30000
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;重点是关注max-wait参数：从连接池获取连接等待的时间，其他诸如连接时间、登录时间的超时对于一个正常的连接池反而不是重点，sql执行的时间不建议在连接池上设置超时，因为sql超时后的终止行为需要数据库引擎来执行，应该在数据库层面上设置时间。&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;数据库引擎，sql查询执行超时设置&lt;/li&gt;
&lt;/ul&gt;
 &lt;pre&gt;  &lt;code&gt;-- 默认是0，即无限
select @@max_execution_time;
show variables like &amp;apos;max_execution_time&amp;apos;;

-- 全局设置
SET GLOBAL MAX_EXECUTION_TIME=1000;
-- 对某个session设置
SET SESSION MAX_EXECUTION_TIME=1000;

-- 单独设定sql设置超时时间
SELECT /*+ MAX_EXECUTION_TIME(1000) */ sleep(3), a.* from project_info a;
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;sql执行超时抛出错误：Query execution was interrupted, maximum statement execution time exceeded。&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;数据库事务超时&lt;/li&gt;
&lt;/ul&gt;
 &lt;pre&gt;  &lt;code&gt;# 默认是50秒
select @@innodb_lock_wait_timeout;
SHOW VARIABLES LIKE &amp;apos;innodb_lock_wait_timeout&amp;apos;;

set innodb_lock_wait_timeout=30;
set global innodb_lock_wait_timeout=30;
&lt;/code&gt;&lt;/pre&gt;
 &lt;blockquote&gt;
  &lt;p&gt;注意global的修改对当前线程是不生效的，只有建立新的连接才生效&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;h3&gt;3、建议&lt;/h3&gt;
 &lt;h4&gt;3.1 超时时间设置太大的潜在危害&lt;/h4&gt;
 &lt;p&gt;​可能会有人认为简单的把链路延迟都放大，比如5分钟，这样避免了链路超时的问题。在流量比较小的应用中，不会产生太大影响，在流量较大的微服务架构中，链路设定高延时的同时，如果遇到应用计算或数据库计算发生慢调用，会瞬间拉低TPS，甚至会造成QPS和TPS都为0的恶劣情况。这也是在压测中，某些慢接口会导致整个应用吞吐量急剧下降的原因。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;原因：&lt;/strong&gt;&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;应用的接入是有上限的，在tomcat下，默认就是8192+100，如果高并发下发生慢调用，并且超时时间较长，无法快速失败或降级熔断等，那么所有请求都将等待。&lt;/li&gt;
  &lt;li&gt;cpu的核心数远远小于api的请求数，慢调用较多的时候，cpu应该被拉满，无法及时对正常调用做出响应。&lt;/li&gt;
  &lt;li&gt;慢调用越多，无效的，被废弃的请求就越多（包括正常调用），挤压的请求无法正常响应后，请求失败就会产生雪崩现象。&lt;/li&gt;
&lt;/ol&gt;
 &lt;h4&gt;3.2 优化手段&lt;/h4&gt;
 &lt;ul&gt;
  &lt;li&gt;快慢分离：分库时，不但要考虑按业务分库，还需要考虑按响应和计算分库，例如统计操作一般比较耗时，考虑专门建立聚合统计库，专门的服务来支撑统计功能或慢查询功能。&lt;/li&gt;
  &lt;li&gt;修改默认：数据库相关超时的默认时间都比较长，这些地方是优先需要修改的，在暂时没办法分析流量、响应、计算等要素的情况下，前期是可以考虑将数据库超时设置为30秒到120秒不等，左侧链路依次放大，然后在应用使用过程中观察流量和响应的实际情况，阶段性调整参数。&lt;/li&gt;
  &lt;li&gt;链路优化：找到最右侧（一般是数据库）节点，设定可接受最小超时，然后往左侧逐步放大。&lt;/li&gt;
  &lt;li&gt;计算理论：正常情况下，行业应用，流量流入以及数据计算量、API计算量是有理论最低和理论最高值的，可根据这些数据先预设超时限制。预先分析慢调用链路，在应用服务和数据库上区别设计，做到快慢分离。&lt;/li&gt;
  &lt;li&gt;核心优先：整个微服务架构中，最核心的资源是数据库，它很难做到横向弹性，即使做到，也会有其他诸如分布式事务等因素拉低整体性能，所以，所有的设计都应优先保证数据库相关资源的高效合理利用。&lt;/li&gt;
  &lt;li&gt;监控分析：数据库查询往往是整个微服务调用链路的性能瓶颈，在初期，性能监测阶段通过开启慢查询日志，开启性能分析profiles等手段定位性能问题和找出性能瓶颈，优化整体性能。&lt;/li&gt;
&lt;/ul&gt;
 &lt;h3&gt;9、源码&lt;/h3&gt;
 &lt;h4&gt;9.1 SpringBoot中Tomcat的连接超时源码&lt;/h4&gt;
 &lt;h5&gt;9.1.1 web服务器工厂定制类自动配置  &lt;code&gt;EmbeddedWebServerFactoryCustomizerAutoConfiguration&lt;/code&gt;&lt;/h5&gt;
 &lt;p&gt;注入  &lt;code&gt;TomcatWebServerFactoryCustomizer&lt;/code&gt;，该类用于定制具体的web server工厂。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication
@EnableConfigurationProperties(ServerProperties.class)
public class EmbeddedWebServerFactoryCustomizerAutoConfiguration {
   /**
 * Nested configuration if Tomcat is being used.
 */
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Tomcat.class, UpgradeProtocol.class })
public static class TomcatWebServerFactoryCustomizerConfiguration {

@Bean
public TomcatWebServerFactoryCustomizer tomcatWebServerFactoryCustomizer(Environment environment,
ServerProperties serverProperties) {
return new TomcatWebServerFactoryCustomizer(environment, serverProperties);
}
}
    
    ... ...
}
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;在自动配置类中创建tomcat自定义工厂配置类，这个类的目的就是通过tomcat工厂类  &lt;code&gt;ConfigurableTomcatWebServerFactory&lt;/code&gt;对tomcat的参数进行最后的设置或覆盖，它是通过后置处理器完成调用的。&lt;/p&gt;
 &lt;p&gt;tomcat工厂类  &lt;code&gt;TomcatWebServerFactoryCustomizer&lt;/code&gt;是  &lt;code&gt;WebServerFactoryCustomizer&lt;/code&gt;接口的实现类。&lt;/p&gt;
 &lt;p&gt;以SpringBoot的回调机制，肯定是对  &lt;code&gt;WebServerFactoryCustomizer&lt;/code&gt;接口进行统一处理，通过查找  &lt;code&gt;WebServerFactoryCustomizer&lt;/code&gt;的接口调用或者查找  &lt;code&gt;customize()&lt;/code&gt;的调用都可以追溯到  &lt;code&gt;WebServerFactoryCustomizerBeanPostProcessor&lt;/code&gt;。&lt;/p&gt;
 &lt;h5&gt;9.1.2 tomcat工厂定制类  &lt;code&gt;TomcatWebServerFactoryCustomizer&lt;/code&gt;&lt;/h5&gt;
 &lt;p&gt;用来定制tomcat工厂类，即对SpringBoot注入的  &lt;code&gt;TomcatServletWebServerFactory&lt;/code&gt;进行配置。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;public class TomcatWebServerFactoryCustomizer
implements WebServerFactoryCustomizer&amp;lt;ConfigurableTomcatWebServerFactory&amp;gt;, Ordered {

private final Environment environment;

private final ServerProperties serverProperties;

public TomcatWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties) {
this.environment = environment;
this.serverProperties = serverProperties;
}

@Override
public void customize(ConfigurableTomcatWebServerFactory factory) {
ServerProperties properties = this.serverProperties;

        ... ...

propertyMapper.from(tomcatProperties::getConnectionTimeout).whenNonNull()
.to((connectionTimeout) -&amp;gt; customizeConnectionTimeout(factory, connectionTimeout));
propertyMapper.from(tomcatProperties::getMaxConnections).when(this::isPositive)
.to((maxConnections) -&amp;gt; customizeMaxConnections(factory, maxConnections));
propertyMapper.from(tomcatProperties::getAcceptCount).when(this::isPositive)
.to((acceptCount) -&amp;gt; customizeAcceptCount(factory, acceptCount));
... ...
customizeStaticResources(factory);
customizeErrorReportValve(properties.getError(), factory);
}


private void customizeConnectionTimeout(ConfigurableTomcatWebServerFactory factory, Duration connectionTimeout) {
factory.addConnectorCustomizers((connector) -&amp;gt; {
ProtocolHandler handler = connector.getProtocolHandler();
if (handler instanceof AbstractProtocol) {
AbstractProtocol&amp;lt;?&amp;gt; protocol = (AbstractProtocol&amp;lt;?&amp;gt;) handler;
protocol.setConnectionTimeout((int) connectionTimeout.toMillis());
}
});
}
}
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;在定制工厂类  &lt;code&gt;TomcatWebServerFactoryCustomizer&lt;/code&gt;中，获取ServerProperties属性，重新设置所有可配置项，超时时间的缺省值就是在这里被间接覆盖的。通过  &lt;code&gt;customizeConnectionTimeout&lt;/code&gt;函数，给  &lt;code&gt;TomcatServletWebServerFactory&lt;/code&gt;添加  &lt;code&gt;TomcatConnectorCustomizer&lt;/code&gt;定制连接器参数，在后续的使用  &lt;code&gt;TomcatServletWebServerFactory&lt;/code&gt;创建tomcat中会调用  &lt;code&gt;TomcatConnectorCustomizer&lt;/code&gt;来定制参数。同时，在  &lt;code&gt;customizeConnectionTimeout&lt;/code&gt;可以发现超时时间是在通讯协议里设置的，这点很重要，意味着，我们在跟踪源码时，需要跟踪到具体的HTTP协议创建的类中。&lt;/p&gt;
 &lt;h5&gt;9.1.3 web服务器工厂配置类  &lt;code&gt;ServletWebServerFactoryConfiguration&lt;/code&gt;&lt;/h5&gt;
 &lt;p&gt;用来注入工厂类  &lt;code&gt;TomcatServletWebServerFactory&lt;/code&gt;。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;@Configuration(proxyBeanMethods = false)
class ServletWebServerFactoryConfiguration {

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
static class EmbeddedTomcat {

@Bean
TomcatServletWebServerFactory tomcatServletWebServerFactory(
ObjectProvider&amp;lt;TomcatConnectorCustomizer&amp;gt; connectorCustomizers,
ObjectProvider&amp;lt;TomcatContextCustomizer&amp;gt; contextCustomizers,
ObjectProvider&amp;lt;TomcatProtocolHandlerCustomizer&amp;lt;?&amp;gt;&amp;gt; protocolHandlerCustomizers) {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
factory.getTomcatConnectorCustomizers()
.addAll(connectorCustomizers.orderedStream().collect(Collectors.toList()));
factory.getTomcatContextCustomizers()
.addAll(contextCustomizers.orderedStream().collect(Collectors.toList()));
factory.getTomcatProtocolHandlerCustomizers()
.addAll(protocolHandlerCustomizers.orderedStream().collect(Collectors.toList()));
return factory;
}

}
}    
&lt;/code&gt;&lt;/pre&gt;
 &lt;h5&gt;9.1.4 tomcat工厂类  &lt;code&gt;TomcatServletWebServerFactory&lt;/code&gt;&lt;/h5&gt;
 &lt;p&gt;真正用来创建tomcat的类。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;public class TomcatServletWebServerFactory extends AbstractServletWebServerFactory
implements ConfigurableTomcatWebServerFactory, ResourceLoaderAware {

private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
    public static final String DEFAULT_PROTOCOL = &amp;quot;org.apache.coyote.http11.Http11NioProtocol&amp;quot;;
    private String protocol = DEFAULT_PROTOCOL;
    ... ...

@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
if (this.disableMBeanRegistry) {
Registry.disableRegistry();
}
Tomcat tomcat = new Tomcat();
File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir(&amp;quot;tomcat&amp;quot;);
tomcat.setBaseDir(baseDir.getAbsolutePath());
for (LifecycleListener listener : this.serverLifecycleListeners) {
tomcat.getServer().addLifecycleListener(listener);
}
Connector connector = new Connector(this.protocol);
connector.setThrowOnFailure(true);
tomcat.getService().addConnector(connector);
customizeConnector(connector);
tomcat.setConnector(connector);
tomcat.getHost().setAutoDeploy(false);
configureEngine(tomcat.getEngine());
for (Connector additionalConnector : this.additionalTomcatConnectors) {
tomcat.getService().addConnector(additionalConnector);
}
prepareContext(tomcat.getHost(), initializers);
return getTomcatWebServer(tomcat);
}        
    
}    
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;从  &lt;code&gt;TomcatWebServerFactoryCustomizer&lt;/code&gt;中我们了解到连接超时的参数在通讯协议里，在上述源码里Connector接收协议名称，所以跟踪Connector可以定位到具体内容。&lt;/p&gt;
 &lt;h5&gt;9.1.5 tomcat的连接超时&lt;/h5&gt;
 &lt;ul&gt;
  &lt;li&gt;Connector&lt;/li&gt;
&lt;/ul&gt;
 &lt;pre&gt;  &lt;code&gt;    /**
     * Defaults to using HTTP/1.1 NIO implementation.
     */
    public Connector() {
        this(&amp;quot;HTTP/1.1&amp;quot;);
    }

public Connector(String protocol) {
        boolean apr = AprStatus.getUseAprConnector() &amp;amp;&amp;amp; AprStatus.isInstanceCreated()
                &amp;amp;&amp;amp; AprLifecycleListener.isAprAvailable();
        ProtocolHandler p = null;
        try {
            p = ProtocolHandler.create(protocol, apr);
        } catch (Exception e) {
            log.error(sm.getString(
                    &amp;quot;coyoteConnector.protocolHandlerInstantiationFailed&amp;quot;), e);
        }
        if (p != null) {
            protocolHandler = p;
            protocolHandlerClassName = protocolHandler.getClass().getName();
        } else {
            protocolHandler = null;
            protocolHandlerClassName = protocol;
        }
        // Default for Connector depends on this system property
        setThrowOnFailure(Boolean.getBoolean(&amp;quot;org.apache.catalina.startup.EXIT_ON_INIT_FAILURE&amp;quot;));
    }
&lt;/code&gt;&lt;/pre&gt;
 &lt;ul&gt;
  &lt;li&gt;ProtocolHandler&lt;/li&gt;
&lt;/ul&gt;
 &lt;pre&gt;  &lt;code&gt;    public static ProtocolHandler create(String protocol, boolean apr)
            throws ClassNotFoundException, InstantiationException, IllegalAccessException,
            IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException {
        if (protocol == null || &amp;quot;HTTP/1.1&amp;quot;.equals(protocol)
                || (!apr &amp;amp;&amp;amp; org.apache.coyote.http11.Http11NioProtocol.class.getName().equals(protocol))
                || (apr &amp;amp;&amp;amp; org.apache.coyote.http11.Http11AprProtocol.class.getName().equals(protocol))) {
            if (apr) {
                return new org.apache.coyote.http11.Http11AprProtocol();
            } else {
                return new org.apache.coyote.http11.Http11NioProtocol();
            }
        } else if (&amp;quot;AJP/1.3&amp;quot;.equals(protocol)
                || (!apr &amp;amp;&amp;amp; org.apache.coyote.ajp.AjpNioProtocol.class.getName().equals(protocol))
                || (apr &amp;amp;&amp;amp; org.apache.coyote.ajp.AjpAprProtocol.class.getName().equals(protocol))) {
            if (apr) {
                return new org.apache.coyote.ajp.AjpAprProtocol();
            } else {
                return new org.apache.coyote.ajp.AjpNioProtocol();
            }
        } else {
            // Instantiate protocol handler
            Class&amp;lt;?&amp;gt; clazz = Class.forName(protocol);
            return (ProtocolHandler) clazz.getConstructor().newInstance();
        }
    }
&lt;/code&gt;&lt;/pre&gt;
 &lt;ul&gt;
  &lt;li&gt;Http11NioProtocol&lt;/li&gt;
&lt;/ul&gt;
 &lt;pre&gt;  &lt;code&gt;public class Http11NioProtocol extends AbstractHttp11JsseProtocol&amp;lt;NioChannel&amp;gt; {

    private static final Log log = LogFactory.getLog(Http11NioProtocol.class);


    public Http11NioProtocol() {
        super(new NioEndpoint());
    }    
}    


public abstract class AbstractHttp11JsseProtocol&amp;lt;S&amp;gt;
        extends AbstractHttp11Protocol&amp;lt;S&amp;gt; {

    public AbstractHttp11JsseProtocol(AbstractJsseEndpoint&amp;lt;S,?&amp;gt; endpoint) {
        super(endpoint);
    }
    ... ...
}

public abstract class AbstractHttp11Protocol&amp;lt;S&amp;gt; extends AbstractProtocol&amp;lt;S&amp;gt; {

... ...

    public AbstractHttp11Protocol(AbstractEndpoint&amp;lt;S,?&amp;gt; endpoint) {
        super(endpoint);
        setConnectionTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT);
        ConnectionHandler&amp;lt;S&amp;gt; cHandler = new ConnectionHandler&amp;lt;&amp;gt;(this);
        setHandler(cHandler);
        getEndpoint().setHandler(cHandler);
    }
    
    public void setConnectionTimeout(int timeout) {
        endpoint.setConnectionTimeout(timeout);
    }
}    

public final class Constants {

    public static final int DEFAULT_CONNECTION_TIMEOUT = 60000;
    ... ...
}    
&lt;/code&gt;&lt;/pre&gt;
 &lt;ul&gt;
  &lt;li&gt;AbstractEndpoint&lt;/li&gt;
&lt;/ul&gt;
 &lt;pre&gt;  &lt;code&gt;public abstract class AbstractEndpoint&amp;lt;S,U&amp;gt; {

   ... ... 

    public static long toTimeout(long timeout) {
        // Many calls can&amp;apos;t do infinite timeout so use Long.MAX_VALUE if timeout is &amp;lt;= 0
        return (timeout &amp;gt; 0) ? timeout : Long.MAX_VALUE;
    }
    
    /**
     * Socket timeout.
     *
     * @return The current socket timeout for sockets created by this endpoint
     */
    public int getConnectionTimeout() { return socketProperties.getSoTimeout(); }
    public void setConnectionTimeout(int soTimeout) { socketProperties.setSoTimeout(soTimeout); }    
}

/**
*Properties that can be set in the &amp;lt;Connector&amp;gt; element in server.xml. 
*All properties are prefixed with &amp;quot;socket.&amp;quot; and are currently only working for the Nio connector
*/
public class SocketProperties {
    ...
     /**
     * SO_TIMEOUT option. default is 20000.
     */
    protected Integer soTimeout = Integer.valueOf(20000);
}
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;最终的超时时间SO_TIMEOUT，体现在socket的read()上，并且在socket层面上，缺省超时是20秒，这个值会被tomcat创建Connector类实例化时的60秒常量参数覆盖。&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;SO_TIMEOUT&lt;/li&gt;
&lt;/ul&gt;
 &lt;pre&gt;  &lt;code&gt;Enable/disable SO_TIMEOUT with the specified timeout, in milliseconds. With this option set to a non-zero timeout, a read() call on the InputStream associated with this Socket will block for only this amount of time. If the timeout expires, a java.net.SocketTimeoutException is raised, though the Socket is still valid. The option must be enabled prior to entering the blocking operation to have effect. The timeout must be &amp;gt; 0. A timeout of zero is interpreted as an infinite timeout.
&lt;/code&gt;&lt;/pre&gt;
 &lt;h5&gt;9.1.6 SpringBoot创建tomcat服务&lt;/h5&gt;
 &lt;ul&gt;
  &lt;li&gt;入口   &lt;code&gt;refresh()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
 &lt;pre&gt;  &lt;code&gt;//org.springframework.context.support.AbstractApplicationContext#refresh
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
......
try {
....       
// Initialize other special beans in specific context subclasses.                    
onRefresh();
                ....
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);                    
}
}
}

//org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#onRefresh
@Override
protected void onRefresh() {
    super.onRefresh();
    try {
        createWebServer();
    }
    catch (Throwable ex) {
        throw new ApplicationContextException(&amp;quot;Unable to start web server&amp;quot;, ex);
   
    }
}

//org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#createWebServer
private void createWebServer() {
    WebServer webServer = this.webServer;
    ServletContext servletContext = getServletContext();
    if (webServer == null &amp;amp;&amp;amp; servletContext == null) {
        ServletWebServerFactory factory = getWebServerFactory();
        this.webServer = factory.getWebServer(getSelfInitializer());
    }
    else if (servletContext != null) {
        try {
            getSelfInitializer().onStartup(servletContext);
        }
        catch (ServletException ex) {
            throw new ApplicationContextException(&amp;quot;Cannot initialize servlet context&amp;quot;, ex);
        }
    }
    initPropertySources();
}

//org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory#getWebServer
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
    if (this.disableMBeanRegistry) {
        Registry.disableRegistry();
    }
    Tomcat tomcat = new Tomcat();
    File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir(&amp;quot;tomcat&amp;quot;);
    tomcat.setBaseDir(baseDir.getAbsolutePath());
    //初始化
    Connector connector = new Connector(this.protocol);
    connector.setThrowOnFailure(true);
    tomcat.getService().addConnector(connector);
    customizeConnector(connector);
    tomcat.setConnector(connector);
    tomcat.getHost().setAutoDeploy(false);
    configureEngine(tomcat.getEngine());
    for (Connector additionalConnector : this.additionalTomcatConnectors) {
        tomcat.getService().addConnector(additionalConnector);
    }
    prepareContext(tomcat.getHost(), initializers);
    return getTomcatWebServer(tomcat);
}

&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;在  &lt;code&gt;onRefresh()&lt;/code&gt;完成tomcat服务器创建，并赋予默认参数。&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;创建bean以及后置处理   &lt;code&gt;finishBeanFactoryInitialization(beanFactory)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
 &lt;pre&gt;  &lt;code&gt;/**
 * Finish the initialization of this context&amp;apos;s bean factory,
 * initializing all remaining singleton beans.
 */
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {

        ... ...

// Instantiate all remaining (non-lazy-init) singletons.
beanFactory.preInstantiateSingletons();
}



public &amp;lt;T&amp;gt; T getBean(String name, @Nullable Class&amp;lt;T&amp;gt; requiredType, @Nullable Object... args)
throws BeansException {

return doGetBean(name, requiredType, args, false);
}


protected &amp;lt;T&amp;gt; T doGetBean(
String name, @Nullable Class&amp;lt;T&amp;gt; requiredType, @Nullable Object[] args, boolean typeCheckOnly)
throws BeansException {

... ...
return createBean(beanName, mbd, args);
         ... ...
}


/**
 * Central method of this class: creates a bean instance,
 * populates the bean instance, applies post-processors, etc.
 * @see #doCreateBean
 */
@Override
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {
        
        
Object beanInstance = doCreateBean(beanName, mbdToUse, args);
...
return beanInstance;

... ...
}

/**
 * Actually create the specified bean. Pre-creation processing has already happened
 * at this point, e.g. checking {@code postProcessBeforeInstantiation} callbacks.
 * &amp;lt;p&amp;gt;Differentiates between default bean instantiation, use of a
 * factory method, and autowiring a constructor.
 * @param beanName the name of the bean
 * @param mbd the merged bean definition for the bean
 * @param args explicit arguments to use for constructor or factory method invocation
 * @return a new instance of the bean
 * @throws BeanCreationException if the bean could not be created
 * @see #instantiateBean
 * @see #instantiateUsingFactoryMethod
 * @see #autowireConstructor
 */
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {

// Instantiate the bean.
    ... ...
Object bean = instanceWrapper.getWrappedInstance();
... ...

// Initialize the bean instance.
Object exposedObject = bean;
        // Populate the bean instance in the given BeanWrapper with the property values from the bean definition.
populateBean(beanName, mbd, instanceWrapper);
        
exposedObject = initializeBean(beanName, exposedObject, mbd);
... ...
}


/**
 * Initialize the given bean instance, applying factory callbacks
 * as well as init methods and bean post processors.
 * &amp;lt;p&amp;gt;Called from {@link #createBean} for traditionally defined beans,
 * and from {@link #initializeBean} for existing bean instances.
 * @param beanName the bean name in the factory (for debugging purposes)
 * @param bean the new bean instance we may need to initialize
 * @param mbd the bean definition that the bean was created with
 * (can also be {@code null}, if given an existing bean instance)
 * @return the initialized bean instance (potentially wrapped)
 * @see BeanNameAware
 * @see BeanClassLoaderAware
 * @see BeanFactoryAware
 * @see #applyBeanPostProcessorsBeforeInitialization
 * @see #invokeInitMethods
 * @see #applyBeanPostProcessorsAfterInitialization
 */
protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
if (System.getSecurityManager() != null) {
AccessController.doPrivileged((PrivilegedAction&amp;lt;Object&amp;gt;) () -&amp;gt; {
invokeAwareMethods(beanName, bean);
return null;
}, getAccessControlContext());
}
else {
invokeAwareMethods(beanName, bean);
}

Object wrappedBean = bean;
if (mbd == null || !mbd.isSynthetic()) {
wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
}

try {
invokeInitMethods(beanName, wrappedBean, mbd);
}
catch (Throwable ex) {
throw new BeanCreationException(
(mbd != null ? mbd.getResourceDescription() : null),
beanName, &amp;quot;Invocation of init method failed&amp;quot;, ex);
}
if (mbd == null || !mbd.isSynthetic()) {
wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
}

return wrappedBean;
}
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;真正的bean后置处理在  &lt;code&gt;initializeBean&lt;/code&gt;中完成。&lt;/p&gt;
 &lt;h4&gt;9.2 Gateway连接数源码&lt;/h4&gt;
 &lt;ul&gt;
  &lt;li&gt;网关自动配置   &lt;code&gt;GatewayAutoConfiguration&lt;/code&gt;，连接相关主要是   &lt;code&gt;HttpClient&lt;/code&gt;，注意：它是基于netty的响应式客户端，不是apache的HttpClient。&lt;/li&gt;
&lt;/ul&gt;
 &lt;pre&gt;  &lt;code&gt;@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(name = &amp;quot;spring.cloud.gateway.enabled&amp;quot;, matchIfMissing = true)
@EnableConfigurationProperties
@AutoConfigureBefore({ HttpHandlerAutoConfiguration.class,
WebFluxAutoConfiguration.class })
@AutoConfigureAfter({ GatewayLoadBalancerClientAutoConfiguration.class,
GatewayClassPathWarningAutoConfiguration.class })
@ConditionalOnClass(DispatcherHandler.class)
public class GatewayAutoConfiguration {
    ... ...
    
    @Configuration(proxyBeanMethods = false)
@ConditionalOnClass(HttpClient.class)
protected static class NettyConfiguration {

... ...

@Bean
@ConditionalOnMissingBean
public HttpClient gatewayHttpClient(HttpClientProperties properties,
List&amp;lt;HttpClientCustomizer&amp;gt; customizers) {

// configure pool resources
HttpClientProperties.Pool pool = properties.getPool();

ConnectionProvider connectionProvider;
if (pool.getType() == DISABLED) {
connectionProvider = ConnectionProvider.newConnection();
}
else if (pool.getType() == FIXED) {
connectionProvider = ConnectionProvider.fixed(pool.getName(),
pool.getMaxConnections(), pool.getAcquireTimeout(),
pool.getMaxIdleTime(), pool.getMaxLifeTime());
}
else {
connectionProvider = ConnectionProvider.elastic(pool.getName(),
pool.getMaxIdleTime(), pool.getMaxLifeTime());
}

HttpClient httpClient = HttpClient.create(connectionProvider)
                ...
// TODO: move customizations to HttpClientCustomizers
.tcpConfiguration(tcpClient -&amp;gt; {

if (properties.getConnectTimeout() != null) {
tcpClient = tcpClient.option(
ChannelOption.CONNECT_TIMEOUT_MILLIS,
properties.getConnectTimeout());
}


... ...

return httpClient;
}

... ...

}    
    
｝
                                      
static ConnectionProvider elastic(String name, @Nullable Duration maxIdleTime, @Nullable Duration maxLifeTime) {
return builder(name).maxConnections(Integer.MAX_VALUE)
                    .pendingAcquireTimeout(Duration.ofMillis(0))
                    .pendingAcquireMaxCount(-1)
                    .maxIdleTime(maxIdleTime)
                    .maxLifeTime(maxLifeTime)
                    .build();
}                                      
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;在elastic类型下，连接数是  &lt;code&gt;Integer.MAX_VALUE&lt;/code&gt;。&lt;/p&gt;
 &lt;h4&gt;9.3 Gateway连接超时源码&lt;/h4&gt;
 &lt;ul&gt;
  &lt;li&gt;缺省值45秒，是硬代码&lt;/li&gt;
&lt;/ul&gt;
 &lt;pre&gt;  &lt;code&gt;public abstract class TcpClient {
    ... ...
/**
 * Block the {@link TcpClient} and return a {@link Connection}. Disposing must be
 * done by the user via {@link Connection#dispose()}. The max connection
 * timeout is 45 seconds.
 *
 * @return a {@link Mono} of {@link Connection}
 */
public final Connection connectNow() {
return connectNow(Duration.ofSeconds(45));
}

/**
 * Block the {@link TcpClient} and return a {@link Connection}. Disposing must be
 * done by the user via {@link Connection#dispose()}.
 *
 * @param timeout connect timeout
 *
 * @return a {@link Mono} of {@link Connection}
 */
public final Connection connectNow(Duration timeout) {
Objects.requireNonNull(timeout, &amp;quot;timeout&amp;quot;);
try {
return Objects.requireNonNull(connect().block(timeout), &amp;quot;aborted&amp;quot;);
}
catch (IllegalStateException e) {
if (e.getMessage().contains(&amp;quot;blocking read&amp;quot;)) {
throw new IllegalStateException(&amp;quot;TcpClient couldn&amp;apos;t be started within &amp;quot;
+ timeout.toMillis() + &amp;quot;ms&amp;quot;);
}
throw e;
}
}    
｝
&lt;/code&gt;&lt;/pre&gt;
 &lt;ul&gt;
  &lt;li&gt;参数覆盖&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;在  &lt;code&gt;GatewayAutoConfiguration&lt;/code&gt;中&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;public class GatewayAutoConfiguration {
    ... ...
    
    @Configuration(proxyBeanMethods = false)
@ConditionalOnClass(HttpClient.class)
protected static class NettyConfiguration {

... ...

@Bean
@ConditionalOnMissingBean
public HttpClient gatewayHttpClient(HttpClientProperties properties,
List&amp;lt;HttpClientCustomizer&amp;gt; customizers) {


HttpClient httpClient = HttpClient.create(connectionProvider)
                ...

.tcpConfiguration(tcpClient -&amp;gt; {

if (properties.getConnectTimeout() != null) {
tcpClient = tcpClient.option(
ChannelOption.CONNECT_TIMEOUT_MILLIS,
properties.getConnectTimeout());
}


... ...

return httpClient;
}

... ...

}    
    
｝
     
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;  &lt;code&gt;HttpClient&lt;/code&gt;是抽象类，其实现类  &lt;code&gt;HttpClientConnect&lt;/code&gt;聚合了  &lt;code&gt;TcpClient&lt;/code&gt;的实现类  &lt;code&gt;HttpTcpClient&lt;/code&gt;。&lt;/p&gt;
 &lt;h4&gt;9.4 ribbon超时源码&lt;/h4&gt;
 &lt;p&gt;ribbon 的默认配置在   &lt;code&gt;DefaultClientConfigImpl&lt;/code&gt; 。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;    public static final int DEFAULT_READ_TIMEOUT = 5000;

    public static final int DEFAULT_CONNECTION_MANAGER_TIMEOUT = 2000;

    public static final int DEFAULT_CONNECT_TIMEOUT = 2000;
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;在使用 ribbon 请求接口时，第一次会构建一个 IClienConfig 对象，这个方法在 RibbonClientConfiguration 类中，此时，重新设置了 ConnectTimeout、ReadTimeout等。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;public class RibbonClientConfiguration {

    /**
     * Ribbon client default connect timeout.
     */
    public static final int DEFAULT_CONNECT_TIMEOUT = 1000;

    /**
     * Ribbon client default read timeout.
     */
    public static final int DEFAULT_READ_TIMEOUT = 1000;

    /**
     * Ribbon client default Gzip Payload flag.
     */
    public static final boolean DEFAULT_GZIP_PAYLOAD = true;

    @RibbonClientName
    private String name = &amp;quot;client&amp;quot;;

    @Autowired
    private PropertiesFactory propertiesFactory;

    @Bean
    @ConditionalOnMissingBean
    public IClientConfig ribbonClientConfig() {
        DefaultClientConfigImpl config = new DefaultClientConfigImpl();
        config.loadProperties(this.name);
        config.set(CommonClientConfigKey.ConnectTimeout, DEFAULT_CONNECT_TIMEOUT);
        config.set(CommonClientConfigKey.ReadTimeout, DEFAULT_READ_TIMEOUT);
        config.set(CommonClientConfigKey.GZipPayload, DEFAULT_GZIP_PAYLOAD);
        return config;
    }
    
    //...
}
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;ribbon 的默认 ConnectTimeout 和 ReadTimeout 都是 1000 ms。&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/62713-%E5%BE%AE%E6%9C%8D%E5%8A%A1-%E6%9E%B6%E6%9E%84-%E5%88%86%E6%9E%90</guid>
      <pubDate>Wed, 29 Mar 2023 08:47:10 CST</pubDate>
    </item>
    <item>
      <title>从系统架构分析安全问题及应对措施</title>
      <link>https://itindex.net/detail/62656-%E7%B3%BB%E7%BB%9F%E6%9E%B6%E6%9E%84-%E5%88%86%E6%9E%90-%E5%AE%89%E5%85%A8</link>
      <description>&lt;p&gt;  &lt;strong&gt;作者：京东物流 廖宗雄&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;在日常生产生活中，我们常说，“安全第一”、“安全无小事”。围绕着安全问题，在各行各业都有对各类常见安全问题的解决方案和突发安全问题的应急预案。在互联网、软件开发领域，我们日常工作中对各类常见的安全问题又有哪些常见的解决方案呢？在此，结合经典架构图做一个梳理。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3a2fe48a641147db9d6b27370c77d214~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;经典架构图&lt;/p&gt;
 &lt;p&gt;下面，结合上述的经典架构图，对数据存储、微服务接口、外网数据传输及APP层可能出现的安全问题进行分析，并给出一些常见的应对措施。&lt;/p&gt;
 &lt;h1&gt;  &lt;strong&gt;1 数据存储&lt;/strong&gt;&lt;/h1&gt;
 &lt;p&gt;为了保证数据存储的安全，对于敏感数据在进行存储时，需要进行加密存储，同时，敏感数据建议在全公司进行收口管理，便于统一管理。对敏感数据进行加密存储时，常见的加密方式有可逆加密和不可逆加密，分别适用于不同的敏感数据。&lt;/p&gt;
 &lt;h2&gt;  &lt;strong&gt;1.1 可逆加密或对称加密&lt;/strong&gt;&lt;/h2&gt;
 &lt;p&gt;可逆加密，即通过对密文进行解密后，能把密文解密还原出明文。对称加密算法加密和解密用到的密钥是相同的，这种加密方式加密速度非常快，适合经常发送数据的场合，缺点是密钥的传输比较麻烦。比如：网络购物的收货地址、姓名、手机号等就适合用该加密方式，常用的对称加密算法有DES、AES，下面以AES为例说明对称加密的过程。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c8289205ef7744c6adb78a4d40a1ba0f~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;在该加解密中，对于秘钥K的生成需要加解密双方共同制定并妥善保管。通常，我们会把该秘钥K存储在需要使用加解密程序的进程内，便于在程序使用时直接进行使用。&lt;/p&gt;
 &lt;h2&gt;  &lt;strong&gt;1.2 不可逆加密&lt;/strong&gt;&lt;/h2&gt;
 &lt;p&gt;不可逆加密，即不需要解密出明文，如：用户的密码。不可逆加密常用的算法有RES、MD5等，在此以MD5为例进行说明。但大家都知道，MD5算法是存在碰撞的，即不同的明文通过MD5加密后，存在相同的密文。因此，直接使用MD5对密码进行加密在生产上是不严谨的，通常还需要配合盐（salt）进行使用。对于盐的使用，也有一定的技巧，一种盐值是固定的，即所有的明文在进行加密时都使用相同的盐进行加密；另一种是结合具体的业务场景，用可变盐值，比如：就密码加密而言，可以把用户名的部分或全部作为盐值，和密码进行一起加密后存储。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c6edb7899754476da0f459095c69fbf2~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h1&gt;  &lt;strong&gt;2 微服务接口&lt;/strong&gt;&lt;/h1&gt;
 &lt;p&gt;微服务的安全，需要从请求鉴权和请求容量限制这2个方面来考虑。对于请求鉴权，可以设置请求IP黑名单的方式，对该IP的所有请求或全部放行或全部拒绝，该方式的粒度较粗。而如果要做得较细粒度一些，可以针对具体的API进行token鉴权，相比粗粒度该方式会控制得比较精准；&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;&amp;lt;jsf:consumer id=&amp;quot;setmentService&amp;quot; interface=&amp;quot;com.jd.lfsp.jsf.service.SetmentService&amp;quot;
                  protocol=&amp;quot;jsf&amp;quot; alias=&amp;quot;${jsf.consumer.alias}&amp;quot; timeout=&amp;quot;${jsf.consumer.timeout}&amp;quot; retries=&amp;quot;0&amp;quot;&amp;gt;
        &amp;lt;jsf:parameter key=&amp;quot;token&amp;quot; value=&amp;quot;${jsf.consumer.token}&amp;quot; hide=&amp;quot;true&amp;quot; /&amp;gt;
&amp;lt;/jsf:consumer&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;除了对请求鉴权外，在实际的生产中，还可以对请求容量进行限制，对请求容量进行限制时，可以按QPS进行限制，也可以对每天的最大请求次数进行限制。在jsf平台管理端，可以对具体的方法进行请求的QPS限流。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4cfa9c01831a4a86992b156f7fbea747~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h1&gt;  &lt;strong&gt;3 数据传输&lt;/strong&gt;&lt;/h1&gt;
 &lt;p&gt;数据传输主要分为数据通过前端APP的请求，进入到服务网关前和进入服务网关后这俩部分，对于数据已经进入服务网关后，这属于机房内的数据传输，通常这类加密意义不大，对这类的数据传输的安全需要建立相应的内部安全机制及流程规范，通过制度措施来保证。而数据在进入服务网关前，对数据的安全传输有哪些可做的。在数据请求进入服务网关前，通常我们有基于SSL协议的传输加密以及现在普遍通用的HTTPS加密。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/30f8701cb5c4498fa133eba60890fb51~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;HTTPS也是HTTP和SSL协议的结合体，所以在数据传输中，SSL协议扮演了至关重要的角色。那SSL协议的工作过程是怎么样的，他是怎么保证数据传输过程中的安全的呢？下面为大家解析一下SSL协议的工作过程。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/bb2c517aa49e4cccb05bb5aeb827fb0a~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;SSL客户端与SSL服务端验证的过程如下：&lt;/strong&gt;&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;SSL客户端向SSL服务端发送随机消息ClientHello的同时把自己支持的SSL版本、加密算法、秘钥交换算法、MAC算法等信息一并发送；&lt;/li&gt;
  &lt;li&gt;SSL服务端收到SSL客户端的请求后，确定本次通信采用的SSL版本及加密组件和MAC算法，并通过ServerHello发送给SSL客户端；&lt;/li&gt;
  &lt;li&gt;SSL服务端将携带自己公钥信息的数字证书通过Certificate发送给SSL客户端；&lt;/li&gt;
  &lt;li&gt;SSL服务端通过ServerHelloDone消息通知SSL客户端版本和加密组件协商结束，开始进行秘钥交换；&lt;/li&gt;
  &lt;li&gt;SSL客户端验证SSL服务端发送的证书合法后，利用证书中的公钥加密随机数生成ClientKeyExchange发送给SSL服务端；&lt;/li&gt;
  &lt;li&gt;SSL客户端发送ChangeCipherSpec消息，通知SSL服务端后续将用协商好的秘钥及加密组件和MAC值；&lt;/li&gt;
  &lt;li&gt;SSL客户端计算已交互的握手消息的hash值，利用协商好的秘钥和加密组件加密hash值，并通过Finished消息发送给SSL服务端，SSL服务端用相同的方法计算已交互的hash值，并与Finished消息进行对比，二者相同且MAC值相同，则秘钥和加密组件协商成功；&lt;/li&gt;
  &lt;li&gt;同样地，SSL服务端也通过ChangeCipherSpec消息通知客户端后续报文将采用协商好的秘钥及加密组件和MAC算法；&lt;/li&gt;
  &lt;li&gt;SSL服务端端计算已交互的握手消息的hash值，利用协商好的秘钥和加密组件加密hash值，并通过Finished消息发送给SSL客户，SSL客户端用相同的方法计算已交互的hash值，并与Finished消息进行对比，二者相同且MAC值相同，则秘钥和加密组件协商成功；&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;通过上面的这个交互过程，我们可以看出，在使用SSL的过程中，除了客户端（浏览器）跟服务器之间的通讯外，其他的任何第三方想要获取到协商的秘钥是比较困难的。即使有比较厉害的人获取到了，基于目前用户在某个网站上的时效性，会影响我们对应秘钥的时效性，因此，造成的破坏性也比较有限。&lt;/p&gt;
 &lt;h1&gt;  &lt;strong&gt;4 APP&lt;/strong&gt;&lt;/h1&gt;
 &lt;p&gt;在APP层的安全问题，需要结合服务端一并来解决，在这主要介绍验证码这种形式。验证码作为一种人机识别手段，其主要作用是区分正常人操作还是机器的操作，拦截恶意行为。当前互联网中，大多数系统为了更好地提供服务，通常都需要用户进行注册。注册后，用户每次在使用系统时需要进行登录，登录过程中，为了防止系统非法使用，通常都需要用户进行登录操作，登录过程中，常用的验证方式主要通过验证码进行验证，当前比较常用的验证码有以下几种类型。&lt;/p&gt;
 &lt;h2&gt;  &lt;strong&gt;4.1 短信验证码&lt;/strong&gt;&lt;/h2&gt;
 &lt;p&gt;目前用得比较广泛的一种验证码形式，输入有效的手机号后，系统给手机号发送相应的短信验证码完成验证。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/be625cfe56cd4c519a8f2011afd84df9~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h2&gt;  &lt;strong&gt;4.2 语音验证码&lt;/strong&gt;&lt;/h2&gt;
 &lt;p&gt;通过输入有效的手机号，系统给手机号拨打电话后，用语音播报的方式完成验证码的验证。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e5ebac1b1adc4d13b7bc519e1d1841ee~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h2&gt;  &lt;strong&gt;4.3 图片验证码&lt;/strong&gt;&lt;/h2&gt;
 &lt;p&gt;较传统的验证码验证方式，由系统给出验证码在页面显示，在进行页面提交时，验证码一并提交到系统后台验证。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a3332a6442ab421e86c37e7ee2d54305~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h2&gt;  &lt;strong&gt;4.4 语义验证码&lt;/strong&gt;&lt;/h2&gt;
 &lt;p&gt;比较新颖的一种验证码形式，但是该种方式相比较而言对用户不是特别友好，需要慎用。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/61a362d4869340ceba10f7b5c8c400a2~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;除了上述的几种目前常用的验证码外，还有文本验证码、拼图验证码、问题类验证码等，在此就不再一一列举，大家如果感兴趣可以自己去搜索、学习。&lt;/p&gt;
 &lt;p&gt;这主要从系统的架构上，分析了日常工作中我们所接触到的比较常见的一些安全问题及其应对措施，在实际工作的安全问题远不止这里提到的内容。希望在日常工作中，我们大家都绷紧安全的神经，时刻关注自己工作中的各类潜在的安全问题，争取把安全问题消灭在系统发布前。&lt;/p&gt;
 &lt;h1&gt;  &lt;strong&gt;5 参考文献&lt;/strong&gt;&lt;/h1&gt;
 &lt;p&gt;  &lt;strong&gt;SSL是如何加密传输的数据的：&lt;/strong&gt;  &lt;br /&gt;
  &lt;a href="https://evergreen-tree.github.io/articles/2016-05/daily-ssl-rsa-des-algorithm"&gt;[技术每日说] - SSL是如何加密传输的数据的!&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;名词解释：&lt;/strong&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;SSL：（Secure Socket Layer，安全套接字层），位于可靠的面向连接的网络层协议和应用层协议之间的一种协议层。SSL通过互相认证、使用数字签名确保完整性、使用加密确保私密性，从而实现客户端和服务器之间的安全通讯。该协议由两层组成：SSL记录协议和SSL握手协议。&lt;/li&gt;
  &lt;li&gt;HTTPS：（全称：Hypertext Transfer Protocol over Secure Socket Layer），是以安全为目标的HTTP通道，简单讲是HTTP的安全版（HTTP+SSL）。即HTTP下加入SSL层，HTTPS的安全基础是SSL，因此加密的详细内容就需要SSL。&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/62656-%E7%B3%BB%E7%BB%9F%E6%9E%B6%E6%9E%84-%E5%88%86%E6%9E%90-%E5%AE%89%E5%85%A8</guid>
      <pubDate>Sat, 04 Mar 2023 13:58:52 CST</pubDate>
    </item>
    <item>
      <title>这是我见过最好的用户增长分析模型</title>
      <link>https://itindex.net/detail/62648-%E6%88%91%E8%A7%81-%E6%9C%80%E5%A5%BD-%E7%94%A8%E6%88%B7</link>
      <description>&lt;blockquote&gt;  &lt;p&gt;数据分析师在进行数据驱动增长分析时，不但要用AARRR呈现增长结果，更要量化展现增长决策的全过程，从而发现更深层的问题。这篇文章通过围绕六个模块深入整个分析模型，数据分析师们快来学习学习吧。&lt;/p&gt;&lt;/blockquote&gt;
 &lt;p&gt;  &lt;img alt="" src="https://image.yunyingpai.com/wp/2023/03/FFhrYeaffnh3BtW8km72.jpg"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;数据驱动增长，是很多公司对数据分析师的要求，可具体到操作上，大家就开始纠结了。虽然增长黑客上白纸黑字写了AARRR五个大字，可真到分析的时候，就总被吐槽：&lt;/p&gt;
 &lt;blockquote&gt;  &lt;p&gt;“新客户数10000人，所以呢？”&lt;/p&gt;
  &lt;p&gt;“活跃率50%，又怎样？”&lt;/p&gt;
  &lt;p&gt;“转化率跌了，干啥能升起来？”&lt;/p&gt;&lt;/blockquote&gt;
 &lt;p&gt;单纯看AARRR五个指标，很容易给出“转化率跌了，要搞高”这种无脑结论。要怎么分析才能成体系地输出结论？今天系统分享一下。&lt;/p&gt;
 &lt;h2&gt;00 用户增长的本质&lt;/h2&gt;
 &lt;p&gt;先忘记模型、数据、方法论。就问一个最简单的问题：“如果让你自己做生意，你会思考啥？”你肯定不会先去听成功学大师讲“心法”“模型”“底层逻辑”（如果真有人兜售这些，要溜快点！），而是问几个简单的问题：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;我要卖啥？&lt;/li&gt;
  &lt;li&gt;卖给哪些客户？&lt;/li&gt;
  &lt;li&gt;在哪里找到客户？&lt;/li&gt;
  &lt;li&gt;怎么让客户买单？&lt;/li&gt;
  &lt;li&gt;我要投入多少？&lt;/li&gt;
  &lt;li&gt;我能赚回多少？&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;这些实打实的问题，才是生意成功的关键。企业也是一样，不管发明多少新名词，做用户增长，就是得解决这六个核心问题：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;赛道选择（线上/线下，快消、耐用、零食、服务……）&lt;/li&gt;
  &lt;li&gt;客群选择（一个具体赛道下的高、中、低客户）&lt;/li&gt;
  &lt;li&gt;获客渠道（广告投放、门店、裂变、传统销售……）&lt;/li&gt;
  &lt;li&gt;转化方式（买赠、拼团、秒杀、优惠、限购……）&lt;/li&gt;
  &lt;li&gt;投入成本（商品成本+广告成本+营销成本+运营成本）&lt;/li&gt;
  &lt;li&gt;产出收益（销售利润，融资目标）&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;这里个问题之间，有内在逻辑（如下图）：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="&amp;#36825;&amp;#26159;&amp;#25105;&amp;#35265;&amp;#36807;&amp;#26368;&amp;#22909;&amp;#30340;&amp;#29992;&amp;#25143;&amp;#22686;&amp;#38271;&amp;#20998;&amp;#26512;&amp;#27169;&amp;#22411;" height="358" src="https://image.yunyingpai.com/wp/2023/03/B6rmOxib7jkAgAvTHGfX.png" title="&amp;#36825;&amp;#26159;&amp;#25105;&amp;#35265;&amp;#36807;&amp;#26368;&amp;#22909;&amp;#30340;&amp;#29992;&amp;#25143;&amp;#22686;&amp;#38271;&amp;#20998;&amp;#26512;&amp;#27169;&amp;#22411;" width="707"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;整个分析模型，就是围绕这六个模块做深入。不但要用AARRR呈现增长结果，更要量化展现增长决策的  &lt;strong&gt;全过程&lt;/strong&gt;，从而发现更深层的问题。&lt;/p&gt;
 &lt;h2&gt;01 第一：赛道选择&lt;/h2&gt;
 &lt;p&gt;用户增长本身有两大方式：&lt;/p&gt;
 &lt;p&gt;初创型企业，需要在公共市场上竞争，大海捕鱼。&lt;/p&gt;
 &lt;p&gt;集团企业内部孵化新业务，可以从内部引流，池塘养鱼。&lt;/p&gt;
 &lt;p&gt;两个方式下，数据看法不同：&lt;/p&gt;
 &lt;p&gt;大海捕鱼式：需要评估市场空间，市场增长速度，竞争对手情况，需要大量二手数据。&lt;/p&gt;
 &lt;p&gt;池塘养鱼式：内部客户已有数据基础，待转化的范围是有限的，直接做客群分析即可。&lt;/p&gt;
 &lt;p&gt;相比之下，大海捕鱼式更麻烦，因此重点讲一下，此时需要收集三个数据（如下图）：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;目前市场评估：评估增长空间有多大&lt;/li&gt;
  &lt;li&gt;存量玩家的规模：发现竞争格局，评估竞争难度&lt;/li&gt;
  &lt;li&gt;存量玩家的增速：发现增长标杆，选择对标对象&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;  &lt;img alt="&amp;#36825;&amp;#26159;&amp;#25105;&amp;#35265;&amp;#36807;&amp;#26368;&amp;#22909;&amp;#30340;&amp;#29992;&amp;#25143;&amp;#22686;&amp;#38271;&amp;#20998;&amp;#26512;&amp;#27169;&amp;#22411;" height="287" src="https://image.yunyingpai.com/wp/2023/03/NwcRb2J8Z5gHbRhYr1Yu.png" title="&amp;#36825;&amp;#26159;&amp;#25105;&amp;#35265;&amp;#36807;&amp;#26368;&amp;#22909;&amp;#30340;&amp;#29992;&amp;#25143;&amp;#22686;&amp;#38271;&amp;#20998;&amp;#26512;&amp;#27169;&amp;#22411;" width="696"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;这三个数据，都不太可能直接获取准确数据，因此需要结合第三方数据、行业报告、主要竞争对手的新闻，甚至一些不太光彩的手段获取。&lt;/p&gt;
 &lt;p&gt;此时，不必太纠结数据准确度（一定不100%准），而是要评估来自各个渠道的信息，指向是否一致。比如各渠道都反应：行业是垄断竞争装填，行业在快速扩张期。定性判断是准的即可。&lt;/p&gt;
 &lt;h2&gt;02 第二：客群选择&lt;/h2&gt;
 &lt;p&gt;当赛道具体到一个具体领域后（快消、耐用、零食、服务……），其目标用户群体的画像、消费力、人群数量是可以锁定的。这里有两个要重点关注的东西：用户消费力分层与用户复购行为。这两点，直接决定了增长打法。&lt;/p&gt;
 &lt;h3&gt;1、用户消费力分层&lt;/h3&gt;
 &lt;p&gt;原则上，头部客户的消费力越强，人数越少。则越应该采取“大浪淘沙”式的增长策略，大量获客之后，通过高门槛+重服务，筛选出大客户，紧紧抓住大客户的需求。如果头部客户消费力与底部差异不大，或者用户普遍有大额消费刚需，则要采取“放水养鱼”策略，做好基础服务，做大客群。&lt;/p&gt;
 &lt;h3&gt;2、用户成长路径&lt;/h3&gt;
 &lt;p&gt;客户自然复购率高，通过少量投入能引发复购，则可以打造用户成长路径，鼓励用户多消费，鼓励累积消费。如果天然复购率就低，则应采用收割策略：大量获取新人，鼓励老人带新人，从而保持持续增长。（如下图所示）&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="&amp;#36825;&amp;#26159;&amp;#25105;&amp;#35265;&amp;#36807;&amp;#26368;&amp;#22909;&amp;#30340;&amp;#29992;&amp;#25143;&amp;#22686;&amp;#38271;&amp;#20998;&amp;#26512;&amp;#27169;&amp;#22411;" height="313" src="https://image.yunyingpai.com/wp/2023/03/RgEQBiWTpbY2oXivNA5I.png" title="&amp;#36825;&amp;#26159;&amp;#25105;&amp;#35265;&amp;#36807;&amp;#26368;&amp;#22909;&amp;#30340;&amp;#29992;&amp;#25143;&amp;#22686;&amp;#38271;&amp;#20998;&amp;#26512;&amp;#27169;&amp;#22411;" width="558"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;注意，这里有个典型的分析陷阱：把自己的存量用户，当成了市场上全量用户。当一个企业在市场上没有处于垄断地位的时候，很有可能存量的用户只是整体用户的一部分。市场上的用户全貌和基于存量分析出的用户画像不一样（如下图）。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="&amp;#36825;&amp;#26159;&amp;#25105;&amp;#35265;&amp;#36807;&amp;#26368;&amp;#22909;&amp;#30340;&amp;#29992;&amp;#25143;&amp;#22686;&amp;#38271;&amp;#20998;&amp;#26512;&amp;#27169;&amp;#22411;" height="315" src="https://image.yunyingpai.com/wp/2023/03/Ga1WvJLhMMHKRY0kCTm3.png" title="&amp;#36825;&amp;#26159;&amp;#25105;&amp;#35265;&amp;#36807;&amp;#26368;&amp;#22909;&amp;#30340;&amp;#29992;&amp;#25143;&amp;#22686;&amp;#38271;&amp;#20998;&amp;#26512;&amp;#27169;&amp;#22411;" width="627"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;因此在客群选择阶段做分析，要结合调研/竞品分析开展，及时了解竞争对手的客群结构，避免盲人摸象，越做越瞎。&lt;/p&gt;
 &lt;h2&gt;03 第三：获取渠道&lt;/h2&gt;
 &lt;p&gt;用户获取渠道与转化方式，与用户群体的定位有直接关系。理论上，有四种常见的形式可以选：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;线上广告投放（根据目标用户喜好的渠道、内容进行投放）&lt;/li&gt;
  &lt;li&gt;线上用户裂变（目标用户中有KOL存在/KOC有足够分享意愿才行）&lt;/li&gt;
  &lt;li&gt;线下门店（目标用户聚集在特定城市/特定区域）&lt;/li&gt;
  &lt;li&gt;线下销售（有足够多大客户，值得销售一对一跟进）&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;这四种方式，对应着特定用户群体需求。因此在评估获客方式的时候，优先看的是每一类方式是否能触达对应的用户，再看转化效果。因此要区分局部影响因素和全局影响因素，优先看投放渠道和触达人数，是否达成目标（如下图）。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="&amp;#36825;&amp;#26159;&amp;#25105;&amp;#35265;&amp;#36807;&amp;#26368;&amp;#22909;&amp;#30340;&amp;#29992;&amp;#25143;&amp;#22686;&amp;#38271;&amp;#20998;&amp;#26512;&amp;#27169;&amp;#22411;" height="300" src="https://image.yunyingpai.com/wp/2023/03/M6HOE6gV0uEQnroegCQw.png" title="&amp;#36825;&amp;#26159;&amp;#25105;&amp;#35265;&amp;#36807;&amp;#26368;&amp;#22909;&amp;#30340;&amp;#29992;&amp;#25143;&amp;#22686;&amp;#38271;&amp;#20998;&amp;#26512;&amp;#27169;&amp;#22411;" width="592"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;之后，才是每一类方式的转化漏斗分析。转化漏斗分析在很多文章已经有讲到，这里不再赘述了，传统的投放分析/获客分析也经常做这一块。&lt;/p&gt;
 &lt;h2&gt;04 第四：转化方式&lt;/h2&gt;
 &lt;p&gt;转化方式的分析，在很多文章也已经讲过，这里不再赘述了。实际上，传统的投放分析/获客分析也会做转化方式的研究，很多ABtest也是围绕“哪种转化方式更有效”进行的。&lt;/p&gt;
 &lt;p&gt;比如测试一个在线课程获客效果，可以用如下图方法，通过多个版本测试，逐步实现。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="&amp;#36825;&amp;#26159;&amp;#25105;&amp;#35265;&amp;#36807;&amp;#26368;&amp;#22909;&amp;#30340;&amp;#29992;&amp;#25143;&amp;#22686;&amp;#38271;&amp;#20998;&amp;#26512;&amp;#27169;&amp;#22411;" height="341" src="https://image.yunyingpai.com/wp/2023/03/LfRQRPNMKJoZ0s8JsGvX.png" title="&amp;#36825;&amp;#26159;&amp;#25105;&amp;#35265;&amp;#36807;&amp;#26368;&amp;#22909;&amp;#30340;&amp;#29992;&amp;#25143;&amp;#22686;&amp;#38271;&amp;#20998;&amp;#26512;&amp;#27169;&amp;#22411;" width="569"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;要注意的是：  &lt;strong&gt;测试不是无节制的&lt;/strong&gt;。每一种打法可能有其转化能力的上限。因此在设计方案的时候，可以预设测试的次数、投入费用与期望值。比如一个月测试3次，如果都不能满意，就果断地换方案，避免在细节里陷得太深，只见树木不见森林。&lt;/p&gt;
 &lt;h2&gt;05 第五：投入产出核算&lt;/h2&gt;
 &lt;p&gt;投入产出核算，是评估增长的最重要尺子。这一步，常规的投放分析/获客分析也会做，但经常陷入细节，过分纠结每个渠道的ROI，形成“瘸子里边挑将军”的局面（如下图）。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="&amp;#36825;&amp;#26159;&amp;#25105;&amp;#35265;&amp;#36807;&amp;#26368;&amp;#22909;&amp;#30340;&amp;#29992;&amp;#25143;&amp;#22686;&amp;#38271;&amp;#20998;&amp;#26512;&amp;#27169;&amp;#22411;" height="242" src="https://image.yunyingpai.com/wp/2023/03/bnRMXAvtfuU7pBcaXuBz.png" title="&amp;#36825;&amp;#26159;&amp;#25105;&amp;#35265;&amp;#36807;&amp;#26368;&amp;#22909;&amp;#30340;&amp;#29992;&amp;#25143;&amp;#22686;&amp;#38271;&amp;#20998;&amp;#26512;&amp;#27169;&amp;#22411;" width="604"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;做投入产出核算，首先应该将增长策略打包，同类策略下若干具体推广措施/活动，作为一个整体。  &lt;strong&gt;先评估整体效果，再看细节&lt;/strong&gt;。&lt;/p&gt;
 &lt;p&gt;作为一个策略包，在其作用下：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;增长速度是否令人满意&lt;/li&gt;
  &lt;li&gt;增长数量是否达到要求&lt;/li&gt;
  &lt;li&gt;投入产出比是否可接受&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;对整体评估之后，再看细节。这样既容易在内部树立标杆，又能避免“只见树木，不见森林”。如果发现竞争对手有新的策略推出，还能跟踪观察其效果，即时验证新方法可行性，避免局限于过往经验，错失新增长机会。&lt;/p&gt;
 &lt;h2&gt;06 小结&lt;/h2&gt;
 &lt;p&gt;这一套增长模型的做法，主要是为了避免增长分析只盯着眼前的一亩三分地，而导致的短视问题。领导们期望的深度洞察，比如下面三个问题，都得从全局出发，系统观测才能得到：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;是否有还没采取的，但是很有用的手段&lt;/li&gt;
  &lt;li&gt;是否有更多突破常规，出奇制胜的技巧&lt;/li&gt;
  &lt;li&gt;是否已经触达上限，需要更换赛道/客群&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;当然，这样做也有挑战，就是数据分析的范畴，突破了现有数据，需要结合大量的行业数据与测试数据，才能下结论。这样对于数据分析师的工作是有很大挑战的，但是对增长来说非常有帮助。&lt;/p&gt;
 &lt;p&gt; &lt;/p&gt;
 &lt;p&gt;作者：接地气的陈老师&lt;/p&gt;
 &lt;p&gt;微信公众号：接地气的陈老师（ID：gh_abf29df6ada8）&lt;/p&gt;
 &lt;p&gt;本文由 @接地气的陈老师 原创发布于运营派。未经许可，禁止转载。&lt;/p&gt;
 &lt;p&gt;题图来自 Unsplash，基于 CC0 协议。&lt;/p&gt;
 &lt;div&gt;&lt;/div&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>数据分析 2年 初级 用户运营</category>
      <guid isPermaLink="true">https://itindex.net/detail/62648-%E6%88%91%E8%A7%81-%E6%9C%80%E5%A5%BD-%E7%94%A8%E6%88%B7</guid>
      <pubDate>Thu, 02 Mar 2023 16:05:42 CST</pubDate>
    </item>
    <item>
      <title>小程序是如何设计百亿级用户画像分析系统的？</title>
      <link>https://itindex.net/detail/62645-%E7%A8%8B%E5%BA%8F-%E8%AE%BE%E8%AE%A1-%E7%99%BE%E4%BA%BF</link>
      <description>&lt;p&gt;  &lt;img alt="&amp;#22270;&amp;#29255;" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0e0f3a6a15564719a18ded052dc33237~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;导语 |&lt;/strong&gt; We 分析是微信小程序官方推出的、面向小程序服务商的数据分析平台，其中画像洞察是一个非常重要的功能模块。微信开发工程师钟文波将描述 We 分析画像系统各模块是如何设计，在介绍基础标签模块之后，重点讲解用户分群模块设计。希望相关的技术实现思路，能够对你有所启发。&lt;/p&gt;
 &lt;p&gt;目录&lt;/p&gt;
 &lt;p&gt;1 背景介绍&lt;/p&gt;
 &lt;p&gt;1.1 画像系统简述&lt;/p&gt;
 &lt;p&gt;1.2 画像系统设计目标&lt;/p&gt;
 &lt;p&gt;2 画像系统整体概述&lt;/p&gt;
 &lt;p&gt;3 基础标签模块&lt;/p&gt;
 &lt;p&gt;3.1 功能描述&lt;/p&gt;
 &lt;p&gt;3.2 技术实现&lt;/p&gt;
 &lt;p&gt;4 用户分群模块&lt;/p&gt;
 &lt;p&gt;4.1 功能描述&lt;/p&gt;
 &lt;p&gt;4.2 人群包实时预估&lt;/p&gt;
 &lt;p&gt;4.3 人群创建&lt;/p&gt;
 &lt;p&gt;4.4 人群跟踪应用&lt;/p&gt;
 &lt;p&gt;5 总结&lt;/p&gt;
 &lt;h2&gt;01、背景介绍&lt;/h2&gt;
 &lt;h4&gt;  &lt;strong&gt;1.1 画像系统简述&lt;/strong&gt;&lt;/h4&gt;
 &lt;p&gt;We 分析是小程序官方推出的、面向小程序服务商的数据分析平台，其中画像洞察是一个重要的功能模块。该功能将为使用者提供基础的画像标签分析能力，提供自定义的用户分群功能，从而满足更多个性化的分析需求及支撑更多的画像应用场景。&lt;/p&gt;
 &lt;p&gt;在此之前，原有 MP 的画像分析仅有基础画像，相当于只能分析小程序大盘固定周期的基础属性，而无法针对特定人群或自定义人群进行分析及应用。平台头部的使用者均希望平台提供完善的画像分析能力。除最基础的画像属性之外，也为使用者提供更丰富的标签及更灵活的用户分群应用能力。因此， We 分析在相关能力上计划进行优化。&lt;/p&gt;
 &lt;h4&gt;  &lt;strong&gt;1.2 画像系统设计目标&lt;/strong&gt;&lt;/h4&gt;
 &lt;ul&gt;
  &lt;li&gt;
   &lt;p&gt;    &lt;strong&gt;易用性&lt;/strong&gt;：易用性主要指使用者在体验画像洞察功能的时候，不需要学习成本就能直接上手使用。使用者可以结合自身业务场景解决问题，做到开箱即用 0 门槛。&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;    &lt;strong&gt;稳定性&lt;/strong&gt;：稳定性指系统稳定可靠体验好。例如画像标签数据、人群包按时稳定产出，在交互使用过程中查询速度快，做到如丝般顺滑的手感。&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;    &lt;strong&gt;完备性&lt;/strong&gt;：指数据丰富、规则灵活、功能完善；支持丰富的人群圈选数据，预置标签、人群标签、平台行为、自定义上报行为等。做到在不违反隐私的情况下平台基本提供使用者想要的数据。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;整体来看，平台支持灵活的标签及人群创建方式，使用者按照自己的想法任意圈选出想要的人群，按不同周期手动或自动选出人群包。此外也支持人群的跟踪分析，人群在多场景的应用等。&lt;/p&gt;
 &lt;h2&gt;02、画像系统整体概述&lt;/h2&gt;
 &lt;p&gt;系统从  &lt;strong&gt;产品形态&lt;/strong&gt;的角度出发，在下文分成2个模块进行阐述——分别是  &lt;strong&gt;基础标签模块及用户分群模块&lt;/strong&gt;。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="&amp;#22270;&amp;#29255;" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/52a4f1c9f71842d88f7635b40455cb8d~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;
   &lt;p&gt;    &lt;strong&gt;多源数据&lt;/strong&gt;：数据源包括用户属性、人群标签、平台行为数据、自定义上报数据。&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;    &lt;strong&gt;画像加工&lt;/strong&gt;：主要是对用户属性、人群标签、平台行为，进行相应的 ETL（Extract Transform  Load ，提取转换加载）及预计算。&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;    &lt;strong&gt;人群计算&lt;/strong&gt;：根据使用者定义的用户分群规则，从多源数据中计算出对应的人群。&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;    &lt;strong&gt;画像导入&lt;/strong&gt;：画像及人群数据在 TWD 加工好后，从 TWD 分布式 HDFS 集群导入到线上的 TDSQL 、 ClickHouse 存储；其中，预计算的数据导入到线上 TDSQL 存储，用户行为等明细数据导入到线上 ClickHouse 集群中。&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;    &lt;strong&gt;画像服务&lt;/strong&gt;：提供在线的画像服务接口。其中标签管理使用通用配置系统，数据服务采用 RPC 框架开发，在上一层是平台的数据中间件。此处也统一做了流量控制、异步调用、调用监控、及参数安全校验。&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;    &lt;strong&gt;画像应用&lt;/strong&gt;：提供基础标签分析及针对特定人群的标签分析，另外还提供人群圈选跟踪分析及线上应用等。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
 &lt;h2&gt;03、基础标签模块&lt;/h2&gt;
 &lt;h4&gt;  &lt;strong&gt;3.1 功能描述&lt;/strong&gt;&lt;/h4&gt;
 &lt;p&gt;该模块主要满足使用者对画像的基础分析需求，预期能满足绝大部分中长尾使用者对画像的使用深度要求。主要提供的是  &lt;strong&gt;针对小程序大盘的基础标签分析，及针对特定人群&lt;/strong&gt;（如活跃：1天活跃、7天活跃、30天活跃、180天活跃）  &lt;strong&gt;的特定标签分析&lt;/strong&gt;。如下所示：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="&amp;#22270;&amp;#29255;" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f1a7641308fb4f9abc84f5ceb1f6f350~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h4&gt;  &lt;strong&gt;3.2 技术实现&lt;/strong&gt;&lt;/h4&gt;
 &lt;h4&gt;  &lt;strong&gt;3.2.1 数据计算&lt;/strong&gt;&lt;/h4&gt;
 &lt;p&gt;从上述功能的描述，可以看出功能的特点是官方定义数据范围可控，支持的是针对特定人群的特定标签分析。&lt;/p&gt;
 &lt;p&gt;针对特定人群的特定标签分析数据是用离线 T + 1 的 hive 任务进行计算。流程如下。&lt;/p&gt;
 &lt;p&gt;分别计算官方特定标签的统计数据、特定人群的统计数据，以及计算特定人群交叉特定标签的数据。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="&amp;#22270;&amp;#29255;" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/406833e7bc544e9fb07c646e73c8aca0~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h4&gt;  &lt;strong&gt;3.2.2 数据存储&lt;/strong&gt;&lt;/h4&gt;
 &lt;p&gt;  &lt;strong&gt;不同存储对比存在差异&lt;/strong&gt;。在上述分析之后，需要存储的是预计算好的结果数据。此外，业务的特点是按照小程序进行多个数据主题统计的存储，所以第一直觉是适合用分布式 OLTP 存储。团队也对比了不同的数据库，  &lt;strong&gt;在选型过程中，主要考虑对比的点包括数据的写入、读取性能。&lt;/strong&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;
   &lt;p&gt;    &lt;strong&gt;写入&lt;/strong&gt;：包括是否可以支持快速的建表等 DDL 操作。平台数据指标多，例如 We 分析平台数据指标近千个。&lt;/p&gt;
   &lt;p&gt;不同的场景主题指标一般会分别进行计算，写入到不同的在线存储数据表中，所以这需要具备快速 DDL 及高效出库数据的能力。&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;    &lt;strong&gt;读取&lt;/strong&gt;：包括查询性能、读取接口是否简单灵活、开发是否简单；以及相关运维配套设施是否完善，如监控告警、扩容、权限、辅助优化等。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;img alt="&amp;#22270;&amp;#29255;" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b80c70a3ef434a4cb8410f3400cb58b2~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h4&gt;从上图和 Datacube / FeatureKV / HBase 的对比可以发现 TDSQL 更符合此业务诉求、更具备优势。&lt;/h4&gt;
 &lt;h4&gt;&lt;/h4&gt;
 &lt;h4&gt;因此 We 分析平台基本所有的预计算结果数据，最终选用 TDSQL 来存储离线预计算结果数据，关于 TDSQL 的几个关键点如下：&lt;/h4&gt;
 &lt;ul&gt;
  &lt;li&gt;
   &lt;p&gt;    &lt;strong&gt;存储容量&lt;/strong&gt;：We 画像分析系统采用的 TDSQL 服务中，当前支持最大 64 个分片，每个分片最大 3 T ，单个实例最大能支持存储 192 T 的数据。&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;    &lt;strong&gt;数据出库&lt;/strong&gt;：通过数平 US 上的出库组件可以完成数据从 TDW 直接出库到 TDSQL ，近 1 亿数据量可以在 40 min + 完成出库，出库组件的监控及日志完善。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;img alt="&amp;#22270;&amp;#29255;" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e03d52224c134b61aae6240163fca061~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;
   &lt;p&gt;    &lt;strong&gt;查询性能&lt;/strong&gt;：2 个分片，8 核 32 G 进行测试，查询某小程序一段时间数据，查询  QPS 5 W。&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;    &lt;strong&gt;读取方式&lt;/strong&gt;：通过 jdbc 连接查询，拼接不同 sql 进行查询，查询方式简单灵活。&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;    &lt;strong&gt;运维方面&lt;/strong&gt;：实例申请 、 账号设置 、 监控告警 、 扩容和慢查询分析等能力，都可以开发自助在云控制台完成。&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;    &lt;strong&gt;开发效率&lt;/strong&gt;：DDL 操作简单，数据开发从建表到出库基本没有学习成本，问题定位简单高效。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;当前整个平台的预计算数据出库到 TDSQL 的数据达到十亿级别，数据表超百张，实际使用存储上百 T 。TDSQL 整体功能较为全面，开发者仅需要补充开发数据生命周期管理工具，删除方式的注意点跟 MySQL 一样。&lt;/p&gt;
 &lt;p&gt;如果采用 KV 类型的引擎进行存储，需要根据 KV 的特性合理设计存储 Key 。在查询端对 Key 进行拼接组装，发送 BatchGet 请求进行查询。整个过程开发逻辑会相对繁复些， 需要更加注重 Key 的设计。若要实现一个只有概要数据的趋势图，那么存储的 Key 需要设计成类似格式：{日期} # {小程序} # {指标类型} 。&lt;/p&gt;
 &lt;h2&gt;04、用户分群模块&lt;/h2&gt;
 &lt;h4&gt;  &lt;strong&gt;4.1 功能描述&lt;/strong&gt;&lt;/h4&gt;
 &lt;p&gt;  &lt;strong&gt;该模块主要提供自定义的用户分群能力&lt;/strong&gt;。用户分群依据用户的属性及行为特征将用户群体进行分类，以便使用者对其进行观察分析及应用。自定义的用户分群能够满足中头部客户的个性化分析运营需求，例如客户想看上次 618 参加了某活动的用户人群，在接下来的活跃交易趋势跟大盘的差异对比；或者客户想验证对比某些人群对优惠券的敏感程度、圈选人群后通过 AB 实验进行验证。上述类似的应用会非常多。&lt;/p&gt;
 &lt;p&gt;在功能设计上，平台需要做到数据丰富、规则灵活、查询快速，需要支持丰富的人群圈选数据，并且预置标签、人群标签、平台行为、自定义上报行为等。支持灵活的标签及人群创建方式，让客户能按照自己的想法任意圈选出想要的人群，按不同周期手动或自动选出人群包，支持人群的跟踪分析、人群在多场景的应用能力。&lt;/p&gt;
 &lt;h4&gt;  &lt;strong&gt;4.2 人群包实时预估&lt;/strong&gt;&lt;/h4&gt;
 &lt;p&gt;人群包实时预估是根据使用者客户定义的规则，计算出当前规则下有多少用户命中了该规则。产品交互通常如下所示：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="&amp;#22270;&amp;#29255;" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/64fae313e0b24c569537ca5443211edb~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h4&gt;  &lt;strong&gt;4.2.1 数据加工&lt;/strong&gt;&lt;/h4&gt;
 &lt;p&gt;为了满足客户能随意根据自己的想法圈出想要的人群，平台支持丰富的数据源。整体画像的数据量较大，其中预置的标签画像在离线 HDFS 上的竖表存储达近万亿/天，平台行为百亿级/天，且维度细，自定义上报行为百亿级/天。&lt;/p&gt;
 &lt;p&gt;怎么设计能  &lt;strong&gt;节省存储同时加速查询&lt;/strong&gt;是重点考虑的问题之一。大体的思路是：对预置标签画像转成 Bitmap 进行压缩存储，对平台行为明细进行预聚合及对维度枚举值进行 ID 自增编码，字符串转成数据整型节省存储空间。同时在产品层面增加启用按钮，开通后导入近期数据，从而控制存储消耗，具体细节如下。&lt;/p&gt;
 &lt;p&gt;属性标签数据通常建设用户画像的核心工作就是给用户打标签，  &lt;strong&gt;标签是人为规定的高度精炼的特征标识&lt;/strong&gt;，如性别、年龄、地域、兴趣，也可以是用户的一些行为集合。这些标签集合抽象出一个用户的信息全貌，每个标签分别描述该用户的一个维度，各标签维度间相互联系，构成对用户的整体描述。当前的用户属性及人群标签是由平台方提供，由平台每天进行统一的加工处理生成官方标签。平台暂时没有支持用户自定义的标签，因此这里主要说明平台标签是如何计算加工管理。&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;strong&gt;第一，标签编码管理。&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;img alt="&amp;#22270;&amp;#29255;" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c96b7967657d4895a1feb3b5a548b72a~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;例如活跃标签 10002 ，对标签的每个标签值进行编码如下：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="&amp;#22270;&amp;#29255;" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7e8e7fbe1bf9436ab9ca3d675acb1362~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;对特定人群进行编码，基准人群是作为必选的过滤条件，用于限定用户的范围：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="&amp;#22270;&amp;#29255;" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/efbfdfa671bc47bc875ff183ba34afcc~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;strong&gt;第二，标签离线存储。&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;strong&gt;标签数据在离线的存储上，采用竖表的存储方式&lt;/strong&gt;。表结构如下所示，标签之间可以并行构建相互独立不影响。采用竖表的结构设计，好处是不需要开发画像大宽表，即使任务出现异常延时也不会影响到其它标签的产出。而画像大宽表需要等待所有画像标签均完成后才能开始执行该宽表数据的生成，会导致数据的延时风险增大。当需要新增或删除标签时，需要修改表结构。因此，在线的存储引擎是否支持与离线竖表模式相匹配的存储结构，成为很重要的考量点。&lt;/p&gt;
 &lt;p&gt;采用大宽表方式的存储如 Elasticsearch 和 Hermes 存储，等待全部需要线上用到的画像标签在离线计算环节加工完成才能开始入库。而像 ClickHouse 、Doris 则可以采用与竖表相对应的表结构，标签加工完成就可以马上出库到线上集群，从而减小因为一个标签的延时而导致整体延时的风险。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;CREATE TABLE table_xxx(  
    ds BIGINT COMMENT &amp;apos;数据日期&amp;apos;,  
    label_name STRING COMMENT &amp;apos;标签名称&amp;apos;,  
    label_id BIGINT COMMENT &amp;apos;标签id&amp;apos;,  
    appid STRING COMMENT &amp;apos;小程序appid&amp;apos;,  
    useruin BIGINT COMMENT &amp;apos;useruin&amp;apos;,  
    tag_name STRING COMMENT &amp;apos;tag名称&amp;apos;,  
    tag_id BIGINT COMMENT &amp;apos;tag id&amp;apos;,  
    tag_value BIGINT COMMENT &amp;apos;tag权重值&amp;apos;  
)  
PARTITION BY LIST( ds )  
SUBPARTITION BY LIST( label_name )(  
    SUBPARTITION sp_xxx VALUES IN ( &amp;apos;xxx&amp;apos; ),  
    SUBPARTITION sp_xxxx VALUES IN ( &amp;apos;xxxx&amp;apos; )  
)
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;‍&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;strong&gt;第三，标签在线存储。&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;如果把标签理解成对用户的分群，那么符合某个标签的某个取值的所有用户 ID（UInt类型） 就构成了一个个的人群。  &lt;strong&gt;Bitmap 是用于存储标签-用户的映射关系的、非常理想的数据结构&lt;/strong&gt;，最终需要的是构建出每个标签的每个取值所对应的 Bitmap。例如性别这个标签组，背后对应的是男性用户群和女性用户群。&lt;/p&gt;
 &lt;p&gt;性别标签：男 -&amp;gt; 男性用户人群包，女 →女性用户人群包。&lt;/p&gt;
 &lt;p&gt;平台行为数据是指官方进行上报的行为数据，例如访问、分享等行为数据，使用者不需要进行任何埋点等操作。团队主要是会对平台行为进行预聚合，计算同一维度下的 PV 数据，已减少后续数据的存储及计算量。&lt;/p&gt;
 &lt;p&gt;同时会对维度枚举值进行 ID 自增编码，目的是减少存储占用，写入以及读取性能；从效果来看，团队对可枚举类型进行字典 ID 编码对比原本字符类型能节省60%的线上存储空间，同时相同数据量条件下带来 2 倍查询速度提升。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="&amp;#22270;&amp;#29255;" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a800189ce39d4af6a5a4ea762a376411~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;自定义上报数据是使用者自己埋点进行数据的上报，上报的内容包括公共参数及自定义内容，其中自定义内容是 key-value 的格式，在 OLAP 引擎中，团队会将客户自定义的内容转成 map 结构类型进行存储。&lt;/p&gt;
 &lt;h4&gt;  &lt;strong&gt;4.2.2 数据写入存储&lt;/strong&gt;&lt;/h4&gt;
 &lt;ul&gt;
  &lt;li&gt;
   &lt;h4&gt;    &lt;strong&gt;在线OLAP存储选型：&lt;/strong&gt;&lt;/h4&gt;
&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;首先讲下，在线 OLAP 存储选型。标签及行为明细数据的存储引擎选型对于画像系统至关重要，不同的存储引擎决定了系统不同的设计方式。业务团队调研了解到，行业内建设画像系统时有多种不同的存储方案。团队对常用的画像 OLAP 引擎做了对比，如下：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="&amp;#22270;&amp;#29255;" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/fa6f58b832f243f5b7c998b4ec5e486a~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;综合上述调研，  &lt;strong&gt;团队采用 ClickHouse 作为画像数据存储引擎&lt;/strong&gt;。在 ClickHouse 中使用 RoaringBitmap 作为 Bitmap 的解决方案。该方案支持丰富的 Bitmap 操作函数，可以十分灵活方便的判重和进行基数统计操作，如下所示。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="&amp;#22270;&amp;#29255;" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/77225122a0a742a9b833e82c53274683~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;采用 RoaringBitmap（RBM）&lt;/strong&gt; 对稀疏位图进行压缩，可以减少内存占用并提高效率。该方案的核心思路是，将 32 位无符号整数按照高 16 位分桶，即最多可能有 216=65536 个桶，称为 container。存储数据时，按照数据的高 16 位找到 container （找不到则会新建一个），再将低 16 位放入 container 中。也就是说，一个 RBM 就是很多 container 的集合，具体参考高效压缩位图 RoaringBitmap 的原理与应用。&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;
   &lt;h4&gt;    &lt;strong&gt;数据导入线上存储：&lt;/strong&gt;&lt;/h4&gt;
&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;接下来讲讲，数据导入线上存储。在确定了采用什么存储引擎存储线上数据后，团队需要将离线集群的数据导入到线上存储。其中对于标签数据通常的做法是将原始明细的 id 数据直接导入到 ClickHouse 表中，再通过创建物化视图的方式构建 RBM 结构进行使用。&lt;/p&gt;
 &lt;p&gt;然而，业务明细数据非常大，每天近万亿。这样的导入方式给 ClickHouse 集群带来了很大资源开销。而通常业务团队处理大规模数据都是用 Spark 这样的离线计算框架来完成处理。  &lt;strong&gt;最后团队把预处理工作全部交给了 Spark 框架，这种方式大大的减少了写入的数据量，同时也减少了 ClickHosue 集群的处理压力。&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;具体步骤是 Spark 任务首先会按照 id 进行分片处理，然后对每个分片中标签的每个标签值生成一个 Bitmap ，保证定制的序列化方式与 ClickHouse 中的 RBM 兼容。其中通过 Spark 处理后的 Bitmap 转成 string 类型，然后写入到线上的标签表中，在表中业务团队定义了一个物化列字段，用于实际存储 Bitmap。在写入过程中会将序列化后的 Bitmap 字符串通过 base64Decode 函数转成 ClickHouse 中的 AggregateFunction (groupBitmap, UInt32) 数据结构。&lt;/p&gt;
 &lt;p&gt;具体表结构如下：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;CREATE TABLE xxxxx_table_local on CLUSTER xxx  
(  
    `ds` UInt32,  
    `appid` String,  
    `label_group_id` UInt64,  
          `label_id` UInt64,  
          `bucket_num` UInt32,  
    `base64rbm` String,  
         `rbm` AggregateFunction(groupBitmap, UInt32) MATERIALIZED base64Decode(base64rbm)  
)  
ENGINE = ReplicatedMergeTree(&amp;apos;/clickhouse/tables/{layer}-{shard}/xxx_table_local&amp;apos;, &amp;apos;{replica}&amp;apos;)  
PARTITION BY toYYYYMMDD(toDateTime(ds))  
ORDER BY (appid, label_group_id, label_id)  
TTL toDate(ds) + toIntervalDay(5)  
SETTINGS index_granularity = 16
&lt;/code&gt;&lt;/pre&gt;
 &lt;ul&gt;
  &lt;li&gt;
   &lt;h4&gt;    &lt;strong&gt;存储占用问题：&lt;/strong&gt;&lt;/h4&gt;
&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;值得关注的还有存储占用问题。标签类型数据用 Bitmap 类型存储，平台行为采用编码方式存储，存储占用大幅减少。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;4.2.3 数据查询&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;数据查询方式：  &lt;strong&gt;人群圈选过程中，如何保障大的APP查询、在复杂规则情况下的查询速度&lt;/strong&gt;？团队在导入过程中对预置画像、平台行为、自定义上报行为，均按相同分桶规则导入集群。这保证一个用户仅会在同一台机器，查询时始终进行本地表查询，避免进行分布式表查询。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="&amp;#22270;&amp;#29255;" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5cfda53600a64aff99a6b2bc078589a3~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;对于查询性能的保障，团队始终保证所有查询均在  &lt;strong&gt;本地表&lt;/strong&gt;完成。上面已经介绍到数据在入库时，均会按照相同用户 ID 的 hash 分桶规则出库到相应的机器节点中。使用维度数字编码，测试数字编码后对比字符方式查询性能有2倍以上提升。对标签对应的人群转成 Bitmap 方式处理，用户的不同规则到最后都会转成针 Bitmap 的交并差补集操作。&lt;/p&gt;
 &lt;p&gt;对于平台行为，如果在用户用模糊匹配的情况下，会先查询维度 ID 映射表，将用户可见维度转化成维度编码 ID，后通过编码 ID 及规则构建查询 SQL。整个查询的核心逻辑是根据圈选规则组合不同查询语句，然后将不同子查询通过规则组合器最终判断该用户是否命中人群规则。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;基于rpc开发服务接口&lt;/strong&gt;：查询的服务接口采用 rpc 框架进行开发。&lt;/p&gt;
 &lt;p&gt;在数据服务的上一层是团队的数据中间件，统一做了流量控制、异步调用、调用监控及参数安全校验，特别是针对用户量较大的 app 在多规则查询时，耗时较大，因此业务团队配置了细粒度的流量控制，保障查询请求的有序及服务的稳定可用。&lt;/p&gt;
 &lt;h4&gt;  &lt;strong&gt;查询性能数据&lt;/strong&gt;：不同 DAU 等级小程序查询性能。&lt;/h4&gt;
 &lt;p&gt;  &lt;img alt="&amp;#22270;&amp;#29255;" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d41c4b0238c141f9b919307ea0c727a9~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;从性能数据看，对用户量大的 app 来说，在规则非常多的情况下还是要大几十秒，等待这么长时间体验不佳。因此对于这部分用户量大的 app，业务团队采用的策略是抽样。通过抽样，速度能得到非常大的提升，并且预估的准确率误差不大，在可接受的范围内。&lt;/p&gt;
 &lt;h4&gt;  &lt;strong&gt;4.3 人群创建&lt;/strong&gt;&lt;/h4&gt;
 &lt;h4&gt;  &lt;strong&gt;4.3.1 人群实时创建&lt;/strong&gt;&lt;/h4&gt;
 &lt;p&gt;人群包实时创建类似上面描述的人群大小实时预估，区别是在最后人群创建是需要将圈选的人群包用户明细写入到存储中，然后返回人群包的大小给到用户。同样是在本地表执行，生成的人群包写入到同一台机器中，保持分桶规则的一致。&lt;/p&gt;
 &lt;h4&gt;  &lt;strong&gt;4.3.2 人群例行化创建&lt;/strong&gt;&lt;/h4&gt;
 &lt;p&gt;客户创建的例行化人群包，需要每天计算。  &lt;strong&gt;如何持续跟踪分析趋势，并且不会对集群造成过大的计算压力&lt;/strong&gt;？团队的做法利用离线超大规模计算的能力，在凌晨启动所有人群计算任务，从而减小对线上 ClickHouse 集群的计算压力。所有小程序客户创建的例行化人群包计算集中到凌晨的一个任务中进行，做到读一次数据，计算完成所有人群包，最大限度节省计算资源，详细的设计如下：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="&amp;#22270;&amp;#29255;" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/eb31cad7dea048c897a9b15cc53c8a73~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;首先，团队会先将  &lt;strong&gt;全量的数据&lt;/strong&gt;（标签属性数据+行为数据）  &lt;strong&gt;按照小程序粒度及选择的时间范围进行过滤&lt;/strong&gt;，保留有效的数据；&lt;/p&gt;
 &lt;p&gt;其次，  &lt;strong&gt;对数据进行预聚合处理&lt;/strong&gt;，将用户在一段时间范围的行为数据，标签属性镜像数据按照小程序的用户粒度进行聚合处理，最终的数据将会是对于每个小程序的一个用户仅会有一行数据；那么人群包计算，实际上就是看这个用户在某个时间范围内所产生的行为、标签属性特征是否满足客户定义的人群包规则；&lt;/p&gt;
 &lt;p&gt;最后，  &lt;strong&gt;对数据按用户粒度聚合后进行复杂的规则匹配&lt;/strong&gt;，核心是拿到一个用户某段时间的行为及人群标签属性，判断这个用户满足了用户定义的哪几个人群包规则，满足则属于该人群包的用户。&lt;/p&gt;
 &lt;h4&gt;  &lt;strong&gt;4.4 人群跟踪应用&lt;/strong&gt;&lt;/h4&gt;
 &lt;h4&gt;  &lt;strong&gt;4.4.1 人群跟踪分析&lt;/strong&gt;&lt;/h4&gt;
 &lt;p&gt;在按照用户规则圈选出人群后，统一对人群进行常用指标（如活跃、交易等指标）的跟踪。整个过程用离线任务进行处理，会从在线存储中导出实时生成的人群包，以及离线批量生成的定时人群包，汇总一起，后关联对应指标表，输出到线上 OLTP 存储进行在线的查询分析。其中，导出在线人群包会在凌晨空闲时间进行，通过将人群 RBM 转成用户明细 ID。&lt;/p&gt;
 &lt;p&gt;具体方法为：arrayJoin(bitmapToArray(groupBitmapMergeState(rbm)))。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="&amp;#22270;&amp;#29255;" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/32111f6d5e624008aeb68180b92239b1~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h4&gt;  &lt;strong&gt;4.4.2 人群基础分析&lt;/strong&gt;&lt;/h4&gt;
 &lt;p&gt;人群基础分析对一个自定义的用户分群进行基础标签的分析，如该人群的省份、城市、交易等标签分布。人群行为分析，分析该人群不同的事件行为等。&lt;/p&gt;
 &lt;h4&gt;  &lt;strong&gt;4.4.3 实验人群定向&lt;/strong&gt;&lt;/h4&gt;
 &lt;p&gt;在 AB 实验中的人群实验，使用者通过规则圈选出指定人群作为实验组（如想验证某地区的符合某条件的人群是否更喜欢参与该活动），跟对照组做相应指标的对比，以便验证假设。&lt;/p&gt;
 &lt;h2&gt;05‍、总结‍&lt;/h2&gt;
 &lt;p&gt;本篇回顾了 We 画像分析系统各模块的设计思路。在基础模块中，业务团队根据功能特性，选用了腾讯云 TDSQL 作为在线数据的存储引擎，将所有预计算数据都使用 TDSQL 进行存储。在人群分析模块中，为了实现灵活的人群创建、分析及应用，业务团队使用 ClickHouse 作为画像数据的存储引擎，根据该存储的特性进行上层服务的开发，以达到最优的性能。&lt;/p&gt;
 &lt;p&gt;后续，小程序 We 画像分析系统在产品能力上会持续丰富功能及体验，同时扩展更多的应用场景。以上是 We 画像分析系统模块设计与实现思路的全部内容，欢迎感兴趣的读者在评论区交流。&lt;/p&gt;
 &lt;p&gt;-End-&lt;/p&gt;
 &lt;p&gt;原创作者｜钟文波‍‍‍&lt;/p&gt;
 &lt;p&gt;技术责编｜钟文波、谢慧志‍‍‍‍‍‍&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;你可能感兴趣的腾讯工程师作品&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;|&lt;/strong&gt;   &lt;a href="http://mp.weixin.qq.com/s?__biz=MzI2NDU4OTExOQ==&amp;mid=2247588018&amp;idx=1&amp;sn=91639f3f2d83565ab320e92d0ab49616&amp;chksm=eaa982e2ddde0bf4f00be43de589a21d6f359e7efa185941276aa00a7141a2d89eafdb9e718b&amp;scene=21#wechat_redirect"&gt;ChatGPT深度解析：GPT家族进化史&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;|    &lt;a href="http://mp.weixin.qq.com/s?__biz=MzI2NDU4OTExOQ==&amp;mid=2247588095&amp;idx=1&amp;sn=4e68b4a7e5e719dc4c28396feca08f4c&amp;chksm=eaa982afddde0bb92d5bffa73bce37fec0e64e4c79c3e4a46dd013bc3efe36f9de32d00e2db8&amp;scene=21#wechat_redirect"&gt;&lt;/a&gt;&lt;/strong&gt;   &lt;a href="http://mp.weixin.qq.com/s?__biz=MzI2NDU4OTExOQ==&amp;mid=2247588095&amp;idx=1&amp;sn=4e68b4a7e5e719dc4c28396feca08f4c&amp;chksm=eaa982afddde0bb92d5bffa73bce37fec0e64e4c79c3e4a46dd013bc3efe36f9de32d00e2db8&amp;scene=21#wechat_redirect"&gt;腾讯工程师聊 ChatGPT 技术「文集」&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;|&lt;/strong&gt;   &lt;a href="http://mp.weixin.qq.com/s?__biz=MzI2NDU4OTExOQ==&amp;mid=2247582311&amp;idx=1&amp;sn=33949a7d43a4b6c088f5c506222112fe&amp;chksm=eaa99837ddde11214ec7e7c4ccfcb73435317dfda22702931ad946d185e44cc891414e8a71e5&amp;scene=21#wechat_redirect"&gt;微信全文搜索耗时降94%？我们用了这种方案&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;|    &lt;a href="http://mp.weixin.qq.com/s?__biz=MzI2NDU4OTExOQ==&amp;mid=2247583332&amp;idx=1&amp;sn=646f9423bed5990f75c0d99e618c0fa6&amp;chksm=eaa99c34ddde15228c45f00fa6e8d07de8097dfa4c0fb2ba448288748dec534165ac6538168e&amp;scene=21#wechat_redirect"&gt;&lt;/a&gt;&lt;/strong&gt;   &lt;a href="http://mp.weixin.qq.com/s?__biz=MzI2NDU4OTExOQ==&amp;mid=2247583332&amp;idx=1&amp;sn=646f9423bed5990f75c0d99e618c0fa6&amp;chksm=eaa99c34ddde15228c45f00fa6e8d07de8097dfa4c0fb2ba448288748dec534165ac6538168e&amp;scene=21#wechat_redirect"&gt;10w单元格滚动卡顿如何解决？腾讯文档的7个秘笈&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;技术盲盒：&lt;/strong&gt;  &lt;a href="http://mp.weixin.qq.com/s?__biz=MzI2NDU4OTExOQ==&amp;mid=2247568617&amp;idx=1&amp;sn=d3409583764c4877964765a6b774b1de&amp;chksm=eaa9d6b9ddde5faff511c416033948f76b056b209df76c6eb12adfea3f618422297b9b11895b&amp;scene=21#wechat_redirect"&gt;前端&lt;/a&gt;  &lt;strong&gt;｜&lt;/strong&gt;  &lt;a href="http://mp.weixin.qq.com/s?__biz=MzI2NDU4OTExOQ==&amp;mid=2247568512&amp;idx=1&amp;sn=5a2e887c0ac511e9a4fe5cd68a388e48&amp;chksm=eaa9d6d0ddde5fc6376f1ffcc6e7b050fefded23d5b24c5f7b801885f509df06cd53d99f0a45&amp;scene=21#wechat_redirect"&gt;后端&lt;/a&gt;  &lt;strong&gt;｜&lt;/strong&gt;  &lt;a href="http://mp.weixin.qq.com/s?__biz=MzI2NDU4OTExOQ==&amp;mid=2247568656&amp;idx=1&amp;sn=98f7033418fc1fd7d019eeb18008b616&amp;chksm=eaa9d740ddde5e56aa0b7df55dc2f70c65f329d37246453c2b3316356f3f84cc9f87eb6b8db4&amp;scene=21#wechat_redirect"&gt;AI与算法&lt;/a&gt;  &lt;strong&gt;｜&lt;/strong&gt;  &lt;a href="http://mp.weixin.qq.com/s?__biz=MzI2NDU4OTExOQ==&amp;mid=2247568672&amp;idx=1&amp;sn=85e4b3e1c46289058398b216edb40941&amp;chksm=eaa9d770ddde5e669cfaa25c37887ae058c433e4296ca04f8ff5373184bc76d4420f1d2049a7&amp;scene=21#wechat_redirect"&gt;运维   &lt;strong&gt;｜&lt;/strong&gt;&lt;/a&gt;  &lt;a href="http://mp.weixin.qq.com/s?__biz=MzI2NDU4OTExOQ==&amp;mid=2247568677&amp;idx=1&amp;sn=e95255553777c53d38cb1e64c1c16432&amp;chksm=eaa9d775ddde5e633a75d20eb484181c0e03cb6f8237a4141c599e4f13ad3af6748c5e8d1a9a&amp;scene=21#wechat_redirect"&gt;工程师文化&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;公众号后台回复“小程序”，领本文作者推荐的更多资料&lt;/p&gt;
 &lt;p&gt;  &lt;a href="https://mp.weixin.qq.com/s/9HPciYWiqsdxEv4-55urWQ"&gt;阅读原文&lt;/a&gt;&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/62645-%E7%A8%8B%E5%BA%8F-%E8%AE%BE%E8%AE%A1-%E7%99%BE%E4%BA%BF</guid>
      <pubDate>Thu, 02 Mar 2023 10:18:00 CST</pubDate>
    </item>
    <item>
      <title>分布式数据库存储透析：B-TREE 和 LSM-TREE 的性能差别</title>
      <link>https://itindex.net/detail/62628-%E5%88%86%E5%B8%83-%E6%95%B0%E6%8D%AE%E5%BA%93-%E9%80%8F%E6%9E%90</link>
      <description>&lt;div&gt;  &lt;div&gt;&lt;/div&gt;  &lt;div&gt;   &lt;p&gt;    &lt;strong&gt;作者介绍&lt;/strong&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;p&gt;    &lt;strong&gt;宇文湛泉，&lt;/strong&gt;现任金融行业核心业务系统DBA，主要涉及Oracle、DB2、Cassandra、MySQL、GoldenDB、TiDB等数据库开发工作。&lt;/p&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;h2&gt;    &lt;strong&gt;一、引子&lt;/strong&gt;&lt;/h2&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;p&gt;最近一两年里，每次做分布式数据库的内容分享活动时，总是会提及现在数据库的两个重要的存储结构，B-TREE和LSM-TREE。因为，我觉得作为数据库的存储根基，无论是要选型，或者是用好一个数据库，清楚这两的差别和各自特点，都特别重要。但是几乎每一次都只能提一下，哪种数据库用了哪个存储。再多就是稍微介绍LSM-TREE的写入友好。对于有些朋友，正面临二选一项目抉择时，这点信息显然是不够的。于是他们会想要知道，更多二者差异细节，以及到底哪一种适合自己的系统。可惜我总是只能遗憾讲个大概，并解释要彻底讲清楚的话，整个分享就只能光讲这个可能时间还不太够。然而，我还是觉得每次都含含糊糊的过去，未免也有点耍流氓的感觉。总得找个机会，把这里面一些有意思的内容拿出来罗列一下。适逢国庆宅家，想想也是时候把这个坑给填一填了。&lt;/p&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;p&gt;当然，本文并不打算从0开始介绍LSM-TREE，那样篇幅也太冗长了。本文默认各位读者具有一点B-TREE和LSM-TREE的基础背景知识。&lt;/p&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;h2&gt;    &lt;strong&gt;二、背景板-分布式关系型数据库&lt;/strong&gt;&lt;/h2&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;p&gt;其实，抛开分布式数据库来纯写这两个存储引擎，似乎要更加简明一点。但是，这样的话实用性不太行。单机版LSM-TREE数据库有排名在100名左右的ROCKSDB，LEVELDB。它们被划分为KEY-VALUE类型，而且它们通常不会直接出现在我们的视野，也极少直接被使用在项目中。然而，基于LSM-TREE的分布式数据库则是非常常见的，比如这些：&lt;/p&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='432' height='126'&gt;&lt;/svg&gt;" width="432"&gt;&lt;/img&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;p&gt;甚至可能有些人，是有用分布式数据库的需求，才去学习和理解LSM-TREE的（比如说我就是先要使用CASSANDRA）。不过带上分布式的这个背景，未免多少会使得存储结构差异比对，变得不那么纯粹。因为分布式数据库嘛，总是带来了一些额外的开销。比如说数据库层面的SQL解析到分布式执行计划产生。再比如说分布式的场景下，分布式事务、全局时间戳获取等等。不过这个还是可以大致对比一下基于B树的分布式数据库情况来说明一下。但就具体到每一个数据库的话，因为各个数据库的具体实现方式各有不同，实际情况就得看某一个具体的数据库还做了些什么额外的动作，再把它附加上去。&lt;/p&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;p&gt;还有关系型也算是一个重要背景。因为有些特殊项目场景、特殊的数据格式，使用某种数据库或者存储结构时快得飞起，然而却缺乏了一般通用性。其实，像REDIS和CASSANDRA这样的数据库已经非常的热门，使用范围也非常的广。但是我们在项目中使用这些数据库，也还是充当某种功能性的角色比较多，我们很难把它做成系统的主数据库。尤其是那些有历史包袱迁移而来的系统。无论如何，关系型模型，基本上还是我们见到的主要情况。&lt;/p&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;p&gt;所以我准备在分布式和关系型的数据库大背景下，以最为常见的几种数据访问形态来描述一下，这样就更加具有真实的参考性价值。先摊开来数据库的主要操作都做了些什么。然后再套几个经典的案例场景进去看看。这样子的话，围绕着分布式关系型数据库来展开这两颗树的内容，也就更贴近大家现实使用这两存储结构的实际需求，起码我感觉上是如此。&lt;/p&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;p&gt;在开始眼花缭乱的各种操作叙述之前，我们可以先草率的做一点假定。比如说，我们SQL的响应时长，认为是最关键的性能指标。比如说，我们认为内存中的操作比较快，对性能的影响稍微小一些。而磁盘操作比较慢，比较可能影响交易时长。再比如，异步动作，基本不怎么影响交易性能。这些假定虽然草率，但是我认为这可以使我们对一些数据库操作的性能开销，有一些更粗暴而直观的感性认知。&lt;/p&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;h2&gt;    &lt;strong&gt;三、LSM-TREE之优势的写&lt;/strong&gt;&lt;/h2&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;p&gt;写友好，几乎是LSM-TREE的标志性特点，那么我们就从写流程开始。先来个经典的LSM-TREE的图。&lt;/p&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='1080' height='627'&gt;&lt;/svg&gt;" width="1080"&gt;&lt;/img&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;h3&gt;    &lt;strong&gt;STEP 1 WAL日志写入(Write Ahead Log) 磁盘操作&lt;/strong&gt;&lt;/h3&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;p&gt;这个步骤基本上各个数据库都有，有各种各样的叫法。比如说Innodb的Redo log，Cassandra 的Commit logs。但是，作用都是一样的，在数据库宕机之后，这份日志可以保证数据的恢复。而这个日志，都是以顺序写入的方式不断追加。所以，感觉上，这部分开销应该是非常的接近的。&lt;/p&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;p&gt;我觉得MYSQL的BINLOG也可以放在这里提一下，从严格意义上来说，BINLOG不属于存储引擎而是属于MYSQL，它与B-TREE这个存储结构没有必然关系。功能上来看，我觉得它有点接近某些数据库的归档日志。它开销是显然有的，但是我觉得应该把这笔账算在高可用的头上去。&lt;/p&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;h3&gt;    &lt;strong&gt;STEP 2 树数据结构维护&lt;/strong&gt;&lt;/h3&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;p&gt;有观点说B-TREE至少要写两次，一次是写WAL日志，一次是写B-TREE本身，以此推出B-TREE写入比LSM-TREE更加慢。这个说法我觉得有些歧义的。因为LSM-TREE其实也是写两次，也是一次写WAL，一次写树。如果非要说，LSM-TREE能少一次，除非是某种LSM-TREE数据库在WAL写完即认为写入成功返回，不需要等MemTable维护好，而这就意味着这种数据库存在以提交的数据读不到的情况。&lt;/p&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;p&gt;我个人觉得比较精准的说法应该是，LSM-TREE中MemTable的追加写入速度，要比B树的维护快得多。首先无论是哪种树，写树本身是个内存操作，两种结构都不需要等树结构落盘数据库才算Commit成功。数据库脏页通常都是异步进程慢慢刷出的。所以单纯的写树动作并不是关键。但是，B-TREE的写树动作，并非一个纯粹内存操作。因为只要从根节点开始，一直到数据页。B-TREE这一条路上，无论是索引页还是数据页，有任何页不在缓存里，数据库都会触发磁盘IO来读取。所以在一般的写入场景下，B树的维护就慢了，它是一个先读后写的过程。&lt;/p&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;p&gt;我们可以比对一下，在Cassandra的写入步骤里面，资料上的描述，仅有ADD TO MEMTABLE这么简单来描述。首先，它体现了这条数据是顺序的追加上去的简单性。如果说还要干点啥，就只剩下一句，如果在ROW CACHES里存在，废弃存在的数据。对于LSM-TREE而言，MemTable写完，交易就应该是成功返回了。并且这些全都是内存操作。&lt;/p&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;p&gt;然后我们随便来看看B树在这个阶段的维护都做了什么：&lt;/p&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;p&gt;1）写Undo日志，内存操作，一般只有Undo空间不够才写盘。&lt;/p&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;p&gt;2）从索引根节点开始，找到记录行应该存储的数据页，内存加磁盘操作。若为命中缓存，则可能促发多次磁盘IO。&lt;/p&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;p&gt;3）可能B-TREE索引分裂，一大波内存加磁盘操作。&lt;/p&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;p&gt;突然感觉有点什么不对劲对不对，首先追加写入MemTable显然是做不了约束性检查的，如果你的应用想写入重KEY回滚，那么在LSM-TREE的写入这里挡不住。那么你需要，那么它需要以另一种方式实现，比如说：读后写。那这时候的时间，其实有点近似与UPDATE的操作。&lt;/p&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;p&gt;这个地方显然需要两分的来看待LSM-TREE的优势。不需要约束性检查的超大规模写入场景有没有？当然有，而且还很多。比如一个专门写入流水的数据表。在互联网特别常见的记录PV数据的表，只要有人点了某个页面，就等于一条记录。不需要约束，不需要事务。这样的场景，这样的系统，用LSM-TREE自然快得飞起。&lt;/p&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;p&gt;然而，如果你的系统，几乎每个写入都要判定是不是重主键、重唯一索引的话。那么这个写入优势显然是有限的。&lt;/p&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;h3&gt;    &lt;strong&gt;STEP 3 脏数据落盘 磁盘操作&lt;/strong&gt;&lt;/h3&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;p&gt;由于这部分操作都是异步执行，只要机器资源没有问题的话，通常已经不太影响交易响应时间了。所以，这里的性能差别不是我想说的重点。我想说一些虚无缥缈的，想法。刚才说的这个脏页落盘异步，其实是很多数据库性能优化的一个空间。很多数据库的优化思路都在基于这一点去展开。比如说INNODB的DOUBLE-WRITE，Merge Insert Buffer等等。这些优化的核心思想都围绕着落盘可以异步，如何减少磁盘交互而展开。在看到这个地方的时候，不知道有没有隐隐约约的感受到，数据库的千差万别之中，总是存在着某些相似的地方。所以，接下来，我准备一边聊数据库的更新（UPDATE）操作，一边说一说这一条隐约的线索。&lt;/p&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;h2&gt;    &lt;strong&gt;四、关于存储结构的思考&lt;/strong&gt;&lt;/h2&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;p&gt;在其他枯燥的知识点对比介绍之间呢，让我们先乱入一波。&lt;/p&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;p&gt;在我没有学过LSM-TREE之前，在B树还是我脑子里，唯一占统治地位的存储结构的少年时代。我一直有一个潜在的观念，即“数据库的存储结构是为数据查询服务的”。如果非要说的不那么武断，也至少是主要为查询服务的。比如说，最常见的索引吧。我们建立一个索引，损耗插入时索引维护的成本，使用额外磁盘空间。为啥？显然是为了加快查询。再比如说“聚簇”。维持数据库中数据某种维度的顺序，为啥？显然还是为了某种查询。再往比如说列存储，为了分析型SQL在计算过程中，减少无关列的读取，还是为了查询吧。这些概念没有一个是为了数据写入服务的吧。&lt;/p&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;p&gt;正如上面所描述的，当我接触了LSM-TREE的存储结构之后，我有一个特别深刻而直观的印象，这个“数据库的存储结构是为数据写入服务的”。它和B树有根源性的不同，B树的存储结构，处处损耗写入的性能来提高查询性能。而LSM-TREE在提高写入性能，并且可能在某些时候损耗了读取的性能。&lt;/p&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;p&gt;容我大胆的在这里丢一个问题和一个猜想。&lt;/p&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;p&gt;问题：为什么数据库的发展历史中，是先有B树而后有LSM树？只是偶然的巧合吗？&lt;/p&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;p&gt;猜想：在古老的数据库使用场景时，绝大部分数据产生的是比较慢的，这些数据变化也比较慢，但是他们反反复复的被使用和读取。于是，数据库使用了B树的存储结构。后来，随着时间的变迁。有些新兴的应用场景数据产生的速度，大幅度的加快了，数据的更新速度也加快了。甚至于出现了超大量的数据产生，但是这些数据快速的产生出来，但是被反复读取的使用的次数大幅度减少了，于是LSM树出现了。&lt;/p&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;p&gt;为什么要写这个猜测？因为，如果我的猜想是对的，那么就是时候反问一句，你的数据库系统哪一种？你系统的数据是相对稳定的还是快速膨胀的？你系统的数据是反复读取吗？那么你感觉它更适合哪种存储引擎呢？&lt;/p&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;p&gt;先别感觉豁然开朗，故事当然没有这么简单，选择也当然没有这么容易。注意，我描述LSM-TREE用的词汇是“可能”在某些时候损耗了读取性能。这个地方有破绽的点起码有两条。第一，既然是可能，那有没有什么情况没有损耗。那就变成，用LSM-TREE我的系统可能写入变快了，读取没变慢。第二个大破绽是，这个损耗没有量化。不同的系统对于损耗的容忍程度天差地别。有的项目一个交易慢几毫秒都会被人盯着追杀，而有的场景SQL语句跑个几秒几十秒都不叫事。&lt;/p&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;p&gt;所以，要做出更准确的选择，我们还需要把自己系统的实际情况往里带入并量化差异。最佳的选择当然是实测。因为它优势的地方你的系统不一定优势，它劣势的地方你的系统也不一定就劣势，就是这么神奇。&lt;/p&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;h2&gt;    &lt;strong&gt;五、写流程的衍生-更新动作&lt;/strong&gt;&lt;/h2&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;p&gt;回到前面介绍写流程的主线上来。数据库的增删改查，并称数据库的四大操作。但是我觉得把UPDATE当作是写流程的某种衍生是合适的。我们前面讲了，在B树数据写入维护B树的过程，其实是一个先读后写的过程。如果我们把INSERT一条记录，看成是要更新这条记录所在的数据页的内容。假设空间够用，也没有分裂等等，那INSERT和UPDATE动作可不就是一个流程吗？&lt;/p&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;p&gt;另一方面，你会发现我在描述LSM-TREE写优的反例竟然用的是约束性检查，而并没有用UPDATE操作来反例读后写。因为纯粹的LSM-TREE的UPDATE更加是一个纯纯的INSERT动作，不存在半点读后写。来看一下引用于2020年VLDB论文《LSM-based Storage Techniques:A survey》中的描述和图。&lt;/p&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='738' height='316'&gt;&lt;/svg&gt;" width="738"&gt;&lt;/img&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;p&gt;“通常，索引结构可以选择两种策略之一种来处理更新，即就地（in-place）更新和非就地（即异位，out-of-place）更新。就地更新结构，如B+树，直接覆盖旧记录来存储新更新。例如，在图1a中，为了将key k1的值从v1更新到v4，索引条目(k1, v1)被直接修改以应用该更新。这些结构通常是读优化的，因为只存储每个记录的最新版本。然而，这种设计牺牲了写性能，因为更新会导致随机I/O。此外，索引页可以被更新和删除所分割，从而减少空间利用率。&lt;/p&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;p&gt;相反，异位（out-of-place）的更新结构，例如LSM-tree总是将更新存储到新的位置，而不是覆盖旧的条目。例如在图1b中，更新(k1, v4)被存储到一个新的位置，而不是直接更新旧的条目(k1, v1)。这种设计提高了写性能，因为它可以利用顺序I/O来处理写。它还可以通过不覆盖旧数据来简化恢复过程。然而，这种设计的主要问题是牺牲了读取性能，因为记录可能存储在多个位置中的任何一个。此外，这些结构通常需要一个独立的数据重组过程，以不断提高存储和查询效率。顺序的、异位的更新并不是新的想法；自20世纪70年代以来，它已成功地应用于数据库系统。”&lt;/p&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;p&gt;可以看出LSM-TREE的异位（OUT-OF-PLACE）更新结构，压根就不是读后写，它就是一个INSERT动作。那这样子来看，是不是感觉把更新动作当作是一个写流程的衍生物，无论是对于B-TREE而言，还是LSM-TREE而言，基本上是没有什么违和感的。&lt;/p&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;p&gt;然而，在这一堆反反复复的文字中，你可能已经建立起一个LSM-TREE读取性能被牺牲的概念，并且可能认为读取性能不佳，可能会是阻碍你的系统选取LSM-TREE的重要障碍。因为，哪怕你就是简简单单的看最前面那个彩图，也能知道，你的一条记录可能要读好几个文件才能得到，从而质疑它的读取性能。&lt;/p&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;p&gt;如果你内心敏感，你也许能从我举得例子中察觉到另一朵重要的乌云。这个乌云是资源的冲突。LSM-TREE的写优势根源是，用追加写取代读后写。如果，你的系统有任何主要的场景，避免不掉读后写。那这个优势的根基便被动摇了。约束性检查，是最容易想到的场景之一，因为不读，就不能确定能不能写。继而，我们很容易的想到另一个重要更新场景，悲观锁和事务。为啥？长期的B-TREE经验告诉我们，SELECT FOR UPDATE，得先SELECT才能上锁FOR UPDATE。那不然我异位INSERT的时候，我去哪检查这条记录有没有锁呢？我怎么确定我INSERT的时候，别人不能INSERT呢？那这一部分内容，我们放在后面的章节里再去展开。先把基本的动作来看完。&lt;/p&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;h2&gt;    &lt;strong&gt;六、B-TREE之优势的读&lt;/strong&gt;&lt;/h2&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;p&gt;再看一下这个图&lt;/p&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='1080' height='627'&gt;&lt;/svg&gt;" width="1080"&gt;&lt;/img&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;p&gt;数据读取，数据库通常视为最最主要的能力。就像我前面说的，有一段时间我都一直觉得，数据如何写，如何摆放最终都是为了读起来方便。从图上看的感觉总是直观的，前面提到了，很多人眼看着LSM-TREE那张图读取动作出现了5条读取线，得出了LSM-TREE读取性能不行的结论（很显然，我初看这张图，也是这么认为的）。当然，读多次，性能是不是受损，那肯定是受损的。损得大不大？那就不好说了。&lt;/p&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;p&gt;在来来回回看这张图之后，我不再紧盯那五根线，而是开始细细的观察Level 1开始往下的这些SSTABLE文件。这些文件由异步的Compaction操作产生。这个动作有很多文档都把它翻译成数据压缩，我觉得这很容易和数据库的DATA COMPRESS概念混淆。我更喜欢把它译为数据整理。我们看看它做了什么，去重多版本数据、删除多余的老数据、数据按KEY排序。没错，它竟然是排序的。如果说SSTABLE的文件头里面，标上了起止的KEY，它像不像一个小小的B树呢？还记得我在前面说那个隐隐约约的线索吗？数据库总是把一些异步操作，作为一些优化的空间。LSM-TREE的Compaction就绝不是仅仅为了防止数据量不断增长而设计的清理机制。它的存在还有更重要的意义。LSM-TREE其实并没有自暴自弃的在优化写入的时候就放弃查询。其实它遵循了我们之前那个现索，数据库在异步的流程中，暗暗的优化数据查询时的速度。&lt;/p&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;p&gt;我们来接着填一下前面的坑。如果说，相比B树而言，LSM-TREE的读取总是很慢的。是不是数据相对稳定的系统就不能选LSM-TREE吗？如果，我们的数据一次INSERT之后，就没不动了，那会是什么情况？我们来细细的推敲一下，首先必然的，LSM-TREE写优势用不上不对，因为你就不怎么写嘛？但是读的真的就慢吗？我们来看看，首先，我们在内存里找到了这条记录，CACHE命中了，或者MEMTABLE里有最新的数据，都没有磁盘IO，那妥妥的快。假设没命中，那么只做个一次INSERT的记录，可能出现几层的几个SSTABLE里面？好像只有一个吧。当然，这里会有一个疑惑，就是我没读这个SSTABLE，我怎么知道里面有没有这条记录？在我印象中，通常SSTABLE的数据文件，通常都要配一个BLOOM过滤器，来告诉你这条KEY它有没有来解决这件事情。诶，这时候，再来整理整理感觉，是不是上面那两根内存里的线，还挺快的。下面指到磁盘上的三根线，好像也没有三根线也就只有一根，剩下这一根，感觉还和B-TREE有那么一点点的像，搞不好在这一个SSTABLE里面，拿得还比B-TREE快。&lt;/p&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;p&gt;好啦，这个例子其实有点过，我并不是要证明LSM-TREE的读取性能要比B-TREE好，这是不可能的。我只是想再提醒前面那个观点，它优势的地方你的系统不一定优势，它劣势的地方你的系统也不一定就劣势，就是这么神奇。比如我还常常被问到类似的问题，我的系统以更新为主，SSTABLE的线特别多是不是不能选LSM-TREE？那也不一定啊，如果你平时查都不查，那读取有一百根线对你的系统性能又有什么影响呢？那我的系统又改得多又查得多，又不是以写入为主呢？对的，它不适合，因为SSTABLE的读取线很多，而且它的写优势又发挥不出来。所以无论如何，你还是得把自己系统的场景往里面带一带，不要凭看图的直觉。&lt;/p&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;p&gt;LSM-TREE的读取性能或许确实的受损的。但是显而易见的是，对于很多时候，这种受损是可以通过各种各样的手段优化、缓解。使得这种受损处于可接受的范围，不然LSM-TREE是怎么越来越火的呢？对吧。&lt;/p&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;p&gt;而B树的读取方面，我觉得这里就不太需要再展开来讨论了，因为前面在说明它写的时候，其实也是要先完成读取流程的，再者B树大家也相对而言比较熟悉。&lt;/p&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;h2&gt;    &lt;strong&gt;七、资源的冲突-数据锁&lt;/strong&gt;&lt;/h2&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;p&gt;接下来，是时候来讲一讲这一朵乌云了。资源冲突问题，一直以来都在数据库的重要困难点附近出没。而且，冲突问题又和分布式的问题互相之间有一些纠缠。举个例子，在数据库单机架构往SHARE DISK的架构演变过程中，它就一度成为了很多数据库厂商搞不定的难点。比如，我在集群中的某一个数据库实例上，上锁并修改了数据。这个锁和修改信息，就需要立刻在内存中，直接通讯给其他的数据库实例。最后比较成熟的方案只有几个少数像ORACLE 的RAC，DB2的DATA SHARING这样的能够解决。这个例子特别清楚的能体现，资源冲突处理，要么要集中处理，要么需要特别靠谱的互相通信，这个通讯在分布式水平扩展的情况下会有所放大。所以，有一些SHARE NOTHING的数据库产品，会选择在数据存储节点，来集中处理数据的上锁问题。&lt;/p&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;p&gt;对于基于LSM-TREE的数据库产品而言，有的产品选择不支持锁和事务，有的选择通过其它巧妙的手段来解决。而我们还是可以显著的看到，B-TREE结构中，上锁与不上锁，也许只是内存里面一个记录标志位的修改差别，上锁与不上锁的性能差别似乎并不是很大。但是LSM-TREE结构的数据库里，似乎很有必要把上锁流程的开销拿出来额外关注一下。因为如果数据库做的是一般性先读后写，那么写优势没了。如果是别的冲突处理机制，那这部分显然属于额外开销。&lt;/p&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;p&gt;为什么不支持，竟然也可以算一个出路，我们可以往回退一步。资源冲突问题的源头是并行的处理。而在冲突的资源点上，我们需要转并行为串行，其实大致上我觉得可以分开成两个问题来看，第一个解决时序问题，就是我们认为后面的更新才是对的，第二个是解决并发过程中更新丢失和脏读问题。像CASSANDRA这样的数据，用时间戳来解决时序问题，即改更新为追加，并在数据读取合并时，以最新时间戳的数据为准。再利用时间戳来实现多版本的读取。算是解决半个资源冲突的本源问题。所以对没有第二部分问题的系统，这的确也是一个解决方案。&lt;/p&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;p&gt;另外半个问题，就比较棘手，比如说钱的转入转出模型，有的转入有的转出，若钱不够就不能够转出。&lt;/p&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;p&gt;我们来看一个我觉得比较优秀的基于LSM-TREE存储结构的 Percolator模型的实现方案。比A有10块钱，B有2块钱，A向B转7块钱。&lt;/p&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;p&gt;首先，使用三个列簇（COLUMN FAILY）来存三样东西，一个是数据本身，一个是锁信息，一个是写入的版本。要有一个时间戳和一个版本号来解决时序问题，那么在PREWRITE阶段，数据库除了写入数据之外，还要写入LOCK信息。最后再提交阶段，把LOCK信息干掉，再写一个版本信息。&lt;/p&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='511' height='173'&gt;&lt;/svg&gt;" width="511"&gt;&lt;/img&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;p&gt;作为读后写替代，那么这种方案下的冲突解决能不能比B树的维护代价更小一点。我们先看看，在理想LSM-TREE的UPDATE只有一个追加写 MEMTABLE的操作上，又附加了什么。首先，我们看A记录，一共写了 三次，最后COMMIT结束之后，还需要把锁信息干掉。草算一算，好像时间翻了四倍。再细细的看一下，写数据和写版本看起来应该是可以追加写的。但是，这个锁信息可不就是一个妥妥的读后写吗？因为对一条记录上锁之前，起码得看看前面有没有锁吧。但是感觉上，这个锁的CF应该不大，应该基本上都是内存操作。&lt;/p&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;p&gt;我们来细细推敲一下。普通更新的话，把A从版本5的10改为版本7的3。追加写数据，追加写版本，两次内存操作。检查读一次锁，如果没锁，写一次锁，先算他是内存操作。如果在B树上，一般随机转账命中概率不大，把A读起来，那么有一波磁盘IO，再加同样的内存上锁放锁，感觉还是B树不一定快。为啥，因为同样是读后写，这个锁CF信息的读后写很可能是内存里没有磁盘操作的。而B树的读后写，那基本上磁盘IO跑不了的。在这样的情况下，其实LSM基本上不落下风的。&lt;/p&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;p&gt;但是，程序可不一定是这么写的。比如说，悲观锁。我相信很多时候，你很难把上面这个场景的SQL写成，UPDATE TABLE SET YUAN=YUAN-7 WHERE KEY=A吧。比较常见的写法不应该是，SELECT YUAN FROM TABLE WHERE KEY=A FOR UPDATE。然后IF YUAN&amp;gt;=7 YUAN_NEW=YUAN-7。再然后UPDATE TABLE SET YUAN=YUAN_NEW WHERE KEY=A。&lt;/p&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;p&gt;这中间出现了什么变化，响应时间对比其实从单一的UPDATE对比变成了两个SQL符合在一起的形态对比。首先，由于SELECT FOR UPDATE这个动作出现，磁盘读变成了一个跑不掉的步骤。B树上，记录会在SELECT FOR UPDATE的时候就被IO到内存里面来。这时候，你再看B树的UPDATE，那就纯粹是个内存操作，那应该不会比写三个CF慢的。上锁放锁的内存动作，我们姑且认为二者差不多，那这个场景就变成纯粹的比读取。而且，考虑的有冲突和并发，表示这是一个频繁变更数据的情况，那么我会猜测LSM-TREE读取的SSTABLE的线比较多，最终直接成为影响系统性能的重要因素。&lt;/p&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;p&gt;再稍微说一下分布式的叠加，如果A和B存在不同的节点里。B中的副锁还要再跨网络去访问A节点的主锁有没有释放。感觉也是有一些跟硬件相关的开销在里面。但应该不是重点，因为类比我们前面说的，RAC各实例间也有基于网络的锁信息交互开销。&lt;/p&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;h2&gt;    &lt;strong&gt;八、高可用附加&lt;/strong&gt;&lt;/h2&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;p&gt;其实这个内容，跟存储引擎的联系有限，但是前面写入的时候提到了BINLOG。MYSQL的BINLOG承担了主备机同步的桥梁作用，但它对于很多重要系统来说都是必开的。最关键的是，再RPO=0的前提下，BINLOG的远程落盘，是COMMIT成功的必要条件。它的开销对性能影响是必然的，写BINLOG也几乎写流程中的一部分。不过我觉得它还是可以剥离出来看，因为它本质并不是WAL日志。如果一定要对标的话，我觉得它类似于RAFT LOG的写入，这两个东西都是围绕着高可用和多节点数据复制展开的。对于写入的性能开销增加，似乎也是差不多的。&lt;/p&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;h2&gt;    &lt;strong&gt;九、结尾&lt;/strong&gt;&lt;/h2&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;   &lt;p&gt;终于把这个坑稍微填了一填，感觉说了好多，又感觉好多内容没有说。想必以我的认知力，也并没有能力把所有的内容说的很全。基本上把实践中，遇到过、顾虑过的一些我觉得的关键点整理了一下吧，也算可以给需要的朋友提供个参考。另外，从文中想必也能看得出来，有些内容基本我全凭猜测，未必准确，也期待有大佬看到并帮我指出其中的错漏。&lt;/p&gt;   &lt;h2&gt;    &lt;strong&gt;关于我们&lt;/strong&gt;&lt;/h2&gt;   &lt;p&gt;    &lt;strong&gt;dbaplus社群&lt;/strong&gt;是围绕Database、BigData、AIOps的企业级专业社群。资深大咖、技术干货，每天精品原创文章推送，每周线上技术分享，每月线下技术沙龙，每季度Gdevops&amp;amp;DAMS行业大会。&lt;/p&gt;   &lt;p&gt;关注公众号【dbaplus社群】，获取更多原创技术文章和精选工具下载&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>dev</category>
      <guid isPermaLink="true">https://itindex.net/detail/62628-%E5%88%86%E5%B8%83-%E6%95%B0%E6%8D%AE%E5%BA%93-%E9%80%8F%E6%9E%90</guid>
      <pubDate>Fri, 24 Feb 2023 00:00:00 CST</pubDate>
    </item>
    <item>
      <title>Nginx日志分析常用脚本 |</title>
      <link>https://itindex.net/detail/62393-nginx-%E6%97%A5%E5%BF%97-%E5%88%86%E6%9E%90</link>
      <description>&lt;div&gt;    &lt;p&gt;IP相关统计 统计IP访问量（独立ip访问数量）&lt;/p&gt;    &lt;pre&gt;awk &amp;apos;{print $1}&amp;apos; access.log | sort -n | uniq | wc -l&lt;/pre&gt;    &lt;p&gt;查看某一时间段的IP访问量(4-5点)&lt;/p&gt;    &lt;pre&gt;grep &amp;quot;07/Apr/2017:0[4-5]&amp;quot; access.log | awk &amp;apos;{print $1}&amp;apos; | sort | uniq -c| sort -nr | wc -l&lt;/pre&gt;    &lt;p&gt;查看访问最频繁的前100个IP&lt;/p&gt;    &lt;pre&gt;awk &amp;apos;{print $1}&amp;apos; access.log | sort -n |uniq -c | sort -rn | head -n 100&lt;/pre&gt;    &lt;p&gt;查看访问100次以上的IP&lt;/p&gt;    &lt;pre&gt;awk &amp;apos;{print $1}&amp;apos; access.log | sort -n |uniq -c |awk &amp;apos;{if($1 &amp;gt;100) print $0}&amp;apos;|sort -rn&lt;/pre&gt;    &lt;p&gt;查询某个IP的详细访问情况,按访问频率排序&lt;/p&gt;    &lt;pre&gt;grep &amp;apos;127.0.01&amp;apos; access.log |awk &amp;apos;{print $7}&amp;apos;|sort |uniq -c |sort -rn |head -n 100&lt;/pre&gt;    &lt;p&gt;页面访问统计 查看访问最频的页面(TOP100)&lt;/p&gt;    &lt;pre&gt;awk &amp;apos;{print $7}&amp;apos; access.log | sort |uniq -c | sort -rn | head -n 100&lt;/pre&gt;    &lt;p&gt;查看访问最频的页面([排除php页面】(TOP100)&lt;/p&gt;    &lt;pre&gt;grep -v &amp;quot;.php&amp;quot;  access.log | awk &amp;apos;{print $7}&amp;apos; | sort |uniq -c | sort -rn | head -n 100&lt;/pre&gt;    &lt;p&gt;查看页面访问次数超过100次的页面&lt;/p&gt;    &lt;pre&gt;cat access.log | cut -d &amp;apos; &amp;apos; -f 7 | sort |uniq -c | awk &amp;apos;{if ($1 &amp;gt; 100) print $0}&amp;apos; | less&lt;/pre&gt;    &lt;p&gt;查看最近1000条记录，访问量最高的页面&lt;/p&gt;    &lt;pre&gt;tail -1000 access.log |awk &amp;apos;{print $7}&amp;apos;|sort|uniq -c|sort -nr|less&lt;/pre&gt;    &lt;p&gt;每秒请求量统计 统计每秒的请求数,top100的时间点(精确到秒)&lt;/p&gt;    &lt;pre&gt;awk &amp;apos;{print $4}&amp;apos; access.log |cut -c 14-21|sort|uniq -c|sort -nr|head -n 100&lt;/pre&gt;    &lt;p&gt;每分钟请求量统计 统计每分钟的请求数,top100的时间点(精确到分钟)&lt;/p&gt;    &lt;pre&gt;awk &amp;apos;{print $4}&amp;apos; access.log |cut -c 14-18|sort|uniq -c|sort -nr|head -n 100&lt;/pre&gt;    &lt;p&gt;每小时请求量统计 统计每小时的请求数,top100的时间点(精确到小时)&lt;/p&gt;    &lt;pre&gt;awk &amp;apos;{print $4}&amp;apos; access.log |cut -c 14-15|sort|uniq -c|sort -nr|head -n 100&lt;/pre&gt;    &lt;p&gt;性能分析 在nginx log中最后一个字段加入$request_time&lt;/p&gt;    &lt;p&gt;列出传输时间超过 3 秒的页面，显示前20条&lt;/p&gt;    &lt;pre&gt;cat access.log|awk &amp;apos;($NF &amp;gt; 3){print $7}&amp;apos;|sort -n|uniq -c|sort -nr|head -20&lt;/pre&gt;    &lt;p&gt;列出php页面请求时间超过3秒的页面，并统计其出现的次数，显示前100条&lt;/p&gt;    &lt;pre&gt;cat access.log|awk &amp;apos;($NF &amp;gt; 1 &amp;amp;&amp;amp;  $7~/\.php/){print $7}&amp;apos;|sort -n|uniq -c|sort -nr|head -100&lt;/pre&gt;    &lt;p&gt;蜘蛛抓取统计 统计蜘蛛抓取次数&lt;/p&gt;    &lt;pre&gt;grep &amp;apos;Baiduspider&amp;apos; access.log |wc -l&lt;/pre&gt;    &lt;p&gt;统计蜘蛛抓取404的次数&lt;/p&gt;    &lt;pre&gt;grep &amp;apos;Baiduspider&amp;apos; access.log |grep &amp;apos;404&amp;apos; | wc -l&lt;/pre&gt;    &lt;p&gt;TCP连接统计 查看当前TCP连接数&lt;/p&gt;    &lt;pre&gt;netstat -tan | grep &amp;quot;ESTABLISHED&amp;quot; | grep &amp;quot;:80&amp;quot; | wc -l&lt;/pre&gt;    &lt;p&gt;用tcpdump嗅探80端口的访问看看谁最高&lt;/p&gt;    &lt;pre&gt;tcpdump -i eth0 -tnn dst port 80 -c 1000 | awk -F&amp;quot;.&amp;quot; &amp;apos;{print $1&amp;quot;.&amp;quot;$2&amp;quot;.&amp;quot;$3&amp;quot;.&amp;quot;$4}&amp;apos; | sort | uniq -c | sort -nr
awk &amp;apos;{print $23}&amp;apos; access_json.log | sort -n |uniq -c | sort -rn | head -n 10&lt;/pre&gt;    &lt;h4&gt;根据时间段查询&lt;/h4&gt;    &lt;p&gt;查shop-bussiness.log.2018-11-06文件中2018年11月6号11：34至11点37之间的日志信息，可以这么做：&lt;/p&gt;    &lt;pre&gt;grep   &amp;apos;2018-Nov-06 11:3[4-7]&amp;apos; shop-bussiness.log.2018-11-06&lt;/pre&gt;    &lt;p&gt;截取一段时间内的log日志可以使用sed命令对log文件进行抽取操作:&lt;/p&gt;    &lt;p&gt;1，sed查看某时间段到现在的系统日志：&lt;/p&gt;    &lt;pre&gt;sed  -n  &amp;apos;/May 20 17/,$p&amp;apos;   /var/log/messages  |  less&lt;/pre&gt;    &lt;p&gt;2，sed 截选时间段日志： 假如日志的格式是--&lt;/p&gt;    &lt;pre&gt;“2015-05-04 09:25:55,606 后面跟日志内容 ”这样的
目标是需要将05-04的09:25:55 和09:28:08 之间的日志截取出来：
使用sed命令如下：
sed -n ‘/2015-05-04 09:25:55/,/2015-05-04 09:28:55/p’  logfile&lt;/pre&gt;    &lt;p&gt;这样可以精确地截取出来某个时间段的日志。&lt;/p&gt;    &lt;p&gt;如果需要截取的日志太大，达到几个G的话，不能去vi打开文件:&lt;/p&gt;    &lt;p&gt;根据之前的日志格式，使用正则表达式:&lt;/p&gt;    &lt;pre&gt;sed -n ‘/2010-11-17 09:[0-9][0-9]:[0-9][0-9]/,/2010-11-17 16:[0-9][0-9]:[0-9][0-9]/p’  logfile&lt;/pre&gt;    &lt;p&gt;如果没有问题的话，上面就能筛选出指定的时间段的日志。&lt;/p&gt;    &lt;p&gt; &lt;/p&gt;&lt;/div&gt;
    &lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/62393-nginx-%E6%97%A5%E5%BF%97-%E5%88%86%E6%9E%90</guid>
      <pubDate>Wed, 31 Aug 2022 10:52:07 CST</pubDate>
    </item>
    <item>
      <title>用 Wireshark 分析 TCP 吞吐瓶颈</title>
      <link>https://itindex.net/detail/62372-wireshark-%E5%88%86%E6%9E%90-tcp</link>
      <description>&lt;p&gt;Debug 网络质量的时候，我们一般会关注两个因素：延迟和吞吐量（带宽）。延迟比较好验证，Ping 一下或者   &lt;a href="https://www.kawabangga.com/posts/4275"&gt;mtr&lt;/a&gt; 一下就能看出来。这篇文章分享一个 debug 吞吐量的办法。&lt;/p&gt;
 &lt;p&gt;看重吞吐量的场景一般是所谓的长肥管道(Long Fat Networks, LFN,   &lt;a href="https://datatracker.ietf.org/doc/html/rfc7323"&gt;rfc7323&lt;/a&gt;). 比如下载大文件。吞吐量没有达到网络的上限，主要可能受 3 个方面的影响：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;发送端出现了瓶颈&lt;/li&gt;
  &lt;li&gt;接收端出现了瓶颈&lt;/li&gt;
  &lt;li&gt;中间的网络层出现了瓶颈&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;发送端出现瓶颈一般的情况是 buffer 不够大，因为发送的过程是，应用调用 syscall，将要发送的数据放到 buffer 里面，然后由系统负责发送出去。如果 buffer 满了，那么应用会阻塞住（如果使用 block 的 API 的话），直到 buffer 可用了再继续 write，生产者和消费者模式。&lt;/p&gt;
 &lt;div&gt;  &lt;a href="https://www.kawabangga.com/wp-content/uploads/2022/08/tcp-bufer.gif"&gt;   &lt;img alt="" height="298" src="https://www.kawabangga.com/wp-content/uploads/2022/08/tcp-bufer.gif" width="266"&gt;&lt;/img&gt;&lt;/a&gt;  &lt;p&gt;图片来自    &lt;a href="https://www.ciscopress.com/articles/article.asp?p=769557&amp;seqNum=2"&gt;cisco&lt;/a&gt;&lt;/p&gt;&lt;/div&gt;
 &lt;p&gt;发送端出现瓶颈一般都比较好排查，甚至通过应用的日志看何时阻塞住了即可。大部分情况都是第 2，3 种情况，比较难以排查。这种情况发生在，发送端的应用已经将内容写入到了系统的 buffer 中，但是系统并没有很快的发送出去。&lt;/p&gt;
 &lt;p&gt;TCP 为了优化传输效率（注意这里的传输效率，并不是单纯某一个 TCP 连接的传输效率，而是整体网络的效率），会:&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;保护接收端，发送的数据不会超过接收端的 buffer 大小 (Flow control)。数据发送到接受端，也是和上面介绍的过程类似，kernel 先负责收好包放到 buffer 中，然后上层应用程序处理这个 buffer 中的内容，如果接收端的 buffer 过小，那么很容易出现瓶颈，即应用程序还没来得及处理就被填满了。那么如果数据继续发过来，buffer 存不下，接收端只能丢弃。&lt;/li&gt;
  &lt;li&gt;保护网络，发送的数据不会 overwhelming 网络 (Congestion Control, 拥塞控制), 如果中间的网络出现瓶颈，会导致长肥管道的吞吐不理想；&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;对于接收端的保护，在两边连接建立的时候，会协商好接收端的 buffer 大小 (receiver window size, rwnd), 并且在后续的发送中，接收端也会在每一个 ack 回包中报告自己剩余和接受的 window 大小。这样，发送端在发送的时候会保证不会发送超过接收端 buffer 大小的数据。（意思是，发送端需要负责，receiver 没有 ack 的总数，不会超过 receiver 的 buffer.）&lt;/p&gt;
 &lt;div&gt;  &lt;a href="https://www.kawabangga.com/wp-content/uploads/2022/08/receiver-buffer.gif"&gt;   &lt;img alt="" height="388" src="https://www.kawabangga.com/wp-content/uploads/2022/08/receiver-buffer.gif" width="500"&gt;&lt;/img&gt;&lt;/a&gt;  &lt;p&gt;图片来自    &lt;a href="https://www.ciscopress.com/articles/article.asp?p=769557&amp;seqNum=2"&gt;cisco&lt;/a&gt;&lt;/p&gt;&lt;/div&gt;
 &lt;p&gt;对于网络的保护，原理也是维护一个 Window，叫做 Congestion window，拥塞窗口，cwnd, 这个窗口就是当前网络的限制，发送端不会发送超过这个窗口的容量（没有 ack 的总数不会超过 cwnd）。&lt;/p&gt;
 &lt;p&gt;怎么找到这个 cwnd 的值呢？&lt;/p&gt;
 &lt;p&gt;这个就是关键了，默认的算法是 cubic, 也有其他算法可以使用，比如 Google 的   &lt;a href="https://www.kawabangga.com/posts/4086"&gt;BBR&lt;/a&gt;.&lt;/p&gt;
 &lt;p&gt;主要的逻辑是，慢启动(Slow start), 发送数据来测试，如果能正确收到 receiver 那边的 ack，说明当前网络能容纳这个吞吐，将 cwnd x 2，然后继续测试。直到下面一种情况发生：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;发送的包没有收到 ACK&lt;/li&gt;
  &lt;li&gt;cwnd 已经等于 rwnd 了&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;第 2 点很好理解，说明网络吞吐并不是一个瓶颈，瓶颈是在接收端的 buffer 不够大。cwnd 不能超过 rwnd，不然会 overload 接收端。&lt;/p&gt;
 &lt;p&gt;对于第 1 点，本质上，发送端是用丢包来检测网络状况的，如果没有发生丢包，表示一切正常，如果发生丢包，说明网络处理不了这个发送速度，这时候发送端会直接将 cwnd 减半。&lt;/p&gt;
 &lt;p&gt;但实际造成第 1 点的情况并不一定是网络吞吐瓶颈，而可能是以下几种情况：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;网络达到了瓶颈&lt;/li&gt;
  &lt;li&gt;网络质量问题丢包&lt;/li&gt;
  &lt;li&gt;中间网络设备延迟了包的送达，导致发送端没有在预期时间内收到 ACK&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;2 和 3 原因都会造成 cwnd 下降，无法充分利用网络吞吐。&lt;/p&gt;
 &lt;p&gt;以上就是基本的原理，下面介绍如何定位这种问题。&lt;/p&gt;
 &lt;h2&gt;rwnd 查看方式&lt;/h2&gt;
 &lt;p&gt;这个 window size 直接就在 TCP header 里面，抓下来就能看这个字段。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="https://www.kawabangga.com/wp-content/uploads/2022/08/tcp-handshake-factor.png"&gt;   &lt;img alt="" height="1350" src="https://www.kawabangga.com/wp-content/uploads/2022/08/tcp-handshake-factor.png" width="2728"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;但是真正的 window size 需要乘以 factor, factor 是在   &lt;a href="https://en.wikipedia.org/wiki/TCP_window_scale_option"&gt;TCP 握手节点通过 TCP Options 协商的&lt;/a&gt;。所以如果分析一条 TCP 连接的 window size，必须抓到握手阶段的包，不然就不可以知道协商的 factor 是多少。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="https://www.kawabangga.com/wp-content/uploads/2022/08/tcp-rwnd-size.png"&gt;   &lt;img alt="" height="2008" src="https://www.kawabangga.com/wp-content/uploads/2022/08/tcp-rwnd-size.png" width="2728"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;h2&gt;cwnd 查看方式&lt;/h2&gt;
 &lt;p&gt;Congestion control 是发送端通过算法得到的一个动态变量，会试试调整，并不会体现在协议的传输数据中。所以要看这个，必须在发送端的机器上看。&lt;/p&gt;
 &lt;p&gt;在 Linux 中可以使用   &lt;code&gt;ss -i&lt;/code&gt; 选项将 TCP 连接的参数都打印出来。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="https://www.kawabangga.com/wp-content/uploads/2022/08/tcp-cwnd-ss.png"&gt;   &lt;img alt="" height="600" src="https://www.kawabangga.com/wp-content/uploads/2022/08/tcp-cwnd-ss.png" width="1466"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;这里展示的单位是   &lt;a href="https://www.cloudflare.com/learning/network-layer/what-is-mss/"&gt;TCP MSS.&lt;/a&gt; 即实际大小是 1460bytes * 10.&lt;/p&gt;
 &lt;h2&gt;Wireshark 分析&lt;/h2&gt;
 &lt;p&gt;Wireshark 提供了非常使用的统计功能，可以让你一眼就能看出当前的瓶颈是发生在了哪里。但是第一次打开这个图我不会看，一脸懵逼，也没查到资料要怎么看。好在我  &lt;a href="https://github.com/royzhr"&gt;同事&lt;/a&gt;会，他把我教会了，我在这里记录一下，把你也教会。&lt;/p&gt;
 &lt;p&gt;首先，打开的方式如下：&lt;/p&gt;
 &lt;p&gt;  &lt;a href="https://www.kawabangga.com/wp-content/uploads/2022/08/wireshark-open-tcptrace.png"&gt;   &lt;img alt="" height="1344" src="https://www.kawabangga.com/wp-content/uploads/2022/08/wireshark-open-tcptrace.png" width="1902"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;然后你会看到如下的图。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="https://www.kawabangga.com/wp-content/uploads/2022/08/wireshark-tcptrace-howtoread.png"&gt;   &lt;img alt="" height="1238" src="https://www.kawabangga.com/wp-content/uploads/2022/08/wireshark-tcptrace-howtoread.png" width="1728"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;首先需要明确，tcptrace 的图表示的是单方向的数据发送，因为 tcp 是双工协议，两边都能发送数据。其中最上面写了你当前在看的图数据是从 10.0.0.1 发送到 192.168.0.1 的，然后按右下角的按钮可以切换看的方向。&lt;/p&gt;
 &lt;p&gt;X轴表示的是时间，很好理解。&lt;/p&gt;
 &lt;p&gt;然后理解一下 Y 轴表示的 Sequence Number, 就是 TCP 包中的 Sequence Number，这个很关键。图中所有的数据，都是以 Sequence Number 为准的。&lt;/p&gt;
 &lt;p&gt;所以，你如果看到如上图所示，那么说明你看反了，因为数据的 Sequence Number 并没有增加过，说明几乎没有发送过数据，需要点击 Switch Direction。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="https://www.kawabangga.com/wp-content/uploads/2022/08/wireshark-real-dataflow.png"&gt;   &lt;img alt="" height="1238" src="https://www.kawabangga.com/wp-content/uploads/2022/08/wireshark-real-dataflow.png" width="1728"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;这就对了，可以看到我们传输的 Sequence Number 在随着时间增加而增加。&lt;/p&gt;
 &lt;p&gt;这里面有 3 条线，含义如下：&lt;/p&gt;
 &lt;p&gt;  &lt;a href="https://www.kawabangga.com/wp-content/uploads/2022/08/wireshark-tcptrace-read.png"&gt;   &lt;img alt="" height="1238" src="https://www.kawabangga.com/wp-content/uploads/2022/08/wireshark-tcptrace-read.png" width="1728"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;除此之外，另外还有两种线：&lt;/p&gt;
 &lt;p&gt;  &lt;a href="https://www.kawabangga.com/wp-content/uploads/2022/08/tcptrace-sack.png"&gt;   &lt;img alt="" height="1238" src="https://www.kawabangga.com/wp-content/uploads/2022/08/tcptrace-sack.png" width="1728"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;需要始终记住的是 Y 轴是 Sequence Number，红色的线表示 SACK 的线表示这一段 Sequence Number 我已经收到了，然后配合黄色线表示 ACK 过的 Sequence Number，那么发送端就会知道，在中间这段空挡，包丢了，红色线和黄色线纵向的空白，是没有被 ACK 的包。所以，需要重新传输。而蓝色的线就是表示又重新传输了一遍。&lt;/p&gt;
 &lt;p&gt;学会了看这些图，我们可以认识几种常见的 pattern：&lt;/p&gt;
 &lt;h3&gt;丢包&lt;/h3&gt;
 &lt;p&gt;  &lt;a href="https://www.kawabangga.com/wp-content/uploads/2022/08/wireshark-packet-loss.png"&gt;   &lt;img alt="" height="1238" src="https://www.kawabangga.com/wp-content/uploads/2022/08/wireshark-packet-loss.png" width="1728"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;很多红色 SACK，说明接收端那边重复在说：中间有一个包我没有收到，中间有一个包我没有收到。&lt;/p&gt;
 &lt;h3&gt;吞吐受到接收 window size 限制&lt;/h3&gt;
 &lt;p&gt;  &lt;a href="https://www.kawabangga.com/wp-content/uploads/2022/08/rwnd-too-low.png"&gt;   &lt;img alt="" height="1238" src="https://www.kawabangga.com/wp-content/uploads/2022/08/rwnd-too-low.png" width="1728"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;从这个图可以看出，黄色的线（接收端一 ACK）一上升，蓝色就跟着上升（发送端就开始发），直到填满绿色的线（window size）。说明网络并不是瓶颈，可以调大接收端的 buffer size.&lt;/p&gt;
 &lt;h3&gt;吞吐受到网络质量限制&lt;/h3&gt;
 &lt;p&gt;  &lt;a href="https://www.kawabangga.com/wp-content/uploads/2022/08/wireshark-cwnd-low.png"&gt;   &lt;img alt="" height="1238" src="https://www.kawabangga.com/wp-content/uploads/2022/08/wireshark-cwnd-low.png" width="1728"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;从这张图中可以看出，接收端的 window size 远远不是瓶颈，还有很多空闲。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="https://www.kawabangga.com/wp-content/uploads/2022/08/wireshark-cwnd-low-zoom.png"&gt;   &lt;img alt="" height="1238" src="https://www.kawabangga.com/wp-content/uploads/2022/08/wireshark-cwnd-low-zoom.png" width="1728"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;放大可以看出，中间有很多丢包和重传，并且每次只发送一点点数据，这说明很有可能是 cwnd 太小了，受到了拥塞控制算法的限制。&lt;/p&gt;
 &lt;p&gt; &lt;/p&gt;
 &lt;p&gt;本文中用到的抓包文件可以从这里下载(credit:   &lt;a href="https://www.youtube.com/watch?v=yUmACeSmT7o"&gt;https://www.youtube.com/watch?v=yUmACeSmT7o&lt;/a&gt;):&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;   &lt;a href="https://www.cloudshark.org/captures/f5eb7c033728"&gt;https://www.cloudshark.org/captures/f5eb7c033728&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://www.cloudshark.org/captures/c967765aef38"&gt;https://www.cloudshark.org/captures/c967765aef38&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;其他的一些参考资料：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;   &lt;a href="https://www.stackpath.com/edge-academy/what-is-cwnd-and-rwnd/"&gt;https://www.stackpath.com/edge-academy/what-is-cwnd-and-rwnd/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://www.baeldung.com/cs/tcp-flow-control-vs-congestion-control"&gt;https://www.baeldung.com/cs/tcp-flow-control-vs-congestion-control&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://www.cs.cornell.edu/courses/cs4450/2020sp/lecture21-congestion-control.pdf"&gt;https://www.cs.cornell.edu/courses/cs4450/2020sp/lecture21-congestion-control.pdf&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://www.mi.fu-berlin.de/inf/groups/ag-tech/teaching/2011-12_WS/L_19531_Telematics/08_Transport_Layer.pdf"&gt;https://www.mi.fu-berlin.de/inf/groups/ag-tech/teaching/2011-12_WS/L_19531_Telematics/08_Transport_Layer.pdf&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://wiki.aalto.fi/download/attachments/69901948/TCP-CongestionControlFinal.pdf"&gt;https://wiki.aalto.fi/download/attachments/69901948/TCP-CongestionControlFinal.pdf&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://paulgrevink.wordpress.com/2017/09/08/about-long-fat-networks-and-tcp-tuning/"&gt;https://paulgrevink.wordpress.com/2017/09/08/about-long-fat-networks-and-tcp-tuning/&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt; &lt;p&gt;The post   &lt;a href="https://www.kawabangga.com/posts/4794"&gt;用 Wireshark 分析 TCP 吞吐瓶颈&lt;/a&gt; first appeared on   &lt;a href="https://www.kawabangga.com"&gt;卡瓦邦噶！&lt;/a&gt;.&lt;/p&gt; &lt;div&gt;  &lt;h3&gt;相关文章:&lt;/h3&gt;  &lt;ul&gt;   &lt;li&gt;    &lt;a href="https://www.kawabangga.com/posts/1878"&gt;Django的日志配置&lt;/a&gt;&lt;/li&gt;   &lt;li&gt;    &lt;a href="https://www.kawabangga.com/posts/2029"&gt;部署Sentry&lt;/a&gt;&lt;/li&gt;   &lt;li&gt;    &lt;a href="https://www.kawabangga.com/posts/4275"&gt;使用 mtr 检查网络问题，以及注意事项&lt;/a&gt;&lt;/li&gt;   &lt;li&gt;    &lt;a href="https://www.kawabangga.com/posts/4484"&gt;Django 优化数据库查询的一些经验&lt;/a&gt;&lt;/li&gt;   &lt;li&gt;    &lt;a href="https://www.kawabangga.com/posts/837"&gt;Java 问答：终极父类（五）——toString()&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>程序开发笔记 bbr congestion control cubic cwnd</category>
      <guid isPermaLink="true">https://itindex.net/detail/62372-wireshark-%E5%88%86%E6%9E%90-tcp</guid>
      <pubDate>Wed, 17 Aug 2022 23:24:57 CST</pubDate>
    </item>
    <item>
      <title>电商商品系统的演进分析</title>
      <link>https://itindex.net/detail/62331-%E7%94%B5%E5%95%86-%E5%95%86%E5%93%81-%E7%B3%BB%E7%BB%9F</link>
      <description>&lt;p&gt;好久没有给大家带来新的知识分享了，2022的第一篇（是的，你没看错！第一篇）就给大家讲讲商品模型的演进过程吧！希望对大家做的工作有所帮助~~&lt;/p&gt;
 &lt;p&gt;我们来假设：现在需要从零开始做电商，毫无经验，也没有竞对可参考，你就是全球独一份！你的系统会做成什么样呢？我们一起拨云见日吧！&lt;/p&gt;
 &lt;h2&gt;历史烟云&lt;/h2&gt;
 &lt;p&gt;站在技术的视角，要去做一个电商商品系统，毫无疑问你需要一个商品实体，同时为了方便用户在C端筛选浏览，继承自CMS思想（栏目--&amp;gt;文章模型），很容易想到给每类商品增加一个分类，把相似的商品归属到相同的类目下。比如：超市中看到的酒水区、粮油区就是对同类商品的一个分类。此时我们的模型如下：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c7ad4af2f9c94237bc59a7391ab8b56c~tplv-k3u1fbpfcp-watermark.image?"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;别看这个简单的模型，初期他就可以跑起来了。但是这个分类的方式伴随业务发展遇到一个问题，一些知名品牌他不仅仅只生产一个分类的商品，比如小米，又造手机、又造手机壳！那么消费者想找小米手机壳，在上面的模型下怎么办？只能先找到手机壳这个分类，然后一个一个的翻！这个时候的诉求就是，系统怎么能够快速的把是小米的手机壳找出来呢？思路无非是两个：一是让消费者选择完手机壳后再选一下小米，或者消费者先选小米，然后再选一下手机壳！当然不管采用这两种方式的哪一种都有一个问题，【小米】该怎么在模型上表达呢？对应真实的物理世界，又抽象出品牌这个概念。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/40ea3b66cd8d44199a3a4932a40f82d1~tplv-k3u1fbpfcp-watermark.image?"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;上面的模型演进后，消费者还想更进一步的快速找到自己想要的商品。比如裙子有风格、有长短、有材质各种差异；电子产品如手机有CPU、摄影头像素、电池容量等等差异。也就是说同一个类目下商品的属性会有差异，通过这些属性可以进行快速的筛选定位商品。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a777ae9b7e4a4e1ba0ef926f3cd2db6c~tplv-k3u1fbpfcp-watermark.image?"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;一个分类是可以有若干属性的，每个属性又有多个属性值；又因为一个商品对应一个分类，所以一个商品会有多个属性值。基于此，模型此时演化成:&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/554a3c342ee64de2a255a2c37e981346~tplv-k3u1fbpfcp-watermark.image?"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;到这里，好像基本的能力都可以满足了，但是在线上开展业务中，我们发现还有一个特点。比如买手机有：Xiaomi 12S Ultra 8GB+256GB 经典黑；Xiaomi 12S Ultra 12GB+256GB 冷杉绿 等多种选择，那么按照上面的模型，我们就针对这两种创建对应的两个商品。虽然从技术上可以满足，但是站在消费者角度，他想要先看到 Xiaomi 12S 的全部信息然后逐步选择，页面上要去怎么聚合这两个相关性很高的商品呢？&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8bad8a5110224e2ca329ab898edcc2f5~tplv-k3u1fbpfcp-watermark.image?"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;此时有两种选择，典型的就是京东与淘宝各自的方案（由于没有实际待过、看过代码。所以部分内容纯属YY+网络资料来源，如有雷同，纯属意外！）&lt;/p&gt;
 &lt;h2&gt;适合的就是最好的&lt;/h2&gt;
 &lt;h3&gt;京东的选择&lt;/h3&gt;
 &lt;p&gt;京东选择的就是：每一种不同规格就对应一个商品（我们这里先不说sku概念），然后具体到消费端，会去把这些不同规格的商品进行一个聚合，我们可以大概猜测（因为没有真实见过，只能猜）他们的模型如下：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/53bfa1394bb14e6fae968657758067d5~tplv-k3u1fbpfcp-watermark.image?"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;实际实施的时候，商品-包关系可能直接冗余存储在商品中。&lt;/p&gt;
 &lt;h3&gt;淘宝的选择&lt;/h3&gt;
 &lt;p&gt;在淘宝的商品模型中，将 Xiaomi12s 中的不同规格单独抽象出来，不同规格的商品我们称之为SKU（Stock Keeping Unit），他上面承载价格、库存等信息，商品上去承载 12s 不同sku的共有信息，如：类目、品牌、属性值等。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/72c8bb2b31844e7db7f1adb2deab21a9~tplv-k3u1fbpfcp-watermark.image?"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;采用这种模型时，在消费端透出是基于商品维度的；通过商品去找对应的sku然后做页面渲染。  &lt;br /&gt;
京东、淘宝这种不同的选择各有什么优缺点呢？还是说会存在一种模式碾压另一种？我们可以通过表格做一个简单的分析:&lt;/p&gt;
 &lt;p&gt;京东模型：单层商品模型&lt;/p&gt;
 &lt;p&gt;淘宝模型：双层商品模型&lt;/p&gt;
 &lt;p&gt;比较&lt;/p&gt;
 &lt;p&gt;消费端的SKU直接与供应链的货品对应&lt;/p&gt;
 &lt;p&gt;消费端的商品下的SKU与供应链的货品对应&lt;/p&gt;
 &lt;p&gt;京东自营历史背景，这种与供应链的交互更直接&lt;/p&gt;
 &lt;p&gt;信息治理、优化重复动作多&lt;/p&gt;
 &lt;p&gt;结构化信息治理只看商品&lt;/p&gt;
 &lt;p&gt;淘宝模型信息质量治理更便捷&lt;/p&gt;
 &lt;p&gt;每个SKU存的都是完整信息&lt;/p&gt;
 &lt;p&gt;SKU共有信息在商品上，SKU存差异化信息&lt;/p&gt;
 &lt;p&gt;淘宝模型存储成本更低，消费端呈现更直接（京东为了聚合展示还是需要虚拟加一层SKU包的概念）&lt;/p&gt;
 &lt;p&gt;SKU差异化更容易做（比如SKU维度的渠道、交易、履约等）&lt;/p&gt;
 &lt;p&gt;SKU维度差异化实施成本高（同前）&lt;/p&gt;
 &lt;p&gt;需要到SKU更细节差异化的业务，淘宝模型链路成本高（主要是稳定性与研发成本）&lt;/p&gt;
 &lt;h2&gt;SPU 的诞生记&lt;/h2&gt;
 &lt;p&gt;通过上面的分析应该能够理解类目、品牌、属性、规格、商品、SKU这几个概念是如何演化而来的了。但是这里好像还少了一个环节，SPU在哪里体现呢？接下来我们就继续上面的逻辑来推导出SPU的诞生过程。  &lt;br /&gt;
其实SPU并不是所有电商一定要具备的，一般来说在自营电商中 SPU==商品，而在平台型电商中 SPU 是商品的上一级。为什么会有这种差异呢？我们逐步拆解来分析。注意，下面都是按照product-sku的两层模型来推导，没有涉及京东的一层SKU模型，个人觉得演进思路应该是大同小异的。&lt;/p&gt;
 &lt;h3&gt;自营：只有自己平台一个商家&lt;/h3&gt;
 &lt;p&gt;在只有自己一个商家的时候，每一个品在平台上都是独一无二的，比如小米官网买Xiaomi 12s，不会说有两个入口，进去对应的信息、价格、服务还有差异化，它一定是天然统一的。这个时候我们如果在商品上再抽象一个SPU独立概念出来没有意义，所以，此时的实际模型可以理解为下面的样子&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8b50b6205f534847b50b0ba24cb9d54e~tplv-k3u1fbpfcp-watermark.image?"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h3&gt;平台：支撑多个商家在这里玩&lt;/h3&gt;
 &lt;p&gt;自营不需要区分开SPU，为什么平台就一定需要区分开呢？这里继续用 Xiaomi 12s 来举例子，既然是平台型电商，那么卖这款商品的商家可能就会有很多。从物理概念上讲我们知道大家买的都是 Xiaomi 12s这款手机，在系统中都是独立的商家，我们怎么识别这些手机的关联性呢？除了关联性还有这些手机都是同一家生产的，他们有些信息都是一样的，每个商家都要重复填写吗？我们可以至少从下面三个视角来看问题：&lt;/p&gt;
 &lt;p&gt;供给&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;将SPU理解为一个模板，所有商家可以基于该模板快速填充公共信息&lt;/li&gt;
  &lt;li&gt;平台也可以基于SPU对部分类型商品做强管控，提升供给商品结构化信息完整度与质量&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;消费&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;通过导购位置聚合同类品，提升消费者筛选效率&lt;/li&gt;
  &lt;li&gt;同款识别辅助算法提升精准度&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;平台&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;有了SPU的关联标识，可以在平台范围内实现某个SPU的统计、运营、管控&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/22d5b0c24ff4434eaa7b20b1fcfa8e19~tplv-k3u1fbpfcp-watermark.image?"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;基于上面的SPU逻辑，我们可以更进一步的提出CSPU的概念，因为既然商品维度已经是标准化的，那么SKU维度一些信息也是可以标准化的，他可以更细粒度的解决上面所说的供给、导购问题。到此可以推导出下面的结构：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/587df4cfdd0c4e2c988f43c3e56d6cd3~tplv-k3u1fbpfcp-watermark.image?"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;那么到这里是不是就终止了呢？并没有，现实世界的业务、政治环境不断的发展，变得越来越复杂，比如：出现的全球经营、区域化精细经营等。上面的形态从供给、到消费都有新的诉求。比如之前平台所有商家都基于SPU来发，但是在连锁经营中一个商家可能在平台上不再只对应一个店铺，而是一对多的情况（典型的盒马、每日优鲜等），会基于用户的位置匹配对应门店。但是这些门店本质卖的商品针对这个商家来说还是相同的，他们只是要到不同的门店有信息差异化，甚至是这些门店商品哪些信息、权限可以供门店修改也是要总部商家进行管控的，在这样的情况下我们是需要总部有一个主档的概念，门店基于主档去做分发。  &lt;br /&gt;
至此，商品域中核心的概念大家都基本能够get是如何演进而来的了。但是上面我们的推导还没有涉及到供应链，因为早期的业务，一般都是商家自己把自己仓库里的货品上翻成平台的商品进行售卖，但伴随平台发展能力逐步健全，不管是为了为商家提供更多服务能力，还是业务深耕开始自营，都会开始涉及到供应链。这里最核心的几个问题是：商货关系（主要是仓、库存）、库存共享、库存专享、以及货品到商品的快速上翻等等，这些问题怎么解呢？&lt;/p&gt;
 &lt;p&gt;欢迎你私信我，我们一起碰撞火花~~&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/62331-%E7%94%B5%E5%95%86-%E5%95%86%E5%93%81-%E7%B3%BB%E7%BB%9F</guid>
      <pubDate>Thu, 14 Jul 2022 13:25:35 CST</pubDate>
    </item>
    <item>
      <title>使用 curl 命令分析请求的耗时情况</title>
      <link>https://itindex.net/detail/62317-curl-%E5%91%BD%E4%BB%A4-%E5%88%86%E6%9E%90</link>
      <description>&lt;p&gt;最近工作中遇到一个问题，某个请求的响应特别慢，因此我就希望有一种方法能够分析到底请求的哪一步耗时比较长，好进一步找到问题的原因。&lt;/p&gt; &lt;p&gt;在网络上搜索了一下，发现了一个非常好用的方法，  &lt;code&gt;curl&lt;/code&gt; 命令就能帮助分析请求的各个部分耗时情况。&lt;/p&gt; &lt;h3&gt;  &lt;a href="https://jueee.github.io/#curl-&amp;#21442;&amp;#25968;" title="curl &amp;#21442;&amp;#25968;"&gt;&lt;/a&gt;curl 参数&lt;/h3&gt; &lt;p&gt;curl 命令提供了 -w 参数，这个参数在 manpage 是这样解释的：&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;-w, --write-out &amp;lt;format&amp;gt;              Make curl display information on stdout after a completed transfer. The format is a string that may contain plain text mixed with any number of variables. The  format              can  be  specified  as  a literal &amp;quot;string&amp;quot;, or you can have curl read the format from a file with &amp;quot;@filename&amp;quot; and to tell curl to read the format from stdin you write              &amp;quot;@-&amp;quot;.              The variables present in the output format will be substituted by the value or text that curl thinks fit, as described below. All variables are specified  as  %{vari‐              able_name} and to output a normal % you just write them as %%. You can output a newline by using \n, a carriage return with \r and a tab space with \t.&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;它能够按照指定的格式打印某些信息，里面可以使用某些特定的变量，而且支持 \n、\t 和 \r 转义字符。提供的变量很多，比如 status_code、local_port、size_download 等等，这篇文章我们只关注和请求时间有关的变量（以 time_ 开头的变量）。&lt;/p&gt; &lt;h3&gt;  &lt;a href="https://jueee.github.io/#&amp;#35797;&amp;#29992;" title="&amp;#35797;&amp;#29992;"&gt;&lt;/a&gt;试用&lt;/h3&gt; &lt;pre&gt;  &lt;code&gt;$ curl -o /dev/null -s -w &amp;quot;time_connect: %{time_connect}\ntime_starttransfer: %{time_starttransfer}\ntime_nslookup:%{time_namelookup}\ntime_total: %{time_total}\n&amp;quot; &amp;quot;https://www.baidu.com&amp;quot;time_connect: 0.009time_starttransfer: 0.065time_nslookup:0.007time_total: 0.065&lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;  &lt;a href="https://jueee.github.io/#&amp;#27493;&amp;#39588;" title="&amp;#27493;&amp;#39588;"&gt;&lt;/a&gt;步骤&lt;/h3&gt; &lt;p&gt;先往文本文件 curl.txt 写入下面的内容：&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;   time_namelookup:  %{time_namelookup}\n      time_connect:  %{time_connect}\n   time_appconnect:  %{time_appconnect}\n     time_redirect:  %{time_redirect}\n  time_pretransfer:  %{time_pretransfer}\ntime_starttransfer:  %{time_starttransfer}\n                   ----------\n        time_total:  %{time_total}\n&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;每个变量的解释如下：&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;time_namelookup：DNS 域名解析的时候，就是把 https://baidu.com 转换成 ip 地址的过程time_connect：TCP 连接建立的时间，就是三次握手的时间time_appconnect：SSL/SSH 等上层协议建立连接的时间，比如 connect/handshake 的时间time_redirect：从开始到最后一个请求事务的时间time_pretransfer：从请求开始到响应开始传输的时间time_starttransfer：从请求开始到第一个字节将要传输的时间time_total：这次请求花费的全部时间&lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;  &lt;a href="https://jueee.github.io/#&amp;#31034;&amp;#20363;" title="&amp;#31034;&amp;#20363;"&gt;&lt;/a&gt;示例&lt;/h3&gt; &lt;p&gt;例子：&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;$ curl -w &amp;quot;@curl.txt&amp;quot; -o /dev/null -s -L  &amp;apos;http://www.baidu.com&amp;apos;time_namelookup:  0.004       time_connect:  0.015    time_appconnect:  0.000      time_redirect:  0.000   time_pretransfer:  0.015 time_starttransfer:  0.027                    ----------         time_total:  0.027&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;可以看到这次请求各个步骤的时间都打印出来了，每个数字的单位都是秒（seconds），这样可以分析哪一步比较耗时，方便定位问题。这个命令各个参数的意义：&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;-w：从文件中读取要打印信息的格式-o /dev/null：把响应的内容丢弃，因为我们这里并不关心它，只关心请求的耗时情况-s：不要打印进度条从这个输出，我们可以算出各个步骤的时间：DNS 查询：4msTCP 连接时间：pretransfter(15) - namelookup(4) = 11ms服务器处理时间：starttransfter(27) - pretransfer(15) = 12ms内容传输时间：total(27) - starttransfer(27) = 0ms&lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;  &lt;a href="https://jueee.github.io/#w-&amp;#21442;&amp;#25968;&amp;#35814;&amp;#35299;" title="-w &amp;#21442;&amp;#25968;&amp;#35814;&amp;#35299;"&gt;&lt;/a&gt;-w 参数详解&lt;/h3&gt; &lt;p&gt;以下是 - w 参数对应的一些变量以及对应的解释：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;strong&gt;url_effective&lt;/strong&gt; 最终获取的 url 地址，尤其是当你指定给 curl 的地址存在 301 跳转，且通过 - L 继续追踪的情形。&lt;/li&gt;  &lt;li&gt;   &lt;strong&gt;http_code&lt;/strong&gt; http 状态码，如 200 成功，301 转向，404 未找到，500 服务器错误等。(The numerical response code that was found in the last retrieved HTTP (S) or FTP (s) transfer. In 7.18.2 the alias response_code was added to show the same info.)&lt;/li&gt;  &lt;li&gt;   &lt;strong&gt;http_connect&lt;/strong&gt; The numerical code that was found in the last response (from a proxy) to a curl CONNECT request. (Added in 7.12.4)&lt;/li&gt;  &lt;li&gt;   &lt;strong&gt;time_total&lt;/strong&gt; 总时间，按秒计。精确到小数点后三位。 （The total time, in seconds, that the full operation lasted. The time will be displayed with millisecond resolution.）&lt;/li&gt;  &lt;li&gt;   &lt;strong&gt;time_namelookup&lt;/strong&gt; DNS 解析时间，从请求开始到 DNS 解析完毕所用时间。(The time, in seconds, it took from the start until the name resolving was completed.)&lt;/li&gt;  &lt;li&gt;   &lt;strong&gt;time_connect&lt;/strong&gt; 连接时间，从开始到建立 TCP 连接完成所用时间，包括前边 DNS 解析时间，如果需要单纯的得到连接时间，用这个 time_connect 时间减去前边 time_namelookup 时间。以下同理，不再赘述。(The time, in seconds, it took from the start until the TCP connect to the remote host (or proxy) was completed.)&lt;/li&gt;  &lt;li&gt;   &lt;strong&gt;time_appconnect&lt;/strong&gt; 连接建立完成时间，如 SSL/SSH 等建立连接或者完成三次握手时间。(The time, in seconds, it took from the start until the SSL/SSH/etc connect/handshake to the remote host was completed. (Added in 7.19.0))&lt;/li&gt;  &lt;li&gt;   &lt;strong&gt;time_pretransfer&lt;/strong&gt; 从开始到准备传输的时间。(The time, in seconds, it took from the start until the file transfer was just about to begin. This includes all pre-transfer commands and negotiations that are specific to the particular protocol (s) involved.)&lt;/li&gt;  &lt;li&gt;   &lt;strong&gt;time_redirect&lt;/strong&gt; 重定向时间，包括到最后一次传输前的几次重定向的 DNS 解析，连接，预传输，传输时间。(The time, in seconds, it took for all redirection steps include name lookup, connect, pretransfer and transfer before the final transaction was started. time_redirect shows the complete execution time for multiple redirections. (Added in 7.12.3))&lt;/li&gt;  &lt;li&gt;   &lt;strong&gt;time_starttransfer&lt;/strong&gt; 开始传输时间。在发出请求之后，Web 服务器返回数据的第一个字节所用的时间 (The time, in seconds, it took from the start until the first byte was just about to be transferred. This includes time_pretransfer and also the time the server needed to calculate the result.)&lt;/li&gt;  &lt;li&gt;   &lt;strong&gt;size_download&lt;/strong&gt; 下载大小。(The total amount of bytes that were downloaded.)&lt;/li&gt;  &lt;li&gt;   &lt;strong&gt;size_upload&lt;/strong&gt; 上传大小。(The total amount of bytes that were uploaded.)   &lt;br /&gt;size_header 下载的 header 的大小 (The total amount of bytes of the downloaded headers.)&lt;/li&gt;  &lt;li&gt;   &lt;strong&gt;size_request&lt;/strong&gt; 请求的大小。(The total amount of bytes that were sent in the HTTP request.)&lt;/li&gt;  &lt;li&gt;   &lt;strong&gt;speed_download&lt;/strong&gt; 下载速度，单位 - 字节每秒。(The average download speed that curl measured for the complete download. Bytes per second.)&lt;/li&gt;  &lt;li&gt;   &lt;strong&gt;speed_upload&lt;/strong&gt; 上传速度，单位 - 字节每秒。(The average upload speed that curl measured for the complete upload. Bytes per second.)&lt;/li&gt;  &lt;li&gt;   &lt;strong&gt;content_type&lt;/strong&gt; 就是 content-Type，不用多说了，这是一个访问我博客首页返回的结果示例 (text/html; charset=UTF-8)；(The Content-Type of the requested document, if there was any.)&lt;/li&gt;  &lt;li&gt;   &lt;strong&gt;num_connects&lt;/strong&gt; 最近的的一次传输中创建的连接数目。Number of new connects made in the recent transfer. (Added in 7.12.3)&lt;/li&gt;  &lt;li&gt;   &lt;strong&gt;num_redirects&lt;/strong&gt; 在请求中跳转的次数。Number of redirects that were followed in the request. (Added in 7.12.3)   &lt;br /&gt;redirect_url When a HTTP request was made without -L to follow redirects, this variable will show the actual URL a redirect would take you to. (Added in 7.18.2)&lt;/li&gt;  &lt;li&gt;   &lt;strong&gt;ftp_entry_path&lt;/strong&gt; 当连接到远程的 ftp 服务器时的初始路径。The initial path libcurl ended up in when logging on to the remote FTP server. (Added in 7.15.4)&lt;/li&gt;  &lt;li&gt;   &lt;strong&gt;ssl_verify_result&lt;/strong&gt; ssl 认证结果，返回 0 表示认证成功。(The result of the SSL peer certificate verification that was requested. 0 means the verification was successful. (Added in 7.19.0))&lt;/li&gt;&lt;/ul&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>Blog Blog</category>
      <guid isPermaLink="true">https://itindex.net/detail/62317-curl-%E5%91%BD%E4%BB%A4-%E5%88%86%E6%9E%90</guid>
      <pubDate>Thu, 23 Jun 2022 08:00:00 CST</pubDate>
    </item>
    <item>
      <title>hive insert模式分析</title>
      <link>https://itindex.net/detail/62185-hive-insert-%E6%A8%A1%E5%BC%8F</link>
      <description>&lt;p&gt;hive写入数据有2种模式，一种是insert into，一种是insert overwrite&lt;/p&gt;



 &lt;p&gt;参考资料：https://cwiki.apache.org/confluence/display/Hive/LanguageManual+DML#LanguageManualDML-InsertingdataintoHiveTablesfromqueries&lt;/p&gt;



 &lt;p&gt;insert into 写入数据&lt;/p&gt;



 &lt;ol&gt;  &lt;li&gt;insert 写入数据可以指定字段写入数据，参考资料：As of Hive    &lt;a href="https://issues.apache.org/jira/browse/HIVE-9481"&gt;1.2.0&lt;/a&gt; each INSERT INTO T can take a column list like INSERT INTO T (z, x, c1).  See Description of    &lt;a href="https://issues.apache.org/jira/browse/HIVE-9481"&gt;HIVE-9481&lt;/a&gt; for examples.&lt;/li&gt;  &lt;li&gt;insert 写入数据的劣势，追溯历史数据需要手工清除源数据，清除数据的方法可以删除hdfs文件，或者清除分区(需要转换成内部表ALTER TABLE {schema_name}.{table_name} SET TBLPROPERTIES (&amp;apos;EXTERNAL&amp;apos;=&amp;apos;false&amp;apos;);)&lt;/li&gt;&lt;/ol&gt;



 &lt;p&gt;insert overwrite 写入数据&lt;/p&gt;



 &lt;ol&gt;  &lt;li&gt;   &lt;li&gt;insert overwrite 写入数据时，当数据不连续会存在分险，比如耗材数据，并不是每天都有耗材成本，当使用日期+耗材分区时，第一天1.10号-&amp;gt;写入1.8号和1.9号耗材数据，第二天1.11号-&amp;gt;写入1.7和1.9号数据【如果恰好上游财务调账，将1.8号数据调整为1.7号】此时数据将异常，1.7号数据，1.8号数据重复&lt;/li&gt;&lt;/li&gt;



  &lt;p&gt;两种数据写入方法的适应场景&lt;/p&gt;



  &lt;ol&gt;   &lt;li&gt;insert into 写入数据模式比较适应与会出现数据不连续的分区模型表，写入数据前先清除分区，这样方便刷新表结构信息，同时支持alter table调整表结构，对历史数据非常友好，如果数据同步需要展示会出现较长的展示空白&lt;/li&gt;   &lt;li&gt;insert overwrite 写入数据比较适应连续的分区展示表，或者非分区表，数据几乎无缝变化，如果用于模型层，变更表结构需要尽量走重建表的模式。&lt;/li&gt;&lt;/ol&gt;



  &lt;p&gt;&lt;/p&gt;



  &lt;p&gt;这里编写了一个sql重写的方式让insert into模式转换insert overwrite的效果&lt;/p&gt;



  &lt;p&gt;简单描述就是先获取insert表的表结构，然后排序成默认的字段顺序，不足补null就ok了&lt;/p&gt;



  &lt;pre&gt;例如：tab表有col1，col2，col3，col4字段，insert模式是
insert into tab (col3,col2,col4)
select col3,col2,col4
from tab
应该转换成：
insert overwrite table tab
select null col1,col2,col3,col4
from tab&lt;/pre&gt;



  &lt;p&gt;具体代码如下：&lt;/p&gt;



  &lt;pre&gt;    def spark_rewrite_sql(self,sql_str,if_overwrite=&amp;apos;overwrite&amp;apos;):
        &amp;apos;&amp;apos;&amp;apos;
        spark insert 需要将字段写满才可以执行
        传入一个SQL , 返回写满的SQL insert语句 , 将SQL转换成 insert overwrite table 的模式
        &amp;apos;&amp;apos;&amp;apos;
        try:
            #方法1的实现
            #去掉注释
            sql_str = re.sub(r&amp;apos;[\s]*--.*&amp;apos;,&amp;apos;&amp;apos;,sql_str)
            #r1 表示获取表名，分区名，insert字段名，select字段名
            r1 = re.search(r&amp;apos;insert\s+into\s+(?:table\s+)?(.*)\s+partition\s+\((.*)\)\s+\((.*)\)\s+select\s+(.*?)\s+from[\s]&amp;apos;,sql_str,re.S|re.I)
            table_name    = r1.group(1)
            partition_col = r1.group(2)
            insert_col    = r1.group(3)
            select_col    = r1.group(4) + &amp;apos;\n&amp;apos;
            # 将没有写的字段添加到insert_col的后面，同时将第一个select后面添加空字段
            col_list = re.sub(r&amp;apos;\s&amp;apos;,&amp;apos;&amp;apos;,insert_col).split(&amp;apos;,&amp;apos;)
            hive_tab_dict = self.run_hive_get_desc_tab_dict(desc_tab_name=self.table_name,get_tab_len=&amp;apos;0&amp;apos;,use_db=self.schema_name)
            for col in hive_tab_dict.keys():
                if col not in col_list:
                    insert_col += &amp;apos;,&amp;apos;+col
                    select_col += &amp;apos;,null&amp;apos;
            #将select_col转换成有序的insert列
            act_insert_col = re.sub(r&amp;apos;\s&amp;apos;,&amp;apos;&amp;apos;,insert_col).split(&amp;apos;,&amp;apos;)
            act_select_col = self.comma_split(select_col)
            change_select_col = &amp;apos;&amp;apos;
            for col in hive_tab_dict.keys():
                hive_col = re.sub(r&amp;apos;\s&amp;apos;,&amp;apos;&amp;apos;,col)
                change_select_col += act_select_col[act_insert_col.index(hive_col)]+&amp;apos;,&amp;apos;
            change_select_col = change_select_col[:-1]
            #查询重写
            sql_str = sql_str.replace(r1.group(0)
                                    ,&amp;apos;&amp;apos;&amp;apos;insert {if_overwrite} table {table_name} partition ({partition_col})
select {change_select_col} 
from &amp;apos;&amp;apos;&amp;apos;.format(table_name=table_name,partition_col=partition_col,change_select_col=change_select_col,if_overwrite=if_overwrite))
        except Exception as e:
            print(&amp;apos;没有查询重写insert overwrite&amp;apos;)
            print(r1)
        return sql_str

    def run_hive(self,use_db=&amp;apos;app&amp;apos;,sql_str=&amp;apos;&amp;apos;,hive_set=True):
        &amp;apos;&amp;apos;&amp;apos;
        #shell模式执行执行hive脚本
        #use_db 库名app
        #sql_str 执行的sql
        &amp;apos;&amp;apos;&amp;apos;
        if hive_set:
            hive_set = self.hive_set
        else:
            hive_set = &amp;apos;&amp;apos;
        hive_parameter = &amp;apos;&amp;apos;&amp;apos;hive -e &amp;quot;{hive_set}use {use_db};{sql_str}&amp;quot;
        &amp;apos;&amp;apos;&amp;apos;.format(sql_str=sql_str,use_db=use_db,hive_set=self.re_replace(hive_set,r&amp;apos;[\s]*--.*&amp;apos;,&amp;apos;&amp;apos;).replace(&amp;apos;\n&amp;apos;,&amp;apos;&amp;apos;))
        cmd_list_values = self.popen(hive_parameter)
        return cmd_list_values

    def run_hive_get_desc_tab_dict(self, desc_tab_name ,use_db=&amp;apos;app&amp;apos;,get_tab_len=&amp;apos;1&amp;apos;):
        &amp;apos;&amp;apos;&amp;apos;
        获取hive表结构  get_tab_len
        0.  返回格式：字段名，类型，注释
            {&amp;apos;send_tm&amp;apos;: [&amp;apos;send_tm&amp;apos;, &amp;apos;string&amp;apos;, &amp;apos;发货日期&amp;apos;],&amp;apos;operation_type&amp;apos;: [&amp;apos;operation_type&amp;apos;, &amp;apos;string&amp;apos;, &amp;apos;仓配类型&amp;apos;]}
        &amp;apos;&amp;apos;&amp;apos;
        hive_tab_dict = {}
        cmd_list_values = self.run_hive(use_db,&amp;apos;desc {use_db}.{desc_tab_name}&amp;apos;.format(use_db=use_db,desc_tab_name=desc_tab_name))
        for line in cmd_list_values:
            col = line.split(&amp;apos;\t&amp;apos;)
            column_name = col[0].replace(&amp;apos;\n&amp;apos;,&amp;apos;&amp;apos;).strip()
            column_type = col[1].replace(&amp;apos;\n&amp;apos;,&amp;apos;&amp;apos;).strip()
            comment_name = col[2].replace(&amp;apos;\n&amp;apos;,&amp;apos;&amp;apos;).replace(&amp;apos;,&amp;apos;,&amp;apos;，&amp;apos;).strip()
            if column_name not in [&amp;apos;&amp;apos;] and column_name[0] != &amp;apos;#&amp;apos;:
                hive_tab_dict[column_name]=[column_name,column_type,comment_name]
        return hive_tab_dict

    def comma_split(self,input_str):
        &amp;apos;&amp;apos;&amp;apos;
        # 依次递归出括号内的逗号修改成替代的值，然后完成数据切分,这里应该有更好的方式，这里用分割符简单处理的
        &amp;apos;&amp;apos;&amp;apos;
        out_put_str = input_str
        loop_num = max([input_str.count(&amp;apos;,&amp;apos;),input_str.count(&amp;apos;(&amp;apos;),input_str.count(&amp;apos;)&amp;apos;)])
        for _ in range(loop_num):
            r1 = re.search(r&amp;apos;\([^()]*\)&amp;apos;,out_put_str,re.S|re.I)
            if r1 is not None:
                r1 = r1.group()
                r1_change = r1.replace(&amp;apos;,&amp;apos;,&amp;apos;@#_#@#&amp;apos;).replace(&amp;apos;(&amp;apos;,&amp;apos;_@__##&amp;apos;).replace(&amp;apos;)&amp;apos;,&amp;apos;##__@_&amp;apos;)
                out_put_str = out_put_str.replace(r1,r1_change)
        out_put_list = []
        for line in out_put_str.split(&amp;apos;,&amp;apos;):
            out_put_list.append(line.replace(&amp;apos;@#_#@#&amp;apos;,&amp;apos;,&amp;apos;).replace(&amp;apos;_@__##&amp;apos;,&amp;apos;(&amp;apos;).replace(&amp;apos;##__@_&amp;apos;,&amp;apos;)&amp;apos;))
        return out_put_list&lt;/pre&gt;



  &lt;p&gt;&lt;/p&gt;



  &lt;p&gt;&lt;/p&gt;



  &lt;p&gt;&lt;/p&gt;



  &lt;p&gt;&lt;/p&gt;



  &lt;p&gt;&lt;/p&gt;



  &lt;p&gt;&lt;/p&gt;
&lt;/ol&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>hive python</category>
      <guid isPermaLink="true">https://itindex.net/detail/62185-hive-insert-%E6%A8%A1%E5%BC%8F</guid>
      <pubDate>Sat, 02 Apr 2022 18:00:12 CST</pubDate>
    </item>
    <item>
      <title>一次大量 JVM Native 内存泄露的排查分析（64M 问题）</title>
      <link>https://itindex.net/detail/62176-jvm-native-%E5%86%85%E5%AD%98%E6%B3%84%E9%9C%B2</link>
      <description>&lt;p&gt;我们有一个线上的项目，刚启动完就占用了使用 top 命令查看 RES 占用了超过 1.5G，这明显不合理，于是进行了一些分析找到了根本的原因，下面是完整的分析过程，希望对你有所帮助。&lt;/p&gt;
 &lt;p&gt;会涉及到下面这些内容&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;Linux 经典的 64M 内存问题&lt;/li&gt;
  &lt;li&gt;堆内存分析、Native 内存分析的基本套路&lt;/li&gt;
  &lt;li&gt;tcmalloc、jemalloc 在 native 内存分析中的使用&lt;/li&gt;
  &lt;li&gt;finalize 原理&lt;/li&gt;
  &lt;li&gt;hibernate 毁人不倦&lt;/li&gt;
&lt;/ul&gt;
 &lt;h2&gt;现象&lt;/h2&gt;
 &lt;p&gt;程序启动的参数&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;ENV=FAT java
-Xms1g -Xmx1g 
-XX:MetaspaceSize=120m 
-XX:MaxMetaspaceSize=400m 
-XX:+UseConcMarkSweepGC  
-jar 
EasiCareBroadCastRPC.jar
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;启动后内存占用如下，惊人的 1.5G，Java 是内存大户，但是你也别这么玩啊。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8053824a903741f09782b0b2efb742ca~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;下面是愉快的分析过程。&lt;/p&gt;
 &lt;h2&gt;柿子先挑软的捏&lt;/h2&gt;
 &lt;p&gt;先通过 jcmd 或者 jmap 查看堆内存是否占用比较高，如果是这个问题，那很快就可以解决了。&lt;/p&gt;
 &lt;p&gt;可以看到堆内存占用 216937K + 284294K = 489.48M，Metaspace 内存虽然不属于 Java 堆，这里也显示了出来占用 80M+，这两部分加起来，远没有到 1.5G。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/65cc6ac42fe846a5826a500733c8d50a~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;那剩下的内存去了哪里？到这里，已经可以知道可能是堆以外的部分占用了内存，接下来就是开始使用   &lt;code&gt;NativeMemoryTracking&lt;/code&gt; 来进行下一步分析。&lt;/p&gt;
 &lt;h2&gt;NativeMemoryTracking 使用&lt;/h2&gt;
 &lt;p&gt;如果要跟踪其它部分的内存占用，需要通过   &lt;code&gt;-XX:NativeMemoryTracking&lt;/code&gt; 来开启这个特性&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;java -XX:NativeMemoryTracking=[off | summary | detail]
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;加入这个启动参数，重新启动进程，随后使用 jcmd 来打印相关的信息。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;$ jcmd `jps | grep -v Jps | awk &amp;apos;{print $1}&amp;apos;` VM.native_memory detail

Total: reserved=2656938KB, committed=1405158KB
-                 Java Heap (reserved=1048576KB, committed=1048576KB)
                            (mmap: reserved=1048576KB, committed=1048576KB)

-                     Class (reserved=1130053KB, committed=90693KB)
                            (classes #15920)
                            (malloc=1605KB #13168)
                            (mmap: reserved=1128448KB, committed=89088KB)

-                    Thread (reserved=109353KB, committed=109353KB)
                            (thread #107)
                            (stack: reserved=108884KB, committed=108884KB)
                            (malloc=345KB #546)
                            (arena=124KB #208)

-                      Code (reserved=257151KB, committed=44731KB)
                            (malloc=7551KB #9960)
                            (mmap: reserved=249600KB, committed=37180KB)

-                        GC (reserved=26209KB, committed=26209KB)
                            (malloc=22789KB #306)
                            (mmap: reserved=3420KB, committed=3420KB)

-                  Compiler (reserved=226KB, committed=226KB)
                            (malloc=95KB #679)
                            (arena=131KB #7)

-                  Internal (reserved=15063KB, committed=15063KB)
                            (malloc=15031KB #20359)
                            (mmap: reserved=32KB, committed=32KB)

-                    Symbol (reserved=22139KB, committed=22139KB)
                            (malloc=18423KB #196776)
                            (arena=3716KB #1)
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;很失望，这里面显示的所有的部分，看起来都很正常，没有特别大异常占用的情况，到这里我们基本上可以知道是不受 JVM 管控的 native 内存出了问题，那要怎么分析呢？&lt;/p&gt;
 &lt;h2&gt;pmap 初步查看&lt;/h2&gt;
 &lt;p&gt;通过 pmap 我们可以查看进程的内存分布，可以看到有大量的 64M 内存区块区域，这部分是 linux 内存 ptmalloc 的典型现象，这个问题在之前的一篇「一次 Java 进程 OOM 的排查分析（glibc 篇）」已经介绍过了，详见：https://juejin.cn/post/6854573220733911048&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/90a10f614a004a4f93fdcf6cc1a4c912~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;那这 64M 的内存区域块，是不是在上面 NMT 统计的内存区域里呢？&lt;/p&gt;
 &lt;p&gt;NMT 工具的地址输出 detail 模式会把每个区域的起始结束地址输出出来，如下所示。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/51a41c82ef5a4c5a8ad66732659c9b0f~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;写一个简单的代码（自己正则搞一下就行了）就可以将 pmap、nmt 两部分整合起来，看看真正的堆、栈、GC 等内存占用分布在内存地址空间的哪一个部分。&lt;/p&gt;
 &lt;p&gt;可以看到大量 64M 部分的内存区域不属于任何 NMT 管辖的部分。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/cf756676bbcc46d0b7b1cbe2e4a671d5~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h2&gt;tcmalloc、jemalloc 来救场&lt;/h2&gt;
 &lt;p&gt;我们可以通过 tcmalloc 或者 jemalloc 可以做 native 内存分配的追踪，它们的原理都是 hook 系统 malloc、free 等内存申请释放函数的实现，增加 profile 的逻辑。&lt;/p&gt;
 &lt;p&gt;下面以 tcmalloc 为例。&lt;/p&gt;
 &lt;p&gt;从源码编译 tcmalloc（http://github.com/gperftools/gperftools），然后通过   &lt;code&gt;LD_PRELOAD&lt;/code&gt; 来 hook 内存分配释放的函数。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;HEAPPROFILE=./heap.log 
HEAP_PROFILE_ALLOCATION_INTERVAL=104857600 
LD_PRELOAD=./libtcmalloc_and_profiler.so
java -jar xxx ...
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;启动过程中就会看到生成了很多内存 dump 的分析文件，接下来使用 pprof 将 heap 文件转为可读性比较好的 pdf 文件。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;pprof --pdf /path/to/java heap.log.xx.heap &amp;gt; test.pdf
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;内存申请的链路如下图所示。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c6dd9a89e11f4cc5b0c7d7facbc6913f~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;可以看到绝大部分的内存申请都耗在了   &lt;code&gt;Java_java_util_zip_Inflater_inflateBytes&lt;/code&gt;，jar 包本质就是一个 zip 包，
在读取 jar 包文件过程中大量使用了 jni 中的 cpp 代码来处理，这里面大量申请释放了内存。&lt;/p&gt;
 &lt;h2&gt;不用改代码的解决方式&lt;/h2&gt;
 &lt;p&gt;既然是因为读取 jar 包这个 zip 文件导致的内存疯长，那我不用   &lt;code&gt;java -jar&lt;/code&gt;，直接把原 jar 包解压，然后用   &lt;code&gt;java -cp . AppMain&lt;/code&gt; 来启动是不是可以避免这个问题呢？因为我们项目因为历史原因是使用 shade 的方式，里面已经没有任何 jar 包了，全是 class 文件。奇迹出现了，不用 jar 包启动，RES 占用只有 400M，神奇不神奇！&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e6ad779011a046f1b8085344d642fed3~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;到这里，我们更加确定是 jar 包启动导致的问题，那为什么 jar 包启动会导致问题呢？&lt;/p&gt;
 &lt;h2&gt;探究根本原因&lt;/h2&gt;
 &lt;p&gt;通过 tcmalloc 可以看到大量申请释放内存的地方在 java.util.zip.Inflater 类，调用它的 end 方法会释放 native 的内存。&lt;/p&gt;
 &lt;p&gt;我本以为是 end 方法没有调用导致的，这种的确是有可能的，java.util.zip.InflaterInputStream 类的 close 方法在一些场景下是不会调用 Inflater.end 方法，如下所示。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/dda87f385ed84122879c6e6dd782ebeb~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;但是 Inflater 类有实现 finalize 方法，在 Inflater 对象不可达以后，JVM 会帮忙调用 Inflater 类的 finalize 方法，&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;public class Inflater {

    public void end() {
        synchronized (zsRef) {
            long addr = zsRef.address();
            zsRef.clear();
            if (addr != 0) {
                end(addr);
                buf = null;
            }
        }
    }

    /**
     * Closes the decompressor when garbage is collected.
     */
    protected void finalize() {
        end();
    }

    private native static void initIDs();
    // ...
    private native static void end(long addr);
}
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;有两种可能性&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;Inflater 因为被其它对象引用，没能释放，导致 finalize 方法不能被调用，内存自然没法释放&lt;/li&gt;
  &lt;li&gt;Inflater 的 finalize 方法被调用，但是被 libc 的  ptmalloc 缓存，没能真正释放回操作系统&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;第二种可能性，我之前在另外一篇文章「一次 Java 进程 OOM 的排查分析（glibc 篇）」已经介绍过了，详见：https://juejin.cn/post/6854573220733911048 ，经验证，不是这个问题。&lt;/p&gt;
 &lt;p&gt;我们来看第一个可能性，通过 dump 堆内存来查看。果然，有 8 个 Inflater 对象还存活没能被 GC，除了被 JVM 内部的 java.lang.ref.Finalizer 引用，还有其它的引用，导致 Inflater 在 GC 时无法被回收。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/79633c89eb3c4a5c9fca5a298a286b7c~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;那这些内存是不是真的跟 64M 的内存区块有关呢？空口无凭，我们来确认一把。Inflater 类有一个 zsRef 字段，其实它就是一个指针地址，我们看看未释放的 Inflater 的 zsRef 地址是不是位于我们所说的 64M 内存区块里。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;public class Inflater {
    private final ZStreamRef zsRef;
}

class ZStreamRef {
    private volatile long address;
    ZStreamRef (long address) {
        this.address = address;
    }

    long address() {
        return address;
    }

    void clear() {
        address = 0;
    }
}
    
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;通过一个 ZStreamRef 找到 address 等于 140686448095872，转为 16 进制为 0x7ff41dc37280，这个地址位于的虚拟地址空间在这里：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0b331eab2f274f2b9138b11936578424~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;正是在我们所说的 64M 内存区块中。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b7bb258de7e24ef6a6801b551c3ba760~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;如果你还不信，我们可以 dump 这块内存，我这里写了一个脚本&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;cat /proc/$1/maps | grep -Fv &amp;quot;.so&amp;quot; | grep &amp;quot; 0 &amp;quot; | awk &amp;apos;{print $1}&amp;apos; | grep $2 | ( IFS=&amp;quot;-&amp;quot;
while read a b; do
dd if=/proc/$1/mem bs=$( getconf PAGESIZE ) iflag=skip_bytes,count_bytes \
skip=$(( 0x$a )) count=$(( 0x$b - 0x$a )) of=&amp;quot;$1_mem_$a.bin&amp;quot;
done )
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;通过传入进程号和你想 dump 的内存起始地址，就可以把这块内存 dump 出来。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;./dump.sh `pidof java` 7ff41c000000
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;执行上面的脚本，传入两个参数，一个是进程 id，一个地址，会生成一个 64M 的内存 dump 文件，通过 strings 查看。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;strings 6095_mem_7ff41c000000.bin
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;输出结果如下，满屏的都是类文件相关的信息。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1fb9bd58f272463d9a399b899ed06fca~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;到这里已经应该无需再证明什么了，剩下的就是分析的事了。&lt;/p&gt;
 &lt;p&gt;那到底是被谁引用的呢？展开引用链，看到出现了一堆 ClassLoader。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/91bf915aadb44609a4efa4afcbfd7dc5~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h2&gt;一个意外的发现（与本问题关系不大，顺手解决一下）&lt;/h2&gt;
 &lt;p&gt;这里出现了一个很奇怪的 nashorn 相关的 ClassLoader，众所周不知，nashorn 是处理 JavaScript 相关的逻辑的，那为毛这个项目会用到 nashorn 呢？经过仔细搜索，项目代码并没有使用。&lt;/p&gt;
 &lt;p&gt;那是哪个坑货中间件引入的呢？debug 一下马上就找到了原因，原来是臭名昭著的 log4j2，用了这么多年 log4j，头一回知道，原来 log4j2 是支持 javaScript、Groovy 等脚本语言的。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;
&amp;lt;Configuration status=&amp;quot;debug&amp;quot; name=&amp;quot;RoutingTest&amp;quot;&amp;gt;
  &amp;lt;Scripts&amp;gt;
    &amp;lt;Script name=&amp;quot;selector&amp;quot; language=&amp;quot;javascript&amp;quot;&amp;gt;&amp;lt;![CDATA[
            var result;
            if (logEvent.getLoggerName().equals(&amp;quot;JavascriptNoLocation&amp;quot;)) {
                result = &amp;quot;NoLocation&amp;quot;;
            } else if (logEvent.getMarker() != null &amp;amp;&amp;amp; logEvent.getMarker().isInstanceOf(&amp;quot;FLOW&amp;quot;)) {
                result = &amp;quot;Flow&amp;quot;;
            }
            result;
            ]]&amp;gt;&amp;lt;/Script&amp;gt;
    &amp;lt;ScriptFile name=&amp;quot;groovy.filter&amp;quot; path=&amp;quot;scripts/filter.groovy&amp;quot;/&amp;gt;
  &amp;lt;/Scripts&amp;gt;
&amp;lt;/Configuration&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;我们项目中并没有用到类似的特性（因为不知道），只能说真是无语，你好好的当一个工具人日志库不好吗，搞这么多花里胡哨的东西，肤浅！&lt;/p&gt;
 &lt;p&gt;代码在这里&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e3cdb1daf7ac4218ae1707a37cbc412f~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;这个问题我粗略看了一下，截止到官方最新版还没有一个开关可以关掉 ScriptEngine，不行就自己上，自己拉取项目中 log4j 对应版本的代码，做了修改，重新打包运行，&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3be1cf3e4cec4f7db88ce6a9099c3dc4~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;重新运行后 nashorn 部分的 ClassLoader 确实没有了，不过这里只是一个小插曲，native 内存占用的问题并没有解决。&lt;/p&gt;
 &lt;h2&gt;凶手浮出水面&lt;/h2&gt;
 &lt;p&gt;接下来我们就要找哪些代码在疯狂调用   &lt;code&gt;java.util.zip.Inflater.inflateBytes&lt;/code&gt; 方法&lt;/p&gt;
 &lt;p&gt;使用 watch 每秒 jstack 一下线程，马上就看到了 hibernate 在疯狂的调用。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/74d404c710c44d859ae16e953ef60a7e~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;hibernate 是我们历史老代码遗留下来的，一直没有移除掉，看来还是踩坑了。&lt;/p&gt;
 &lt;p&gt;找到这个函数对应代码   &lt;code&gt;org.hibernate.jpa.boot.archive.internal.JarFileBasedArchiveDescriptor#visitArchive#146&lt;/code&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/95a0c8d240584a2c8e9d41b637ef1690~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;垃圾代码，  &lt;code&gt;jarFile.getInputStream( zipEntry )&lt;/code&gt; 生成了一个新的流但没有做关闭处理。&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;其实我也不知道，为啥 hibernate 要把我 jar 包中所有的类都扫描解析一遍，完全有毛病。&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;我们来把这段代码扒出来，写一个最简 demo。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;public class JarFileTest {
    public static void main(String[] args) throws IOException {
        new JarFileTest().process();
        System.in.read();
    }

    public static byte[] getBytesFromInputStream(InputStream inputStream) throws IOException {
        // 省略 read 的逻辑
        return result;
    }

    public void process() throws IOException {
        JarFile jarFile = null;
        try {
            jarFile = new JarFile(&amp;quot;/data/dev/broadcast/EasiCareBroadCastRPC.jar&amp;quot;);
            final Enumeration&amp;lt;? extends ZipEntry&amp;gt; zipEntries = jarFile.entries();
            while (zipEntries.hasMoreElements()) {
                final ZipEntry zipEntry = zipEntries.nextElement();
                if (zipEntry.isDirectory()) {
                    continue;
                }

                byte[] bytes = getBytesFromInputStream(jarFile.getInputStream(zipEntry));

                System.out.println(&amp;quot;processing: &amp;quot; + zipEntry.getName() + &amp;quot;\t&amp;quot; + bytes.length);
            }
        } finally {
            try {
                if (jarFile != null) jarFile.close();
            } catch (Exception e) {
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;运行上面的代码。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;javac JarFileTest.java
java -Xms1g -Xmx1g -XX:MetaspaceSize=120m -XX:MaxMetaspaceSize=400m -XX:+UseConcMarkSweepGC  -cp . JarFileTest
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;内存 RES 占用立马飙升到了 1.2G 以上，且无论如何 GC 都无法回收，但堆内存几乎等于 0。&lt;/p&gt;
 &lt;p&gt;RES 内存占用如下所示。
  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/01ec02d28b6747e4a891f1566692787b~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;堆内存占用如下所示，经过 GC 以后新生代占用为 0，老年代占用为 275K&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2b08880afcea410eaadd53edc9996d91~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;全被 64M 内存占满。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2b42de97c5c24e22b5a8de27834ffa98~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;通过修改代码，将流关闭&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;while (zipEntries.hasMoreElements()) {
    final ZipEntry zipEntry = zipEntries.nextElement();
    if (zipEntry.isDirectory()) {
        continue;
    }

    InputStream is = jarFile.getInputStream(zipEntry);
    byte[] bytes = getBytesFromInputStream(is);

    System.out.println(&amp;quot;processing: &amp;quot; + zipEntry.getName() + &amp;quot;\t&amp;quot; + bytes.length);
    try {
        is.close();
    } catch (Exception e) {

    }
}
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;再次测试，问题解决了，native 内存占用几乎消失了，接下来就是解决项目中的问题。一种是彻底移除 hibernate，将它替换为我们现在在用的 mybatis，这个我不会。我打算来改一下 hibernate 的源码。&lt;/p&gt;
 &lt;h2&gt;尝试修改&lt;/h2&gt;
 &lt;p&gt;修改这段代码（ps这里是不成熟的改动，close 都应该放 finally，多个 close 需要分别捕获异常，但是为了简单，这里先简化），加入 close 的逻辑。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/91bb90d08d33437590409959909b426f~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;重新编译 hibernate，install 到本地，然后重新打包运行。此时 RES 占用从 1.5G 左右降到了 700 多 M。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/31b4e990b6fc4abc931eb6e730dd31ae~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;而且比较可喜的是，64M 区块的 native 内存占用非常非常小，这里 700M 内存有 448M 是 dirty 的 heap 区，这部分只是 JVM 预留的。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8ec2adcdbb1e4ab79d307d8f4069a878~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;到这里，我们基本上已经解决了这个问题。后面我去看了一下 hibernate 的源码，在新版本里面，已经解决了这个问题，但是我不打算升级了，干掉了事。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d925eb434e334a519488735bf309714d~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;详见：&lt;/p&gt;
 &lt;p&gt;https://github.com/hibernate/hibernate-orm/blob/72e0d593b997681125a0f12fe4cb6ee7100fe120/hibernate-core/src/main/java/org/hibernate/boot/archive/internal/JarFileBasedArchiveDescriptor.java#L116&lt;/p&gt;
 &lt;h1&gt;后记&lt;/h1&gt;
 &lt;p&gt;因为不是本文的重点，文章涉及的一些工具的使用，我没有展开来聊，大家感兴趣可以自己搞定。&lt;/p&gt;
 &lt;p&gt;其实 native 内存泄露没有我们想象的那么复杂，可以通过 NMT、pmap、tcmalloc 逐步逐步进行分析，只要能复现，都不叫 bug。&lt;/p&gt;
 &lt;p&gt;最后珍爱生命，远离 hibernate。&lt;/p&gt;
 &lt;p&gt;有任何 JVM 相关的问题，欢迎加微或者公众号联系我，一起交流。&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/62176-jvm-native-%E5%86%85%E5%AD%98%E6%B3%84%E9%9C%B2</guid>
      <pubDate>Thu, 24 Mar 2022 11:20:06 CST</pubDate>
    </item>
    <item>
      <title>数据分析终极一问：指标波动多大才算是异常？</title>
      <link>https://itindex.net/detail/62152-%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90-%E7%BB%88%E6%9E%81-%E6%8C%87%E6%A0%87</link>
      <description>&lt;div&gt;  &lt;hr&gt;&lt;/hr&gt;  &lt;p&gt;   &lt;strong&gt;导读：&lt;/strong&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;先举个例子，体温37.4度vs体温36.5度，只有2.5%的波动，可如果有人在测温点被发现体温37.4度，估计马上就被拉走做核酸。为啥？因为人们不是怕2.5%的波动，而是怕新冠！&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;所以：指标波动不可怕，指标波动代表的业务场景才可怕！脱离业务场景谈指标波动就是耍流氓。&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;在各种业务指标中，数据往往不是静止不变的，尤其是当一些核心的指标发生了变化、波动时，就需要判断这样的波动是否属于异常的情况。那么波动了多大才能算是异常？本文将结合一些实际业务场景，来说明数据波动的异常判别方法。&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;指标数据波动，是各种业务场景下都会遇见的情况，如每日GMV、每日订单量等，都是在不断变化的。大多数情况下，变化是“正常”的波动，但有一些波动，源于突然发生的外部原因或其他未被预期的因素，导致其表现出不同于正常模式的异常状态。若能准确地识别异常波动，从而做出波动预警，并及时应对，就能一定程度上保证所关心的业务场景系统的整体稳定性。 &lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;strong&gt;1&lt;/strong&gt;  &lt;br /&gt;  &lt;p&gt;   &lt;strong&gt;波动类型&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;数据波动绕不开时间特性。业务中最常遇到的就是今天的指标是什么样子？过去几天是什么趋势？未来一段时间会怎么样的变化？数据+时间构成了波动的两个基本属性。&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;根据时间的不同特征，常见的波动类型有：&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;一次性波动：&lt;/strong&gt;偶发的、突然性的波动。一般是由于短期、突发的事件而影响的指标的波动，比如说某头部主播在某次直播里上了严选的商品、某明星的同款商品在严选有库存等，就会造成订单量临时性的超出预期的上涨。&lt;/p&gt;  &lt;p&gt;这样的波动影响时间短，往往几天的时间便会恢复正常波动。举个单量的例子，在大促期间都是单量的爆发期，大促即为一次“偶发事件”，此时单量的波动即为一次性波动。其具有如下的特征图：&lt;/p&gt;  &lt;p&gt;   &lt;img&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;周期性波动：&lt;/strong&gt;这种波动和时间节点强相关，且经常以周或者季、年为循环节点。如羽绒服秋冬季节卖的比较好，到了春天销量就下降，夏天几乎没有销量，且每年几乎都是这样。&lt;/p&gt;  &lt;p&gt;   &lt;img&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;持续性波动：&lt;/strong&gt;从某一时间开始，指标一直呈现上涨/下降趋势。如从今年4月开始，浴室香氛品类的销售量一直呈现上涨趋势，这就属于持续性波动。而持续性波动背后的原因往往是更深刻的，如订单结构的变化、环境因素的影响，从而出现了这种持续性趋势。&lt;/p&gt;  &lt;p&gt;   &lt;img&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;strong&gt;2&lt;/strong&gt;  &lt;br /&gt;  &lt;p&gt;   &lt;strong&gt;异常识别&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;那么什么样的波动可以看作是“异常”呢？异常识别也可以认为是异常检测。这里主要从绝对值预警、相对值预警两个方面来说明。&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;2.1 绝对值预警&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;绝对值预警，即是通过设定一定的阈值，当指标低于/高于阈值的时候，就认为此时指标波动为异常，并进行预警。&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;举个例子，严选作为一个品牌，毛利是其核心的一个指标。对毛利可设置绝对值预警：当毛利为负时，就认为此时是异常的情况，需要探究其发生的原因，并解释这种异常的波动。通过对毛利的绝对值预警，严选及时发现了部分用户利用咖啡机进行薅羊毛、从而导致咖啡机毛利为负的行为，并完善了规则减少了严选的损失。&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;不仅可以设置低于某一个定值，也可以当指标高于某一定值的时候进行预警，比如在供应链中，某个大仓的分仓比高于40%，就会导致仓库负荷过重从而影响生产。&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;绝对值预警往往是一次性的波动，这样的异常判定比较简单，只需要设定对应的阈值即可。而阈值的设定可以根据具体的业务的不同和规则而变化。&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;目前在有数BI中可以直接设置绝对值预警：&lt;/p&gt;  &lt;p&gt;   &lt;img&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;2.2 相对值预警&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;然而实际业务中，绝对的阈值只能提供一个“底线”。除了一些非常确定性的业务场景外，在其他情况下，过高的“底线”就会导致误报，过低的“底线”可能会漏掉很多需要预警的情况。于是作为绝对值预警的补充，相对值预警可以根据历史数据及波动情况，来判断当前的波动是否为异常。&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;（1）同比环比&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;同比环比是业务场景中比较常用的一种异常检测方式，利用当前时间周期与前一个时间周期（同比）和过去的同一个时间周期（同比）比较，超过一定的阈值即认为该点是异常的。实际中常用周/日环比、年同比来进行比较。&lt;/p&gt;  &lt;p&gt;   &lt;img&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;如上图，(1)的数据为所要判断的值。当(1)的数值为百分比时，如来源于主站订单的比例，则同比环比一般为：&lt;/p&gt;  &lt;p&gt;    环比：(1)-(2) pt&lt;/p&gt;  &lt;p&gt;    同比：(1)-(3) pt&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;而当(1)的数值为非百分比时，如来源于主站的订单数量时：&lt;/p&gt;  &lt;p&gt;    环比：((1)-(2))/(2) %&lt;/p&gt;  &lt;p&gt;    同比：((1)-(3))/(3) %&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;根据值得正负来判断是上涨还是下降。通过与上周/昨天和去年同期的数据表现进行对比，计算波动值，再将波动值和阈值进行对比，从而得到当前时刻数值是否在正常的波动中（阈值的设定方法在后面介绍）。&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;如在上述的周期性波动的例子中，在11月环比波动都会较大，这时设置同比波动预警会比设置环比波动预警更为合理。于是在波动判别中，需要注意业务实际背景。&lt;/p&gt;  &lt;p&gt;   &lt;img&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;（2）周期平滑&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;同比/环比仅使用1~2个时间点的数据，容易受到数据本身质量的影响：当历史同期或上个周期的数据本身就是“异常”的时候，用“异常”的数据来判断是否“异常”就不太合适。&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;一个很自然的想法就是将所参考的时间点拓展，利用多个时间点的周期数据进行平滑，得到当前时刻指标的对比值。如：&lt;/p&gt;  &lt;p&gt;   &lt;img&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;则比较值：&lt;/p&gt;  &lt;p&gt;   &lt;img&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;   &lt;img&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;其中   &lt;img&gt;&lt;/img&gt;为平滑系数，当   &lt;img&gt;&lt;/img&gt;都为相同的值的时候，此时即为平均值。也可越靠近所研究时间点，赋予更高的平滑系数。所选的时间点可以根据业务需求自行定义。&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;利用比较值b和所研究的值(1)对比，超过一定的阈值即可认为是“异常”，其波动需要关注。&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;（3）假设检验（3σ原则）&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;前面提到比较值需要和所研究的值进行对比，通过阈值来判断波动是否异常。阈值的定义方法和预警方法类似，分为绝对值阈值和自适应（相对值）阈值。&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;    &lt;p&gt;绝对值阈值：根据历史正常情况下的数据波动情况，计算比较值和所研究的值之间的差异情况，从而定义一个上/下限值，即为阈值。&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;自适应阈值：根据数据波动情况而变化的阈值，其理论基础为假设检验和大数定律，来判断是否为异常。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;不妨假设当前时间点的指标数据为b，历史用于对比的指标数据为： &lt;/p&gt;  &lt;p&gt;   &lt;img&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;其中：   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;   &lt;img&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;分别表示对比数据的平均水平和波动情况。则根据大数定律和假设检验，当&lt;/p&gt;  &lt;p&gt;   &lt;img&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;即可认为当前时间点的指标数据为异常波动。其中z为置信水平所对应的值，如当z=1.96时，置信水平为95%，即可认为在100次的波动下，有95次是在正常范围内波动的（置信水平及其对应的值可参考标准正态分布表）。当z=2.58，置信水平为99%，即为著名的   &lt;strong&gt;“3σ原则”。&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;3.3 其他方法&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;除了以上所介绍的一些常用的、便捷的方法外，也可以通过时间序列、算法模型等来判断异常值。异常值判别是比较常见的研究场景，但由于实操的复杂性，这里仅做一个介绍。&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;（1）时间序列&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;业务上的数据往往具有时间属性，如单量随时间的变化、GMV随时间的变化等。那么在时间序列中，通过异常检测的方法，也可以对当前波动是否异常做出判断。常用的方法有：&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;    &lt;p&gt;平均法：移动平均、加权移动平均、指数加权移动平均、累加移动平均等。和上述的“周期平均”的方法类似，可自定义窗口大小和加权系数。&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;ARIMA模型：自回归移动平均模型（ARIMA）是时间序列中一个基础模型，利用过去的几个数据点来生成下一个数据点的预测，并在过程中加入一些随机变量。使用该模型需要确定ARIMA所需的参数，即需要对数据点进行拟合。利用拟合后的方程确定下一个时间点的数据的区间，从而判断当前波动是否为异常。&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;此外还有ESD、S-ESD、S-H-ESD、STL分解等算法，来检测异常点。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;（2）算法模型&lt;/strong&gt;&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;    &lt;p&gt;基于分类方法：根据历史已有的数据，将其分为正常、异常的两类，之后产生的新的观测值，就可以使用分类的方法去判断新的观测值是否为异常。如使用距离判别的K-means算法、SVM算法等。&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;神经网络方法：可以对具有时间特性进行建模的LSTM算法、用卷积神经网络来做时间序列分类的Time  Le-Net，以及各种的监督式模型，都是能够对异常数据进行识别的算法。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;strong&gt;3&lt;/strong&gt;  &lt;br /&gt;  &lt;p&gt;   &lt;strong&gt;总结&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;在实际应用中，还需要结合业务背景来进行方法的选择。一般来说，判断异常的主要方法有：&lt;/p&gt;  &lt;p&gt;   &lt;img&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;br /&gt;作者简介  &lt;br /&gt;  &lt;p&gt;九数，网易严选数据分析师，负责严选供应链仓配域的分析工作。&lt;/p&gt;  &lt;br /&gt;  &lt;br /&gt;  &lt;strong&gt;获取最新动态&lt;/strong&gt;  &lt;p&gt;   &lt;strong&gt;最新的推文无法在第一时间看到？&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;以前的推文还需要复杂漫长的翻阅？&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;进入“网易有数”公众号介绍页，点击右上角&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;“设为星标”&lt;/strong&gt;！&lt;/p&gt;  &lt;p&gt;置顶公众号，从此消息不迷路&lt;/p&gt;  &lt;p&gt;   &lt;img&gt;&lt;/img&gt;&lt;/p&gt;  &lt;br /&gt;设为星标，最新推文不迷路  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;img&gt;&lt;/img&gt;分享，点赞，在看，安排一下？&lt;/div&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>dev</category>
      <guid isPermaLink="true">https://itindex.net/detail/62152-%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90-%E7%BB%88%E6%9E%81-%E6%8C%87%E6%A0%87</guid>
      <pubDate>Mon, 07 Mar 2022 00:00:00 CST</pubDate>
    </item>
    <item>
      <title>流量威胁分析系统与Tenable生产实践</title>
      <link>https://itindex.net/detail/61874-%E6%B5%81%E9%87%8F-%E5%88%86%E6%9E%90-%E7%B3%BB%E7%BB%9F</link>
      <description>&lt;p&gt;0×01 概要&lt;/p&gt;

 &lt;p&gt;信息安全体系构建中流量监听是一种常见的防护手段，从流量抓取到日志落地，从日志分析到威胁报警，相应产品基于流量分析模式，从最上层的处理逻辑来看是相近的，使用Suricata还是Snort处理流程类似接近，最粗放的方式去理解他们，这些系统都属于“大型字符串处理过滤系统”。&lt;/p&gt;

 &lt;p&gt;实际生产中可能会使多家厂商的产品配型开源产品使用，或自主开发，无论采用那种方案，我们都可抽象出一种共通的顶层流量数据处理模式，典型的流量过滤与日志分析处理流程。我们有基于Tenable产品的实践经验， 这种经验展开也可以横向扩展到其它的同类产品上、开源产品上、自主研发产品上。如果说闭源商业软件系统和开源社区软件系统的区别，除了产品本身，产品的生态和后期支持也是不同，我们在开源软件使用中得到了同行和社区的很多帮助。我们在Tenable使用实践过程中，得到专家级的指导， 这些对我们理解产品并应用于实践中起到了莫大的帮助，为什么要选择Tenable举例呢？因为此系统设计理念独到，功能经典，因些我们选择用这个系统进行举例说明，介绍流量分析系统中存在的共性内容。&lt;/p&gt;

 &lt;p&gt;0×02 基本处理模式&lt;/p&gt;

 &lt;p&gt;  &lt;img alt="" src="https://image.3001.net/images/20190308/1552029709_5c82180d128c7.png"&gt;&lt;/img&gt;&lt;/p&gt;

 &lt;p&gt;从我们的角度出发，流量分析型安全威胁系统有5个基本任务要完成：&lt;/p&gt;

 &lt;p&gt;1．流量获取：如何抓取网络流量内容是首要基础功能，只有抓取流量数据，得到traffic交通流量，才能去通过应用过滤规则取得威胁事件数据。（上图值得注意的是我们使用了流量分发设备。）&lt;/p&gt;

 &lt;p&gt;2． 流量过滤：在流量获取阶段，对数据进行过滤成本开销比较大，通过对滤过滤条的件设定，规则解析，规则下发执行，可在在流量读取阶段对数据进行过滤，异常检测。问题是过滤规则越多系统效率越低，甚至产生时间瓶颈或是系统延迟。&lt;/p&gt;

 &lt;p&gt;3． 数据落地：流量获取阶段，分析数据耗时耗性能，如果我们将数据缓存下来，将日志数据落地，准实时的分析数据，一方面可以减轻系统负担， 另外数据落地后可以应用数据分析模型和关联聚合数据，做基于算法的更精准的异常捕获分析，所以要把数据保存一定周期时间，在数据的生命周期内保持数据不挥发。&lt;/p&gt;

 &lt;p&gt;4．数据分析：单纯保存数据不是目的，还要在日志数据中挖掘出威胁事件的特征数据，如果流量监听算先知性预警处理，将风险提前预告防患于未然之中。如果过滤是针对网络流量实时规则配对的，数据分析就是对流量日志落地后再次深入的信息挖掘。&lt;/p&gt;

 &lt;p&gt;5． 威胁报警：流量数据作为系统的输入并不直接产生收益，当系统产生有效的威胁报警，就能体现出系统威胁感知价值，将威胁情况第一时间通知责任相关人，防患于未然。如果主机威胁定位于威胁动作执行阶段算“后知后觉”，那在流量监听规则过滤阶段报警，或是在准数据分析阶段报警，能不能算是“先知先觉”是个问题。&lt;/p&gt;

 &lt;p&gt;0×03 “流”经典设计处理模式&lt;/p&gt;

 &lt;p&gt;我们用“流”的工作模式来解释介绍流量威胁情报系统的工作过程，用Stream A和Stream B两个“流”概括系统处理的五个组成部分：&lt;/p&gt;

 &lt;p&gt;  &lt;img alt="" src="https://image.3001.net/images/20190308/1552029724_5c82181c45868.png"&gt;&lt;/img&gt;&lt;/p&gt;

 &lt;p&gt;关于流量威胁系统的核心指标：漏报率和误报率。&lt;/p&gt;

 &lt;p&gt;漏报率：在Stream A阶段进行对流量的过滤 ，可以是与历史累计聚合数据无关碰撞的，如果不考虑黑白名单机制，不进行瞬时关联统计，过滤是基于异常规则碰撞的，而规则是否完备，决定了威胁的漏报率高低，如果不是自学习， 系统的异常规则都是人为来定义的。&lt;/p&gt;

 &lt;p&gt;  &lt;img alt="" src="https://image.3001.net/images/20190308/1552029739_5c82182be5136.png"&gt;&lt;/img&gt;&lt;/p&gt;

 &lt;p&gt;误报率：在Stream B阶段，基于Traffic流量规则过滤的威胁预判结论，在一定有限的数据集合范围的，与规则定义的多少成正比。假定规则完备理想状态下没有漏报，但会存在误报，降低误报有以下几种常见手段：&lt;/p&gt;

 &lt;p&gt;1：多个威胁系统报警横向比较确认。&lt;/p&gt;

 &lt;p&gt;2：基于聚合数学统计模型进行辅助判断。&lt;/p&gt;

 &lt;p&gt;3：特征标签打分，积分累计判断，高于多少分才报。&lt;/p&gt;

 &lt;p&gt;4：对威胁payload进行第三方库再确认。&lt;/p&gt;

 &lt;p&gt;一定还会有其它大类别的方法来解决，对已有报警进行误报确认的更好方法，规则策略是一个动态变化的过程。&lt;/p&gt;

 &lt;p&gt;有些朋友的系统突然被入侵后，我们可以在一个环境下部署多个安全系统，A系统和B系统都有基本的威胁异常分析规则，但有些规则是对外不可见的，有的支持自定义插件，有的不支持自定义规则。系统间的规则有重合或不重合，我们横向比较系统的报警结果，系统间重合的报警应该是被重示的。数据情况往往是多样性，关联性的。蜜罐、防火墙、流量分析，同时作用于一个网络环境，对他们来说， 输入的数据是一样。他们会从不同的角度发现威胁，如果报警高度重合，就越可能是高威胁。&lt;/p&gt;

 &lt;p&gt;0×04 流量监听与蜜罐监听的异同&lt;/p&gt;

 &lt;p&gt;  &lt;img alt="" src="https://image.3001.net/images/20190308/1552029754_5c82183a7c66b.png"&gt;&lt;/img&gt;&lt;/p&gt;

 &lt;p&gt;蜜罐可以对网络异常行为流量进行一种守株待兔式的监听，蜜罐部署利用交换机的端口聚合trunk模式，把不同的VLAN网段中的一个IP聚合到一台机器的入口端口中。&lt;/p&gt;

 &lt;p&gt;  &lt;img alt="" src="https://image.3001.net/images/20190308/1552029771_5c82184b533ca.png"&gt;&lt;/img&gt;&lt;/p&gt;

 &lt;p&gt;设定多IP和标记，接收不同VLAN中的流量访问，ARP级与负载均衡DR模式类似的技术。而监听流量是要把一定总合的流量推送到不同的监控机，关系形式多对一，多对多。 每台监控机上，使用相同的过滤规则，规则与异常行为一对一对应，一个规则对应一个威胁事件ID。在Tenable系统中分为SC、NC，名字是代号，任务内容都是监听与存储分析。&lt;/p&gt;

 &lt;p&gt;0×05  实践与课题解决&lt;/p&gt;

 &lt;p&gt;如上所说，威胁分析系统本质是一个“大型字符串处理系统“。从流量变字符串文件，威胁匹配就是字符串按”规则条件“的查找特征子串的过程。Tenable是一款优秀的产品，特色于具备独特设计理念和专家支持。Tenable规则系统是面象用户友好的，用户可以自定义插件，增加检测规则，扩展系统的功能。同时其它系统一样，也要面临多机部署配置分发的场景，针对这点提出解决方案是，基于Ansible进行自动化分配下发。如果你使用Suricata面临面样境遇的话，也可以考虑这种方案尝试。&lt;/p&gt;

 &lt;p&gt;对于流量监听，辅助分析工具必不可少，在调试设备，监听实际流量，可以是使用像Wireshark、TcpDump、WinDump这些工具。Wireshark是经典工具。可以方便的在网上找到类似于《Wireshark网络分析从入门到实践》这种命名规则的书， 找一本好的出版社出的书，对流量日常分析工作有事半功倍的效用。&lt;/p&gt;

 &lt;p&gt;关于日志数据落地之前已经介绍很多，紧的值得关注的是Graylog正式发行了第三版:Graylog3。很多威胁分析系统都使用了ES作数据持久化，Graylog应用ES存储安全数据相对比较方便，作为一个基于ES的管理信息系统有自身的特色。&lt;/p&gt;

 &lt;p&gt;  &lt;img alt="" src="https://image.3001.net/images/20190308/1552029787_5c82185b764e1.png"&gt;&lt;/img&gt;&lt;/p&gt;

 &lt;p&gt;对于最近可能被入侵生产系统影响的朋友，提供一个开源安全系统列表，可以用些对自己的系统进行先期加固：Graylog、Snort、Suricata、Wazuh、Moloch、Ansible、OpenVAS等，希望可解燃眉之急。&lt;/p&gt;

 &lt;p&gt;0×06总结&lt;/p&gt;

 &lt;p&gt;同网络环境下，如何部署多种威胁检测系统，抽象出核心处理模型， 归纳系统关键处理任务，是因近期非安全领域朋友遭遇网络入侵事件。突如其来的状况如何快速选型安全系统部署加固生产环境，快速保护安全资产势在必行，本文灵感也源于Tenable生产实践，个人观点，仅供参考！&lt;/p&gt;

&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/61874-%E6%B5%81%E9%87%8F-%E5%88%86%E6%9E%90-%E7%B3%BB%E7%BB%9F</guid>
      <pubDate>Sat, 09 Mar 2019 08:00:00 CST</pubDate>
    </item>
    <item>
      <title>竞品分析7000字方法论——7个视角，50+维度，附赠竞品思维下的撩妹技巧</title>
      <link>https://itindex.net/detail/61836-%E5%88%86%E6%9E%90-%E6%96%B9%E6%B3%95%E8%AE%BA-%E8%A7%86%E8%A7%92</link>
      <description>&lt;p&gt;知彼知己，百战不殆。&lt;/p&gt; &lt;p&gt;——《孙子兵法》&lt;/p&gt; &lt;p&gt;  &lt;img alt="94ea7a6bfdf5a10e926cd9f06911b266-picture" src="http://img.pmcaff.com/94ea7a6bfdf5a10e926cd9f06911b266-picture"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;产品经理的岗位职责之一是在市场中建立和维护产品的竞争优势，竞品分析是产品经理的一项常规工作，分析质量决定着决策质量，影响着对业务取长补短的效果。&lt;/p&gt; &lt;p&gt;但在竞品分析时，常遇到以下问题：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;没有养成日常习惯，分析时无从下手   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;企图以此寻找需求或印证自己的观点   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;由领导发起，找模板套公式，交作业   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;分析维度杂乱浅显，结论无参考价值&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;那么，竞品分析应该怎么做呢？&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;当我刚刚喜欢上一个姑娘时，却发现已经有人喜欢她了，而且张三已经表白，李四也有好感，怎么办呢？&lt;/p&gt; &lt;p&gt;大脑会在荷尔蒙的牵动下情不自禁做起了分析和策略。先从人类视角思考一下世界观、人生观、价值观、婚恋观，确定一下自己要谈一场什么样的恋爱。再从恋爱角度来了解姑娘，哪里人呀？啥家庭呀？啥衣食住行呀？啥爱好？啥性格呀？符合梦中情人的样子么？这么一通观察下来，那多久约一次会、吃什么饭、唱什么歌、送什么礼也就心里有数了。张三李四都干啥了就得有差异化。进一步了解之后又要返回到婚恋观，问自己真诚否？关系合适否？只有对自己真诚，对姑娘真诚才能情投意合。既然双方感觉都不错，继续更深一步互动，少了一些遮掩，偶有矛盾，知道双方喜欢啥，雷区是啥。如此，言谈举止便要有所分类，分类即尊重，相容即相爱。咔，到这里，没有张三李四的事了。良久，两人有了结婚的想法，要双方家长见面聊聊呀，聊聊两个大家庭的想法，聊聊小家庭的打算，真心地切合实际地聊。靠谱！咔，姑娘到手。结婚的过程又一次巩固了小家庭的使命、愿景、价值观，那就过日子吧。总之，在与姑娘相处的每个阶段、每个事上都要真诚以待。&lt;/p&gt; &lt;p&gt;竞品分析更是如此了。要与用户谈恋爱。&lt;/p&gt; &lt;p&gt;当然，更准确地来说是竞争分析，产品只是价值的载体。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;做一款产品时，却发现已经有直接竞争者、替代竞争者、潜在竞争者、预算竞争者了，而且还有一些可参考的友商。就从7个角度观察分析：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;strong&gt;从上帝视角看市场&lt;/strong&gt;，了解产业和行业的运作模式、所处环境和市场数据，便于筛选目标市场；   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;   &lt;strong&gt;从市场角度看用户&lt;/strong&gt;，了解目标市场中全部用户的分层及画像，便于筛选目标用户；   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;   &lt;strong&gt;用研发角度看产品&lt;/strong&gt;，了解开发生命周期中的差异；   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;   &lt;strong&gt;从市场角度看产品&lt;/strong&gt;，了解产品生命周期中的差异；   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;   &lt;strong&gt;从产品角度看用户&lt;/strong&gt;，了解产品中存量用户的细分，便于精细化运营；   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;   &lt;strong&gt;从用户角度看产品&lt;/strong&gt;，了解用户不同的体验和评价；   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;   &lt;strong&gt;从产品背后看企业&lt;/strong&gt;，了解竞争者的资源配置情况；   &lt;br /&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;在频率上可以日常分析、定期分析、突发性地专项分析；在分析粒度上可以大到对行业趋势的分析，中到对竞品方案的分析，小到对竞品功能点的分析；在呈阅对象上，可以是BOSS、PM、设计师、研发人员等。&lt;/p&gt; &lt;p&gt;总之，PM做竞争分析时要在产品所处的每个阶段、工作的每个细节上都真诚以待，这样才能得到客观有用的结论。这是对竞品分析的工作思想。&lt;/p&gt; &lt;p&gt;开始分析吧！&lt;/p&gt; &lt;hr&gt;&lt;/hr&gt; &lt;ul&gt;  &lt;li&gt;分析目的有哪些？   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;分析对象有哪些？   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;分析角度有哪些？   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;竞品信息的来源有哪些？   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;竞品信息的处理流程有哪些？   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;分析方法有哪些？   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;结论如何呈现？&lt;/li&gt;&lt;/ul&gt; &lt;hr&gt;&lt;/hr&gt; &lt;p&gt; &lt;/p&gt; &lt;p&gt;  &lt;strong&gt;一、7个分析目的&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;用户价值、商业价值、用户价值与商业价值间平衡，这三类的预期和现状之间有所差距，想找到解决方案减少差距呗，那还是会遇到7种不同的情况：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;知道差距，没方案，寻找方案&lt;/li&gt;  &lt;li&gt;知道差距，有方案，不知道方案对不对&lt;/li&gt;  &lt;li&gt;知道差距，有方案，不知道方案如何实施&lt;/li&gt;  &lt;li&gt;知道差距，有方案，但方案行不通，咋办&lt;/li&gt;  &lt;li&gt;知道差距，多种方案，不知道选择哪个最好&lt;/li&gt;  &lt;li&gt;知道现状不是想要的，说不清预期，没方案&lt;/li&gt;  &lt;li&gt;知道预期就是想要的，不清楚现状，没方案&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;往细来说，大概会在开拓市场时，寻找市场切入点、寻求差异化、规划业务、策划运营方案等；在挖掘用户需求时，分析用户和分析需求等；在观测市场时，做行业预测、竞争预测等；在......时，进行......&lt;/p&gt; &lt;hr&gt;&lt;/hr&gt; &lt;p&gt;  &lt;strong&gt;   &lt;br /&gt;&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;二、5个分析对象&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;以“目标用户类型”、“核心需求”、“产品或服务的特征”和“用户购买预算”为依据大致可以划分为5类竞品。&lt;/p&gt; &lt;p&gt;  &lt;img alt="cb49699d7d51e2c316d263d0f2be4c23-picture" src="http://img.pmcaff.com/cb49699d7d51e2c316d263d0f2be4c23-picture"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;★代表相似，✘代表不同，- 代表可有可无&lt;/p&gt; &lt;p&gt; &lt;/p&gt; &lt;hr&gt;&lt;/hr&gt; &lt;p&gt;  &lt;strong&gt;   &lt;br /&gt;&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;三、7个分析角度&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;3.1、从上帝视角看市场&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;从整体上看，市场的构成如下图要素构成。在这个视角，我们主要做产业分析、行业分析和市场分析。&lt;/p&gt; &lt;p&gt;  &lt;img alt="888a49be9e13e2087b7402d3d0237a82-picture" src="http://img.pmcaff.com/888a49be9e13e2087b7402d3d0237a82-picture"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;   &lt;br /&gt;&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;3.1.1、产业分析&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;产业是指由利益相互联系的、具有不同分工的、由各个相关行业所组成的业态总称。一个产业可以跨越（包含）几个行业。&lt;/p&gt; &lt;p&gt;产业结构、产业分类、技术结构、技术分类一般都有国际或国家的分类标准，百度可见；其中产业结构中各角色的互动关系如下图所示：&lt;/p&gt; &lt;p&gt;  &lt;img alt="9e24dcde9667be447758a14cc1341096-picture" src="http://img.pmcaff.com/9e24dcde9667be447758a14cc1341096-picture"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;图中的互动关系也可称之为“产业链”中的“供需链”。&lt;/p&gt; &lt;p&gt;产业布局是指产业在一国或一地区范围内的空间分布和组合的经济现象。在静态上看是指形成产业的各部门、各要素、各链环在空间上的分布态势和地域上的组合。在动态上表现为各种资源、各生产要素甚至各产业和各企业为选择最佳区位而形成的在空间地域上的流动、转移或重新组合的配置与再配置过程。可用“产业地图”来表示：&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;示例一：按资源、生成要素的流动展示&lt;/p&gt; &lt;p&gt;  &lt;img alt="5a4d165c0379b8f45b7818344497fd62-picture" src="http://img.pmcaff.com/5a4d165c0379b8f45b7818344497fd62-picture"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;图中的流动关系也可称之为“产业链”中的“价值链”，而完整的价值链如下图：&lt;/p&gt; &lt;p&gt;  &lt;img alt="f9a9ca38fb94ce0c3f65fae1d856d93d-picture" src="http://img.pmcaff.com/f9a9ca38fb94ce0c3f65fae1d856d93d-picture"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;示例二：按在各产业和各企业的流动展示&lt;/p&gt; &lt;p&gt;  &lt;img alt="41caeae761397bfa5e65a0c141d4b2c9-picture" src="http://img.pmcaff.com/41caeae761397bfa5e65a0c141d4b2c9-picture"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;图中的企业关系也可称之为“产业链”中的“企业链”。&lt;/p&gt; &lt;p&gt; &lt;/p&gt; &lt;p&gt;示例三：按地理空间展示&lt;/p&gt; &lt;p&gt;  &lt;img alt="feda66fa9e27f244d726ef591aa86e9a-picture" src="http://img.pmcaff.com/feda66fa9e27f244d726ef591aa86e9a-picture"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;图中的地理关系也可称之为“产业链”中的“空间链”。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;汇总一下，对产业的了解可从时间、空间和程度三个方面来研究，如图所示：&lt;/p&gt; &lt;p&gt;  &lt;img alt="32d453fc15d72f0827de40379e99c357-picture" src="http://img.pmcaff.com/32d453fc15d72f0827de40379e99c357-picture"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;   &lt;br /&gt;&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;3.1.2、行业分析&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;行业指一组提供同类相互密切替代商品或服务的公司。选定行业就一定能看到我们的竞争者有哪些。从供需关系上看，就是研究目标市场的供给侧的生产者有哪些？&lt;/p&gt; &lt;p&gt;  &lt;img alt="7ed5a3f8e28f0f1df14354186a8ac5bb-picture" src="http://img.pmcaff.com/7ed5a3f8e28f0f1df14354186a8ac5bb-picture"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;当然了，分类的话，还是分为竞争分析的5个对象。&lt;/p&gt; &lt;p&gt;  &lt;img alt="e5df41f1349efa0c40491e68854163aa-picture" src="http://img.pmcaff.com/e5df41f1349efa0c40491e68854163aa-picture"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;那么，对行业分析，有哪些维度呢？&lt;/p&gt; &lt;p&gt;（1）生命周期。即老生常谈的引入期、成长期、成熟期、衰退期。&lt;/p&gt; &lt;p&gt;  &lt;img alt="92348b73b1592a1753202695c5f18d07-picture" src="http://img.pmcaff.com/92348b73b1592a1753202695c5f18d07-picture"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;（2）竞争类型。分为完全竞争、垄断竞争、寡头竞争和完全垄断。&lt;/p&gt; &lt;p&gt;  &lt;img alt="c4e85a63519da77732d04b4aadd60706-picture" src="http://img.pmcaff.com/c4e85a63519da77732d04b4aadd60706-picture"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;（3）行业要素。分析一个行业的主要影响要素及要素密集度。包括五类：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;资本型，如房地产   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;技术型，如制造业   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;劳动型，如防治业   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;知识型，如创意设计   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;资源型，如煤炭、发电   &lt;br /&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;（4）经济周期。分为三类：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;增长型，如人工智能、云计算、物联网   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;周期型，如钢铁、煤炭、金融产品   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;防守型，如医药、旅游、家电   &lt;br /&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;（5）行业结构。常从三个维度分析：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;各产品的市场规模及结构变化   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;各地区的市场规模及结构变化   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;各消费群体的规模及结构变化   &lt;br /&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;  &lt;img alt="df1397e6fd7a16615d0aff351479ba6a-picture" src="http://img.pmcaff.com/df1397e6fd7a16615d0aff351479ba6a-picture"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;（6）主要数据。行业的发展分析时经常回顾历史、分析现状和预测未来。回顾、分析和预测哪些数据呢？主要有市场规模、毛利率、销售增长率和净资产收益率等。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;   &lt;br /&gt;&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;3.1.3、市场分析&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;市场泛指商品交换的领域。&lt;/p&gt; &lt;p&gt;（1）分析对象:&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;针对产业做分析&lt;/li&gt;  &lt;li&gt;针对行业做分析&lt;/li&gt;  &lt;li&gt;针对企业做分析&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;（2）分析范围：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;全局的   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;细分的   &lt;br /&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;（3）市场概览：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;过去近十年的数据：市场规模、GDP占比、复合增长率（GAGR）、线上化率（=线上市场规模/总市场规模）   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;现状分析：宏观环境因素分析（PEST）、对标其他国家和相近行业   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;判断未来趋势：数据拟合预测   &lt;br /&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;（4）市场集中程度：&lt;/p&gt; &lt;p&gt;CRn（concentration ratio），n一般用10。也就是在这个市场市占前10的公司，加起来的总和占整个市场的百分比，百分比越高，证明这个市场的集中度也就越高。&lt;/p&gt; &lt;p&gt;  &lt;img alt="b22819a84ce2ab3843bca0cf733a45af-picture" src="http://img.pmcaff.com/b22819a84ce2ab3843bca0cf733a45af-picture"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;（5）市场消费模式：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;价格型   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;理智型   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;冲动型   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;想象型   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;习惯型   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;随意型   &lt;br /&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;（6）上下游市场分析：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;上游：政策、上游原材料构成、原材料价格走势、主要供应企业的供应量；   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;下游：政策、下游消费市场构成、消费市场结构变化趋势、主要消费群体的消费量；   &lt;br /&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;（7）市场数据：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;规模   &lt;br /&gt;   &lt;ul&gt;    &lt;li&gt;市场整体规模     &lt;br /&gt;&lt;/li&gt;    &lt;li&gt;线上市场规模     &lt;br /&gt;&lt;/li&gt;    &lt;li&gt;我司市场规模（市场占有率）     &lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;  &lt;li&gt;数据   &lt;br /&gt;   &lt;ul&gt;    &lt;li&gt;量（用户数）：下载量、注册量、活跃用户量、付费用户量等     &lt;br /&gt;&lt;/li&gt;    &lt;li&gt;次（订单数）     &lt;br /&gt;&lt;/li&gt;    &lt;li&gt;额（交易额）     &lt;br /&gt;&lt;/li&gt;    &lt;li&gt;利（营收、毛利润、净利润）     &lt;br /&gt;&lt;/li&gt;    &lt;li&gt;率（转化率、毛利率、增长率、净资产收益率）     &lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;  &lt;li&gt;趋势   &lt;br /&gt;   &lt;ul&gt;    &lt;li&gt;增长     &lt;br /&gt;&lt;/li&gt;    &lt;li&gt;持平     &lt;br /&gt;&lt;/li&gt;    &lt;li&gt;下降     &lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;  &lt;strong&gt;~~&lt;/strong&gt;  &lt;strong&gt;~&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;   &lt;br /&gt;&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;3.2、从市场角度看用户&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;从供需上看，就是研究目标市场的需求侧的消费者有哪些，该角度一般是在做新产品前对所有的消费者进行研究。&lt;/p&gt; &lt;p&gt;  &lt;img alt="d2ad1b137b56b712e7f28f67f7982a03-picture" src="http://img.pmcaff.com/d2ad1b137b56b712e7f28f67f7982a03-picture"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;（1）分析对象：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;目标市场中的消费者&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;（2）分析目的：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;消费者分层，即市场细分，或是叫用户分层&lt;/li&gt;  &lt;li&gt;找准目标用户&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;（3）分析范围：更多是全局分析&lt;/p&gt; &lt;p&gt;（4）分析方法：定性调研：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;访谈、焦点小组、卡片分类、参与式设计&lt;/li&gt;  &lt;li&gt;定量调研：调查问卷&lt;/li&gt;  &lt;li&gt;场景调研：分为三类场景   &lt;ul&gt;    &lt;li&gt;用户场景：用户遇到问题、产生想法，发现解决方案的场景&lt;/li&gt;    &lt;li&gt;使用场景：用户使用产品时的场景&lt;/li&gt;    &lt;li&gt;营销场景：用户看到产品、选择产品、购买产品时的场景&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;（5）分析维度：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;口碑调查：   &lt;ul&gt;    &lt;li&gt;口碑结构&lt;/li&gt;    &lt;li&gt;口碑的地域性差异&lt;/li&gt;    &lt;li&gt;品牌满意度&lt;/li&gt;    &lt;li&gt;净推荐值&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;  &lt;li&gt;产品价格：客户希望为这个产品支付多少钱？竞品价格如何？&lt;/li&gt;  &lt;li&gt;购买动机，购买的影响因素及比重&lt;/li&gt;  &lt;li&gt;购买习惯，如购买渠道、购买时段等等&lt;/li&gt;  &lt;li&gt;产品感知及体验&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;（6）分析产出：&lt;/p&gt; &lt;p&gt;产出一：用户故事版，或是用户画像之User Persona&lt;/p&gt; &lt;p&gt;  &lt;img alt="28f75cce296051d4c60c2733b95cd665-picture" src="http://img.pmcaff.com/28f75cce296051d4c60c2733b95cd665-picture"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;产出二：价格分析图&lt;/p&gt; &lt;p&gt;  &lt;img alt="a4473e321d77a7261e93b246f2c143fb-picture" src="http://img.pmcaff.com/a4473e321d77a7261e93b246f2c143fb-picture"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;产出三：满意度和净推荐值&lt;/p&gt; &lt;p&gt;  &lt;img alt="44c0538a20b5f1ed857e9c801b16fb2a-picture" src="http://img.pmcaff.com/44c0538a20b5f1ed857e9c801b16fb2a-picture"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;产出四：购买因素及比重&lt;/p&gt; &lt;p&gt;  &lt;img alt="8a893f03e814da87f37eb3028ba0b37d-picture" src="http://img.pmcaff.com/8a893f03e814da87f37eb3028ba0b37d-picture"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;产出五：购买习惯&lt;/p&gt; &lt;p&gt;  &lt;img alt="1b058fe40a2c747d02dbfb4af3d82364-picture" src="http://img.pmcaff.com/1b058fe40a2c747d02dbfb4af3d82364-picture"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;~~&lt;/strong&gt;  &lt;strong&gt;~&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;   &lt;br /&gt;&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;3.3、用研发角度看产品&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;想要了解竞品是如何研发出来的，就需要关注和比较双方的开发什么周期的各个动作。要“比、学、赶、超”不断迭代，当然也要“你打你的，我打我的”，坚持自己得定位。&lt;/p&gt; &lt;p&gt;  &lt;img alt="c14484699d5f6dc89838487f9664fe0d-picture" src="http://img.pmcaff.com/c14484699d5f6dc89838487f9664fe0d-picture"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;（1）产品定位：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;我是一个什么样的产品   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;什么背景基因下产生   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;为哪些目标市场服务   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;服务的边界是什么   &lt;br /&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;（2）产品定义：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;定义用户：   &lt;br /&gt;   &lt;ul&gt;    &lt;li&gt;选择哪类用户作为目标用户     &lt;br /&gt;&lt;/li&gt;    &lt;li&gt;目标用户画像之User Persona特征     &lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;  &lt;li&gt;定义场景：   &lt;br /&gt;   &lt;ul&gt;    &lt;li&gt;用户场景：问题、想法、发现解决方案     &lt;br /&gt;&lt;/li&gt;    &lt;li&gt;使用场景：使用     &lt;br /&gt;&lt;/li&gt;    &lt;li&gt;营销场景：看到、选择、购买     &lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;  &lt;li&gt;定义价值   &lt;br /&gt;   &lt;ul&gt;    &lt;li&gt;用户价值     &lt;br /&gt;&lt;/li&gt;    &lt;li&gt;商业价值     &lt;br /&gt;&lt;/li&gt;    &lt;li&gt;用户价值与商业价值的平衡     &lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;  &lt;li&gt;全局思考：   &lt;br /&gt;   &lt;ul&gt;    &lt;li&gt;产品的持续性     &lt;br /&gt;&lt;/li&gt;    &lt;li&gt;产品的增长性     &lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;（3）战略规划：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;组织架构从上到下的战略传递   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;产品组合策略   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;产品路线图   &lt;br /&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;（4）产品设计：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;基于场景和需求拆分用户的任务   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;基于任务拆分为功能和交互、内容和信息架构   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;基于全局考虑业务闭环、产品结构的可拓展性   &lt;br /&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;（5）产品研发：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;界面设计（设备、交互、UI等）   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;技术研发   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;可用性测试及用户体验测试等   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;持续迭代   &lt;br /&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;（6）产品运营&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;上线前基于产品的运营计划   &lt;br /&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;（7）分析产出&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;用户画像之User Persona&lt;/li&gt;  &lt;li&gt;用户旅程图&lt;/li&gt;  &lt;li&gt;产品阶段战略&lt;/li&gt;  &lt;li&gt;产品路线图&lt;/li&gt;  &lt;li&gt;应用架构图&lt;/li&gt;  &lt;li&gt;产品功能框架图&lt;/li&gt;  &lt;li&gt;界面对比结论&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;  &lt;strong&gt;~~&lt;/strong&gt;  &lt;strong&gt;~&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;   &lt;br /&gt;&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;3.4、从市场角度看产品&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;在新产品推向市场后的销售营销、运营、根据需求演变的产品迭代，也要经过进入期、成长期、成熟期和衰退期是个阶段。  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;  &lt;img alt="408442df858527b4515a7985e8e24ac3-picture" src="http://img.pmcaff.com/408442df858527b4515a7985e8e24ac3-picture"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;在不同的产品生命周期阶段，企业经营行为的侧重点不同。  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;  &lt;img alt="eeabddab2d82f2c8f4212fd178d06be6-picture" src="http://img.pmcaff.com/eeabddab2d82f2c8f4212fd178d06be6-picture"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;（1）产品商业模式&lt;/p&gt; &lt;p&gt;  &lt;img alt="1a703f59ae3678985fe4bf1b194248c0-picture" src="http://img.pmcaff.com/1a703f59ae3678985fe4bf1b194248c0-picture"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;（2）产品/市场匹配（PMF）&lt;/p&gt; &lt;p&gt;PMF是Product Market Fit的简写，是指产品和市场达到最佳的契合点。象征着产品正好满足市场的需求，令客户满意，这是产品成功的第一步。&lt;/p&gt; &lt;p&gt;  &lt;img alt="322bc3070c4c8c1666693ccf3b5294f1-picture" src="http://img.pmcaff.com/322bc3070c4c8c1666693ccf3b5294f1-picture"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;  &lt;img alt="95806b2758fbe596ad73ede2f67acc0d-picture" src="http://img.pmcaff.com/95806b2758fbe596ad73ede2f67acc0d-picture"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;PMF的实现标准（临界点）的判断，各行各业因其特性均不同，网上有些判断方法仅供参考：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;财务状况增长率+利润率大于40%&lt;/li&gt;  &lt;li&gt;次日留存大于30%&lt;/li&gt;  &lt;li&gt;每周使用天数超过3天&lt;/li&gt;  &lt;li&gt;付费转化率大于5%&lt;/li&gt;  &lt;li&gt;LTV／CAC&amp;gt;3&lt;/li&gt;  &lt;li&gt;用户月流失低于5%&lt;/li&gt;  &lt;li&gt;用户获取成本的回本时间少于12个月&lt;/li&gt;  &lt;li&gt;不能再使用该产品会感觉非常失望的用户量占比大于40%（调研分四挡：非常失望、有点失望、没有失望和不适用）&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;（3）产品功能：  &lt;br /&gt;&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;主要功能   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;次要功能   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;附加功能   &lt;br /&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;（4）所用技术：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;技术类型   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;技术架构   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;技术水平   &lt;br /&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;  &lt;img alt="7e3f7cf3ae86ab2a75793dc09423bb6f-picture" src="http://img.pmcaff.com/7e3f7cf3ae86ab2a75793dc09423bb6f-picture"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;（5）运营推广：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;em&gt;    &lt;strong&gt;内容运营&lt;/strong&gt;&lt;/em&gt;   &lt;ul&gt;    &lt;li&gt;企业介绍&lt;/li&gt;    &lt;li&gt;企业新闻     &lt;ul&gt;      &lt;li&gt;融资信息&lt;/li&gt;      &lt;li&gt;客户签约信息&lt;/li&gt;      &lt;li&gt;战略合作信息&lt;/li&gt;      &lt;li&gt;产品动态信息&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;    &lt;li&gt;产品手册&lt;/li&gt;    &lt;li&gt;客户成功案例&lt;/li&gt;    &lt;li&gt;行业白皮书&lt;/li&gt;    &lt;li&gt;行业解决方案&lt;/li&gt;    &lt;li&gt;行业报告、干货资料     &lt;ul&gt;      &lt;li&gt;行业热点分析&lt;/li&gt;      &lt;li&gt;行业趋势解读&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;    &lt;li&gt;期刊、书籍等出版物&lt;/li&gt;    &lt;li&gt;电子书&lt;/li&gt;    &lt;li&gt;课程讲义     &lt;ul&gt;      &lt;li&gt;老板专栏/高管专栏/大咖专栏&lt;/li&gt;      &lt;li&gt;特定选题的课程&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;  &lt;li&gt;   &lt;strong&gt;    &lt;em&gt;活动运营&lt;/em&gt;&lt;/strong&gt;   &lt;ul&gt;    &lt;li&gt;线上     &lt;ul&gt;      &lt;li&gt;微课&lt;/li&gt;      &lt;li&gt;直播&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;    &lt;li&gt;线下     &lt;ul&gt;      &lt;li&gt;主办型活动&lt;/li&gt;      &lt;li&gt;联合型活动&lt;/li&gt;      &lt;li&gt;赞助型活动&lt;/li&gt;      &lt;li&gt;企业参访活动&lt;/li&gt;      &lt;li&gt;会销活动&lt;/li&gt;      &lt;li&gt;培训活动、沙龙活动&lt;/li&gt;      &lt;li&gt;游学活动&lt;/li&gt;      &lt;li&gt;线下公开课&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;  &lt;li&gt;   &lt;strong&gt;    &lt;em&gt;渠道运营&lt;/em&gt;&lt;/strong&gt;   &lt;ul&gt;    &lt;li&gt;官网媒体&lt;/li&gt;    &lt;li&gt;自媒体：公众号、头条、搜狐号等等&lt;/li&gt;    &lt;li&gt;全员营销&lt;/li&gt;    &lt;li&gt;联合推广渠道     &lt;ul&gt;      &lt;li&gt;自媒体大号联盟&lt;/li&gt;      &lt;li&gt;行业俱乐部及协会&lt;/li&gt;      &lt;li&gt;行业媒体、杂志体&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;    &lt;li&gt;贡献线索渠道     &lt;ul&gt;      &lt;li&gt;广告渠道、SEM&lt;/li&gt;      &lt;li&gt;垂直行业网站&lt;/li&gt;      &lt;li&gt;同客异业合作&lt;/li&gt;      &lt;li&gt;产业上下游企业合作&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;    &lt;li&gt;代理商渠道     &lt;ul&gt;      &lt;li&gt;渠道加盟商&lt;/li&gt;      &lt;li&gt;交易平台，如用友云市场&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;  &lt;li&gt;   &lt;strong&gt;    &lt;em&gt;社群运营&lt;/em&gt;&lt;/strong&gt;&lt;/li&gt;  &lt;li&gt;   &lt;strong&gt;    &lt;em&gt;运营体系及风格&lt;/em&gt;&lt;/strong&gt;&lt;/li&gt;  &lt;li&gt;   &lt;strong&gt;    &lt;em&gt;运营事件分析，关注和研究显著的增长点&lt;/em&gt;&lt;/strong&gt;&lt;/li&gt;  &lt;li&gt;   &lt;strong&gt;    &lt;em&gt;关注各类运营数据及转化率&lt;/em&gt;&lt;/strong&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;（6）销售/营销&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;定价策略&lt;/li&gt;  &lt;li&gt;市场营销&lt;/li&gt;  &lt;li&gt;销售模式   &lt;ul&gt;    &lt;li&gt;直销     &lt;br /&gt;&lt;/li&gt;    &lt;li&gt;代销     &lt;br /&gt;&lt;/li&gt;    &lt;li&gt;经销     &lt;br /&gt;&lt;/li&gt;    &lt;li&gt;网络销售     &lt;br /&gt;&lt;/li&gt;    &lt;li&gt;电话销售     &lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;  &lt;li&gt;销售渠道及渠道策略   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;品牌管理   &lt;br /&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;关于定价方法，UCPM的总结比较全面到位，这里罗列一下，百度即可。重在比较不同竞争者的定价策略。&lt;/p&gt; &lt;p&gt;  &lt;img alt="ec564fb2a9c3334a2b59fcd17d521eba-picture" src="http://img.pmcaff.com/ec564fb2a9c3334a2b59fcd17d521eba-picture"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;  &lt;img alt="7e537eb216e5540a69d5ea3cab9236db-picture" src="http://img.pmcaff.com/7e537eb216e5540a69d5ea3cab9236db-picture"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;~~~&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;   &lt;br /&gt;&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;3.5、从产品角度看用户&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;当产品中沉淀了一些存量用户之后，为了实现用户需求的异质性、并集中有限资源进行有效市场竞争的行为。企业在明确的战略业务模式和特定的市场中，根据用户的属性，行为等因素对用户进行分类，并提供有针对性的产品、服务、销售、运营模式，达到用户价值和产品目标的最大化。&lt;/p&gt; &lt;p&gt;系统实施层面，是在抽象理论的指导下，用算法进行标签化统计、分类，并以用户画像的形式表现，最后在策略上、界面上、运营方式上进行“量体裁衣”。&lt;/p&gt; &lt;p&gt;  &lt;img alt="0be7ad333efaf361a0f761b72f109752-picture" src="http://img.pmcaff.com/0be7ad333efaf361a0f761b72f109752-picture"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;（1）分析对象：产品中现有的用户&lt;/p&gt; &lt;p&gt;（2）分析目的：对用户细分，精细化运营，不同用户采用不同的运营策略&lt;/p&gt; &lt;p&gt;（3）分析内容：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;用户画像之User Profile&lt;/li&gt;  &lt;li&gt;标签分析、标签标注&lt;/li&gt;  &lt;li&gt;用户相关方利益分析&lt;/li&gt;  &lt;li&gt;不同用户消费特点   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;用户习惯   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;用户同理心分析   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;用户行为旅程   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;用户体验   &lt;br /&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;越来越多的产品也逐渐从更多细致的维度来分析消费者，对2C用户的分析维度分为以下五类：&lt;/p&gt; &lt;p&gt;  &lt;img alt="656fd0037b2f0b67381ee1cdcf1d9870-picture" src="http://img.pmcaff.com/656fd0037b2f0b67381ee1cdcf1d9870-picture"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;对2B企业的分析维度，大致分为以下三类：&lt;/p&gt; &lt;p&gt;  &lt;img alt="8ad1f0a90c0e6ab57374df3734bc9d8f-picture" src="http://img.pmcaff.com/8ad1f0a90c0e6ab57374df3734bc9d8f-picture"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;（4）分析方法：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;定性：人种学现场调查、眼动跟踪、可用性实验室研究、用户反馈分析   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;定量：埋点数据分析、A/B测试、用户体验调查问卷   &lt;br /&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;  &lt;strong&gt;~~~&lt;/strong&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;   &lt;br /&gt;&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;3.6、从用户角度看产品&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;我们通常需要以不同的角色来体验和感受产品，而且能在各个角色之间切换自如。角色可以分以下几类：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;用户、商家、广告商&lt;/li&gt;  &lt;li&gt;小白用户、普通用户、专家用户&lt;/li&gt;  &lt;li&gt;决策者、购买者、使用者、影响者、信息管理者&lt;/li&gt;  &lt;li&gt;界面设计面向的主要人物、次要人物、补充人物、客户人物、接受服务人物、负面人物&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;（1）产品覆盖的场景有哪些？能满足哪些需求？能给我带来什么价值？  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;（2）多端比较：  &lt;br /&gt;&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;Android   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;IOS   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;WP   &lt;br /&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;（3）多商业入口分析：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;APP   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;H5   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;PC   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;公众号   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;小程序   &lt;br /&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;（4）功能：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;主要功能   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;次要功能   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;附加功能   &lt;br /&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;（5）UI与交互&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;主要功能入口是否清晰明确？   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;各入口间跳转是否会迷失？   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;最重要的页面有没有直接展示？   &lt;br /&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;（6）其他&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;安全性&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;性能：是否稳定，不卡顿，响应速度   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;获得性：客户能否方便的获得服务   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;易用性：操作便利性、学习的难易程度   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;后续服务：不断升级，维护报修服务的便利性   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;用户评价   &lt;br /&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;  &lt;strong&gt;~~~&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;   &lt;br /&gt;&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;3.7、从产品背后看企业&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;企业本质上是“一种资源配置的机制”，其能够实现整个社会经济资源的优化配置，降低整个社会的“交易成本”。从产品背后看企业，才能看到产品的基因与营养。&lt;/p&gt; &lt;p&gt;（1）分析对象：产品所属的企业&lt;/p&gt; &lt;p&gt;（2）分析目的：分析竞品所属企业的资源配置机制&lt;/p&gt; &lt;p&gt;（3）分析方法：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;定性：历史追踪法、主观体验与评价&lt;/li&gt;  &lt;li&gt;定量：特征罗列、要素列举&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;（4）分析领域：  &lt;br /&gt;&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;企业背景   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;企业历程及重大节点   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;企业定位   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;企业愿景   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;发展战略   &lt;br /&gt;   &lt;ul&gt;    &lt;li&gt;进攻     &lt;br /&gt;&lt;/li&gt;    &lt;li&gt;防御     &lt;br /&gt;&lt;/li&gt;    &lt;li&gt;横向扩张     &lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;  &lt;li&gt;业务模式   &lt;br /&gt;   &lt;ul&gt;    &lt;li&gt;上游     &lt;br /&gt;&lt;/li&gt;    &lt;li&gt;下游     &lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;  &lt;li&gt;产品矩阵   &lt;br /&gt;   &lt;ul&gt;    &lt;li&gt;波士顿产品矩阵     &lt;br /&gt;&lt;/li&gt;    &lt;li&gt;产品组合策略     &lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;  &lt;li&gt;产品战略   &lt;br /&gt;   &lt;ul&gt;    &lt;li&gt;组织架构从上到下的战略传递     &lt;br /&gt;&lt;/li&gt;    &lt;li&gt;产品路线图     &lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;  &lt;li&gt;商业模式（商业画布）   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;人力资源：   &lt;br /&gt;   &lt;ul&gt;    &lt;li&gt;创始人概括     &lt;br /&gt;&lt;/li&gt;    &lt;li&gt;核心骨干人员优势     &lt;br /&gt;&lt;/li&gt;    &lt;li&gt;团队背景     &lt;br /&gt;&lt;/li&gt;    &lt;li&gt;组织架构     &lt;br /&gt;&lt;/li&gt;    &lt;li&gt;人员数量     &lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;  &lt;li&gt;财务资源：   &lt;br /&gt;   &lt;ul&gt;    &lt;li&gt;投融资情况     &lt;br /&gt;&lt;/li&gt;    &lt;li&gt;盈利能力     &lt;br /&gt;&lt;/li&gt;    &lt;li&gt;运营投入     &lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;  &lt;li&gt;实物资源：工厂车间、机器设备、工具器具、生产资料、土地、房屋等具有物质形态的固定资产   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;技术资源：   &lt;br /&gt;   &lt;ul&gt;    &lt;li&gt;技术专利     &lt;br /&gt;&lt;/li&gt;    &lt;li&gt;影响用户体验的技术     &lt;br /&gt;&lt;/li&gt;    &lt;li&gt;市场发展的技术     &lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;  &lt;li&gt;社会资源：   &lt;br /&gt;   &lt;ul&gt;    &lt;li&gt;政府关系     &lt;br /&gt;&lt;/li&gt;    &lt;li&gt;合作伙伴     &lt;br /&gt;&lt;/li&gt;    &lt;li&gt;社会名人     &lt;br /&gt;&lt;/li&gt;    &lt;li&gt;重要事件     &lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;  &lt;li&gt;其他资源：时空资源、信息资源、品牌资源、文化资源、管理资源   &lt;br /&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;  &lt;strong&gt;   &lt;br /&gt;&lt;/strong&gt;&lt;/p&gt; &lt;hr&gt;&lt;/hr&gt; &lt;p&gt;  &lt;strong&gt;   &lt;br /&gt;&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;四、&lt;/strong&gt;  &lt;strong&gt;4类信息来源&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;4.1、来自分析者自身的信息&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;（1）成为用户&lt;/p&gt; &lt;p&gt;（2）体验产品&lt;/p&gt; &lt;p&gt;（3）轮岗实习&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;4.2、来自用户的信息&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;（1）用户调研&lt;/p&gt; &lt;p&gt;（2）用户反馈&lt;/p&gt; &lt;p&gt;（3）用户数据分析&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;4.3、来自竞争者的信息&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;（1）公司官网&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;财务报表   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;融资情况   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;活动线索：产品发布会、行业峰会，展览会，推广活动   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;媒体线索：官网、微博、公众号、媒体报道、高管访谈、产品的运营事件和运营信   &lt;br /&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;（2）产品使用：产品体验、产品文档&lt;/p&gt; &lt;p&gt;（3）竞争者的员工&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;客服人员：作为消费者给竞品客服打电话咨询问题   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;离职人员：在合法范围内做咨询   &lt;br /&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;（4）互动交流&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;4.4、来自第三方的信息&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;（1）政府机构：查看政府的工作统计报告&lt;/p&gt; &lt;p&gt;（2）行业研究机构：行业报告、案例研究和论文，能了解行业现状和市场格局。&lt;/p&gt; &lt;p&gt;（3）第三方调研机构&lt;/p&gt; &lt;p&gt;（4）专利机构：可检索竞品涉及的专利&lt;/p&gt; &lt;p&gt;（5）合作伙伴：从合作伙伴处了解竞品&lt;/p&gt; &lt;p&gt;（6）应用商店的数据统计平台：查看产品排名、用户评价、下载量统计、活跃用户规模、版本迭代记录等&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;hr&gt;&lt;/hr&gt; &lt;p&gt;  &lt;strong&gt;   &lt;br /&gt;&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;五、&lt;/strong&gt;  &lt;strong&gt;3个信息处理步骤&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;5.1、信息收集&lt;/strong&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;（1）编写&lt;/p&gt; &lt;p&gt;（2）爬取&lt;/p&gt; &lt;p&gt;（3）问卷、访谈&lt;/p&gt; &lt;p&gt;（4）数据库调取&lt;/p&gt; &lt;p&gt;（5）购买&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;5.2、信息清洗&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;（1）重复信息：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;合并&lt;/li&gt;  &lt;li&gt;删除重复项&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;（2）残缺信息：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;补全。补全缺失的信息&lt;/li&gt;  &lt;li&gt;估算。用样本统计的估算值代替缺失信息&lt;/li&gt;  &lt;li&gt;推导。用样本模型计算出来的值代替缺失信息&lt;/li&gt;  &lt;li&gt;忽略。忽略掉与分析目标相关度小的信息&lt;/li&gt;  &lt;li&gt;遗留。做缺失记录，暂时不做处理。&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;（3）错误信息&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;更正&lt;/li&gt;  &lt;li&gt;删除&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;（4）不一致信息&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;相互关联&lt;/li&gt;  &lt;li&gt;相互统一&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;  &lt;strong&gt;5.3、信息加工&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;将清洗过的信息加工成我们想要的信息&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;提取。从多段信息中提取某个专题需要的信息   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;计算。利用已有数据按公式计算出另一数据   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;分组。合理分组，合并同类项，排列组合   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;转化。信息类型间的转化，格式统一&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;  &lt;strong&gt;5.4、信息&lt;/strong&gt;  &lt;strong&gt;抽样&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;个别信息，尤其数据类的信息，需要抽样检查&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;5.5、信息更新&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;市场变化快，信息须及时更新&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;hr&gt;&lt;/hr&gt; &lt;p&gt;  &lt;strong&gt;   &lt;br /&gt;&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;六、&lt;/strong&gt;  &lt;strong&gt;6类分析方法&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;6.1、主观与客观&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;（1）主观：体验、描述、评价&lt;/p&gt; &lt;p&gt;（2）客观：特征罗列、流程呈现、要素列举、公式计算&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;6.2、定性与定量&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;img alt="b7e1a369309e4c366f6df6ca2442cdfc-picture" src="http://img.pmcaff.com/b7e1a369309e4c366f6df6ca2442cdfc-picture"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;6.3、理论模型&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;有一些现成的理论模型是可以直接套用的，列举如下：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;PEST   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;STP理论   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;波特五力模型   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;波士顿矩阵分析   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;用户体验五要素   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;$APPEALS   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;SWOT   &lt;br /&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;  &lt;strong&gt;6.4、分析方法&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;（1）对比分析法：最简单的对比是罗列要素，进行Yes/No的打钩。&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;同一时空，同样条件下，不同指标的比较   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;同样条件，同样指标，在不同时空的比较   &lt;br /&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;（2）综合评价分析（权重评分法）&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;专家访谈法   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;德尔菲法   &lt;br /&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;（3）结构分析法&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;漏斗分析   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;杜邦分析   &lt;br /&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;（4）四象限法（矩阵分析法）。一般选择两个关键竞争要素，通过四象限来分析竞品之间关键指标的分布情况。如波士顿矩阵法也属于四象限法。&lt;/p&gt; &lt;p&gt;（5）历史跟踪法。&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;迭代版本的追踪   &lt;br /&gt;&lt;/li&gt;  &lt;li&gt;运营手法的追踪   &lt;br /&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;hr&gt;&lt;/hr&gt; &lt;p&gt;  &lt;strong&gt;   &lt;br /&gt;&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;七、&lt;/strong&gt;  &lt;strong&gt;报告模板&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;img alt="35abb18872205e38722f7402c03fa78e-picture" src="http://img.pmcaff.com/35abb18872205e38722f7402c03fa78e-picture"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;~完~&lt;/p&gt; &lt;hr&gt;&lt;/hr&gt; &lt;p&gt;  &lt;em&gt;参考资料：海比研究院《2021年中国SaaS市场研究报告》、艾瑞咨询部分行业报告、《UCPM产品管理知识体系》、《ToB运营-低成本获客与续费》、《产品经理装备书》、《交互设计精髓4》。&lt;/em&gt;&lt;/p&gt; &lt;hr&gt;&lt;/hr&gt; &lt;p&gt;  &lt;strong&gt;感想：   &lt;br /&gt;&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;以前最爱李白诗词的浪漫狂发和神奇瑰丽，近期写文章，越发喜欢白居易诗词的通俗写实和浅显易懂，将最常见的道理用最朴素的语言书写，平头老百姓也能读懂。一个像乔布斯，一个像雷布斯，两个诗人都有很强的用户思维。  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;很多道理和方法是朴素实用的，可我们经常从其他地方寻找更好的，甚至更奇特的观点更喜欢去接受和探究，而更简单的需要长期坚持的东西都容易搁浅。当我反求诸己，将做任何事情的切身感受溶于产品工作时，很多事情倒是容易起来了。&lt;/p&gt; 
            &lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/61836-%E5%88%86%E6%9E%90-%E6%96%B9%E6%B3%95%E8%AE%BA-%E8%A7%86%E8%A7%92</guid>
      <pubDate>Tue, 19 Oct 2021 08:54:38 CST</pubDate>
    </item>
    <item>
      <title>有赞保险业务的分析与架构设计</title>
      <link>https://itindex.net/detail/61782-%E4%BF%9D%E9%99%A9-%E4%B8%9A%E5%8A%A1-%E5%88%86%E6%9E%90</link>
      <description>&lt;h1&gt;1、背景&lt;/h1&gt;

 &lt;p&gt;有赞微商城为商家提供了全行业全场景的电商解决方案，帮助商家在社交电商、直播电商等场景下快速布局。在整个交易流程中，对退货时运费减免的支持已成为了电商场景的标配。有赞也提供了 “退货包运费” 产品来满足消费者及商家在此场景下的诉求。&lt;/p&gt;

 &lt;p&gt;本文从“退货包运费”这个产品出发，分析保险业务的特征，介绍有赞保险业务系统的架构设计。&lt;/p&gt;

 &lt;h1&gt;2、退货包运费及保险业务分析&lt;/h1&gt;

 &lt;p&gt;在目前消费者权益保护法对消费者网购支持的大背景下，许多类目的商品都能支持七天无理由退货。由此也衍生出退货时物流运费支出的一些争议，给消费者和商家带来困扰。“退货包运费”产品的出现正是为电商业务解决在退货流程中这类问题，从而提升购物体验。&lt;/p&gt;

 &lt;h2&gt;2.1 产品价值&lt;/h2&gt;

 &lt;ul&gt;
  &lt;li&gt;从消费者角度，退货运费减免信息的透出能让消费者得到承诺，并在实际发生退货退款时减少运费支出。&lt;/li&gt;
  &lt;li&gt;站在商家角度，产品能降低消费者网购时的选择成本，提高下单转化率，以及有效地减少在售后处理上的人力投入。&lt;/li&gt;
&lt;/ul&gt;

 &lt;h2&gt;2.2 产品流程分析&lt;/h2&gt;

 &lt;p&gt;当商家开通“退货包运费”后，其服务开始作用在交易链路上：&lt;/p&gt;

 &lt;ul&gt;
  &lt;li&gt;消费者支付前，在商品详情页及下单确认页里准确地判断并展示服务内容。&lt;/li&gt;
  &lt;li&gt;消费者支付后，在订单详页面的“退货包运费”一栏中显示当前的服务状态及内容。&lt;/li&gt;
  &lt;li&gt;商家发货后，收取商家一些的费用，向保险公司投保，生成保单，服务进入生效阶段。
   &lt;img alt="" src="https://tech.youzan.com/content/images/2021/07/---------3.jpg"&gt;&lt;/img&gt;&lt;/li&gt;
  &lt;li&gt;退货退款时，如果消费者选择的是上门取件，可以直接抵扣退货运费。如果是自行寄回，消费者需先行支付运费，退款完成后，保司自动赔付给消费者。
   &lt;img alt="" src="https://tech.youzan.com/content/images/2021/07/--------2-4.jpg"&gt;&lt;/img&gt;&lt;/li&gt;
&lt;/ul&gt;

 &lt;h2&gt;2.3 产品风险管控分析&lt;/h2&gt;

 &lt;p&gt;为了避免一些骗保、刷单、恶意退款、薅羊毛等不良行为影响产品的正常运营，“退货包运费”会在关键流程节点接入有赞强大的风控体系来保障产品的健康发展，比如：根据不同的风险因素适当调整服务费率、服务前的风控审核、申请赔付前的审核等。&lt;/p&gt;

 &lt;h2&gt;2.4  保险业务分析&lt;/h2&gt;

 &lt;p&gt;“退货包运费”是一种保险类的业务，消费者或商家支付较低费用，约定了在小概率风险事件（退货）发生时，对于被保的一方（消费者）发起赔付，达到减免退货物流运费的目的。目前市场上许多保险公司（下简称：保司）有对应成熟的保险产品，有赞也与保司合作一起为消费者和商家提供服务。&lt;/p&gt;

 &lt;p&gt;有赞保险类业务的能力要与保司的能力匹配，而保险这种业务形态已经比较成熟，在《中华人民共和国保险法》里对于保险业务活动进行了规范，从中我们能看到保险业的基本业务活动。&lt;/p&gt;

 &lt;p&gt;“保险，是指投保人（注1）根据合同约定，向保险人（注2）支付保险费，保险人对于合同约定的可能发生的事故因其发生所造成的财产损失承担赔偿保险金责任，或者当被保险人（注3）死亡、伤残、疾病或者达到合同约定的年龄、期限等条件时承担给付保险金责任的商业保险行为”。&lt;/p&gt;

 &lt;p&gt;保险合同是保险活动的载体，是投保人与保险人约定保险权利义务关系的协议。围绕保险合同的签订、变更、终止以及当事人权利义务的履行，都是保险业务的范畴。保险分为财产保险和人身保险，在电商场景中，我们一般涉及到的是财产保险。生命周期见下图：&lt;/p&gt;

 &lt;p&gt;  &lt;img alt="" src="https://tech.youzan.com/content/images/2021/06/-------4.jpg"&gt;&lt;/img&gt;&lt;/p&gt;

 &lt;p&gt;保险合同（下简称：保单）里一般包含了以下内容：&lt;/p&gt;

 &lt;ol&gt;
  &lt;li&gt;保险人的名称和住所；  &lt;/li&gt;
  &lt;li&gt;投保人、被保险人的姓名或者名称、住所，以及人身保险的受益人(注4)的姓名或者名称、住所；  &lt;/li&gt;
  &lt;li&gt;保险标的；  &lt;/li&gt;
  &lt;li&gt;保险责任和责任免除；  &lt;/li&gt;
  &lt;li&gt;保险期间和保险责任开始时间；  &lt;/li&gt;
  &lt;li&gt;保险金额（注5）；  &lt;/li&gt;
  &lt;li&gt;保险费以及支付办法；  &lt;/li&gt;
  &lt;li&gt;保险金赔偿或者给付办法；  &lt;/li&gt;
  &lt;li&gt;违约责任和争议处理；  &lt;/li&gt;
  &lt;li&gt;订立合同的年、月、日。&lt;/li&gt;
&lt;/ol&gt;

 &lt;p&gt;在保单的有效期里，可以对保单进行变更、终止、修改条款等，依据保单中条款申请理赔。&lt;/p&gt;

 &lt;h1&gt;3、架构设计&lt;/h1&gt;

 &lt;h2&gt;3.1 业务架构设计&lt;/h2&gt;

 &lt;p&gt;通过上面的价值及流程分析，“退货包运费”产品能分解出以下一些流程活动：&lt;/p&gt;

 &lt;ul&gt;
  &lt;li&gt;服务管理方面：提供服务的生命周期管理，如开通、关闭服务。对于有赞而言，要对已开通的商家做风险管控，还需要支持冻结、解冻、清退等操作，商家也能查看到服务费率的历史变更情况。&lt;/li&gt;
  &lt;li&gt;服务单管理方面：可服务内容的查询，服务单的查询，服务单的生命周期管理，比如下单、生效、抵扣试算，抵扣申请，自动申请理赔，理赔修改。&lt;/li&gt;
&lt;/ul&gt;

 &lt;p&gt;这些流程活动依赖保险领域的业务能力来支撑，具体见下图：&lt;/p&gt;

 &lt;p&gt;  &lt;img alt="" src="https://tech.youzan.com/content/images/2021/06/-----15.jpg"&gt;&lt;/img&gt;&lt;/p&gt;

 &lt;h2&gt;3.2 技术架构设计&lt;/h2&gt;

 &lt;p&gt;在应用设计中，我们将整个保险类业务的应用架构分为业务服务层、领域服务层、组件层、依赖层：&lt;/p&gt;

 &lt;ul&gt;
  &lt;li&gt;业务服务层：对接有赞生态SAAS业务，提供“退货包运费”服务，即感知交易的全流程，推进服务单据状态流转。根据商家的经营情况，批量更新费率。&lt;/li&gt;
  &lt;li&gt;领域服务层：对上层提供标准承保、理赔的服务。另外，为支持上层不同的保险类产品，需要为不同的产品配置不同收费规则、管理对应的保司渠道。&lt;/li&gt;
  &lt;li&gt;组件层：提供领域服务的可复用的基础能力，比如向保司投保理赔、风控校验审核、业务校验、支付退款等。&lt;/li&gt;
  &lt;li&gt;依赖层：组件服务里主要依赖了一些中台性质的服务，如店铺、会员、交易、支付、风控。&lt;/li&gt;
&lt;/ul&gt;

 &lt;p&gt;对于领域服务层的设计，适合应用领域驱动设计(简称：DDD）这种建模方法。从上面的保险领域分析中，我们梳理出几个主要的领域模型：&lt;/p&gt;

 &lt;p&gt;  &lt;img alt="" src="https://tech.youzan.com/content/images/2021/07/-------4.jpg"&gt;&lt;/img&gt;&lt;/p&gt;

 &lt;p&gt;领域服务层主要围绕保单、理赔单对外提供服务。&lt;/p&gt;

 &lt;p&gt;支撑子域有：&lt;/p&gt;

 &lt;ul&gt;
  &lt;li&gt;产品中心：定义一个保险类产品，包括产品的服务明细条款，协议模板，协议约定校验，保险人信息，收付款方式等。&lt;/li&gt;
  &lt;li&gt;标的管理：管理保险标的相关的信息，在“退货包运费”里就是退货运费相关信息，如订单信息，退货商品的信息、物流信息等。&lt;/li&gt;
  &lt;li&gt;渠道管理：出于产品成本及业务稳定性考虑，一个保险产品有多个保司渠道备份，渠道管理负责渠道的定义，与保司接口适配，主要有投保，理赔，保单查询等。&lt;/li&gt;
&lt;/ul&gt;

 &lt;p&gt;通用子域有：&lt;/p&gt;

 &lt;ul&gt;
  &lt;li&gt;计费管理：用于支持产品、商家、消费者、商品等多维度的费率配置及对订单收费计算。&lt;/li&gt;
  &lt;li&gt;产品-渠道路由：支持产品下，根据权重、稳定性、费率、最低单量、定制等不同维度的衡量标准选出匹配的渠道。&lt;/li&gt;
&lt;/ul&gt;

 &lt;p&gt;领域服务层之下，是支撑这些服务的组件：&lt;/p&gt;

 &lt;ul&gt;
  &lt;li&gt;业务规则相关：投保校验、理赔校验。&lt;/li&gt;
  &lt;li&gt;资金相关：保费收取、保费退回、投保记账、理赔记账。&lt;/li&gt;
  &lt;li&gt;渠道相关：渠道投保、渠道理赔。&lt;/li&gt;
  &lt;li&gt;风控相关：风控投保审核、风控理赔审核。&lt;/li&gt;
&lt;/ul&gt;

 &lt;p&gt;应用架构如下图：
  &lt;img alt="" src="https://tech.youzan.com/content/images/2021/07/--L2-------2.jpg"&gt;&lt;/img&gt;&lt;/p&gt;

 &lt;h1&gt;4、后记&lt;/h1&gt;

 &lt;p&gt;在有赞电商生态中，还有许多保险类业务可以切入的场景，比如准时发货保障，针对美妆类过敏包赔的承诺，对生鲜、3C类的损坏赔付等。这些场景对有赞保险业务的能力建设提出了更高的要求，保险系统将向高复用、易扩展的方向继续演进，以支持业务的拓展。&lt;/p&gt;

 &lt;h4&gt;注：&lt;/h4&gt;

 &lt;ol&gt;
  &lt;li&gt;投保人是指与保险人订立保险合同，并按照合同约定负有支付保险费义务的人。  &lt;/li&gt;
  &lt;li&gt;保险人是指与投保人订立保险合同，并按照合同约定承担赔偿或者给付保险金责任的保险公司。  &lt;/li&gt;
  &lt;li&gt;被保险人是指其财产或者人身受保险合同保障，享有保险金请求权的人。投保人可以为被保险人。  &lt;/li&gt;
  &lt;li&gt;受益人是指人身保险合同中由被保险人或者投保人指定的享有保险金请求权的人。投保人、被保险人可以为受益人。  &lt;/li&gt;
  &lt;li&gt;保险金额是指保险人承担赔偿或者给付保险金责任的最高限额。&lt;/li&gt;
&lt;/ol&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>架构设计 退货包运费 保险</category>
      <guid isPermaLink="true">https://itindex.net/detail/61782-%E4%BF%9D%E9%99%A9-%E4%B8%9A%E5%8A%A1-%E5%88%86%E6%9E%90</guid>
      <pubDate>Mon, 13 Sep 2021 15:54:45 CST</pubDate>
    </item>
    <item>
      <title>大规模视频内容理解：淘宝视频内容标签的结构化分析和管理</title>
      <link>https://itindex.net/detail/61773-%E8%A7%86%E9%A2%91-%E7%90%86%E8%A7%A3-%E6%B7%98%E5%AE%9D</link>
      <description>&lt;h4&gt;    &lt;strong&gt;▐&lt;/strong&gt;    &lt;strong&gt; &lt;/strong&gt;    &lt;strong&gt; 为什么要做标签？&lt;/strong&gt;&lt;/h4&gt;  &lt;h4&gt;    &lt;br /&gt;&lt;/h4&gt;  &lt;p&gt;在这种亟需深入理解视频内容的大背景下，不同的表征形态涌现。包括：&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;ul&gt;    &lt;li&gt;      &lt;p&gt;embedding表征&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;      &lt;p&gt;标签表征&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;其中embedding表征常用的方法包括多模态预训练模型、基于用户行为的deep match模型等等，集团内在这方面有大量优秀的工作，使用embedding表征视频内容最大的问题在于不可解释性，只能完成机器对视频的理解。&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;使用标签对内容进行表征的优势在于，标签是一种高度概括的自然语言，不仅完成了机器对视频内容的理解，同时完成了人到视频内容的理解。同时，标签库量级上百万，组合方式多样，保证了内容表征的多样性。&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;综上，对视频完成了标签挂载，可以完成以下目标：&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;ul&gt;    &lt;li&gt;      &lt;p&gt;用户可理解&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;      &lt;p&gt;算法可解释&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;      &lt;p&gt;运营可干预&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;br /&gt;  &lt;h4&gt;    &lt;strong&gt;▐&lt;/strong&gt;    &lt;strong&gt; &lt;/strong&gt;    &lt;strong&gt; &lt;/strong&gt;    &lt;strong&gt;我们需要什么样的标签？&lt;/strong&gt;&lt;/h4&gt;  &lt;h4&gt;    &lt;br /&gt;&lt;/h4&gt;  &lt;p&gt;为了回答这个问题，需要拆解成两个子问题。&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;ul&gt;    &lt;li&gt;      &lt;p&gt;我们需要什么样的标签库？&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;视频是一种分别非常不均匀的数据形态，其拍摄方式、包含物体、拍摄手法、表达重点均非常丰富，为了描述丰富多彩的视频，需要维护海量级、描述维度全面的标签库。由于视频热点更新速度非常快，因此标签库需要自动挖掘、更新，完成标签库的动态扩充。同时，由于标签是一种自然语言，标签和标签之间天然有一种推理关系，因此，标签不应割裂和独立，而是存在一种结构化的数据结构。&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;因此，标签库需要的特性总结如下：&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;ul&gt;    &lt;li&gt;      &lt;p&gt;标签量级大、标签维度全面；&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;      &lt;p&gt;可动态扩展&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;      &lt;p&gt;标签间有关联（结构化）&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;br /&gt;  &lt;ul&gt;    &lt;li&gt;      &lt;p&gt;对于一个视频来说，需要打出什么样的标签？&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;br /&gt;  &lt;p&gt;对于一个视频，必然存在拍摄此视频的核心原因，我们将此原因概括为核心主旨标签，给出核心主旨标签，即可联想到此视频大致的描述方向。同时，视频中必然出现很多相关标签，这类标签可帮助了解视频细节内容。结合核心主旨标签和相关标签，就可以对视频内容进行全局到局部、总体到细节的理解，这也是符合人类认知习惯的。&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;以下面这个淘宝中常见的视频为例，其核心想表达的是一个复古清纯风格的服饰搭配教程，核心主旨为：白月光穿搭、复古清纯穿搭等；而如何穿搭才能展现这种风格？需要穿白色连衣裙、带发箍等等，因此其相关标签应为：白色连衣裙、可爱发箍、vintage等等。&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;&lt;/p&gt;  &lt;p&gt;视频1 一种典型的淘内视频打标的结果    &lt;img&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt; 标签体系框架设计 &lt;/p&gt;  &lt;br /&gt;  &lt;p&gt;按照1.2中的分析，为完成对视频内容全面的理解，我们需要从标签库和打标算法两个大方向对整体标签体系框架进行设计，即：&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;（1）标签库动态维护，包括标签的动态挖掘和标签结构化&lt;/p&gt;  &lt;p&gt;（2）视频内容打标算法&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;完成标签库维护和打标流程后，即可完成数据服务的对外输出和下游的业务应用。数据的对外输出包括数据生产和实时/离线的服务输出；业务应用包括运营工具、下游投放算法和标签外透等。我们设计的标签体系框架如下图所示。&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;    &lt;img&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;图2 标签体系设计架构大图&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;经过标签体系算法流程，内容标签目前对视频内容进行打标、标签库进行动态管理取得的结果如下图所示。在后续章节中，我会根据    &lt;strong&gt;标签库动态管理&lt;/strong&gt;、    &lt;strong&gt;视频内容打标算法流程&lt;/strong&gt;、    &lt;strong&gt;下游应用&lt;/strong&gt;三个大模块进行详细的介绍。&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;    &lt;img&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt; 标签库动态管理 &lt;/p&gt;  &lt;br /&gt;  &lt;h4&gt;    &lt;strong&gt;▐&lt;/strong&gt;    &lt;strong&gt; &lt;/strong&gt;    &lt;strong&gt; &lt;/strong&gt;    &lt;strong&gt;标签挖掘&lt;/strong&gt;&lt;/h4&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;为保证挖掘标签词的时效性、全面性，我们设计了两套互补的标签挖掘算法流程，再将挖掘出的标签融合起来进行人工审核。&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;ul&gt;    &lt;li&gt;      &lt;p&gt;基于autophrase的半监督新词挖掘算法流程&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;如下图所示，将内容语料库和置信的标签集送入autophrase模型，通过半监督的方式获取标签后，再经过一个判别标签是否有效的二分类模型得到最终挖掘出的标签。此方法挖掘标签的特点在于：&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;ul&gt;    &lt;li&gt;      &lt;p&gt;过审率较高（80%+）&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;      &lt;p&gt;新标签和置信集标签pattern相似&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;      &lt;p&gt;无法获取热门标签&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;    &lt;img&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;ul&gt;    &lt;li&gt;      &lt;p&gt;基于标签pattern挖掘的新词挖掘算法流程&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;如下图所示，这是一种无监督的标签挖掘流程，先从内容语料库中挖掘出标签组合的pattern，再用pattern在语料库中进行匹配，获取挖掘的标签。此方法挖掘标签的特点在于：&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;ul&gt;    &lt;li&gt;      &lt;p&gt;过审率较低&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;      &lt;p&gt;标签pattern更加灵活多样&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;      &lt;p&gt;可以获取热门标签&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;    &lt;img&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;目前，我们基于海量的站内/站外数据挖掘出500w+标签，通过数据清洗后的标签量为110w+，其中原子标签数量90w+，短语标签数量130w+，短语型标签每周增量约5000+。&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;h4&gt;    &lt;strong&gt;▐&lt;/strong&gt;    &lt;strong&gt; &lt;/strong&gt;    &lt;strong&gt; &lt;/strong&gt;    &lt;strong&gt;标签结构化&lt;/strong&gt;&lt;/h4&gt;  &lt;h4&gt;    &lt;br /&gt;&lt;/h4&gt;  &lt;h4&gt;高效地维护标签库，不仅在于标签地动态挖掘，更在于挖掘出标签后，如何结构化地管理这些标签。好的标签结构化引入了标签间信息，为标签推理认知的提供了基础知识。首先，我们按照词语粒度将标签分为原子标签和短语标签，其中原子标签是不可继续分词的标签，短语标签为原子标签组合而成的标签。&lt;/h4&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;ul&gt;    &lt;li&gt;      &lt;h4&gt;原子标签分类体系&lt;/h4&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;我们首先为原子标签设计了一套分类体系，可对每个原子标签进行树状结构的划分，一级类目包括“时间”、“地点”、“人物/动植物”、“事情”、“物品”、“IP”、“机构名”、“数值”等8个大类，在一级类目中还有二级、三级、四级的树状延伸等50个小类。&lt;/p&gt;  &lt;p&gt;通过原子标签分类体系，不仅可以细分管理每个原子标签，将短语标签拆解为原子标签进行理解，同时可以分析每个分类下原子标签情况，如发现短缺的情况可以及时补充，保证标签集合的完整充分。&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;ul&gt;    &lt;li&gt;      &lt;h4&gt;短语标签分维度结构化&lt;/h4&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;由于短语标签包含各种不同的高层语义信息，因此仅通过组成短语标签的原子标签进行理解是远远不够的，基于此，我们从不同语义维度设计了四种不同的短语标签结构化方法，包括：内容/商品标签、热门趋势标签/长期标签、人群兴趣标签/视频内容标签、问题型/陈述型标签。在应用中，可按照不同的需求选取不同类型的短语标签。&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;    &lt;img&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;图4 短语标签分类体系&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;h4&gt;    &lt;strong&gt;▐&lt;/strong&gt;    &lt;strong&gt; &lt;/strong&gt;    &lt;strong&gt; &lt;/strong&gt;    &lt;strong&gt;标签管理目前核心进展&lt;/strong&gt;&lt;/h4&gt;  &lt;h3&gt;    &lt;br /&gt;&lt;/h3&gt;  &lt;p&gt;目前标签库已实现动态管理、结构化，具体的指标如下图所示。&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;    &lt;img&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt; 视频内容打标算法 &lt;/p&gt;  &lt;br /&gt;  &lt;h4&gt;    &lt;strong&gt;▐&lt;/strong&gt;    &lt;strong&gt; &lt;/strong&gt;    &lt;strong&gt; &lt;/strong&gt;    &lt;strong&gt;视频内容打标算法流程设计&lt;/strong&gt;&lt;/h4&gt;  &lt;h4&gt;    &lt;br /&gt;&lt;/h4&gt;  &lt;p&gt;在确定视频内容打标算法流程之前，需要确定目前视频内容打标面临的困难和挑战，我对其总结如下：&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;ol&gt;    &lt;li&gt;      &lt;p&gt;标签库规模巨大：目前维护的标签库达百万，且长尾分布严重，无法使用多标签分类模型等常规打标签的解决方案。&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;      &lt;p&gt;标签库不断增长、动态扩充：目前标签库处在动态扩展的状态，标签经常变动，打标算法需要非常灵活&amp;amp;可扩展。&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;      &lt;p&gt;需对多模态信息进行理解：视频是一种多模态的内容形式，包括文本/图像/音频等多种模态的信息。对于不同的视频，不同模态的重要程度也不同，因此需要对多模态的信息进行综合全面的向量表征。&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;      &lt;p&gt;需区分不同标签在内容的权重：如上几条中讨论，对于一个视频，需要给出其核心主旨标签和相关标签。对算法来说，即最终标签结果需给出和视频内容的相关性分数排序。&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;      &lt;p&gt;人工标注非常困难：一个视频具体改打什么标签是一个非常主观的问题，对于不同的标注者，其对内容标签的标注结果会有巨大的差异。因此在打标算法流程设计中，需减少对标注的依赖。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;综合上述对视频内容打标算法流程难点和挑战的分析，我们设计了基于“    &lt;strong&gt;召回-排序&lt;/strong&gt;”的两段式算法流程，召回是视频和标签粗粒匹配过程，排序是视频和标签细粒度匹配过程，调整标签的排序。此算法流程可以很好地解决视频内容打标过程中的难点。&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;    &lt;img&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;图5 视频标签挂载流程大图&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;ul&gt;    &lt;li&gt;      &lt;p&gt;召回阶段&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;其的核心目标在于召回尽可能多、尽可能全的候选标签。为保证召回标签的全面，我按照以下四个维度进行召回：&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;内容信息&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;即内容自带的信息，包括创作者编辑的文本、语音识别结果（ASR）、文本识别结果（OCR）、画面信息。&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;以上面这个制作芝士烤土豆片的食谱教程视频为例：&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;    &lt;img&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;商品信息&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;由于淘内视频的独特性，视频的目的是为了卖货，因此，商品信息也作为描述视频的一个维度。视频下挂载的商品、商品query、同款商品等信息作为商品信息维度的文本召回。&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;用户行为信息&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;淘内视频和用户有着大量的交互，用户行为本身也可以作为标签候选词集。因此内容搜索点击日志清洗出的query可作为表述用户行为的召回候选词。&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;关联内容信息&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;候选词集不仅应包括内容自带的信息，还应该包括关联信息，这一路召回主要通过检索方法实现：&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;ol&gt;    &lt;li&gt;      &lt;p&gt;跨模态检索模型：向量化视频和标签库直接进行跨模态检索，获取最有可能作为视频标签的词。&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;      &lt;p&gt;相似视频检索模型：需要打标的视频作为query，在千万级视频库中检索出topN相似视频，将相似视频的候选词共享到query视频。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;在后续小节中，会对两种检索方法做更详细的介绍。&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;ul&gt;    &lt;li&gt;      &lt;p&gt;排序阶段&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;在排序阶段中，主要目标在于对视频和候选词集进行更细粒度匹配和对齐，完成候选词集的相关性排序，保留和视频相关的标签，去除不相关的词。排序模型的具体建模方式接下去会进行详细的介绍。&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;h4&gt;    &lt;strong&gt;▐&lt;/strong&gt;    &lt;strong&gt; &lt;/strong&gt;    &lt;strong&gt; &lt;/strong&gt;    &lt;strong&gt;核心技术点&lt;/strong&gt;&lt;/h4&gt;  &lt;h4&gt;    &lt;br /&gt;&lt;/h4&gt;  &lt;p&gt;在上一部分中，除了常规模型ASR、OCR等，需要核心解决的技术目标为：&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;ol&gt;    &lt;li&gt;      &lt;p&gt;召回模块中的相似视频检索&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;      &lt;p&gt;召回模块中的跨模态检索模型&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;      &lt;p&gt;排序模块中使用的跨模态相关性判别模型&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;对于这三个需要解决的核心技术目标，我们抽象出三个需攻坚的核心技术点：&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;ol&gt;    &lt;li&gt;      &lt;p&gt;视频多模态内容表征模型&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;      &lt;p&gt;跨模态检索模型&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;      &lt;p&gt;跨模态相关性判别模型&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;在接下去几部分中，会对这三个核心技术点进行详细拆解和介绍。&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;    &lt;img&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;h4&gt;    &lt;strong&gt;▐&lt;/strong&gt;    &lt;strong&gt; &lt;/strong&gt;    &lt;strong&gt; &lt;/strong&gt;    &lt;strong&gt;视频多模态内容表征&lt;/strong&gt;&lt;/h4&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;我们目前提取的embedding基于自研的通用视频内容预训练模型。这个视频内容预训练模型借鉴了VilBert的自监督训练方法，继承了更多通用性强的预训练任务，包括内容挂载商品的一级、二级分类，内容的文本和视觉信息是否匹配。同时，由于淘系内容业务与推荐任务息息相关，因此，我们进行了大幅度的改造，对于推荐大场景改良了预训练网络和特异性预训练任务。&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;    &lt;img&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;参考[5]LXMERT的预训练网络结构，具体输入：&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;visual branch图文内容：&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;（1）图文包含的所有图像经过Resnet抽取的1536维特征序列。维度为101536（2）图像帧位置信息序列：维度为110，表示图像处于图像序列位置视频视频：（1）视频关键帧经过Resnet抽取的1536维特征序列。101536（2）图像真位置信息序列：维度为110，表示图像处于帧序列位置&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;Language branch图文内容：&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;（1）title+body content经过中文bert编码的字id序列，维度为1128（2）字位置信息序列：维度1128，表示字在句子序列中的位置视频内容：（1）title+summary经过中文bert编码的字id序列，维度为1128（2）字位置信息序列：维度1128，表示字在句子序列中的位置&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;    &lt;img&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;图6 视频理解基础模型网络框架&lt;/p&gt;  &lt;br /&gt;目前共5个预训练task：  &lt;br /&gt;  &lt;ol&gt;    &lt;li&gt;图像序列随机mask掉图像序列的feature，最后用以visual为主的cross-modality feature进行mask掉的feature回归预测。&lt;/li&gt;    &lt;li&gt;序列随机mask掉字序列的feature，最后用以Language为主的cross-modality feature进行mask掉的文字id分类预测。&lt;/li&gt;    &lt;li&gt;内容挂载商品的110个一级类目分类&lt;/li&gt;    &lt;li&gt;内容挂载商品的3807个叶子类目分类&lt;/li&gt;    &lt;li&gt;visual信息和language信息是否匹配的二分类（即图像序列和文字序列是否来自同一内容）&lt;/li&gt;&lt;/ol&gt;  &lt;br /&gt;同时，由于同时mask视觉/文本侧特征可能导致信息过度损失，参考[9]UNITER模型中的mask策略，优化训练策略，如下图所示。  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;    &lt;img&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;经过10个epoch，使用100+块GPU，经过5天的训练，将6000w+淘内视频数据完成训练。预训练模型的参数更新参考了海量的视频数据，被上亿的标注数据进行了约束，最终产出的checkpoint可以作为后续跨模态检索、排序模型的Backbone网络，产出的768维向量可作为相似视频检索的视频向量。在视频内容的标签挂载算法流程中，这个pretrain model发挥了重要的作用。&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;内容表征的好坏是根据下游算法性能的提高作为衡量指标的，使用视频标题bert向量、LXMERT向量、UNITER向量在下游的跨模态检索模型、跨模态相关性模型中进行对比实验。&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;    &lt;img&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;跨模态检索模型使用不同向量表征模型的对比结果&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;    &lt;img&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;跨模态相关性判别模型使用不同向量表征模型的对比结果&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;相似视频检索模型&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;视频多模态预训练模型中最后一层768维fc可作为视频内容多模态的表征向量，对视频有很强的表征能力，因此可以使用此向量在视频库中检索出相似视频，用来扩充标签候选集。&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;使用相似视频检索对视频标签候选集进行扩充是一种非常有效的方式，使用相似视频检索的思路如下图所示，一个视频可以基于相似embedding检索的方法，在上千万的视频库中检索出最相似的N个视频，将这些相似视频的标签扩充此视频候选词集合。  &lt;br /&gt;  &lt;img&gt;&lt;/img&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;基于相似视频检索方案的视频内容打标case如下所示：&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;    &lt;img&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;图5 相似视频检索在标签召回的应用流程&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;h4&gt;    &lt;strong&gt;▐&lt;/strong&gt;    &lt;strong&gt; &lt;/strong&gt;    &lt;strong&gt; &lt;/strong&gt;    &lt;strong&gt;跨模态检索&lt;/strong&gt;&lt;/h4&gt;  &lt;br /&gt;  &lt;ul&gt;    &lt;li&gt;      &lt;p&gt;跨模态检索模型&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;如果视频和话题的向量分布被处理到相同分布空间，那么视频可以直接在标签库中检索到与其最相关的N个标签。因此我们设计了融合Graph的跨模态检索模型结构，完成视频直接到标签的检索。网络结构如下图所示。该模型主要包括文本编码模型、视频多模态编码模型、图网络算法模型、度量学习模型。&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;    &lt;img&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;图7 融合Graph的跨模态检索模型网络框架&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;为了环节样本稀疏和均衡问题，单模态和跨模态端到端的度量学习进一步保证不同模态的语义一致性和单模态的判别性，我们引入了图网络构建。&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;我们根据用户点击行为构建文本-视频、文本-文本、视频-视频的同构图，但考虑到仅使用用户点击行为构建的图较为稀疏，因此同时引入了基于语义相似度建图，将相似的文本和相似的视频之间构建边，最终形成的图结构如下所示。&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;    &lt;img&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;ul&gt;    &lt;li&gt;      &lt;p&gt;实验结果&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;检索衡量指标采用检索召回准确率Top1，Top5，Top20 以及Mean Rank值。&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;1K淘宝样本对检索性能如下：&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;    &lt;img&gt;&lt;/img&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;MSCOCO Retrieval数据集的5K检索任务性能结果对比，其中对比方法主要限定采用双塔模型结构、特征提取无需相互计算，适用于大规模检索的方法。本文工作达到了与同期前沿工作具有竞争力的性能。&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;    &lt;img&gt;&lt;/img&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;ul&gt;    &lt;li&gt;      &lt;p&gt;case展示&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;主题标签在千万级首猜精品视频池进行向量检索，手淘全屏页[3]主题标签召回示例case：&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;    &lt;img&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;h4&gt;    &lt;strong&gt;▐&lt;/strong&gt;    &lt;strong&gt; &lt;/strong&gt;    &lt;strong&gt; &lt;/strong&gt;    &lt;strong&gt;跨模态相关性判别&lt;/strong&gt;&lt;/h4&gt;  &lt;h4&gt;    &lt;strong&gt;&lt;/strong&gt;    &lt;br /&gt;&lt;/h4&gt;  &lt;p&gt;完成了最大程度的候选词集召回后，需要一个可以更加更精确判别视频和词语相似度的排序模型，从而将候选词从按照和视频的相关性分数进行排序，剔除掉不相关的词语。&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;    &lt;img&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;此时的排序模型和上一小节的跨模态检索模型不同之处在于，跨模态检索模型目的是从上百万标签库中快速检索到和视频可能相关的词语，因此在训练过程中，视频侧和标签侧的网络结构不能融合。而在排序模型中，目的在于更精细化判断视频和候选词的相关性，且候选词数量有限，无需过度在乎时间复杂度，因此两模态之间可以进行充分融合，我们设计的网络结构如下图所示。&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;    &lt;img&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;图8 视频标签排序模型网络框架&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;可以根据目的将网络结构分成三个较为独立的模块，即：&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;ul&gt;    &lt;li&gt;      &lt;p&gt;表征模块&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;如何对视频、候选词进行表征，可以全面刻画视频、候选词内容。这里我们主要讨论和调整了视频侧表征方式，使用视频标题、LXMERT多模态特征、UNITER多模态特征进行了对比实验，实验AUC结果如下所示。&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;    &lt;img&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;ul&gt;    &lt;li&gt;      &lt;p&gt;融合模块&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;如何对两路特征进行融合，我们主要使用了两种融合策略，一种是最后一层MLP融合的late fusion策略，一种是使用attention metric进行attention fusion的策略，对比实验AUC如下所示。&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;    &lt;img&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;ul&gt;    &lt;li&gt;      &lt;p&gt;对齐模块&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;两路特征表征和充份融合后，需要将两种分布在不同特征空间的模态（视频内容&amp;amp;标签）对齐到相同的空间分布中，才能完成不同模态的相关性度量。在这里，我们使用了融合了难样本挖掘的对比学习TripletLoss，以及将两种模态尽可能混合的Adversarial Loss。&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;    &lt;img&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;我们对Triplet Loss和Adversarial Loss进行了对比实验，对比实验的AUC结果如下图所示，可以看到加入生成Loss后，由于增加了使两模态尽可能混合的目标，对最终相关性判别能力是有提高的。&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;    &lt;img&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;ul&gt;    &lt;li&gt;      &lt;p&gt;加入用户反馈数据&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;由于标签数据在全屏页全量，每天有新增1w的用户点击数据，这部分数据往往更加精准，将这部分数据加入训练数据构造一个动态的训练数据集，相比于不加入这部分数据，相关性判别模型的AUC有将近3%的提升。&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;    &lt;img&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;h4&gt;    &lt;strong&gt;▐&lt;/strong&gt;    &lt;strong&gt; &lt;/strong&gt;    &lt;strong&gt; &lt;/strong&gt;    &lt;strong&gt;视频标签挂靠结果&lt;/strong&gt;&lt;/h4&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;通过我们设计的召回+排序的视频标签挂载流程，目前视频内容打标结果的统计数据如下所示。同时我们也按照标签不同分类对打标结果做了更加精细的分析：&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;    &lt;img&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt; 业务应用 &lt;/p&gt;  &lt;br /&gt;  &lt;h4&gt;    &lt;strong&gt;▐&lt;/strong&gt;    &lt;strong&gt; &lt;/strong&gt;    &lt;strong&gt; &lt;/strong&gt;    &lt;strong&gt;下游推荐算法链路&lt;/strong&gt;&lt;/h4&gt;  &lt;h4&gt;    &lt;br /&gt;&lt;/h4&gt;  &lt;p&gt;    &lt;img&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;图11 标签在淘宝不同场景投放算法中的应用&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;ul&gt;    &lt;li&gt;      &lt;p&gt;全屏页召回链路&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;全屏页召回链路应用    &lt;strong&gt;（均已全量）&lt;/strong&gt;：&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;ul&gt;    &lt;li&gt;      &lt;p&gt;冷启动：增加原子标签+短语标签召回路，完播率+18%&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;      &lt;p&gt;冷启动：增加短语标签相似关系召回，完播率+3%&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;      &lt;p&gt;主链路：增加原子标签+主题标签召回，有效vv+2%, 七天新视频vv+3%&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;      &lt;p&gt;主链路：增加短语标签相似关系，有效vv+1.5%, 七天vv提升3%, 商家视频占比+5%&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;ul&gt;    &lt;li&gt;      &lt;p&gt;逛逛召回链路&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;语义向量召回    &lt;strong&gt;(已全量)&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;使用视频标签、title作为视频侧表征训练视频召回深度语义DeepV2V，指标如下：&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;pctr +1.04% uctr +0.4% 人均时长持平，人均点击+1.1%，人均曝光+0.5%；&lt;/p&gt;  &lt;p&gt;整体指标：pctr +1.2% uctr +0.45% 人均点击+1.36% 人均曝光+0.4%；&lt;/p&gt;  &lt;p&gt;新鲜度指标：30d+发布的视频数量 +2%, 1-7d -4%；&lt;/p&gt;  &lt;p&gt;曝光度指标：0-200vv视频数量：+16%，200-2k: +4%，20w+: -2%；&lt;/p&gt;  &lt;p&gt;多样性指标：唯一视频数量+4.2%。&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;h4&gt;    &lt;strong&gt;▐&lt;/strong&gt;    &lt;strong&gt; &lt;/strong&gt;    &lt;strong&gt; &lt;/strong&gt;    &lt;strong&gt;标签的前台透出&lt;/strong&gt;&lt;/h4&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;在首猜全屏页中，我们使用话题对视频进行圈选，在全屏页透出，基于认知主题知识，构建底bar话题标签体系，目前线上投放话题    &lt;strong&gt;12w&lt;/strong&gt;，覆盖视频vv    &lt;strong&gt;58%&lt;/strong&gt;，覆盖uv    &lt;strong&gt;1000w+&lt;/strong&gt;, uv_ctr    &lt;strong&gt;1.4%&lt;/strong&gt;。目前已全量。&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;    &lt;img&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;图12 短语标签在淘宝不同场景透出的效果展示&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;h4&gt;    &lt;strong&gt;▐&lt;/strong&gt;    &lt;strong&gt; &lt;/strong&gt;    &lt;strong&gt; &lt;/strong&gt;    &lt;strong&gt;用户&lt;/strong&gt;    &lt;strong&gt;的数&lt;/strong&gt;    &lt;strong&gt;据反馈&lt;/strong&gt;&lt;/h4&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;在完成全屏页全量后，每天会产生10w左右的视频-话题点击pair对，我们将上线后的点击反馈数据收集起来，形成线上透出，线下收集数据重新训练模型。&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;    &lt;img&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;图13 线上用户数据反馈到排序模型数据流向图&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;最终在排序模型中，加入线上反馈数据后，AUC有3%的提升。&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt; 总结与展望 &lt;/p&gt;  &lt;br /&gt;  &lt;p&gt;在视频内容标签接近两年的建设中，完成标签库的结构化管理，维护百万级别标签库，同时形成了“召回+排序”的算法打标流程。在下游搜索推荐算法、标签外透产品、运营圈选工具都取得了不错的业务效果。未来希望完成标签更全面的图谱化结构设计，不仅完成标签的识别，更能做到标签、内容、用户之间的认知和推理，相信有了关系、节点丰富多样的图谱化结构，会使视频内容理解如虎添翼。&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt; Reference &lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;[1] Peter Anderson, Xiaodong He, Chris Buehler, Damien Teney, Mark Johnson, Stephen Gould, andLei Zhang. Bottom-up and top-down attention for image captioning and visual question answering. In Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition, pp.6077–6086, 2018.&lt;/p&gt;  &lt;p&gt;[2] Hao Tan and Mohit Bansal. Lxmert: Learning cross-modality encoder representations from transformers. In Proceedings of the 2019 Conference on Empirical Methods in Natural Language&lt;/p&gt;  &lt;p&gt;Processing, 2019.&lt;/p&gt;  &lt;p&gt;[3] Liunian Harold Li, Mark Yatskar, Da Yin, Cho-Jui Hsieh, and Kai-Wei Chang. Visualbert: A simple&lt;/p&gt;  &lt;p&gt;and performant baseline for vision and language. arXiv preprint arXiv:1908.03557, 2019b.&lt;/p&gt;  &lt;p&gt;[4] Gen Li, Nan Duan, Yuejian Fang, Daxin Jiang, and Ming Zhou. Unicoder-vl: A universal encoder&lt;/p&gt;  &lt;p&gt;for vision and language by cross-modal pre-training, 2019a.&lt;/p&gt;  &lt;p&gt;[5] Su W, Zhu X, Cao Y, et al. Vl-bert: Pre-training of generic visual-linguistic representations[J]. arXiv preprint arXiv:1908.08530, 2019.image-20191107143317330.pngimage-20191107151306644.png&lt;/p&gt;  &lt;p&gt;[6] Chen Y C, Li L, Yu L, et al. UNITER: Learning UNiversal Image-TExt Representations[J]. arXiv preprint arXiv:1909.11740, 2019.&lt;/p&gt;  &lt;p&gt;[7] Zhou L, Palangi H, Zhang L, et al. Unified Vision-Language Pre-Training&lt;/p&gt;  &lt;p&gt;for Image Captioning and VQA[J]. arXiv preprint arXiv:1909.11059, 2019.&lt;/p&gt;  &lt;p&gt;[8] Peng, Y. , and  J. Qi . &amp;quot;CM-GANs.&amp;quot; ACM Transactions on Multimedia Computing, Communications, and Applications (TOMM) 15.1(2019):1-24.&lt;/p&gt;  &lt;p&gt;[9] Portillo-Quintero J A ,  Ortiz-Bayliss J C , H Terashima-Marín. A Straightforward Framework For Video Retrieval Using CLIP[J].  2021.&lt;/p&gt;  &lt;p&gt;[10] Xu, R. , et al. &amp;quot;A Proposal-based Approach for Activity Image-to-Video Retrieval.&amp;quot; (2019).&lt;/p&gt;  &lt;p&gt;[11] Tan, M. , and  Q. V. Le . &amp;quot;EfficientNetV2: Smaller Models and Faster Training.&amp;quot; (2021).&lt;/p&gt;  &lt;p&gt;[12] Miech, Antoine , et al. &amp;quot;Thinking Fast and Slow: Efficient Text-to-Visual Retrieval with Transformers.&amp;quot; (2021).&lt;/p&gt;  &lt;p&gt;[13] Li, W. , et al. &amp;quot;UNIMO: Towards Unified-Modal Understanding and Generation via Cross-Modal Contrastive Learning.&amp;quot; (2020).&lt;/p&gt;  &lt;p&gt;[14] Chen, S. , et al. &amp;quot;Fine-grained Video-Text Retrieval with Hierarchical Graph Reasoning.&amp;quot; IEEE (2020).&lt;/p&gt;  &lt;p&gt;[15] Wray M ,  Doughty H ,  Damen D . On Semantic Similarity in Video Retrieval[J].  2021.&lt;/p&gt;  &lt;p&gt;[16] Yuan, L. , et al. &amp;quot;Tokens-to-Token ViT: Training Vision Transformers from Scratch on ImageNet.&amp;quot; (2021).&lt;/p&gt;  &lt;p&gt;&lt;/p&gt;  &lt;strong&gt;作者&lt;/strong&gt;  &lt;strong&gt;|&lt;/strong&gt;云未  &lt;br /&gt;  &lt;strong&gt;编辑|&lt;/strong&gt;橙子君  &lt;strong&gt;出品|&lt;/strong&gt;阿里巴巴新零售淘系技术  &lt;br /&gt;  &lt;br /&gt;  &lt;p&gt;    &lt;strong&gt;      &lt;strong&gt;END,入群👇备注：&lt;/strong&gt;      &lt;strong&gt;视频&lt;/strong&gt;&lt;/strong&gt;&lt;/p&gt;  &lt;br /&gt;  &lt;br /&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;    &lt;img&gt;&lt;/img&gt;&lt;/p&gt;
    &lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/61773-%E8%A7%86%E9%A2%91-%E7%90%86%E8%A7%A3-%E6%B7%98%E5%AE%9D</guid>
      <pubDate>Fri, 10 Sep 2021 09:38:48 CST</pubDate>
    </item>
    <item>
      <title>Doris 一种实时多维分析的解决方案</title>
      <link>https://itindex.net/detail/61743-doris-%E5%AE%9E%E6%97%B6-%E5%A4%9A%E7%BB%B4</link>
      <description>&lt;div&gt;  &lt;p&gt;Doris 这类 MPP 架构的 OLAP 数据库，通常都是通过提高并发，来处理大量数据的。本质上，Doris 的数据存储在类似 SSTable（Sorted String Table）的数据结构中。该结构是一种有序的数据结构，可以按照指定的列进行排序存储。在这种数据结构上，   &lt;strong&gt;以排序列作为条件进行查找，会非常的高效。&lt;/strong&gt;&lt;/p&gt;  &lt;h2&gt;限制&lt;/h2&gt;  &lt;ul&gt;   &lt;li&gt;    &lt;p&gt;在      &lt;code&gt;Count(*)&lt;/code&gt; 语法方面，原生的方式性能不是特别高，需要自行优化（http://doris.apache.org/documentation/cn/getting-started/data-model-rollup.html）&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;不存在除了维度和指标之外的字段类型存在，如果需要实现多种需求场景，需要创建多种表类型来冗余数据方式实现&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;h3&gt;数据存储结构&lt;/h3&gt;  &lt;p&gt;在 Doris 中，数据以表（Table）的形式进行逻辑上的描述。一张表包括行（Row）和列（Column）。Row 即用户的一行数据。Column 用于描述一行数据中不同的字段。&lt;/p&gt;  &lt;p&gt;Column 可以分为两大类：Key 和 Value。从业务角度看，Key 和 Value 可以分别对应维度列和指标列。&lt;/p&gt;  &lt;p&gt;Doris 的数据模型主要分为3类:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;    &lt;p&gt;Aggregate&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;Uniq&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;Duplicate&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;h4&gt;Aggregate 模型&lt;/h4&gt;  &lt;p&gt;在 Doris 通过 key 来来决定 value 的聚合粒度大小。&lt;/p&gt;  &lt;pre&gt;   &lt;ol&gt;    &lt;li&gt;     &lt;p&gt;      &lt;code&gt;CREATE TABLE IF NOT EXISTS example_db.expamle_tbl&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;     &lt;p&gt;      &lt;code&gt;(&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;     &lt;p&gt;      &lt;code&gt;`user_id`LARGEINT NOT NULL COMMENT&amp;quot;用户id&amp;quot;,&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;     &lt;p&gt;      &lt;code&gt;`date`DATE NOT NULL COMMENT&amp;quot;数据灌入日期时间&amp;quot;,&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;     &lt;p&gt;      &lt;code&gt;`city`VARCHAR(20)COMMENT&amp;quot;用户所在城市&amp;quot;,&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;     &lt;p&gt;      &lt;code&gt;`age`SMALLINT COMMENT&amp;quot;用户年龄&amp;quot;,&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;     &lt;p&gt;      &lt;code&gt;`sex`TINYINT COMMENT&amp;quot;用户性别&amp;quot;,&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;     &lt;p&gt;      &lt;code&gt;`last_visit_date`DATETIME REPLACE DEFAULT&amp;quot;1970-01-01 00:00:00&amp;quot;COMMENT&amp;quot;用户最后一次访问时间&amp;quot;,&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;     &lt;p&gt;      &lt;code&gt;`cost`BIGINT SUM DEFAULT&amp;quot;0&amp;quot;COMMENT&amp;quot;用户总消费&amp;quot;,&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;     &lt;p&gt;      &lt;code&gt;`max_dwell_time`INT MAX DEFAULT&amp;quot;0&amp;quot;COMMENT&amp;quot;用户最大停留时间&amp;quot;,&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;     &lt;p&gt;      &lt;code&gt;`min_dwell_time`INT MIN DEFAULT&amp;quot;99999&amp;quot;COMMENT&amp;quot;用户最小停留时间&amp;quot;,&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;     &lt;p&gt;      &lt;code&gt;)&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;     &lt;p&gt;      &lt;code&gt;AGGREGATE KEY(`user_id`,`date`,`timestamp`,`city`,`age`,`sex`)&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;     &lt;p&gt;      &lt;code&gt;.../* 省略 Partition 和 Distribution 信息 */&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;     &lt;p&gt;      &lt;code&gt;；&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/pre&gt;  &lt;p&gt;像带有 REPLACE、SUM、MAX、MIN 这种标记的字段都是属于 value，   &lt;code&gt;user_id&lt;/code&gt;,   &lt;code&gt;date&lt;/code&gt;,   &lt;code&gt;timestamp&lt;/code&gt;,   &lt;code&gt;city&lt;/code&gt;,   &lt;code&gt;age&lt;/code&gt;,   &lt;code&gt;sex&lt;/code&gt;则为key。&lt;/p&gt;  &lt;h4&gt;Uniq模型&lt;/h4&gt;  &lt;p&gt;这类数据没有聚合需求，只需保证主键唯一性。&lt;/p&gt;  &lt;pre&gt;   &lt;ol&gt;    &lt;li&gt;     &lt;p&gt;      &lt;code&gt;CREATE TABLE IF NOT EXISTS example_db.expamle_tbl&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;     &lt;p&gt;      &lt;code&gt;(&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;     &lt;p&gt;      &lt;code&gt;`user_id`LARGEINT NOT NULL COMMENT&amp;quot;用户id&amp;quot;,&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;     &lt;p&gt;      &lt;code&gt;`username`VARCHAR(50)NOT NULL COMMENT&amp;quot;用户昵称&amp;quot;,&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;     &lt;p&gt;      &lt;code&gt;`city`VARCHAR(20)COMMENT&amp;quot;用户所在城市&amp;quot;,&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;     &lt;p&gt;      &lt;code&gt;`age`SMALLINT COMMENT&amp;quot;用户年龄&amp;quot;,&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;     &lt;p&gt;      &lt;code&gt;`sex`TINYINT COMMENT&amp;quot;用户性别&amp;quot;,&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;     &lt;p&gt;      &lt;code&gt;`phone`LARGEINT COMMENT&amp;quot;用户电话&amp;quot;,&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;     &lt;p&gt;      &lt;code&gt;`address`VARCHAR(500)COMMENT&amp;quot;用户地址&amp;quot;,&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;     &lt;p&gt;      &lt;code&gt;`register_time`DATETIME COMMENT&amp;quot;用户注册时间&amp;quot;&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;     &lt;p&gt;      &lt;code&gt;)&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;     &lt;p&gt;      &lt;code&gt;UNIQUE KEY(`user_id`,`user_name`)&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;     &lt;p&gt;      &lt;code&gt;.../* 省略 Partition 和 Distribution 信息 */&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;     &lt;p&gt;      &lt;code&gt;；&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/pre&gt;  &lt;h4&gt;Duplicate 模型&lt;/h4&gt;  &lt;p&gt;在某些多维分析场景下，数据既没有主键，也没有聚合需求。因此，我们引入 Duplicate 数据模型来满足这类需求。&lt;/p&gt;  &lt;p&gt;这种数据模型区别于 Aggregate 和 Uniq 模型。数据完全按照导入文件中的数据进行存储，不会有任何聚合。即使两行数据完全相同，也都会保留。而在建表语句中指定的 DUPLICATE KEY，只是用来指明底层数据按照那些列   &lt;strong&gt;进行排序&lt;/strong&gt;。&lt;/p&gt;  &lt;p&gt;在 DUPLICATE KEY 的选择上，我们建议适当的选择前 2-4 列就可以。&lt;/p&gt;  &lt;pre&gt;   &lt;ol&gt;    &lt;li&gt;     &lt;p&gt;      &lt;code&gt;CREATE TABLE IF NOT EXISTS example_db.expamle_tbl&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;     &lt;p&gt;      &lt;code&gt;(&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;     &lt;p&gt;      &lt;code&gt;`timestamp`DATETIME NOT NULL COMMENT&amp;quot;日志时间&amp;quot;,&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;     &lt;p&gt;      &lt;code&gt;`type`INT NOT NULL COMMENT&amp;quot;日志类型&amp;quot;,&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;     &lt;p&gt;      &lt;code&gt;`error_code`INT COMMENT&amp;quot;错误码&amp;quot;,&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;     &lt;p&gt;      &lt;code&gt;`error_msg`VARCHAR(1024)COMMENT&amp;quot;错误详细信息&amp;quot;,&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;     &lt;p&gt;      &lt;code&gt;`op_id`BIGINT COMMENT&amp;quot;负责人id&amp;quot;,&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;     &lt;p&gt;      &lt;code&gt;`op_time`DATETIME COMMENT&amp;quot;处理时间&amp;quot;&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;     &lt;p&gt;      &lt;code&gt;)&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;     &lt;p&gt;      &lt;code&gt;DUPLICATE KEY(`timestamp`,`type`)&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;     &lt;p&gt;      &lt;code&gt;.../* 省略 Partition 和 Distribution 信息 */&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;     &lt;p&gt;      &lt;code&gt;；&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/pre&gt;  &lt;h4&gt;数据模型的选择建议&lt;/h4&gt;  &lt;p&gt;因为数据模型在建表时就已经确定，且   &lt;strong&gt;无法修改&lt;/strong&gt;。所以，选择一个合适的数据模型   &lt;strong&gt;非常重要&lt;/strong&gt;。&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;    &lt;p&gt;Aggregate 模型可以通过预聚合，极大地降低聚合查询时所需扫描的数据量和查询的计算量，非常适合有固定模式的报表类查询场景。但是该模型对 count(*) 查询很不友好。同时因为固定了 Value 列上的聚合方式，在进行其他类型的聚合查询时，需要考虑语意正确性。&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;Uniq 模型针对需要唯一主键约束的场景，可以保证主键唯一性约束。但是无法利用 ROLLUP 等预聚合带来的查询优势（因为本质是 REPLACE，没有 SUM 这种聚合方式）。&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;Duplicate 适合任意维度的 Ad-hoc 查询。虽然同样无法利用预聚合的特性，但是不受聚合模型的约束，可以发挥列存模型的优势（只读取相关列，而不需要读取所有 Key 列）。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;  &lt;h3&gt;前缀索引&lt;/h3&gt;  &lt;p&gt;在 Aggregate、Uniq 和 Duplicate 三种数据模型中。底层的数据存储，是按照各自建表语句中，AGGREGATE KEY、UNIQ KEY 和 DUPLICATE KEY 中指定的列进行排序存储的。&lt;/p&gt;  &lt;p&gt;而前缀索引，即在排序的基础上，实现的一种根据给定前缀列，快速查询数据的索引方式。&lt;/p&gt;  &lt;p&gt;我们将一行数据的前   &lt;strong&gt;36 个字节&lt;/strong&gt;作为这行数据的前缀索引。当遇到 VARCHAR 类型时，前缀索引会直接截断。我们举例说明：&lt;/p&gt;  &lt;p&gt;1.以下表结构的前缀索引为 user_id(8Byte) + age(4Bytes) + message(prefix 24 Bytes)。&lt;/p&gt;  &lt;table width="680"&gt;   &lt;tr&gt;    &lt;th&gt;ColumnName&lt;/th&gt;    &lt;th&gt;Type&lt;/th&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;user_id&lt;/td&gt;    &lt;td&gt;BIGINT&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;age&lt;/td&gt;    &lt;td&gt;INT&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;message&lt;/td&gt;    &lt;td&gt;VARCHAR(100)&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;maxdwelltime&lt;/td&gt;    &lt;td&gt;DATETIME&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;mindwelltime&lt;/td&gt;    &lt;td&gt;DATETIME&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;  &lt;p&gt;1.以下表结构的前缀索引为 user_name(20 Bytes)。即使没有达到 36 个字节，因为遇到 VARCHAR，所以直接截断，不再往后继续。&lt;/p&gt;  &lt;table width="680"&gt;   &lt;tr&gt;    &lt;th&gt;ColumnName&lt;/th&gt;    &lt;th&gt;Type&lt;/th&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;user_name&lt;/td&gt;    &lt;td&gt;VARCHAR(20)&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;age&lt;/td&gt;    &lt;td&gt;INT&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;message&lt;/td&gt;    &lt;td&gt;VARCHAR(100)&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;maxdwelltime&lt;/td&gt;    &lt;td&gt;DATETIME&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;mindwelltime&lt;/td&gt;    &lt;td&gt;DATETIME&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;  &lt;p&gt;当我们的查询条件，是   &lt;strong&gt;前缀索引的前缀&lt;/strong&gt;时，可以极大的加快查询速度。比如在第一个例子中，我们执行如下查询：&lt;/p&gt;  &lt;pre&gt;   &lt;ol&gt;    &lt;li&gt;     &lt;p&gt;      &lt;code&gt;SELECT*FROM table WHERE user_id=1829239andage=20；&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/pre&gt;  &lt;p&gt;该查询的效率会   &lt;strong&gt;远高于&lt;/strong&gt;如下查询：&lt;/p&gt;  &lt;pre&gt;   &lt;ol&gt;    &lt;li&gt;     &lt;p&gt;      &lt;code&gt;SELECT*FROM table WHERE age=20；&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/pre&gt;  &lt;p&gt;所以在建表时，   &lt;strong&gt;正确的选择列顺序，能够极大地提高查询效率&lt;/strong&gt;。&lt;/p&gt;  &lt;h3&gt;物化视图（rollup）&lt;/h3&gt;  &lt;p&gt;ROLLUP 在多维分析中是“上卷”的意思，即将数据按某种指定的粒度进行进一步聚合。&lt;/p&gt;  &lt;p&gt;在 Doris 中，我们将用户通过建表语句创建出来的表成为 Base 表（Base Table）。Base 表中保存着按用户建表语句指定的方式存储的基础数据。&lt;/p&gt;  &lt;p&gt;在 Base 表之上，我们可以创建任意多个 ROLLUP 表。这些 ROLLUP 的数据是基于 Base 表产生的，并且在物理上是   &lt;strong&gt;独立存储&lt;/strong&gt;的。&lt;/p&gt;  &lt;p&gt;ROLLUP 表的基本作用，在于在 Base 表的基础上，   &lt;strong&gt;获得更粗粒度的聚合数据&lt;/strong&gt;。&lt;/p&gt;  &lt;p&gt;Rollup 本质上可以理解为原始表(Base Table)的一个物化索引。建立 Rollup 时可只选取 Base Table 中的部分列作为 Schema。Schema 中的字段顺序也可与 Base Table 不同。&lt;/p&gt;  &lt;p&gt;ROLLUP 创建完成之后的触发是程序自动的，不需要任何其他指定或者配置。&lt;/p&gt;  &lt;p&gt;例如：创建了 user_id （key），cost（value）格式的 rollup 时，当执行下方语句时，就会触发。&lt;/p&gt;  &lt;pre&gt;   &lt;ol&gt;    &lt;li&gt;     &lt;p&gt;      &lt;code&gt;SELECT user_id,sum(cost)FROM table GROUP BY user_id;&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/pre&gt;  &lt;blockquote&gt;   &lt;p&gt;Aggregate 和 Uniq 两种数据存储格式时，使用 rollup 会改变聚合数据的粒度，但对于 Duplicate 只是调整前缀索引。&lt;/p&gt;&lt;/blockquote&gt;  &lt;p&gt;因为建表时已经指定了列顺序，所以一个表只有一种前缀索引。这对于使用其他不能命中前缀索引的列作为条件进行的查询来说，效率上可能无法满足需求。因此，我们可以通过创建 ROLLUP 来人为的调整列顺序。举例说明。&lt;/p&gt;  &lt;p&gt;Base 表结构如下：&lt;/p&gt;  &lt;table width="680"&gt;   &lt;tr&gt;    &lt;th&gt;ColumnName&lt;/th&gt;    &lt;th&gt;Type&lt;/th&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;user_id&lt;/td&gt;    &lt;td&gt;BIGINT&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;age&lt;/td&gt;    &lt;td&gt;INT&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;message&lt;/td&gt;    &lt;td&gt;VARCHAR(100)&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;maxdwelltime&lt;/td&gt;    &lt;td&gt;DATETIME&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;mindwelltime&lt;/td&gt;    &lt;td&gt;DATETIME&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;  &lt;p&gt;我们可以在此基础上创建一个 ROLLUP 表：&lt;/p&gt;  &lt;table width="680"&gt;   &lt;tr&gt;    &lt;th&gt;ColumnName&lt;/th&gt;    &lt;th&gt;Type&lt;/th&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;age&lt;/td&gt;    &lt;td&gt;INT&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;user_id&lt;/td&gt;    &lt;td&gt;BIGINT&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;message&lt;/td&gt;    &lt;td&gt;VARCHAR(100)&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;maxdwelltime&lt;/td&gt;    &lt;td&gt;DATETIME&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;mindwelltime&lt;/td&gt;    &lt;td&gt;DATETIME&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;  &lt;p&gt;可以看到，ROLLUP 和 Base 表的列完全一样，只是将 user_id 和 age 的顺序调换了。那么当我们进行如下查询时：&lt;/p&gt;  &lt;pre&gt;   &lt;ol&gt;    &lt;li&gt;     &lt;p&gt;      &lt;code&gt;SELECT*FROM tablewhereage=20andmassage LIKE&amp;quot;%error%&amp;quot;;&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/pre&gt;  &lt;p&gt;会优先选择 ROLLUP 表，因为 ROLLUP 的前缀索引匹配度更高。&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;创建 rollup 语法&lt;/strong&gt;&lt;/p&gt;  &lt;pre&gt;   &lt;ol&gt;    &lt;li&gt;     &lt;p&gt;      &lt;code&gt;ALTER TABLE table1 ADD ROLLUP rollup_city(citycode,pv);&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;     &lt;p&gt;      &lt;code&gt;# 取消正在执行的作业&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;     &lt;p&gt;      &lt;code&gt;CANCEL ALTER TABLE ROLLUP FROM table1;&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/pre&gt;  &lt;h4&gt;ROLLUP 调整前缀索引&lt;/h4&gt;  &lt;p&gt;因为建表时已经指定了列顺序，所以一个表只有一种前缀索引。这对于使用其他不能命中前缀索引的列作为条件进行的查询来说，效率上可能无法满足需求。因此，我们可以通过创建 ROLLUP 来人为的调整列顺序。&lt;/p&gt;  &lt;h4&gt;ROLLUP 的几点说明&lt;/h4&gt;  &lt;ul&gt;   &lt;li&gt;    &lt;p&gt;ROLLUP 最根本的作用是提高某些查询的查询效率（无论是通过聚合来减少数据量，还是修改列顺序以匹配前缀索引）。因此 ROLLUP 的含义已经超出了 “上卷” 的范围。这也是为什么我们在源代码中，将其命名为 Materized Index（物化索引）的原因。&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;ROLLUP 是附属于 Base 表的，可以看做是 Base 表的一种辅助数据结构。用户可以在 Base 表的基础上，创建或删除 ROLLUP，但是不能在查询中显式的指定查询某 ROLLUP。是否命中 ROLLUP 完全由 Doris 系统自动决定。&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;ROLLUP 的数据是独立物理存储的。因此，     &lt;strong&gt;创建的 ROLLUP 越多，占用的磁盘空间也就越大。同时对导入速度也会有影响（导入的ETL阶段会自动产生所有 ROLLUP 的数据），但是不会降低查询效率（只会更好）&lt;/strong&gt;。&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;ROLLUP 的数据更新与 Base 表示完全同步的。用户无需关心这个问题。&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;ROLLUP 中列的聚合方式，与 Base 表完全相同。在创建 ROLLUP 无需指定，也不能修改。&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;查询能否命中 ROLLUP 的一个必要条件（非充分条件）是，查询所涉及的     &lt;strong&gt;所有列&lt;/strong&gt;（包括 select list 和 where 中的查询条件列等）都存在于该 ROLLUP 的列中。否则，查询只能命中 Base 表。&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;某些类型的查询（如 count(*)）在任何条件下，都无法命中 ROLLUP。&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;可以通过      &lt;code&gt;EXPLAIN your_sql;&lt;/code&gt; 命令获得查询执行计划，在执行计划中，查看是否命中 ROLLUP。&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;可以通过      &lt;code&gt;DESC tbl_name ALL;&lt;/code&gt; 语句显示 Base 表和所有已创建完成的 ROLLUP。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;blockquote&gt;   &lt;p&gt;rollup 数量没有限制，但数量越多会消耗比较多的内存。支持 SQL 方式变更 rollup 字段数量。&lt;/p&gt;&lt;/blockquote&gt;  &lt;h3&gt;分区和分桶&lt;/h3&gt;  &lt;p&gt;Doris 支持两级分区存储, 第一层为 RANGE 分区(partition), 第二层为 HASH 分桶(bucket)。&lt;/p&gt;  &lt;p&gt;1.3.1. RANGE分区(partition)&lt;/p&gt;  &lt;pre&gt;   &lt;ol&gt;    &lt;li&gt;     &lt;p&gt;      &lt;code&gt;RANGE分区用于将数据划分成不同区间,逻辑上可以理解为将原始表划分成了多个子表。业务上，多数用户会选择采用按时间进行partition,让时间进行partition有以下好处：&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;     &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;     &lt;p&gt;      &lt;code&gt;*可区分冷热数据&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;     &lt;p&gt;      &lt;code&gt;*可用上Doris分级存储(SSD+SATA)的功能&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;     &lt;p&gt;      &lt;code&gt;*按分区删除数据时，更加迅速&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/pre&gt;  &lt;p&gt;1.3.2. HASH分桶(bucket)&lt;/p&gt;  &lt;pre&gt;   &lt;ol&gt;    &lt;li&gt;     &lt;p&gt;      &lt;code&gt;根据hash值将数据划分成不同的bucket。&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;     &lt;p&gt;      &lt;br /&gt;&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;     &lt;p&gt;      &lt;code&gt;*建议采用区分度大的列做分桶,避免出现数据倾斜&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;     &lt;p&gt;      &lt;code&gt;*为方便数据恢复,建议单个bucket的size不要太大,保持在10GB以内,所以建表或增加partition时请合理考虑bucket数目,其中不同partition可指定不同的buckets数。&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/pre&gt;  &lt;h3&gt;稀疏索引和 Bloom Filter&lt;/h3&gt;  &lt;p&gt;Doris对数据进行有序存储, 在数据有序的基础上为其建立稀疏索引,索引粒度为 block(1024行)。&lt;/p&gt;  &lt;p&gt;稀疏索引选取 schema 中固定长度的前缀作为索引内容, 目前 Doris 选取 36 个字节的前缀作为索引。&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;    &lt;p&gt;建表时建议将查询中常见的过滤字段放在 Schema 的前面, 区分度越大，频次越高的查询字段越往前放。&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;这其中有一个特殊的地方,就是 varchar 类型的字段。varchar 类型字段只能作为稀疏索引的最后一个字段。索引会在 varchar 处截断, 因此 varchar 如果出现在前面，可能索引的长度可能不足 36 个字节。具体可以参阅 数据模型、ROLLUP 及前缀索引。&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;除稀疏索引之外, Doris还提供bloomfilter索引, bloomfilter索引对区分度比较大的列过滤效果明显。如果考虑到varchar不能放在稀疏索引中, 可以建立bloomfilter索引。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;h3&gt;Broadcast/Shuffle Join&lt;/h3&gt;  &lt;p&gt;系统默认实现 Join 的方式，是将小表进行条件过滤后，将其广播到大表所在的各个节点上，形成一个内存 Hash 表，然后流式读出大表的数据进行Hash Join。但是如果当小表过滤后的数据量无法放入内存的话，此时 Join 将无法完成，通常的报错应该是首先造成内存超限。&lt;/p&gt;  &lt;p&gt;如果遇到上述情况，建议使用 Shuffle Join 的方式，也被称作 Partitioned Join。即将小表和大表都按照 Join 的 key 进行 Hash，然后进行分布式的 Join。   &lt;strong&gt;这个对内存的消耗就会分摊到集群的所有计算节点上&lt;/strong&gt;。&lt;/p&gt;  &lt;h2&gt;问题&lt;/h2&gt;  &lt;ol&gt;   &lt;li&gt;    &lt;p&gt;在已经创建的表基础上进行表结构字段的变更和 rollup 索引的变更？&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;  &lt;p&gt;支持，但数据模式一旦表创建就无法变更。&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;    &lt;p&gt;rollup 是否存在数量的限制？&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;  &lt;p&gt;不存在，但越多的 rollup 内存资源会消耗更多，同时，导入数据会比较慢。&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;    &lt;p&gt;（A，B，C）构成的索引是否支持仅 A 字段作为查询条件查询？&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;  &lt;p&gt;支持，但要有顺序要求。&lt;/p&gt;  &lt;h2&gt;总结&lt;/h2&gt;  &lt;p&gt;Doris 表结构由 key 和 value 构成，key 为维度，value 为统计指标。适合做简单的聚合计算和维度计算，使用比较低的硬件条件拥有比较高的性能。&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;    &lt;p&gt;查询：满足 MySQL 语法&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;提升查询性能：使用前缀索引+rollup 或者使用 partition、bloom 过滤器。&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;提升 join 方式查询性能：Shuffle Join。&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;表结构和索引都支持变更，但数据模式不支持变更。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;Doris 官方还推出了 Docker 的 Dev 版本进行特性试用。https://hub.docker.com/r/apachedoris/doris-dev&lt;/p&gt;  &lt;p&gt;推荐 |    &lt;a href="http://mp.weixin.qq.com/s?__biz=MzA4NzQwNTM2NQ==&amp;mid=2651055123&amp;idx=1&amp;sn=caab2977bf3bc0b77ac5372c6d1334c5&amp;chksm=8bcea249bcb92b5f05b5c3236e97a11a416b1f0098d5c4c7e39a2dce5062d6c1a27d82b3b77f&amp;scene=21#wechat_redirect" target="_blank"&gt;谈谈 Github Actions 入门&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;上文 |    &lt;a href="http://mp.weixin.qq.com/s?__biz=MzA4NzQwNTM2NQ==&amp;mid=2651055135&amp;idx=1&amp;sn=3ae3966d3393fdf36cfe36134cf40578&amp;chksm=8bcea245bcb92b5384cb1188adebcc4cfcb0a24b9f9fad270f2d9ed03b4ca9c0889b358d81ce&amp;scene=21#wechat_redirect" target="_blank"&gt;JAVA11到底有多强，不看你就out了&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;   &lt;img&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;↘️点击 在看 帮助更多人&lt;/p&gt;&lt;/div&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>dev</category>
      <guid isPermaLink="true">https://itindex.net/detail/61743-doris-%E5%AE%9E%E6%97%B6-%E5%A4%9A%E7%BB%B4</guid>
      <pubDate>Thu, 12 Mar 2020 00:00:00 CST</pubDate>
    </item>
    <item>
      <title>数据分析该知道的IP地址知识</title>
      <link>https://itindex.net/detail/61737-%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90-%E7%9F%A5%E9%81%93-ip</link>
      <description>&lt;p&gt;第一次接触到IP，还是在十多年前使用统计系统时，当时的统计系统中有个指标是IP地址。即记录每天有多少不同的IP访问您的网站，在后来是自己搭建统计系统时涉及到对IP地址省份、城市、区域的解析。最近在推进风控项目时又有遇到，所以抽时间把相关的知识点做下简单的整理。&lt;/p&gt;
 &lt;h2&gt;什么是IP地址？&lt;/h2&gt;
 &lt;p&gt;IP地址（英语：IP Address，全称Internet Protocol Address）。当设备连接网络，设备将被分配一个IP地址，用作标识。通过IP地址，设备间可以互相通讯，如果没有IP地址，我们将无法知道哪个设备是发送方，无法知道哪个是接收方。IP地址有两个主要功能：标识设备或网络 和 寻址（英语：location addressing）。常见的IP地址分为 IPv4 与 IPv6 两大类，IP地址由一串数字组成。IPv4 由十进制数字组成，并以点分隔，如：172.16.254.1；IPv6 由十六进制数字组成，以冒号分割，如：2001:db8:0:1234:0:567:8:1。这里主要讲解的是IPv4。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="400" src="https://www.biaodianfu.com/wp-content/uploads/2021/08/internet.jpg" width="700"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;IP地址表示方法：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;点分十进制(最常用的表示方法)：11.3.31&lt;/li&gt;
  &lt;li&gt;二进制记法：10000000 00001011 00000011 00011111&lt;/li&gt;
  &lt;li&gt;十六进制记法：0X810B0BEF&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;在对IP进行数据存储的时候，我们经常会将IP地址的int型数值。&lt;/p&gt;
 &lt;pre&gt;import ipaddress

print(int(ipaddress.IPv4Address(&amp;apos;36.154.120.82&amp;apos;)))
print(str(ipaddress.IPv4Address(614103122)))
&lt;/pre&gt;
 &lt;h2&gt;IP地址分类&lt;/h2&gt;
 &lt;p&gt;每一类地址都由两个固定长度的字段组成：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;一个字段是网络号 net-id，它标志主机（或路由器）所连接到的网络&lt;/li&gt;
  &lt;li&gt;一个字段则是主机号 host-id，它标志该主机（或路由器）&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;img alt="" height="400" src="https://www.biaodianfu.com/wp-content/uploads/2021/08/ip-class.png" width="754"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;由于近年来已经广泛使用无分类 IP 地址进行路由选择，A 类、B 类和 C 类地址的区分已成为历史[RFC 1812]。所以这里不用过于记忆，只需了解 IP 分类的最初标准。&lt;/p&gt;
 &lt;h3&gt;子网掩码&lt;/h3&gt;
 &lt;p&gt;子网掩码必须结合IP地址一起使用，子网掩码的作用是将某个IP地址划分成网络地址和主机地址两部分。并且子网掩码设置不是任意的，如果设置过大（范围扩大），有可能因为错误的判断使数据不能正确到达目的主机，导致网络传输错误；如果网掩码设置得过小，会增加缺省网关的负担，造成网络效率下降。&lt;/p&gt;
 &lt;p&gt;因此，如果网络的规模不超过254台主机，采用“255.255.255.0”作为子网掩码就可以了，现在多数的局域网都不会超过这个数字，所以这是最常用的IP地址子网掩码。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="128" src="https://www.biaodianfu.com/wp-content/uploads/2021/08/mask.jpg" width="320"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;当一个网段的掩码为255.255.255.0的时候，通常IP地址的最后一位不可以是255或0。因为0是网段的网络地址，而255是网段的广播地址，不能分配给主机。那什么情况下可以是255或0，假设一个网段为155.23.0.0，它的掩码是255.255.254.0，那它的主机可用地址范围是155.23.0.1到155.23.1.254，即155.23.1.0和155.23.0.255这两个地址是可用的。&lt;/p&gt;
 &lt;p&gt;子网掩码决定了可用的主机数量有多少，以及ip是否在同一个网段，举两个例子：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;以网段168.0.0为例：当子网掩码为255.255.255.0时，可用IP为192.168.0.1-192.168.0.254（255为广播地址），子网掩码为255.255.0.0时，可用IP为192.168.0.0-192.168.255.254。&lt;/li&gt;
  &lt;li&gt;以168.10.1和192.168.100.1为例：当子网掩码为255.255.255.0时，他们不在一个网段，子网掩码为255.255.0.0时，他们在同一个网段。所以多个IP地址是否在同一个网段以及该网段有多少个可用的IP地址，由子网掩码决定。&lt;/li&gt;
&lt;/ul&gt;
 &lt;h2&gt;无分类编址CIDR&lt;/h2&gt;
 &lt;p&gt;一个IP地址包含两部分：标识网络的前缀和紧接着的在这个网络内的主机地址。在之前的分类网络中，IP地址的分配把IP地址的32位按每8位为一段分开。这使得前缀必须为8，16或者24位。因此，可分配的最小的地址块有256（24位前缀，8位主机地址，2^8=256）个地址，而这对大多数企业来说太少了。大一点的地址块包含65536（16位前缀，16位主机，2^16=65536）个地址，而这对大公司来说都太多了。这导致不能充分使用IP地址和在路由上的不便，因为大量的需要单独路由的小型网络（C类网络）因在地域上分得很开而很难进行聚合路由，于是给路由设备增加了很多负担。&lt;/p&gt;
 &lt;p&gt;无类别域间路由是基于可变长子网掩码（VLSM）来进行任意长度的前缀的分配的。在RFC 950（1985）中有关于可变长子网掩码的说明。CIDR包括：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;指定任意长度的前缀的可变长子网掩码技术。遵从CIDR规则的地址有一个后缀说明前缀的位数，例如：168.0.0/16。这使得对日益缺乏的IPv4地址的使用更加有效。&lt;/li&gt;
  &lt;li&gt;将多个连续的前缀聚合成超网，以及，在互联网中，只要有可能，就显示为一个聚合的网络，因此在总体上可以减少路由表的表项数目。聚合使得互联网的路由表不用分为多级，并通过VLSM逆转“划分子网”的过程。&lt;/li&gt;
  &lt;li&gt;根据机构的实际需要和短期预期需要而不是分类网络中所限定的过大或过小的地址块来管理IP地址的分配的过程。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;CIDR用可变长子网掩码 （VLSM,Variable Length Subnet Masking），根据各人需要来分配IP地址，而不是按照一个全网络约定的规则。所以，网络/主机的划分可以在地址内的任意位置进行。这个划分可以是递归进行的，即通过增加掩码位数，来使一部分地址被继续分为更小的部分。整个互联网现在都在使用CIDR/VLSM网络地址。除此之外，CIDR也应用在其他方面，尤其是大型私人网络。在普通大小的局域网里则较少应用，因为这些局域网一般使用私有网络。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="229" src="https://www.biaodianfu.com/wp-content/uploads/2021/08/vlsm.png" width="760"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;如何理解可变长子网掩码？假设IP地址是192.168.1.0~192.168.1.255，使用255.255.255.0即可实现。但如果现在需要将这些IP分为四份，分配给不同的网络：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;168.1.0-192.168.1.63&lt;/li&gt;
  &lt;li&gt;168.1.64-192.168.1.127&lt;/li&gt;
  &lt;li&gt;168.1.128 -192.168.1.191&lt;/li&gt;
  &lt;li&gt;168.1.192-192.168.1.255&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;用可变长子网掩码表示就是：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;168.1.0/26&lt;/li&gt;
  &lt;li&gt;168.1.64/26&lt;/li&gt;
  &lt;li&gt;168.1.128/26&lt;/li&gt;
  &lt;li&gt;168.1.192/26&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt; &lt;/p&gt;
 &lt;p&gt;这里的/26，代表的是11111111-11111111-11111111-11000000,即26个1，转化为IP就是255.255.255.192。&lt;/p&gt;
 &lt;p&gt;192.168.1.0对应的二进制是：11000000.10101000.00000001.00000000，在子网掩码下，前面26位保持不变，终止IP为：11000000.10101000.00000001.00111111，即192.168.1.63&lt;/p&gt;
 &lt;h2&gt;内网IP与公网IP&lt;/h2&gt;
 &lt;p&gt;内网IP地址、外网IP地址这个概念并不是固定的，而是相对的。如果用私有IP、公网IP或者局域网IP、互联网IP来理解就容易多了。看网络习惯书籍无法理解很多原因是因为教科书太古老，不与时俱进造成的。几乎所有的教科书都会告诉大家私有IP有3种：A类10.0.0.0～10.255.255.255，B类172.16.0.0～172.31.255.255，C类192.168.0.0～192.168.255.255。&lt;/p&gt;
 &lt;table width="966"&gt;

  &lt;tr&gt;
   &lt;td width="154"&gt;RFC1918 规定区块名&lt;/td&gt;
   &lt;td width="239"&gt;IP地址区块&lt;/td&gt;
   &lt;td width="98"&gt;IP数量&lt;/td&gt;
   &lt;td width="151"&gt;分类网络 说明&lt;/td&gt;
   &lt;td width="225"&gt;最大CIDR区块 （子网掩码）&lt;/td&gt;
   &lt;td width="98"&gt;主机端位长&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="154"&gt;24位区块&lt;/td&gt;
   &lt;td width="239"&gt;10.0.0.0 – 10.255.255.255&lt;/td&gt;
   &lt;td width="98"&gt;16,777,216&lt;/td&gt;
   &lt;td width="151"&gt;单个A类网络&lt;/td&gt;
   &lt;td width="225"&gt;10.0.0.0/8 (255.0.0.0)&lt;/td&gt;
   &lt;td width="98"&gt;24位&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="154"&gt;20位区块&lt;/td&gt;
   &lt;td width="239"&gt;172.16.0.0 – 172.31.255.255&lt;/td&gt;
   &lt;td width="98"&gt;1,048,576&lt;/td&gt;
   &lt;td width="151"&gt;16个连续B类网络&lt;/td&gt;
   &lt;td width="225"&gt;172.16.0.0/12 (255.240.0.0)&lt;/td&gt;
   &lt;td width="98"&gt;20位&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="154"&gt;16位区块&lt;/td&gt;
   &lt;td width="239"&gt;192.168.0.0 – 192.168.255.255&lt;/td&gt;
   &lt;td width="98"&gt;65,536&lt;/td&gt;
   &lt;td width="151"&gt;256个连续C类网络&lt;/td&gt;
   &lt;td width="225"&gt;192.168.0.0/16 (255.255.0.0)&lt;/td&gt;
   &lt;td width="98"&gt;16位&lt;/td&gt;
&lt;/tr&gt;

&lt;/table&gt;
 &lt;p&gt;事实上远远不止：&lt;/p&gt;
 &lt;pre&gt;Address Block                    Name                              RFC                       
0.0.0.0/8                        &amp;quot;This host on this network&amp;quot;       [RFC1122], section 3.2.1.3
10.0.0.0/8                       Private-Use                       [RFC1918]                 
100.64.0.0/10                    Shared Address Space              [RFC6598]                 
127.0.0.0/8                      Loopback                          [RFC1122], section 3.2.1.3
169.254.0.0/16                   Link Local                        [RFC3927]                 
172.16.0.0/12                    Private-Use                       [RFC1918]                 
192.0.0.0/24[2]                  IETF Protocol Assignments         [RFC6890], section 2.1    
192.0.0.0/29                     IPv4 Service Continuity Prefix    [RFC7335]                 
192.0.0.8/32                     IPv4 dummy address                [RFC7600]                 
192.0.0.9/32                     Port Control Protocol Anycast     [RFC-ietf-pcp-anycast-08] 
192.0.0.170/32, 192.0.0.171/32   NAT64/DNS64 Discovery             [RFC7050], section 2.2    
192.0.2.0/24                     Documentation (TEST-NET-1)        [RFC5737]                 
192.31.196.0/24                  AS112-v4                          [RFC7535]                 
192.52.193.0/24                  AMT                               [RFC7450]                 
192.88.99.0/24                   Deprecated (6to4 Relay Anycast)   [RFC7526]                 
192.168.0.0/16                   Private-Use                       [RFC1918]                 
192.175.48.0/24                  Direct Delegation AS112 Service   [RFC7534]                 
198.18.0.0/15                    Benchmarking                      [RFC2544]                 
198.51.100.0/24                  Documentation (TEST-NET-2)        [RFC5737]                 
203.0.113.0/24                   Documentation (TEST-NET-3)        [RFC5737]                 
240.0.0.0/4                      Reserved                          [RFC1112], section 4      
255.255.255.255/32               Limited Broadcast                 [RFC919], section 7 
&lt;/pre&gt;
 &lt;p&gt;另外，部分阿里云服务器使用路由跟踪显示的defense.gov (DoD Network) IP地址，其实是阿里使用了美国国防部的公网IP当内网IP用。原因是无法从公网访问美国国防部的这些IP段，所以不会造成IP混乱。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="336" src="https://www.biaodianfu.com/wp-content/uploads/2021/08/dod.png" width="760"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h2&gt;IP地址匮乏问题&lt;/h2&gt;
 &lt;p&gt;中国所获得的IPv4地址情况：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;中国大陆IPv4地址总数为74,391,296个，合4A+111B+31C&lt;/li&gt;
  &lt;li&gt;中国台湾IPv4地址总数为16,280,064个，合248B+106C&lt;/li&gt;
  &lt;li&gt;中国香港IPv4地址总数为06,288,640个，合95B+245C&lt;/li&gt;
  &lt;li&gt;中国澳门IPv4地址总数为00,144,640个，合2B+53C&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;不管是电脑、手机号是网站服务器都需要使用IP，以上的IP远远不够。这也就导致了我们再也不能像十几年前一样通过简单的统计IP量来统计用户量。&lt;/p&gt;
 &lt;h3&gt;3G/4G网络下的IP地址&lt;/h3&gt;
 &lt;p&gt;用手机本身的流量上网的话，IP是随着你手机信号连接的基站的变化而变化的。手机从基站接入，传递数据给基站，然后基站传递给上层BSC，PCU从中剥离出分组业务数据，传递给SGSN，最后通过SGSN经由GGSN这个网关将数据汇入到茫茫internet中。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="389" src="https://www.biaodianfu.com/wp-content/uploads/2021/08/wireless.jpg" width="540"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;手机使用3g/4g信号上网，在同一区域只有1个IP，如果您去了别的城市用3g/4g信号上网则会变动IP。通过手机数据流量上网，即使是在异地，也会分配手机卡归属地的IP地址。比如手机卡归属地是北京，到了山东之后连接数据流量，这时候的IP地址还会是北京的IP。无论手机漫游到天涯海角，手机漫游所在地的SGSN根据手机号码，可以查询到手机的归属地，还可以查询到该归属地的GGSN的IP地址，然后使用GTP(GPRS Tunnel Protocol)在漫游地与归属地建立一个GTP隧道，如下所示：漫游地SGSN&amp;lt;——- GTP隧道—— &amp;gt; 归属地GGSN&lt;/p&gt;
 &lt;p&gt;漫游地把该手机所有的数据流量统统使用该GTP隧道，流到归属地的GGSN，归属地再剥离掉GTP隧道头，得到用户的报文，再做进一步的处理：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;如果是手机请求分配IP地址等参数，GGSN将IP地址等参数返回给手机用户，通过GTP隧道传输&lt;/li&gt;
  &lt;li&gt;如果是访问互联网的流量，GGSN完成NAT，将用户流量发送到Internet&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;这样做的好处是，所有用户的数据流量都经过归属地的计费系统，方便统一计费。&lt;/p&gt;
 &lt;p&gt;举例：苏州移动用户的无线网络IP均为：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;136.67.48~117.136.67.95&lt;/li&gt;
  &lt;li&gt;136.67.160~117.136.255&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;无论苏州移动的用户漫游到哪里，他的IP永远是上面几个。（由于目前已经取消了漫游费，所以手机归属地并不一定是常住地）&lt;/p&gt;
 &lt;h3&gt;宽带/ADSL网络下的IP地址&lt;/h3&gt;
 &lt;p&gt;以前，ADSL或宽带都是在连接时都会分配公网IP，但随着IPv4缺口的变大，很多运营商都不再提供公网IP，类似长城光缆这种二级运营商只给内网IP。目前联通和电信也开始只提供内网IP，然后NAT后大家共享一个公网地址。而通常运营商NAT后分配的地址为：100.64.0.0/10&lt;/p&gt;
 &lt;h2&gt;IP地址归属地&lt;/h2&gt;
 &lt;p&gt;全球IP地址分配由IANA（Internet Assigned Numbers Authority）负责管理，官方网站是：Internet Assigned Numbers Authority但IANA不负责具体的地址分配，具体的地址分配由其下属的几个互联网中心管理，其中包括：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;APNIC（亚太互联网络信息中心）&lt;/li&gt;
  &lt;li&gt;AFRINIC（非洲互联网信息中心）&lt;/li&gt;
  &lt;li&gt;ARIN（北美互联网号码注册中心）&lt;/li&gt;
  &lt;li&gt;LACNIC（拉美互联网信息中心）&lt;/li&gt;
  &lt;li&gt;RIPE NCC（欧洲网络资讯中心）&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;分别负责具体的IP地址分配。理论上，所有IP地址的归属，都能从以上的几个中心网站上查到，其中APNIC的查询页面是：  &lt;a href="https://ftp.apnic.net/apnic/whois/apnic.db.inetnum.gz"&gt;APNIC – Query the APNIC Whois Database&lt;/a&gt;可以说，大多数IP数据库中的原始数据都是来自这里。但这里只能查询到IP地址申请人所描述的信息，实际上根本无法精确到具体的街道、校区、宿舍等。而且IANA只负责分配，不负责管理具体这个IP地址怎么用。所以，理论上说，完全精确的IP地址数据库是根本不存在的。需要注意的是：凡是那种有精确楼号、街道、校区的IP地址数据库，都不一定是准确，都是经过人工修正的，如果有一天互联网接入商更改了IP地址分配的策略，那么这种数据库就不准了。国内使用的最多的纯真IP数据库就是基于WHOIS数据库创建的。&lt;/p&gt;
 &lt;p&gt;目前国内绝大多数IP库都由WHOIS数据库作为基础数据来源。WHOIS数据仅表示某个IP被哪个机构注册，但无从知晓该IP被用在何处，这就导致许多非运营商自己注册的IP地址无法被正确分类。&lt;/p&gt;
 &lt;h3&gt;基于边界网关的IP数据&lt;/h3&gt;
 &lt;p&gt;随着互联网规模的增加，为了处理大批量的路由数据，边界网关协议（即BGP，下同）应运而生，是互联网的基础协议之一。为了保证了全球网络路由的可达性，但凡需要在互联网中注册一个IP（段），都需要借助BGP协议对外广播，这样互联网中的其他自治域才能学习到这段地址的路由信息，其它主机才能成功访问这个IP（段）。因此可以说，BGP数据是最适合分析运营商IP地址的数据来源之一。&lt;/p&gt;
 &lt;p&gt;但是，目前国内绝大多数IP库都由WHOIS数据库作为基础数据来源。WHOIS数据仅表示某个IP被哪个机构注册，但无从知晓该IP被用在何处，这就导致许多非运营商自己注册的IP地址无法被正确分类。ipip.net是最早开始做BGP/ASN数据分析的公司之一，数据准确性甩其它库几条街。但很可惜是，ipip.net作为商业公司，绝大多数高质量的IP数据都是收费的，且价格不菲。但有类似的  &lt;a href="https://github.com/gaoyifan/china-operator-ip"&gt;开源版本&lt;/a&gt;。&lt;/p&gt;
 &lt;h2&gt;服务器如何获取IP&lt;/h2&gt;
 &lt;p&gt;在讨论获取客户端IP 地址前，我们首先下弄明白的是以下三个的具体含义：REMOTE_ADDR，HTTP_CLIENT_IP，HTTP_X_FORWARDED_FOR&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;REMOTE_ADDR：访问端（有可能是用户，有可能是代理的）IP&lt;/li&gt;
  &lt;li&gt;HTTP_CLIENT_IP：代理端的（有可能存在，可伪造）&lt;/li&gt;
  &lt;li&gt;HTTP_X_FORWARDED_FOR：用户是在哪个IP使用的代理（有可能存在，也可以伪造）&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;strong&gt;没有使用代理服务器的情况：&lt;/strong&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;REMOTE_ADDR = 您的 IP&lt;/li&gt;
  &lt;li&gt;HTTP_CLIENT_IP = 没数值或不显示&lt;/li&gt;
  &lt;li&gt;HTTP_X_FORWARDED_FOR = 没数值或不显示&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;strong&gt;使用透明代理服务器的情况：Transparent Proxies&lt;/strong&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;REMOTE_ADDR = 最后一个代理服务器 IP&lt;/li&gt;
  &lt;li&gt;HTTP_CLIENT_IP = 代理服务器 IP&lt;/li&gt;
  &lt;li&gt;HTTP_X_FORWARDED_FOR = 您的真实 IP ，经过多个代理服务器时，这个值类似如下：98.182.163, 203.98.182.163, 203.129.72.215。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;这类代理服务器还是将您的信息转发给您的访问对象，无法达到隐藏真实身份的目的。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;使用普通匿名代理服务器的情况：Anonymous Proxies&lt;/strong&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;REMOTE_ADDR = 最后一个代理服务器 IP&lt;/li&gt;
  &lt;li&gt;HTTP_CLIENT_IP = 代理服务器 IP&lt;/li&gt;
  &lt;li&gt;HTTP_X_FORWARDED_FOR = 代理服务器 IP ，经过多个代理服务器时，这个值类似如下：98.182.163, 203.98.182.163, 203.129.72.215。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;隐藏了您的真实IP，但是向访问对象透露了您是使用代理服务器访问他们的。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;使用欺骗性代理服务器的情况：Distorting Proxies&lt;/strong&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;REMOTE_ADDR = 代理服务器 IP&lt;/li&gt;
  &lt;li&gt;HTTP_CLIENT_IP = 代理服务器 IP&lt;/li&gt;
  &lt;li&gt;HTTP_X_FORWARDED_FOR = 随机的 IP ，经过多个代理服务器时，这个值类似如下：98.182.163, 203.98.182.163, 203.129.72.215。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;告诉了访问对象您使用了代理服务器，但编造了一个虚假的随机IP代替您的真实IP欺骗它。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;使用高匿名代理服务器的情况：High Anonymity Proxies (Elite proxies)&lt;/strong&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;REMOTE_ADDR = 代理服务器 IP&lt;/li&gt;
  &lt;li&gt;HTTP_CLIENT_IP = 没数值或不显示&lt;/li&gt;
  &lt;li&gt;HTTP_X_FORWARDED_FOR = 没数值或不显示 ，经过多个代理服务器时，这个值类似如下：98.182.163, 203.98.182.163, 203.129.72.215。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;完全用代理服务器的信息替代了您的所有信息，就像您就是完全使用那台代理服务器直接访问对象。&lt;/p&gt;
 &lt;p&gt;备注：服务器如果做了负载均衡，则需要在在获取IP时使用HTTP_X_FORWARDED_FOR获取原IP。&lt;/p&gt;
 &lt;div&gt;
  &lt;h3&gt;相关文章:&lt;/h3&gt;  &lt;ol&gt;
   &lt;li&gt;    &lt;a href="https://www.biaodianfu.com/chi-square-test.html" rel="bookmark" title="&amp;#20551;&amp;#35774;&amp;#26816;&amp;#39564;&amp;#20043;&amp;#21345;&amp;#26041;&amp;#26816;&amp;#39564;"&gt;假设检验之卡方检验 &lt;/a&gt;&lt;/li&gt;
   &lt;li&gt;    &lt;a href="https://www.biaodianfu.com/tencent-word-embedding.html" rel="bookmark" title="&amp;#33150;&amp;#35759;AI Lab&amp;#20013;&amp;#25991;&amp;#35789;&amp;#21521;&amp;#37327;&amp;#25968;&amp;#25454;&amp;#20351;&amp;#29992;"&gt;腾讯AI Lab中文词向量数据使用 &lt;/a&gt;&lt;/li&gt;
   &lt;li&gt;    &lt;a href="https://www.biaodianfu.com/firewalld.html" rel="bookmark" title="Linux&amp;#38450;&amp;#28779;&amp;#22681;FirewallD&amp;#19982;iptables"&gt;Linux防火墙FirewallD与iptables &lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>数据 术→技巧 IP</category>
      <guid isPermaLink="true">https://itindex.net/detail/61737-%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90-%E7%9F%A5%E9%81%93-ip</guid>
      <pubDate>Wed, 25 Aug 2021 21:45:06 CST</pubDate>
    </item>
    <item>
      <title>Clickhouse 在日志存储与分析方面作为 ElasticSearch 和 MySQL 的替代方案</title>
      <link>https://itindex.net/detail/61724-clickhouse-%E6%97%A5%E5%BF%97-%E5%88%86%E6%9E%90</link>
      <description>&lt;h1&gt;    &lt;a href="https://github.com/gocn/translator/blob/master/2021/w10_Clickhouse_for_log_storage_and_analysis_in_2021.md#2021&amp;#24180;clickhouse-&amp;#22312;&amp;#26085;&amp;#24535;&amp;#23384;&amp;#20648;&amp;#19982;&amp;#20998;&amp;#26512;&amp;#26041;&amp;#38754;&amp;#20316;&amp;#20026;-elasticsearch-&amp;#21644;-mysql-&amp;#30340;&amp;#26367;&amp;#20195;&amp;#26041;&amp;#26696;"&gt;&lt;/a&gt;2021年，Clickhouse 在日志存储与分析方面作为 ElasticSearch 和 MySQL 的替代方案&lt;/h1&gt;  &lt;ul&gt;    &lt;li&gt;原文地址：      &lt;a href="https://pixeljets.com/blog/clickhouse-vs-elasticsearch/" rel="nofollow"&gt;https://pixeljets.com/blog/clickhouse-vs-elasticsearch/&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;原文作者：Anton Sidashin&lt;/li&gt;    &lt;li&gt;本文永久链接：      &lt;a href="https://github.com/gocn/translator/blob/master/2021/w10_Clickhouse_for_log_storage_and_analysis_in_2021.md"&gt;https://github.com/gocn/translator/blob/master/2021/w10_Clickhouse_for_log_storage_and_analysis_in_2021.md&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;译者：      &lt;a href="https://github.com/fivezh"&gt;Fivezh&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;校对：      &lt;a href="https://github.com/watermelo"&gt;咔叽咔叽&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;2018年，我写过一篇    &lt;a href="https://pixeljets.com/blog/clickhouse-as-a-replacement-for-elk-big-query-and-timescaledb/" rel="nofollow"&gt;关于Clickhouse的文章&lt;/a&gt;，这段内容在互联网上仍然很流行，甚至被多次翻译。现在已经过去两年多，同时 Clickhouse 的开发节奏    &lt;a href="https://github.com/ClickHouse/ClickHouse/pulse/monthly"&gt;仍然活跃&lt;/a&gt;: 上个月有 800 个合并的 PR ! 这难道没让你大吃一惊吗？或许需要一小时才能查看完这些变更日志和新功能描述，例如 2020 年：    &lt;a href="https://clickhouse.tech/docs/en/whats-new/changelog/2020/" rel="nofollow"&gt;https://clickhouse.tech/docs/en/whats-new/changelog/2020/&lt;/a&gt;&lt;/p&gt;  &lt;blockquote&gt;    &lt;p&gt;为了公平对比，      &lt;a href="https://github.com/elastic/elasticsearch/pulse/monthly"&gt;ElasticSearch仓库在同一个月有惊人的1076个合并PR&lt;/a&gt;，同时在功能性方面，它的节奏也      &lt;em&gt;非常&lt;/em&gt;让人印象深刻！&lt;/p&gt;&lt;/blockquote&gt;  &lt;p&gt;我们正在将 Clickhouse 用于    &lt;a href="https://apiroad.net/" rel="nofollow"&gt;ApiRoad.net&lt;/a&gt;项目（这是一个 API 市场，开发人员出售其 API ，目前活跃开发中）的日志存储和分析，到目前为止，我们对效果感到满意。作为一名 API 开发人员， HTTP 请求/响应周期的可观察性和可分析性对于维护服务质量和快速发现 bug 非常重要，这一点对于纯 API 服务尤其如此。&lt;/p&gt;  &lt;p&gt;    &lt;a href="https://github.com/gocn/translator/blob/master/static/images/w10_Clickhouse_for_log_storage_and_analysis_in_2021/demo2--1-.gif" rel="noopener noreferrer" target="_blank"&gt;      &lt;img alt="img" src="https://github.com/gocn/translator/raw/master/static/images/w10_Clickhouse_for_log_storage_and_analysis_in_2021/demo2--1-.gif"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;我们也在其他项目上使用 ELK（ ElasticSearch，Logstash，filebeat，Kibana）技术栈用于同样目的：获取 HTTP 和邮件日志，使用 Kibana 进行事后的分析与搜索。&lt;/p&gt;  &lt;p&gt;当然，我们也无处不在的使用 MySQL ！&lt;/p&gt;  &lt;p&gt;这篇文章主要介绍我们选择    &lt;code&gt;Clickhouse&lt;/code&gt;而不是    &lt;code&gt;ElasticSearch&lt;/code&gt;（或    &lt;code&gt;MySQL&lt;/code&gt;）作为基础数据（服务请求日志）存储解决方案的主要原因（说明：出于    &lt;code&gt;OLTP&lt;/code&gt;的目的，我们仍会处使用    &lt;code&gt;MySQL&lt;/code&gt;）。&lt;/p&gt;  &lt;h2&gt;    &lt;a href="https://github.com/gocn/translator/blob/master/2021/w10_Clickhouse_for_log_storage_and_analysis_in_2021.md#1-sql-&amp;#25903;&amp;#25345;-json-&amp;#21644;-&amp;#25968;&amp;#32452;&amp;#20316;&amp;#20026;&amp;#19968;&amp;#31561;&amp;#20844;&amp;#27665;"&gt;&lt;/a&gt;1. SQL 支持, JSON 和 数组作为一等公民&lt;/h2&gt;  &lt;p&gt;    &lt;code&gt;SQL&lt;/code&gt;是用于数据分析的理想语言。我喜欢    &lt;code&gt;SQL&lt;/code&gt;查询语言，    &lt;code&gt;SQL schema&lt;/code&gt;是无趣技术的完美示例，我建议在 99％ 项目中使用它从数据中发掘真相：项目代码不完美，而如果你的数据库是结构化 schema 存储的，就可以相对轻松地进行改造。反言之，如果数据库数据是一个巨大的    &lt;code&gt;JSON&lt;/code&gt;块（    &lt;code&gt;NoSQL&lt;/code&gt;），没有人可以完全掌握数据的清晰结构，那么重构将会遇到更多麻烦。&lt;/p&gt;  &lt;p&gt;尤其是在使用    &lt;code&gt;MongoDB&lt;/code&gt;的老项目中，我看到了这种情况。每一次新的分析报告和每一个涉及数据迁移的重构都无比痛苦。如果是新建一个这样的项目还算有趣——因为不需要花太多时间详细设计项目结构，只要“看看它是如何能跑起来”就行，但是维护它将会非常无趣！&lt;/p&gt;  &lt;p&gt;但是，重要的是要注意，这种经验法则（“使用严格schema”）对于日志存储用例而言并不那么关键。这就是 ElasticSearch 如此成功、具有许多优势和灵活架构的原因。&lt;/p&gt;  &lt;p&gt;继续回到    &lt;code&gt;JSON&lt;/code&gt;，就    &lt;code&gt;JSON&lt;/code&gt;数据的查询、语法而言，传统的关系型数据库仍在追赶    &lt;code&gt;NoSQL&lt;/code&gt;数据库，我们必须承认    &lt;code&gt;JSON&lt;/code&gt;对动态结构化数据（如日志存储）而言，是非常方便的格式。&lt;/p&gt;  &lt;p&gt;    &lt;code&gt;Clickhouse&lt;/code&gt;是一种在 JSON 已发展存在后（不同于 MySQL 和 Postgres ）设计和构建的现代引擎。由于    &lt;code&gt;Clickhouse&lt;/code&gt;不必背负这些流行的 RDBMS 向后兼容性和严格 SQL 标准，    &lt;code&gt;Clickhouse&lt;/code&gt;团队可以在功能和改进方面更快速发展，实际上也的确是。    &lt;code&gt;Clickhouse&lt;/code&gt;的开发人员有更多机会在严格    &lt;code&gt;schema&lt;/code&gt;与    &lt;code&gt;JSON&lt;/code&gt;的灵活性之间达到最佳平衡，我认为他们在这方面做得很好。    &lt;code&gt;Clickhouse&lt;/code&gt;试图在分析领域与    &lt;code&gt;Google Big Query&lt;/code&gt;及其他主要对手竞争，因此它对“标准”    &lt;code&gt;SQL&lt;/code&gt;进行了许多改进，这使其语法成为了杀手锏，在许多用于分析和计算目的情形下相比传统    &lt;code&gt;RDBMS&lt;/code&gt;更多优势。&lt;/p&gt;  &lt;p&gt;一些基本的例子：&lt;/p&gt;  &lt;p&gt;在    &lt;code&gt;MySQL&lt;/code&gt;中，你可以提取    &lt;code&gt;JSON&lt;/code&gt;字段，但是复杂的 JSON 处理仅在最新版本（    &lt;a href="https://mysqlserverteam.com/json_table-the-best-of-both-worlds/" rel="nofollow"&gt;具有 JSON_TABLE 函数的版本8&lt;/a&gt;）中可用。在    &lt;code&gt;PosgreSQL&lt;/code&gt;中，情况甚至更糟-在 PostgreSQL 12之前还没有直接的 JSON_TABLE 替代方案！&lt;/p&gt;  &lt;p&gt;而这与    &lt;code&gt;Clickhouse&lt;/code&gt;的 JSON 及相关数组功能相比，也仅仅领先一小步。数组功能相关链接：&lt;/p&gt;  &lt;ul&gt;    &lt;li&gt;      &lt;a href="https://clickhouse.tech/docs/en/sql-reference/statements/select/array-join/" rel="nofollow"&gt;arrayJoin&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;      &lt;a href="https://clickhouse.tech/docs/en/sql-reference/aggregate-functions/reference/grouparray/" rel="nofollow"&gt;groupArray&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;      &lt;a href="https://clickhouse.tech/docs/en/sql-reference/functions/array-functions/#array-map" rel="nofollow"&gt;arrayMap&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;      &lt;a href="https://clickhouse.tech/docs/en/sql-reference/functions/array-functions/#array-filter" rel="nofollow"&gt;arrayFilter&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;在很多情况下，PostgreSQL 的    &lt;code&gt;generate_series()&lt;/code&gt;功能很有用。来自 ApiRoad 的一个具体示例：我们需要在chart.js 时间轴上映射请求数量。每天进行常规的    &lt;code&gt;SELECT .. group by day&lt;/code&gt;，但如果某些天没有任何查询时，就会出现间隙。但我们并不想要间隙，因此需要补零，对吧？ 这正是 PostgreSQL 中    &lt;code&gt;generate_series()&lt;/code&gt;函数有用的地方。在 MySQL 中，    &lt;a href="https://ubiq.co/database-blog/fill-missing-dates-in-mysql/" rel="nofollow"&gt;推荐按日期创建表并进行连接&lt;/a&gt;，不太优雅了吧？&lt;/p&gt;  &lt;p&gt;如下是    &lt;code&gt;ElasticSearch&lt;/code&gt;中如何解决：    &lt;a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-datehistogram-aggregation.html#_missing_value_2" rel="nofollow"&gt;https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-datehistogram-aggregation.html#_missing_value_2&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;关于查询语言：我对 ElasticSearch 的 Lucene 语法、HTTP API 以及为检索数据而编写 json 等几个方面仍然不满意。而 SQL 将是我的首选。&lt;/p&gt;  &lt;p&gt;这是    &lt;code&gt;Clickhouse&lt;/code&gt;用于日期差填充时的解决方案：&lt;/p&gt;  &lt;div&gt;    &lt;pre&gt;      &lt;code&gt;SELECT a.timePeriod as t, b.count as c from (
	with (select toUInt32(dateDiff(&amp;apos;day&amp;apos;, [START_DATE], [END_DATE])) ) 
		as diffInTimeUnits
                
	select arrayJoin(arrayMap(x -&amp;gt; (toDate(addDays([START_DATE], x))), 			range(0, diffInTimeUnits+1))) as timePeriod ) a
            
LEFT JOIN 
            
	(select count(*) as count, toDate(toStartOfDay(started_at)) as timePeriod from logs WHERE 
		[CONDITIONS]
		GROUP BY toStartOfDay(started_at)) b on a.timePeriod=b.timePeriod&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;p&gt;在这里，我们通过    &lt;code&gt;lambda&lt;/code&gt;函数和循环生成一个虚拟表，然后再与按天分组的日志表进行左连接。&lt;/p&gt;  &lt;p&gt;我认为    &lt;code&gt;arrayJoin&lt;/code&gt;+    &lt;code&gt;arrayMap&lt;/code&gt;+    &lt;code&gt;range&lt;/code&gt;函数方式相比    &lt;code&gt;generate_series()&lt;/code&gt;有更多灵活性。通过    &lt;code&gt;WITH FILL&lt;/code&gt;关键词可用于更简洁的语法。&lt;/p&gt;  &lt;h2&gt;    &lt;a href="https://github.com/gocn/translator/blob/master/2021/w10_Clickhouse_for_log_storage_and_analysis_in_2021.md#2-&amp;#28789;&amp;#27963;&amp;#30340;schema---&amp;#20294;&amp;#38656;&amp;#35201;&amp;#26102;&amp;#20063;&amp;#21487;&amp;#20197;&amp;#20005;&amp;#26684;"&gt;&lt;/a&gt;2. 灵活的schema - 但需要时也可以严格&lt;/h2&gt;  &lt;p&gt;对于日志存储任务来说，数据schema通常会在项目生命周期中变化，    &lt;code&gt;ElasticSearch&lt;/code&gt;允许将巨大的 JSON 块放入索引中，然后找出字段类型和索引部分。    &lt;code&gt;Clickhouse&lt;/code&gt;也同样支持这种方法。可以将数据放入 JSON 字段并相对快速地进行过滤，尽管在TB级上并不会很快。然后，当你经常有在特定字段查询需要时，便可以在日志表中添加物化列（materialized columns），这些列能够即时的从 JSON 中提取值。对TB级数据查询时会更加快速。&lt;/p&gt;  &lt;p&gt;我推荐 Altinity 关于日志存储中 JSON 与 表格式对比的专题视频：&lt;/p&gt;  &lt;p&gt;    &lt;a href="https://youtu.be/pZkKsfr8n3M" rel="nofollow"&gt;https://youtu.be/pZkKsfr8n3M&lt;/a&gt;&lt;/p&gt;  &lt;h2&gt;    &lt;a href="https://github.com/gocn/translator/blob/master/2021/w10_Clickhouse_for_log_storage_and_analysis_in_2021.md#3-&amp;#23384;&amp;#20648;&amp;#21644;&amp;#26597;&amp;#35810;&amp;#25928;&amp;#29575;"&gt;&lt;/a&gt;3. 存储和查询效率&lt;/h2&gt;  &lt;p&gt;Clickhouse 在    &lt;code&gt;SELECT&lt;/code&gt;查询时非常快速，这在    &lt;a href="https://pixeljets.com/blog/clickhouse-as-a-replacement-for-elk-big-query-and-timescaledb/" rel="nofollow"&gt;之前文章中已做讨论&lt;/a&gt;。&lt;/p&gt;  &lt;p&gt;有趣的是，    &lt;a href="https://youtu.be/pZkKsfr8n3M?t=2479" rel="nofollow"&gt;      &lt;strong&gt;有证据表明&lt;/strong&gt;&lt;/a&gt;，与 ElasticSearch 相比，Clickhouse 的存储效率可以高出 5-6 倍，而从查询的角度看，它的字面速度也要快一个数量级。还有    &lt;a href="https://habr.com/ru/company/mkb/blog/472912/" rel="nofollow"&gt;      &lt;strong&gt;另一个例子&lt;/strong&gt;&lt;/a&gt;。&lt;/p&gt;  &lt;p&gt;我相信二者没有直接的基准对比测试，至少我还未找到，因为 Clickhouse 和 ElasticSearch 在查询语法、缓存实现及整体特性上都非常不同。&lt;/p&gt;  &lt;p&gt;MySQL 时，在仅有1亿行日志数据的表上进行如索引失效的非最优查询，都会使服务器陷入缓慢并产生内存交换，因此 MySQL 并不适合大型日志查询。但就存储而言，压缩的 InnoDB 表并没有那么糟糕。由于其基于行的特性，与 Clickhouse 相比，数据压缩方面的情况要差得多（抱歉，这次没有相关链接），但是它仍然可以在不显著降低性能的情况下设法降低成本 。因此在某些情况下，对于少量日志来说，我们依然可以使用 InnoDB 表。&lt;/p&gt;  &lt;h2&gt;    &lt;a href="https://github.com/gocn/translator/blob/master/2021/w10_Clickhouse_for_log_storage_and_analysis_in_2021.md#4-&amp;#32479;&amp;#35745;&amp;#20989;&amp;#25968;"&gt;&lt;/a&gt;4. 统计函数&lt;/h2&gt;  &lt;p&gt;在 Clickhouse 中，很容易计算 404 查询的中位数和 99 分位的耗时：&lt;/p&gt;  &lt;div&gt;    &lt;pre&gt;      &lt;code&gt;SELECT count(*) as cnt, 
  quantileTiming(0.5)(duration) as duration_median, 
  quantileTiming(0.9)(duration) as duration_90th, 
  quantileTiming(0.99)(duration) as duration_99th
  FROM logs WHERE status=404&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;p&gt;这里要注意    &lt;code&gt;quantileTiming&lt;/code&gt;函数的用法以及如何优雅地使用    &lt;a href="https://javascript.info/currying-partials" rel="nofollow"&gt;currying&lt;/a&gt;。Clickhouse 具有通用的分位数    &lt;code&gt;quantile&lt;/code&gt;函数！ 但是    &lt;code&gt;quantileTiming&lt;/code&gt;已    &lt;a href="https://clickhouse.tech/docs/en/sql-reference/aggregate-functions/reference/quantiletiming/#quantiletiming" rel="nofollow"&gt;针对序列化数据进行了优化，比如加载网页时间或后端响应时间的日志&lt;/a&gt;.&lt;/p&gt;  &lt;p&gt;还有更多类似的，需要加权算术平均数吗？要计算线性回归吗？这很容易，只需使用专门的函数就可以。&lt;/p&gt;  &lt;p&gt;这是 Clickhouse 相关统计函数的完整列表：&lt;/p&gt;  &lt;p&gt;    &lt;a href="https://clickhouse.tech/docs/en/sql-reference/aggregate-functions/reference/" rel="nofollow"&gt;https://clickhouse.tech/docs/en/sql-reference/aggregate-functions/reference/&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;这些大部分在 MySQL 中都是有问题的。&lt;/p&gt;  &lt;p&gt;ElasticSearch 在这方面比 MySQL 好得多，它既具有分位数又具有加权中位数，但是它还没有线性回归。&lt;/p&gt;  &lt;h2&gt;    &lt;a href="https://github.com/gocn/translator/blob/master/2021/w10_Clickhouse_for_log_storage_and_analysis_in_2021.md#5-mysql-&amp;#21644;-clickhouse-&amp;#32039;&amp;#23494;&amp;#32467;&amp;#21512;"&gt;&lt;/a&gt;5. MySQL 和 Clickhouse 紧密结合&lt;/h2&gt;  &lt;p&gt;MySQL 和 Clickhouse 有多种级别的相互集成，这使它们最小化数据重复的情况下非常方便在一起使用：&lt;/p&gt;  &lt;ul&gt;    &lt;li&gt;      &lt;a href="https://clickhouse.tech/docs/en/sql-reference/dictionaries/external-dictionaries/external-dicts-dict-sources/#dicts-external_dicts_dict_sources-mysql" rel="nofollow"&gt;MySQL 作为外部词典&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;      &lt;a href="https://clickhouse.tech/docs/en/engines/database-engines/materialize-mysql/#materialize-mysql" rel="nofollow"&gt;通过binlog将 MySQL 数据镜像至 Clickhouse&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;      &lt;a href="https://clickhouse.tech/docs/en/engines/database-engines/mysql/" rel="nofollow"&gt;MySQL 数据库引擎&lt;/a&gt;- 和之前方法相似但更灵活，无需 binlog&lt;/li&gt;    &lt;li&gt;      &lt;a href="https://clickhouse.tech/docs/en/sql-reference/table-functions/mysql/" rel="nofollow"&gt;MySQL 表函数&lt;/a&gt;通过特定查询链接 MySQL 表&lt;/li&gt;    &lt;li&gt;      &lt;a href="https://clickhouse.tech/docs/en/engines/table-engines/integrations/mysql/" rel="nofollow"&gt;MySQL 表引擎&lt;/a&gt;在 CREATE TABLE 语句中静态描述特定表&lt;/li&gt;    &lt;li&gt;      &lt;a href="https://clickhouse.tech/docs/en/interfaces/mysql/" rel="nofollow"&gt;Clickhouse 使用 MySQL 协议&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;我不能肯定地说动态数据库和表引擎在    &lt;code&gt;JOIN&lt;/code&gt;上有多么快速和稳定，这肯定是需要基准测试的。但这个概念非常吸引人-你已经可以在 Clickhouse 数据库上完整地复制 MySQL 表 ，而不必处理缓存失效和重新设置索引。&lt;/p&gt;  &lt;p&gt;关于将 MySQL 与 Elasticsearch 结合使用，我的有限经验表明，这两种技术有太多不同。我的印象是他们彼此各说各话，并不会组合出现。所以我通常只需要把 ElasticSearch 需要索引的数据 JSON 化，然后发送到 ElasticSearch 。之后，MySQL 数据一些迁移或任何变更操作（    &lt;code&gt;UPDATE/REPLACE&lt;/code&gt;）之后，在 Elasticseach 端找出需要重新索引的部分。关于 MySQL 和 ElasticSearch 的数据同步，这是一篇    &lt;a href="https://www.elastic.co/blog/how-to-keep-elasticsearch-synchronized-with-a-relational-database-using-logstash" rel="nofollow"&gt;基于 Logstash 实现的文章&lt;/a&gt;。我不太喜欢 Logstash ，因为它的性能一般，对内存要求也很高，同时它也会成为系统中不稳定因素。对于使用 MySQL 的简单项目中，数据同步和索引往往是阻止我们使用 Elasticsearch 的因素。&lt;/p&gt;  &lt;h2&gt;    &lt;a href="https://github.com/gocn/translator/blob/master/2021/w10_Clickhouse_for_log_storage_and_analysis_in_2021.md#6-&amp;#26032;&amp;#29305;&amp;#24615;"&gt;&lt;/a&gt;6. 新特性&lt;/h2&gt;  &lt;p&gt;是否想要附加 S3 存储的    &lt;code&gt;CSV&lt;/code&gt;，并将其作为 Clickhouse 中的表？    &lt;a href="https://clickhouse.tech/docs/en/engines/table-engines/integrations/s3/" rel="nofollow"&gt;这非常简单&lt;/a&gt;。&lt;/p&gt;  &lt;p&gt;是否要更新或删除日志行以符合数据保护规范？ 现在，这很容易！&lt;/p&gt;  &lt;p&gt;在我 2018 年写第一篇文章时，Clickhouse 还没有简单的方法来删除或更新数据，这是一个真正的弊端。现在，这不再是问题。Clickhouse 利用自定义 SQL 语法删除数据行：&lt;/p&gt;  &lt;div&gt;    &lt;pre&gt;      &lt;code&gt;ALTER TABLE [db.]table [ON CLUSTER cluster] DELETE WHERE filter_expr&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;p&gt;原因很明确，对于 Clickhouse（和其他列式数据库）来说，删除仍然是一项相当昂贵的操作，因此最好不要生产环境频繁使用。&lt;/p&gt;  &lt;h2&gt;    &lt;a href="https://github.com/gocn/translator/blob/master/2021/w10_Clickhouse_for_log_storage_and_analysis_in_2021.md#7-&amp;#32570;&amp;#28857;"&gt;&lt;/a&gt;7. 缺点&lt;/h2&gt;  &lt;p&gt;与 ElasticSearch 相比，Clickhouse 也有缺点。首先，如果构建用于日志存储的内部分析，那么就需要最好的 GUI 工具。Kibana 目前在这方面相比 Grafana 会是很好的选择（至少，这种观点非常流行，Grafana UI 有时并不那么顺滑）。如果使用 Clickhouse ，则必须使用 Grafana 或 Redash 。（我们喜欢的    &lt;a href="https://github.com/enqueue/metabase-clickhouse-driver"&gt;Metabase&lt;/a&gt;也获得了 Clickhouse 的支持！）&lt;/p&gt;  &lt;p&gt;但是，在我们的案例中，我们正在构建面向用户的分析方法，因此无论如何我们都必须从头开始构建分析 GUI（我们使用 Laravel，Inertia.js，Vue.js 和 Charts.js 来实现用户界面）。&lt;/p&gt;  &lt;p&gt;另一个与生态系统有关的问题是：消费、处理、发送数据到 Clickhouse 的工具是有限制。对于 Elasticsearch，有Logstash 和 filebeat，它们是 Elastic 生态系统固有的工具，旨在完美地协同工作。幸运的是，Logstash 也可以用于将数据放入Clickhouse，从而缓解了该问题。在 ApiRoad 中，我们使用了自己定制的 Node.js 日志传送程序，该程序将日志汇总，然后分批发送给 Clickhouse（因为 Clickhouse 喜欢大批处理，而不是小的多次插入）。&lt;/p&gt;  &lt;p&gt;我在 Clickhouse 中不喜欢的还有一些函数的奇怪命名，这是因为 Clickhouse 是由 Yandex.Metrika（ Google 分析的竞争对手）创建的。比如，    &lt;code&gt;visitParamHas()&lt;/code&gt;是用于检查JSON中是否存在特定键。通用目的，但并不是通用名称。有一堆名字不错的JSON函数名，例如    &lt;code&gt;JSONHas()&lt;/code&gt;。还有一个有趣的细节：据我所知，它们使用不同的    &lt;a href="https://github.com/simdjson/simdjson"&gt;JSON解析引擎&lt;/a&gt;，虽然更符合标准，但速度稍慢。&lt;/p&gt;  &lt;h2&gt;    &lt;a href="https://github.com/gocn/translator/blob/master/2021/w10_Clickhouse_for_log_storage_and_analysis_in_2021.md#&amp;#24635;&amp;#32467;"&gt;&lt;/a&gt;总结&lt;/h2&gt;  &lt;p&gt;ElasticSearch 是一个非常强大的解决方案，但我认为它最强的方面仍然是超过 10+ 节点的支持，用于大型全文检索和 facets ，复杂的索引和分值计算-这是 ElasticSearch 的亮点。当我们提及时间序列和日志存储时，似乎有更好的解决方案，而 Clickhouse 就是其中之一。ElasticSearch API 的功能非常强大，但在很多情况下，如果不从文档中复制具体 HTTP 请求，就很难记住如何做一件事，它有更多的“企业化”和“ Java 风格”。Clickhouse 和 lasticSearch 都是占用内存很大的程序， Clickhouse 内存要求为4GB，而 ElasticSearch 的内存要求为 16GB 。我还认为 Elastic 团队关注的重点是他们新的    &lt;a href="https://www.elastic.co/what-is/elasticsearch-machine-learning" rel="nofollow"&gt;机器学习功能&lt;/a&gt;，我的愚见是，尽管这些功能听起来非常新潮，但不论你拥有多少开发人员和金钱，这些庞大的功能集很难持续支持和改进。对我来说，ElasticSearch 在不断的进入“博而不精”的状态。或许，是我错了。&lt;/p&gt;  &lt;p&gt;Clickhouse 则与众不同。设置简单、    &lt;code&gt;SQL&lt;/code&gt;也简单、控制台客户端也很棒。通过少量配置，就可以让一切简单有效的工作起来，但是当有需要时，也可以在    &lt;code&gt;TB&lt;/code&gt;级数据上使用丰富的特性、副本和分片能力。&lt;/p&gt;
    &lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/61724-clickhouse-%E6%97%A5%E5%BF%97-%E5%88%86%E6%9E%90</guid>
      <pubDate>Mon, 23 Aug 2021 09:14:35 CST</pubDate>
    </item>
    <item>
      <title>Lenovo x DorisDB：简化数据处理链路，极大提升 BI 分析效率</title>
      <link>https://itindex.net/detail/61701-lenovo-dorisdb-%E6%95%B0%E6%8D%AE</link>
      <description>&lt;p&gt;整个数据分析体系，由数据采集、数据存储与计算、数据查询与分析和数据应用组成。&lt;/p&gt; &lt;p&gt;原始架构图：&lt;/p&gt; &lt;p&gt;  &lt;img&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;数据采集&lt;/strong&gt;&lt;/p&gt; &lt;ol&gt;  &lt;li&gt;   &lt;p&gt;通过Sqoop读取RDBMS导入Hive。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;用Flume来同步日志文件到Hive。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;通过爬虫技术将网上数据爬取下来，存储到RDBMS，再由Sqoop 读取RDBMS，导入到Hive。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt; &lt;p&gt;  &lt;strong&gt;数据存储与计算&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;离线数据处理：利用Hive高可扩展的批处理能力承担所有的离线数仓的ETL和数据模型加工的工作。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;数据查询与分析&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;数据共享层主要提供对外服务的底层数据存储和查询共享界面。离线ETL后的数据写入RDBMS或MPP数据库中，面向下游多种服务，为Tableau BI、多维固定报表、Adhoc即席查询等不同场景提供OLAP查询分析能力。应用侧完美服务于BI报表平台、即席查询分析平台及数据可视化平台（Control Tower） &lt;/p&gt; &lt;p&gt;  &lt;strong&gt;数据应用层&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;数据应用层主要为面向管理和运营人员的报表，查询要求低时延响应，需求也是迭代层出不穷。面向数据分析师的即席查询，更是要求OLAP引擎能支持复杂SQL处理、从海量数据中快速遴选数据的能力。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;各OLAP分析工具选型比较&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;h2&gt;  &lt;strong&gt;ClickHouse&lt;/strong&gt;&lt;/h2&gt; &lt;p&gt;  &lt;strong&gt;优点&lt;/strong&gt;&lt;/p&gt; &lt;ol&gt;  &lt;li&gt;   &lt;p&gt;很强的单表查询性能，适合基于大宽表的OLAP多维分析查询。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;包含丰富的MergeTree Family，支持预聚合。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;非常适合大规模日志明细数据写入分析。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt; &lt;p&gt;  &lt;strong&gt;缺点&lt;/strong&gt;&lt;/p&gt; &lt;ol&gt;  &lt;li&gt;   &lt;p&gt;不支持真正的删除与更新。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;Join方式不是很友好。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;并发能力比较低。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;MergeTree合并不完全。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;DorisDB&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;优点&lt;/strong&gt;&lt;/p&gt; &lt;ol&gt;  &lt;li&gt;   &lt;p&gt;单表查询和多表查询性能都很强，可以同时较好支持宽表查询场景和复杂多表查询。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;支持高并发查询。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;支持实时数据微批ETL处理。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;流式和批量数据写入都能都比较强。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;兼容MySQL协议和标准SQL。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt; &lt;p&gt;  &lt;strong&gt;缺点&lt;/strong&gt;&lt;/p&gt; &lt;ol&gt;  &lt;li&gt;   &lt;p&gt;大规模ETL能力不足。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;资源隔离还不完善。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt; &lt;h2&gt;  &lt;br /&gt;&lt;/h2&gt; &lt;p&gt;  &lt;strong&gt;DorisDB在SEC数据中心的应用实践&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;渠道仓配管理(SEC)的核心数据来自两大块：一个是消费业务；第二个是SMB中小企业务（Think、扬天）。基于这些数据，根据不同的业务场景需求，汇总出相关业务统计指标，对外提供查询分析服务。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;原有解决方案&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;在引入DorisDB之前，用到大量Hive任务进行业务逻辑清洗加工，清洗加工后的数据部分保留在Hive，部分数据写入MySQL/SQL Server，以达到数据的落地。前端BI通过Presto计算引擎连接Hive、MysSQL、SQL Server等，实现报表分析及数据可视化。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;技术痛点&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;原有架构主要有以下两个问题：&lt;/p&gt; &lt;ol&gt;  &lt;li&gt;   &lt;p&gt;数据逻辑没有很好做归拢合并，维护工作量大，新需求无法快速响应。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;Presto的在SQL较多的Tableau复杂报表上响应较慢，不能满足业务即时看数需求。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt; &lt;p&gt;因此我们希望对原有体系进行优化，核心思路是利用一个OLAP引擎进行这一层的统一， 对OLAP引擎的要求是比较高的：&lt;/p&gt; &lt;ol&gt;  &lt;li&gt;   &lt;p&gt;能支撑大吞吐量的数据写入要求。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;可以支持多维度组合的灵活查询，响应时效在100ms以下。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;比较好的支持多表关联。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;单表查询数据量在10亿以上，响应时效在100ms以下。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt; &lt;p&gt;经过大量调研，DorisDB比较契合数据中心的整体要求。DorisDB本身高效的查询能力，可以为数据中心数据报告提供一体化服务。新架构具备以下优点：&lt;/p&gt; &lt;ol&gt;  &lt;li&gt;   &lt;p&gt;结构清晰，RDBMS专注于数据的清洗，业务逻辑计算从Hive迁到DorisDB内实现，DorisDB就是数据业务逻辑的终点。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;可以维护统一的数据口径，一份数据输入，多个APP接口输出。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;MPP分布式架构，得以更好的支持分布式聚合和关联查询。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;和Tableau有较好的兼容性，可以满足核心BI分析需求。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt; &lt;p&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;基于DorisDB的解决方案&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;升级后架构图：&lt;/p&gt; &lt;img&gt;&lt;/img&gt; &lt;p&gt;  &lt;strong&gt;数据表设计&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;1）数据模型设计&lt;/p&gt; &lt;p&gt;DorisDB本身提供三种数据模型：明细模型/聚合模型/更新模型。对SEC业务来说，目前以明细模型为主，后续如果有其他场景，再考虑应用其他模型。&lt;/p&gt; &lt;p&gt;2）数据分区/分桶&lt;/p&gt; &lt;p&gt;DorisDB提供的数据分区和分桶功能，可以很好的提升历史库存及周转场景下明细查询的性能。例如，历史库存查询常见的一种查询场景，是查询过去某一时间段内的库存周转情况，我们可以在DorisDB中根据出库时间进行分区，过滤掉不必要的分区数据，减少整个查询的数据量进行快速定位，尽量减少了查询语句所覆盖的数据范围，分区、分桶、前缀索引等能力，可以大大提高点查并发能力。这些特性对业务迎接增长，面对未来可能出现的高并发场景也具有非常大的意义。查询某一个物料条码（SN）的历史轨迹数据，能够快速的检索出该条码的所有历史出入库轨迹信息，帮助我们高效的完成供应链全生命周期回溯。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;物化视图&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;我们利用DorisDB物化视图能够实时、按需构建，灵活增加删除以及透明化使用的特性，建立了基于库存物料SN粒度、基于产品类型特征粒度、基于库房粒度、基于分销商粒度的物化视图。基于这些物化视图，可以极大加速查询。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;数据导入&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;数据导入DorisDB这里用到了两种方案：&lt;/p&gt; &lt;p&gt;1）在DorisDB提供的Broker Load基础上将离线数仓Hive的表导入到DorisDB中。&lt;/p&gt; &lt;p&gt;2）通过DataX工具，将SQL Server、MySQL上的数据导入到DorisDB。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;DorisDB使用效果&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;灵活建模提升开发效率&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;结合使用宽表模型和星型模型，宽表和物化视图可以保证报表性能和并发能力，而星型模型可以让AP如TP里那样建模，直接进行关联查询，不必所有场景都依赖宽表准备，在数据一致性和开发效率上得到很好提升。另外，有不少表是在MySQL里的，我们通过 DorisDB 外表的方式暴露查询，省去了数据导入的过程，大大降低了业务方的开发和迁移周期。DorisDB的分布式Join能力非常强，结合View的能力构建统一的视图层，面下不同BI报表进行查询，提升了指标口径的一致性，降低了重复开发。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;BI体验极好&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;前期部分BI可视化是基于SQL Server、 MySQL 构建的。部分看板不断优化和丰富需求后，加上多维度灵活条件筛选，每次加载很慢，有些Tableau报表很长时间才能加载出来，业务无法接受。引入DorisDB之后，我们用DataX将SQL Server数据导入DorisDB，这里使用了DorisDB-Writer插件，底层封装的Stream-Load接口，向量化导入效率非常高。MySQL可以通过外表insert into select流式导入，也可以直接外表查询，非常便捷。Tableau图表秒出，体验有了质的飞跃。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;运维成本较低&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;数据中心是非常核心的一个线上服务，因此对高可用及灵活扩容能力有非常高的要求。DorisDB支持数据多副本，FE、BE仅仅2种角色组成的简洁架构，在单个节点故障的时候可以保证整个集群的高可用。另外，DorisDB在大数据规模下可以进行在线弹性扩展，在扩容时无Down Time，不会影响到在线业务，这个能力也是我们非常需要的。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;总结&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;Lenovo联晟智达从今年（2021年）4月份开始调研DorisDB，POC测试阶段用了1/4的资源，就完美替代了数十个节点的Presto集群，当前DorisDB已经上线稳定运行。引入DorisDB后，实现了数据服务统一化，大大简化了离线数据处理链路，同时也能保障查询时延要求，之后将用来提升更多业务场景的数据服务和查询能力。最后，感谢鼎石科技的大力支持，也期望DorisDB作为性能强悍的新一代MPP数据库引领者越来越好！&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;strong&gt;END&lt;/strong&gt; &lt;img&gt;&lt;/img&gt; &lt;strong&gt;【热门文章】&lt;/strong&gt;1.  &lt;a href="http://mp.weixin.qq.com/s?__biz=MzI1MTYxOTkxNQ==&amp;mid=2247484013&amp;idx=1&amp;sn=85f7a55d6bcc34c3ebe71a714d630644&amp;chksm=e9f17d49de86f45f89c0ebc81b4bd1bc2c7a0fbe6a0aad96e1f7e4b46fae311ffb9afcfdcaa7&amp;scene=21#wechat_redirect" target="_blank"&gt;小红书 x DorisDB：实现数据服务平台统一化，简化数据链路，提升高并发极速查询能力&lt;/a&gt;2.  &lt;a href="http://mp.weixin.qq.com/s?__biz=MzI1MTYxOTkxNQ==&amp;mid=2247484118&amp;idx=1&amp;sn=0d001bc1841bee82aca6838993698015&amp;chksm=e9f17df2de86f4e4a74dad71de7d893786332ed902c9396a1bb2bc914f974ce12cba28069151&amp;scene=21#wechat_redirect" target="_blank"&gt;贝壳找房 x DorisDB：全新统一的极速OLAP平台实践&lt;/a&gt;3. &lt;a href="http://mp.weixin.qq.com/s?__biz=MzI1MTYxOTkxNQ==&amp;mid=2247484073&amp;idx=1&amp;sn=c3de3426a7207e3a4de9e6c51e3e6a91&amp;chksm=e9f17d8dde86f49bd83108ca79b02b7b171dc3634a53e417dd4dc1aa4fa8f356279108d61fe7&amp;scene=21#wechat_redirect" target="_blank"&gt; 好未来基于DorisDB构建全新实时数仓，深入释放实时数据价值&lt;/a&gt;4. &lt;a href="http://mp.weixin.qq.com/s?__biz=MzI1MTYxOTkxNQ==&amp;mid=2247483997&amp;idx=1&amp;sn=9f8427d8c33bf3f20f1c883e1761c5b5&amp;chksm=e9f17d79de86f46f20223cebe078dffdb3ca1e9f4fdd4427468bf1681f13b06c0c78f43bd5d8&amp;scene=21#wechat_redirect" target="_blank"&gt; 58集团 x DorisDB：全面升级数据分析能力，满足多场景业务分析需求&lt;/a&gt;5.  &lt;a href="http://mp.weixin.qq.com/s?__biz=MzI1MTYxOTkxNQ==&amp;mid=2247483959&amp;idx=1&amp;sn=34abd2a93aa11c3471c3e273a1cfcad5&amp;chksm=e9f17d13de86f40594360a17208026e3fd67c106ee9705d7d3544a2e8b4e13567094571fb031&amp;scene=21#wechat_redirect" target="_blank"&gt;猿辅导基于构建统一OLAP平台，全面升级数据分析能力&lt;/a&gt;6.  &lt;a href="http://mp.weixin.qq.com/s?__biz=MzI1MTYxOTkxNQ==&amp;mid=2247483896&amp;idx=1&amp;sn=09db0e74dd9c35aaed2ae7678794f82e&amp;chksm=e9f17edcde86f7cac23984cd552b6239fb6b49c4740c1f6fa616d2e0ed93e216ce381357c2d8&amp;scene=21#wechat_redirect" target="_blank"&gt;酷家乐基于DorisDB实现数据分析全面升级，大幅降低平台成本&lt;/a&gt; &lt;p&gt;7.   &lt;a href="https://mp.weixin.qq.com/s?__biz=MzI1MTYxOTkxNQ==&amp;mid=2247483883&amp;idx=1&amp;sn=12ba6eccdefca6dc7a0798c9730394e0&amp;chksm=e9f17ecfde86f7d9432e8214f83286d802306586a29c045ef5f5fef16000f8426b1a08e7eb25&amp;token=1031233021&amp;lang=zh_CN&amp;scene=21#wechat_redirect" target="_blank"&gt;DorisDB在中移物联网PGW实时会话业务领域的应用&lt;/a&gt;&lt;/p&gt; &lt;img&gt;&lt;/img&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>dev</category>
      <guid isPermaLink="true">https://itindex.net/detail/61701-lenovo-dorisdb-%E6%95%B0%E6%8D%AE</guid>
      <pubDate>Thu, 19 Aug 2021 00:00:00 CST</pubDate>
    </item>
    <item>
      <title>潜在语义分析LSA初探</title>
      <link>https://itindex.net/detail/61597-%E8%AF%AD%E4%B9%89%E5%88%86%E6%9E%90-lsa</link>
      <description>&lt;h2&gt;什么是潜在语义分析LSA？&lt;/h2&gt;
 &lt;p&gt;潜在语义分析（Latent Semantic Analysis），是语义学的一个新的分支。传统的语义学通常研究字、词的含义以及词与词之间的关系，如同义，近义，反义等等。潜在语义分析探讨的是隐藏在字词背后的某种关系，这种关系不是以词典上的定义为基础，而是以字词的使用环境作为最基本的参考。这种思想来自于心理语言学家。他们认为，世界上数以百计的语言都应该有一种共同的简单的机制，使得任何人只要是在某种特定的语言环境下长大都能掌握那种语言。在这种思想的指导下，人们找到了一种简单的数学模型，这种模型的输入是由任何一种语言书写的文献构成的文库，输出是该语言的字、词的一种数学表达（向量）。字、词之间的关系乃至任何文章片断之间的含义的比较就由这种向量之间的运算产生。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="753" src="https://www.biaodianfu.com/wp-content/uploads/2021/07/lsa.png" width="720"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;向量空间模型是信息检索中最常用的检索方法，其检索过程是，将文档集D中的所有文档和查询都表示成以单词为特征的向量，特征值为每个单词的TF-IDF值，然后使用向量空间模型(亦即计算查询q的向量和每个文档$d_i$的向量之间的相似度)来衡量文档和查询之间的相似度，从而得到和给定查询最相关的文档。向量空间模型简单的基于单词的出现与否以及TF-IDF等信息来进行检索，但是“说了或者写了哪些单词”和“真正想表达的意思”之间有很大的区别，其中两个重要的阻碍是单词的多义性(polysems)和同义性(synonymys)。&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;多义性指的是一个单词可能有多个意思，比如Apple，既可以指水果苹果，也可以指苹果公司&lt;/li&gt;
  &lt;li&gt;同义性指的是多个不同的词可能表示同样的意思，比如search和find。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;同义词和多义词的存在使得单纯基于单词的检索方法(比如向量空间模型等)的检索精度受到很大影响。总而言之，在基于单词的检索方法中，同义词会降低检索算法的召回率(Recall)，而多义词的存在会降低检索系统的准确率(Precision)。&lt;/p&gt;
 &lt;p&gt;LSA和传统向量空间模型(vector space model)一样使用向量来表示词(terms)和文档(documents)，并通过向量间的关系(如夹角)来判断词及文档间的关系；不同的是，LSA 将词和文档映射到潜在语义空间，从而去除了原始向量空间中的一些“噪音”，提高了信息检索的精确度。如果两个单词之间有很强的相关性，那么当一个单词出现时，往往意味着另一个单词也应该出现(同义词)；反之，如果查询语句或者文档中的某个单词和其他单词的相关性都不大，那么这个词很可能表示的是另外一个意思(比如在讨论互联网的文章中，Apple更可能指的是Apple公司，而不是水果)。&lt;/p&gt;
 &lt;p&gt;LSA工具：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;2009年：Gensim&lt;/li&gt;
  &lt;li&gt;2015年：fastText&lt;/li&gt;
  &lt;li&gt;2016年：text2Vec&lt;/li&gt;
&lt;/ul&gt;
 &lt;h2&gt;潜在语义分析LSA原理&lt;/h2&gt;
 &lt;p&gt;假设有 n 篇文档，这些文档中的单词总数为 m (可以先进行分词、去词根、去停止词操作),我们可以用一个 m∗n 的矩阵 X 来表示这些文档，这个矩阵的每个元素 X_{ij} 表示第 i 个单词在第 j 篇文档中出现的次数(也可用tf-idf值)。下文例子中得到的矩阵见下图。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="229" src="https://www.biaodianfu.com/wp-content/uploads/2021/07/lsa-1.png" width="720"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;LSA试图将原始矩阵降维到一个潜在的概念空间（维度不超过n），然后每个单词或文档都可以用该空间下的一组权值向量（也可认为是坐标）来表示，这些权值反应了与对应的潜在概念的关联程度的强弱。这个降维是通过对该矩阵进行  &lt;a href="https://www.biaodianfu.com/svd.html"&gt;奇异值分解(SVD, singular value decomposition)&lt;/a&gt;做到的，计算其用三个矩阵的乘积表示的等价形式，如下：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="388" src="https://www.biaodianfu.com/wp-content/uploads/2021/07/lsa-2.png" width="720"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;LSA的数学原理是矩阵分解（奇异值分解SVD），本质是线性变换（把文本从单词向量空间映射到语义向量空间，词-&amp;gt;语义）&lt;/p&gt;
 &lt;h2&gt;潜在语义分析LSA优缺点&lt;/h2&gt;
 &lt;p&gt;LSA的优点：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;低维空间表示可以刻画同义词，同义词会对应着相同或相似的主题。&lt;/li&gt;
  &lt;li&gt;降维可去除部分噪声，是特征更鲁棒。&lt;/li&gt;
  &lt;li&gt;充分利用冗余数据。&lt;/li&gt;
  &lt;li&gt;无监督/完全自动化。&lt;/li&gt;
  &lt;li&gt;与语言无关。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;LSA的缺点：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;LSA可以处理向量空间模型无法解决的一义多词(synonymy)问题，但不能解决一词多义(polysemy)问题。因为LSA将每一个词映射为潜在语义空间中的一个点，也就是说一个词的多个意思在空间中对于的是同一个点，并没有被区分。&lt;/li&gt;
  &lt;li&gt;SVD的优化目标基于L-2 norm 或者 Frobenius Norm 的，这相当于隐含了对数据的高斯分布假设。而 term 出现的次数是非负的，这明显不符合 Gaussian 假设，而更接近 Multi-nomial 分布。&lt;/li&gt;
  &lt;li&gt;特征向量的方向没有对应的物理解释。&lt;/li&gt;
  &lt;li&gt;SVD的计算复杂度很高，而且当有新的文档来到时，若要更新模型需重新训练。&lt;/li&gt;
  &lt;li&gt;没有刻画term出现次数的概率模型。&lt;/li&gt;
  &lt;li&gt;对于count vectors 而言，欧式距离表达是不合适的（重建时会产生负数）。&lt;/li&gt;
  &lt;li&gt;维数的选择是ad-hoc的。&lt;/li&gt;
  &lt;li&gt;LSA具有词袋模型的缺点，即在一篇文章，或者一个句子中忽略词语的先后顺序。&lt;/li&gt;
  &lt;li&gt;LSA的概率模型假设文档和词的分布是服从联合正态分布的，但从观测数据来看是服从泊松分布的。因此LSA算法的一个改进PLSA使用了多项分布，其效果要好于LSA。&lt;/li&gt;
&lt;/ul&gt;
 &lt;h2&gt;潜在语义分析LSA代码示例&lt;/h2&gt;
 &lt;pre&gt;# -*- coding: utf-8 -*-
from numpy import zeros
from scipy.linalg import svd
from math import log
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
import pandas as pd


class LSA(object):
    &amp;quot;&amp;quot;&amp;quot;
    定义LSA类，w_dict字典用来记录词的个数，d_count用来记录文档号。
    &amp;quot;&amp;quot;&amp;quot;
    def __init__(self, stop_words, ignore_chars):
        self.stop_words = stop_words
        self.ignore_chars = ignore_chars
        self.w_dict = {}
        self.d_count = 0

    def parse(self, doc):
        &amp;quot;&amp;quot;&amp;quot;
        把文档分词，并滤除停用词和标点，剩下的词会把其出现的文档号填入到w_dict中去，
        例如，词book出现在标题3和4中，则我们有self.w_dict[‘book’] = [3, 4]。相当于建了一下倒排。
        &amp;quot;&amp;quot;&amp;quot;
        words = doc.split()
        for w in words:
            w = w.lower().translate(self.ignore_chars)
            if w in self.stop_words:
                pass
            elif w in self.w_dict:
                self.w_dict[w].append(self.d_count)
            else:
                self.w_dict[w] = [self.d_count]
        self.d_count += 1

    def build(self):
        &amp;quot;&amp;quot;&amp;quot;
        建立索引词文档矩阵
        所有的文档被解析之后，所有出现的词（也就是词典的keys）被取出并且排序。建立一个矩阵，其行数是词的个数，列数是文档个数。
        最后，所有的词和文档对所对应的矩阵单元的值被统计出来。
        &amp;quot;&amp;quot;&amp;quot;
        self.keys = [k for k in self.w_dict.keys() if len(self.w_dict[k]) &amp;gt; 1]
        self.keys.sort()
        self.A = zeros([len(self.keys), self.d_count])
        for i, k in enumerate(self.keys):
            for d in self.w_dict[k]:
                self.A[i, d] += 1

    def print_A(self):
        &amp;quot;&amp;quot;&amp;quot;
        打印出索引词文档矩阵。
        &amp;quot;&amp;quot;&amp;quot;
        print(self.A)

    def TF_IDF(self):
        &amp;quot;&amp;quot;&amp;quot;
        用TF-IDF替代简单计数
        在复杂的LSA系统中，为了重要的词占据更重的权重，原始矩阵中的计数往往会被修改。
        最常用的权重计算方法就是TF-IDF（词频-逆文档频率）。基于这种方法，我们把每个单元的数值进行修改。
        wordsPerDoc 就是矩阵每列的和,也就是每篇文档的词语总数。DocsPerWord 利用asarray方法创建一个0、1数组（也就是大于0的数值会被归一到1），然后每一行会被加起来，从而计算出每个词出现在了多少文档中。最后，我们对每一个矩阵单元计算TFIDF公式
        &amp;quot;&amp;quot;&amp;quot;
        words_per_doc = np.sum(self.A, axis=0)
        docs_per_word = np.sum(np.asarray(self.A &amp;gt; 0, &amp;apos;i&amp;apos;), axis=1)
        rows, cols = self.A.shape
        for i in range(rows):
            for j in range(cols):
                self.A[i, j] = (self.A[i, j] / words_per_doc[j]) * log(float(cols) / docs_per_word[i])

    def calc_SVD(self):
        &amp;quot;&amp;quot;&amp;quot;
        建立完词文档矩阵以后，用奇异值分解（SVD）分析这个矩阵。
        SVD非常有用的原因是，它能够找到我们矩阵的一个降维表示，他强化了其中较强的关系并且扔掉了噪音（这个算法也常被用来做图像压缩）。
        换句话说，它可以用尽可能少的信息尽量完善的去重建整个矩阵。为了做到这点，它会扔掉无用的噪音，强化本身较强的模式和趋势。
        利用SVD的技巧就是去找到用多少维度（概念）去估计这个矩阵。太少的维度会导致重要的模式被扔掉，反之维度太多会引入一些噪音。
        代码中降到了3维
        &amp;quot;&amp;quot;&amp;quot;
        self.U, self.S, self.Vt = svd(self.A)
        target_dimension = 3
        self.U2 = self.U[0:, 0:target_dimension]
        self.S2 = np.diag(self.S[0:target_dimension])
        self.Vt2 = self.Vt[0:target_dimension, 0:]
        print(&amp;quot;U:\n&amp;quot;, self.U2)
        print(&amp;quot;S:\n&amp;quot;, self.S2)
        print(&amp;quot;Vt:\n&amp;quot;, self.Vt2)

    def plot_singular_values_bar(self):
        &amp;quot;&amp;quot;&amp;quot;
        为了去选择一个合适的维度数量，我们可以做一个奇异值平方的直方图。它描绘了每个奇异值对于估算矩阵的重要度。
        下图是我们这个例子的直方图。（每个奇异值的平方代表了重要程度，下图应该是归一化后的结果）
        &amp;quot;&amp;quot;&amp;quot;
        y_value = (self.S * self.S) / sum(self.S * self.S)
        x_value = range(len(y_value))
        plt.bar(x_value, y_value, alpha=1, color=&amp;apos;g&amp;apos;, align=&amp;quot;center&amp;quot;)
        plt.autoscale()
        plt.xlabel(&amp;quot;Singular Values&amp;quot;)
        plt.ylabel(&amp;quot;Importance&amp;quot;)
        plt.title(&amp;quot;The importance of Each Singular Value&amp;quot;)
        plt.show()

    def plot_singular_heatmap(self):
        &amp;quot;&amp;quot;&amp;quot;
        用颜色聚类
        我们可以把数字转换为颜色。例如，下图表示了文档矩阵3个维度的颜色分布。除了蓝色表示负值，红色表示正值，它包含了和矩阵同样的信息。
        &amp;quot;&amp;quot;&amp;quot;
        labels = [&amp;quot;T1&amp;quot;, &amp;quot;T2&amp;quot;, &amp;quot;T3&amp;quot;, &amp;quot;T4&amp;quot;, &amp;quot;T5&amp;quot;, &amp;quot;T6&amp;quot;, &amp;quot;T7&amp;quot;, &amp;quot;T8&amp;quot;, &amp;quot;T9&amp;quot;]
        rows = [&amp;quot;Dim1&amp;quot;, &amp;quot;Dim2&amp;quot;, &amp;quot;Dim3&amp;quot;]
        self.Vt_df_norm = pd.DataFrame(self.Vt2 * (-1))
        self.Vt_df_norm.columns = labels
        self.Vt_df_norm.index = rows
        sns.set(font_scale=1.2)
        ax = sns.heatmap(self.Vt_df_norm, cmap=plt.cm.bwr, linewidths=.1, square=2)
        ax.xaxis.tick_top()
        plt.xlabel(&amp;quot;Book Title&amp;quot;)
        plt.ylabel(&amp;quot;Dimensions&amp;quot;)
        plt.show()


if __name__ == &amp;apos;__main__&amp;apos;:
    # 待处理的文档
    titles = [
        &amp;quot;The Neatest Little Guide to Stock Market Investing&amp;quot;,
        &amp;quot;Investing For Dummies, 4th Edition&amp;quot;,
        &amp;quot;The Little Book of Common Sense Investing: The Only Way to Guarantee Your Fair Share of Stock Market Returns&amp;quot;,
        &amp;quot;The Little Book of Value Investing&amp;quot;,
        &amp;quot;Value Investing: From Graham to Buffett and Beyond&amp;quot;,
        &amp;quot;Rich Dad&amp;apos;s Guide to Investing: What the Rich Invest in, That the Poor and the Middle Class Do Not!&amp;quot;,
        &amp;quot;Investing in Real Estate, 5th Edition&amp;quot;,
        &amp;quot;Stock Investing For Dummies&amp;quot;,
        &amp;quot;Rich Dad&amp;apos;s Advisors: The ABC&amp;apos;s of Real Estate Investing: The Secrets of Finding Hidden Profits Most Investors Miss&amp;quot;
    ]
    # 定义停止词
    stopwords = [&amp;apos;and&amp;apos;, &amp;apos;edition&amp;apos;, &amp;apos;for&amp;apos;, &amp;apos;in&amp;apos;, &amp;apos;little&amp;apos;, &amp;apos;of&amp;apos;, &amp;apos;the&amp;apos;, &amp;apos;to&amp;apos;]
    # 定义要去除的标点符号
    ignore_chars = &amp;apos;&amp;apos;&amp;apos;,:&amp;apos;!&amp;apos;&amp;apos;&amp;apos;

    mylsa = LSA(stopwords, ignore_chars)
    for t in titles:
        mylsa.parse(t)
    mylsa.build()
    mylsa.print_A()
    mylsa.TF_IDF()
    mylsa.print_A()
    mylsa.calc_SVD()
    mylsa.plot_singular_values_bar()
    mylsa.plot_singular_heatmap()
&lt;/pre&gt;
 &lt;p&gt;在 sklearn 中，LSA可以更加方便的实现：&lt;/p&gt;
 &lt;pre&gt;import pandas as pd
import matplotlib.pyplot as plt
from nltk.corpus import stopwords
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.datasets import fetch_20newsgroups
from sklearn.decomposition import TruncatedSVD
import umap

dataset = fetch_20newsgroups(shuffle=True, random_state=1, remove=(&amp;apos;headers&amp;apos;, &amp;apos;footers&amp;apos;, &amp;apos;quotes&amp;apos;))
documents = dataset.data
print(len(documents))
print(dataset.target_names)

news_df = pd.DataFrame({&amp;apos;document&amp;apos;: documents})

# remove everything except alphabets`
news_df[&amp;apos;clean_doc&amp;apos;] = news_df[&amp;apos;document&amp;apos;].replace(&amp;quot;[^a-zA-Z]&amp;quot;, &amp;quot; &amp;quot;)

# remove short words
news_df[&amp;apos;clean_doc&amp;apos;] = news_df[&amp;apos;clean_doc&amp;apos;].apply(lambda x: &amp;apos; &amp;apos;.join([w for w in x.split() if len(w) &amp;gt; 3]))

# make all text lowercase-
news_df[&amp;apos;clean_doc&amp;apos;] = news_df[&amp;apos;clean_doc&amp;apos;].apply(lambda x: x.lower())

# tokenization
tokenized_doc = news_df[&amp;apos;clean_doc&amp;apos;].apply(lambda x: x.split())

# remove stop-words
stop_words = stopwords.words(&amp;apos;english&amp;apos;)
tokenized_doc = tokenized_doc.apply(lambda x: [item for item in x if item not in stop_words])

# de-tokenization
detokenized_doc = []
for i in range(len(news_df)):
    t = &amp;apos; &amp;apos;.join(tokenized_doc[i])
    detokenized_doc.append(t)

news_df[&amp;apos;clean_doc&amp;apos;] = detokenized_doc

vectorizer = TfidfVectorizer(stop_words=&amp;apos;english&amp;apos;, max_features=1000, max_df=0.5, smooth_idf=True)
X = vectorizer.fit_transform(news_df[&amp;apos;clean_doc&amp;apos;])
print(X.shape)

# SVD represent documents and terms in vectors
svd_model = TruncatedSVD(n_components=20, algorithm=&amp;apos;randomized&amp;apos;, n_iter=100, random_state=122)
svd_model.fit(X)
print(len(svd_model.components_))

terms = vectorizer.get_feature_names()
for i, comp in enumerate(svd_model.components_):
    terms_comp = zip(terms, comp)
    sorted_terms = sorted(terms_comp, key=lambda x: x[1],reverse=True)[:7]
    sorted_terms_words = [t[0] for t in sorted_terms]
    print(&amp;quot;Topic &amp;quot; + str(i) + &amp;quot;: &amp;quot; + str(sorted_terms_words))

X_topics = svd_model.fit_transform(X)
embedding = umap.UMAP(n_neighbors=150, min_dist=0.5, random_state=12).fit_transform(X_topics)
plt.figure(figsize=(7, 5))
plt.scatter(embedding[:, 0], embedding[:, 1], c=dataset.target, s=10, edgecolor=&amp;apos;none&amp;apos;)
plt.show()
&lt;/pre&gt;
 &lt;h2&gt;潜在语义分析实战：基于LSA的情感分类&lt;/h2&gt;
 &lt;p&gt;数据集：  &lt;a href="https://www.kaggle.com/snap/amazon-fine-food-reviews?select=Reviews.csv"&gt;Amazon.com 50万点评数据&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;字段说明：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;Id：自增长ID，无含义&lt;/li&gt;
  &lt;li&gt;ProductId：产品ID&lt;/li&gt;
  &lt;li&gt;UserId：会员ID&lt;/li&gt;
  &lt;li&gt;ProfileName：会员昵称&lt;/li&gt;
  &lt;li&gt;HelpfulnessNumerator：评价点评有用数量&lt;/li&gt;
  &lt;li&gt;HelpfulnessDenominator：评价点评总数&lt;/li&gt;
  &lt;li&gt;Score：点评分&lt;/li&gt;
  &lt;li&gt;Time：点评时间&lt;/li&gt;
  &lt;li&gt;Summary：综合评价&lt;/li&gt;
  &lt;li&gt;Text：点评详情&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;这里只会用到2个字段：Score和Text。Score：总共5分，我们将1分、2分的看作是负面评论。4分、5分的看作是正面评论。将3分的中性评论直接删除。&lt;/p&gt;
 &lt;p&gt;加载Python包：&lt;/p&gt;
 &lt;pre&gt;import numpy as np
import pandas as pd
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.model_selection import train_test_split
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.ensemble import RandomForestClassifier
from sklearn.pipeline import Pipeline
from sklearn.metrics import accuracy_score
from sklearn.metrics import classification_report
from sklearn.feature_selection import chi2
import matplotlib.pyplot as plt
&lt;/pre&gt;
 &lt;p&gt;准备数据：&lt;/p&gt;
 &lt;pre&gt;df = pd.read_csv(&amp;apos;Reviews.csv&amp;apos;)
df.dropna(inplace=True)
df[df[&amp;apos;Score&amp;apos;] != 3]
df[&amp;apos;Positivity&amp;apos;] = np.where(df[&amp;apos;Score&amp;apos;] &amp;gt; 3, 1, 0)
X = df[&amp;apos;Text&amp;apos;]
y = df[&amp;apos;Positivity&amp;apos;]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=0)
print(&amp;quot;Train set has total {0} entries with {1:.2f}% negative, {2:.2f}% positive&amp;quot;.format(
    len(X_train),
    (len(X_train[y_train == 0]) / (len(X_train) * 1.)) * 100,
    (len(X_train[y_train == 1]) / (len(X_train) * 1.)) * 100)
)
&lt;/pre&gt;
 &lt;p&gt;我们可以看到正面点评和负面点评并不均衡。Train set has total 426308 entries with 21.91% negative, 78.09% positive。情感分类我们使用决策树算法（随机森林）并设置class_weight=balanced&lt;/p&gt;
 &lt;p&gt;定义一个计算准确率的函数：&lt;/p&gt;
 &lt;pre&gt;def accuracy_summary(pipeline, X_train, y_train, X_test, y_test):
    sentiment_fit = pipeline.fit(X_train, y_train)
    y_pred = sentiment_fit.predict(X_test)
    accuracy = accuracy_score(y_test, y_pred)
    print(&amp;quot;accuracy score: {0:.2f}%&amp;quot;.format(accuracy * 100))
    return accuracy
&lt;/pre&gt;
 &lt;p&gt;在进行LSA的时，如果如果不做限制，我们会使用Text中所有出现过的单词作为特征，这样的计算量和效果并不佳。取而代之的是我们的获取TOP的单词作为特征。我们分别使用10000,20000,30000做测试：&lt;/p&gt;
 &lt;pre&gt;cv = CountVectorizer()
rf = RandomForestClassifier(class_weight=&amp;quot;balanced&amp;quot;)
n_features = np.arange(10000, 30001, 10000)


def nfeature_accuracy_checker(vectorizer=cv, n_features=n_features, stop_words=None, ngram_range=(1, 1), classifier=rf):
    result = []
    print(classifier)
    for n in n_features:
        vectorizer.set_params(stop_words=stop_words, max_features=n, ngram_range=ngram_range)
        checker_pipeline = Pipeline([
            (&amp;apos;vectorizer&amp;apos;, vectorizer),
            (&amp;apos;classifier&amp;apos;, classifier)
        ])
        print(&amp;quot;Test result for {} features&amp;quot;.format(n))
        nfeature_accuracy = accuracy_summary(checker_pipeline, X_train, y_train, X_test, y_test)
        result.append((n, nfeature_accuracy))
    return result


tfidf = TfidfVectorizer()
feature_result_tgt = nfeature_accuracy_checker(vectorizer=tfidf, ngram_range=(1, 3))
&lt;/pre&gt;
 &lt;p&gt;我们可以看到30000特征的时候准确率是最高的。我们可以查看更为详细的指标：&lt;/p&gt;
 &lt;pre&gt;cv = CountVectorizer(max_features=30000, ngram_range=(1, 3))
pipeline = Pipeline([
    (&amp;apos;vectorizer&amp;apos;, cv),
    (&amp;apos;classifier&amp;apos;, rf)
])
sentiment_fit = pipeline.fit(X_train, y_train)
y_pred = sentiment_fit.predict(X_test)
print(classification_report(y_test, y_pred, target_names=[&amp;apos;negative&amp;apos;, &amp;apos;positive&amp;apos;]))
&lt;/pre&gt;
 &lt;p&gt;使用卡方检验选择特征。我们计算所有特征的卡方得分，并将TOP 20进行可视化。&lt;/p&gt;
 &lt;pre&gt;tfidf = TfidfVectorizer(max_features=30000, ngram_range=(1, 3))
X_tfidf = tfidf.fit_transform(df.Text)
y = df.Positivity
chi2score = chi2(X_tfidf, y)[0]

plt.figure(figsize=(12, 8))
scores = list(zip(tfidf.get_feature_names(), chi2score))
chi2 = sorted(scores, key=lambda x: x[1])
topchi2 = list(zip(*chi2[-20:]))
x = range(len(topchi2[1]))
labels = topchi2[0]
plt.barh(x, topchi2[1], align=&amp;apos;center&amp;apos;, alpha=0.5)
plt.plot(topchi2[1], x, &amp;apos;-o&amp;apos;, markersize=5, alpha=0.8)
plt.yticks(x, labels)
plt.xlabel(&amp;apos;$\chi^2$&amp;apos;)
plt.show()
&lt;/pre&gt;
 &lt;h2&gt;潜在语义分析LSA的进化&lt;/h2&gt;
 &lt;p&gt;  &lt;img alt="" height="233" src="https://www.biaodianfu.com/wp-content/uploads/2021/07/lsa-plas-lda.png" width="720"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h3&gt;LSA&lt;/h3&gt;
 &lt;p&gt;LSA 方法快速且高效，但它也有一些主要缺点：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;缺乏可解释的嵌入（我们并不知道主题是什么，其成分可能积极或消极，这一点是随机的）&lt;/li&gt;
  &lt;li&gt;需要大量的文件和词汇来获得准确的结果&lt;/li&gt;
  &lt;li&gt;表征效率低&lt;/li&gt;
&lt;/ul&gt;
 &lt;h3&gt;pLSA&lt;/h3&gt;
 &lt;p&gt;pLSA，即概率潜在语义分析，采取概率方法替代 SVD 以解决问题。其核心思想是找到一个潜在主题的概率模型，该模型可以生成我们在文档-术语矩阵中观察到的数据。特别是，我们需要一个模型 P(D,W)，使得对于任何文档 d 和单词 w，P(d,w) 能对应于文档-术语矩阵中的那个条目。&lt;/p&gt;
 &lt;p&gt;主题模型的基本假设：每个文档由多个主题组成，每个主题由多个单词组成。pLSA 为这些假设增加了概率自旋：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;给定文档 d，主题 z 以 P(z|d) 的概率出现在该文档中&lt;/li&gt;
  &lt;li&gt;给定主题 z，单词 w 以 P(w|z) 的概率从主题 z 中提取出来&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;img alt="" height="178" src="https://www.biaodianfu.com/wp-content/uploads/2021/07/plsa.jpg" width="480"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;从形式上看，一个给定的文档和单词同时出现的联合概率是：&lt;/p&gt;
 &lt;p&gt;$$P(D,W)=P(D)\sum_{Z}P(Z|D)P(W|Z)$$&lt;/p&gt;
 &lt;p&gt;直观来说，等式右边告诉我们理解某个文档的可能性有多大；然后，根据该文档主题的分布情况，在该文档中找到某个单词的可能性有多大。在这种情况下，P(D)、P(Z|D)、和 P(W|Z) 是我们模型的参数。P(D) 可以直接由我们的语料库确定。P(Z|D) 和 P(W|Z) 利用了多项式分布建模，并且可以使用期望最大化算法（EM）进行训练。EM 无需进行算法的完整数学处理，而是一种基于未观测潜变量（此处指主题）的模型找到最可能的参数估值的方法。有趣的是，P(D,W) 可以利用不同的的 3 个参数等效地参数化：&lt;/p&gt;
 &lt;p&gt;$$P(D,W)=\sum_{Z}P(Z)P(Z|D)P(W|Z)$$&lt;/p&gt;
 &lt;p&gt;可以通过将模型看作一个生成过程来理解这种等价性。在第一个参数化过程中，我们从概率为 P(d) 的文档开始，然后用 P(z|d) 生成主题，最后用 P(w|z) 生成单词。而在上述这个参数化过程中，我们从 P(z) 开始，再用 P(d|z) 和 P(w|z) 单独生成文档。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="265" src="https://www.biaodianfu.com/wp-content/uploads/2021/07/plsa-2.jpg" width="480"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;这个新参数化方法非常有趣，因为我们可以发现 pLSA 模型和 LSA 模型之间存在一个直接的平行对应关系：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="141" src="https://www.biaodianfu.com/wp-content/uploads/2021/07/plsa-3.png" width="320"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;其中，主题 P(Z) 的概率对应于奇异主题概率的对角矩阵，给定主题 P(D|Z) 的文档概率对应于文档-主题矩阵 U，给定主题 P(W|Z) 的单词概率对应于术语-主题矩阵 V。&lt;/p&gt;
 &lt;p&gt;尽管 pLSA 看起来与 LSA 差异很大、且处理问题的方法完全不同，但实际上 pLSA 只是在 LSA 的基础上添加了对主题和词汇的概率处理。pLSA 是一个更加灵活的模型，但仍然存在一些问题，尤其表现为：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;因为没有参数来给 P(D) 建模，所以不知道如何为新文档分配概率&lt;/li&gt;
  &lt;li&gt;pLSA 的参数数量随着我们拥有的文档数线性增长，因此容易出现过度拟合问题&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;我们将不会考虑任何 pLSA 的代码，因为很少会单独使用 pLSA。一般来说，当人们在寻找超出 LSA 基准性能的主题模型时，他们会转而使用 LDA 模型。LDA 是最常见的主题模型，它在 pLSA 的基础上进行了扩展，从而解决这些问题。&lt;/p&gt;
 &lt;h3&gt;LDA&lt;/h3&gt;
 &lt;p&gt;LDA 即潜在狄利克雷分布，是 pLSA 的贝叶斯版本。它使用狄利克雷先验来处理文档-主题和单词-主题分布，从而有助于更好地泛化。我们可以对狄利克雷分布其做一个简短的概述：即，将狄利克雷视为「分布的分布」。本质上，它回答了这样一个问题：「给定某种分布，我看到的实际概率分布可能是什么样子？」考虑比较主题混合概率分布的相关例子。假设我们正在查看的语料库有着来自 3 个完全不同主题领域的文档。如果我们想对其进行建模，我们想要的分布类型将有着这样的特征：它在其中一个主题上有着极高的权重，而在其他的主题上权重不大。如果我们有 3 个主题，那么我们看到的一些具体概率分布可能会是：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;混合 X：90% 主题 A，5% 主题 B，5% 主题 C&lt;/li&gt;
  &lt;li&gt;混合 Y：5% 主题 A，90% 主题 B，5% 主题 C&lt;/li&gt;
  &lt;li&gt;混合 Z：5% 主题 A，5% 主题 B，90% 主题 C&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;如果从这个狄利克雷分布中绘制一个随机概率分布，并对单个主题上的较大权重进行参数化，我们可能会得到一个与混合 X、Y 或 Z 非常相似的分布。我们不太可能会抽样得到这样一个分布：33%的主题 A，33%的主题 B 和 33%的主题 C。本质上，这就是狄利克雷分布所提供的：一种特定类型的抽样概率分布法。我回顾一下 pLSA 的模型：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="178" src="https://www.biaodianfu.com/wp-content/uploads/2021/07/plsa-1.jpg" width="480"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;在 pLSA 中，我们对文档进行抽样，然后根据该文档抽样主题，再根据该主题抽样一个单词。以下是 LDA 的模型：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="237" src="https://www.biaodianfu.com/wp-content/uploads/2021/07/lda.png" width="480"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;根据狄利克雷分布 Dir(α)，我们绘制一个随机样本来表示特定文档的主题分布或主题混合。这个主题分布记为θ。我们可以基于分布从θ选择一个特定的主题 Z。&lt;/p&gt;
 &lt;p&gt;接下来，从另一个狄利克雷分布 Dir()，我们选择一个随机样本来表示主题 Z 的单词分布。这个单词分布记为φ。从φ中，我们选择单词 w。&lt;/p&gt;
 &lt;p&gt;从形式上看，从文档生成每个单词的过程如下（注意，该算法使用 c 而不是 z 来表示主题）：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="256" src="https://www.biaodianfu.com/wp-content/uploads/2021/07/lda-2.png" width="720"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;通常而言，LDA 比 pLSA 效果更好，因为它可以轻而易举地泛化到新文档中去。在 pLSA 中，文档概率是数据集中的一个固定点。如果没有看到那个文件，我们就没有那个数据点。然而，在 LDA 中，数据集作为训练数据用于文档-主题分布的狄利克雷分布。即使没有看到某个文件，我们可以很容易地从狄利克雷分布中抽样得来，并继续接下来的操作。&lt;/p&gt;
 &lt;p&gt;LDA 无疑是最受欢迎（且通常来说是最有效的）主题建模技术。它在 gensim 当中可以方便地使用：&lt;/p&gt;
 &lt;pre&gt;from gensim.corpora.Dictionary import load_from_text, doc2bow
from gensim.corpora import MmCorpus
from gensim.models.ldamodel import LdaModel

document = &amp;quot;This is some document...&amp;quot;

# load id-&amp;gt;word mapping (the dictionary)
id2word = load_from_text(&amp;apos;wiki_en_wordids.txt&amp;apos;)

# load corpus iterator
mm = MmCorpus(&amp;apos;wiki_en_tfidf.mm&amp;apos;)

# extract 100 LDA topics, updating once every 10,000
lda = LdaModel(corpus=mm, id2word=id2word, num_topics=100, update_every=1, chunksize=10000, passes=1)

# use LDA model: transform new doc to bag-of-words, then apply lda
doc_bow = doc2bow(document.split())
doc_lda = lda[doc_bow]

# doc_lda is vector of length num_topics representing weighted presence of each topic in the doc
&lt;/pre&gt;
 &lt;p&gt;通过使用 LDA，我们可以从文档语料库中提取人类可解释的主题，其中每个主题都以与之关联度最高的词语作为特征。例如，主题 2 可以用诸如「石油、天然气、钻井、管道、楔石、能量」等术语来表示。此外，在给定一个新文档的条件下，我们可以获得表示其主题混合的向量，例如，5%的主题 1，70% 的主题 2，10%的主题 3 等。通常来说，这些向量对下游应用非常有用。&lt;/p&gt;
 &lt;h3&gt;深度学习中的 LDA：lda2vec&lt;/h3&gt;
 &lt;p&gt;那么，这些主题模型会将哪些因素纳入更复杂的自然语言处理问题中呢？我们谈到能够从每个级别的文本（单词、段落、文档）中提取其含义是多么重要。在文档层面，我们现在知道如何将文本表示为主题的混合。在单词级别上，我们通常使用诸如 word2vec 之类的东西来获取其向量表征。  &lt;a href="https://github.com/cemoody/lda2vec"&gt;lda2vec&lt;/a&gt; 是 word2vec 和 LDA 的扩展，它共同学习单词、文档和主题向量。&lt;/p&gt;
 &lt;p&gt;lda2vec 专门在 word2vec 的 skip-gram 模型基础上建模，以生成单词向量。skip-gram 和 word2vec 本质上就是一个神经网络，通过利用输入单词预测周围上下文词语的方法来学习词嵌入。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="396" src="https://www.biaodianfu.com/wp-content/uploads/2021/07/lda2vec.png" width="320"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;通过使用 lda2vec，我们不直接用单词向量来预测上下文单词，而是使用上下文向量来进行预测。该上下文向量被创建为两个其它向量的总和：单词向量和文档向量。&lt;/p&gt;
 &lt;p&gt;单词向量由前面讨论过的 skip-gram word2vec 模型生成。而文档向量更有趣，它实际上是下列两个组件的加权组合：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;文档权重向量，表示文档中每个主题的「权重」（稍后将转换为百分比）&lt;/li&gt;
  &lt;li&gt;主题矩阵，表示每个主题及其相应向量嵌入&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;文档向量和单词向量协同起来，为文档中的每个单词生成「上下文」向量。lda2vec 的强大之处在于，它不仅能学习单词的词嵌入（和上下文向量嵌入），还同时学习主题表征和文档表征。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="649" src="https://www.biaodianfu.com/wp-content/uploads/2021/07/lda2vec.gif" width="382"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;参考链接 ：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;a href="https://medium.com/nanonets/topic-modeling-with-lsa-psla-lda-and-lda2vec-555ff65b0b05"&gt;Topic Modeling with LSA, PLSA, LDA &amp;amp; lda2Vec&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://medium.com/analytics-vidhya/text-mining-101-a-stepwise-introduction-to-topic-modeling-using-latent-semantic-analysis-using-add9c905efd9"&gt;Text Mining 101: A Stepwise Introduction to Topic Modeling using Latent Semantic Analysis (using Python)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://ai.plainenglish.io/discovering-hidden-themes-of-documents-in-python-using-latent-semantic-analysis-5da7f8ea45ee"&gt;Discovering Hidden Themes of Documents&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://towardsdatascience.com/latent-semantic-analysis-sentiment-classification-with-python-5f657346f6a3"&gt;Latent Semantic Analysis &amp;amp; Sentiment Classification with Python&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
 &lt;div&gt;
  &lt;h3&gt;相关文章:&lt;/h3&gt;  &lt;ol&gt;
   &lt;li&gt;    &lt;a href="https://www.biaodianfu.com/arima.html" rel="bookmark" title="&amp;#26102;&amp;#38388;&amp;#24207;&amp;#21015;&amp;#39044;&amp;#27979;&amp;#20043;ARIMA"&gt;时间序列预测之ARIMA &lt;/a&gt;&lt;/li&gt;
   &lt;li&gt;    &lt;a href="https://www.biaodianfu.com/sklearn-anomaly-detection.html" rel="bookmark" title="Scikit-Learn&amp;#20013;&amp;#30340;&amp;#24322;&amp;#24120;&amp;#26816;&amp;#27979;&amp;#31639;&amp;#27861;"&gt;Scikit-Learn中的异常检测算法 &lt;/a&gt;&lt;/li&gt;
   &lt;li&gt;    &lt;a href="https://www.biaodianfu.com/xgboost.html" rel="bookmark" title="&amp;#26426;&amp;#22120;&amp;#23398;&amp;#20064;&amp;#31639;&amp;#27861;&amp;#20043;XGBoost"&gt;机器学习算法之XGBoost &lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>数据 术→技巧 法→原理 自然语言处理 语义分析</category>
      <guid isPermaLink="true">https://itindex.net/detail/61597-%E8%AF%AD%E4%B9%89%E5%88%86%E6%9E%90-lsa</guid>
      <pubDate>Wed, 07 Jul 2021 21:47:27 CST</pubDate>
    </item>
  </channel>
</rss>

