在UR5机械臂末端添加robotiq 2f 85夹爪并在Gazebo中仿真
原文连接: 在UR5机械臂末端添加robotiq 2f 85夹爪并在Gazebo中仿真
需求
在UR5机械臂末端添加robotiq 2f 85夹爪并在Gazebo中仿真
环境
ubuntu20.04 + ROS1 noetice
准备工作
- 创建工作空间
mkdir -p catkin_UR5/src
- 进入catkin_UR5/src目录,分别下载机械臂和夹爪对应的功能包
cd ~/catkin_UR5/src git clone https://github.com/ros-industrial/universal_robot.git git clone https://github.com/jr-robotics/robotiq.git
- 进入catkin_UR5工作空间目录,检查依赖关系并编译
cd ~/catkin_UR5 sudo rosdepc init rosdepc update --rosdistro=noetic rosdepc install --rosdistro noetic --ignore-src --from-paths src catkin_make
机械臂末端添加夹爪
修改夹爪功能包robotiq_2f_85_gripper_visualization
-
打开robotiq_arg2f_85_macro.xacro,其路径如下
catkin_UR5/src/robotiq/robotiq_2f_85_gripper_visualization/urdf/robotiq_arg2f_85_macro.xacro
-
注释或删除掉第196-200行的代码
这主要是因为功能包中默认夹爪的父级是base_link,而要将夹爪安装在机械臂末端,它的父级应该是机械臂末端的tool0。当然,如果要单独仿真夹爪,则需要取消这个注释。
195 <!-- base link --> 196 <!-- <link name="${prefix}base_link"/> 197 <joint name="${prefix}base_link-${prefix}robotiq_arg2f_base_link" type="fixed"> 198 <parent link="${prefix}base_link" /> 199 <child link="${prefix}robotiq_arg2f_base_link" /> 200 </joint> -->
修改夹爪功能包robotiq_2f_85_gripper_gazebo
-
打开robotiq_arg2f_85_macro.xacro,其路径如下
catkin_UR5/src/robotiq/src/robotiq/robotiq_2f_85_gripper_gazebo/urdf/robotiq_arg2f_85_macro.xacro
-
注释或删除掉第102-105行的代码
这主要是因为在catkin_UR5/src/universal_robot/ur_gazebo/urdf/ur_macro.xacro中已经添加了这个插件,因此不再重复添加。
98 <!-- 99 Inject Gazebo ROS Control plugin, which allows us to use ros_control 100 controllers to control the virtual robot hw. 101 --> 102 <!-- <gazebo> 103 <plugin name="ros_control" filename="libgazebo_ros_control.so"> 104 </plugin> 105 </gazebo> -->
添加夹爪
- 打开ur.xacro,其路径如下
catkin_UR5/src/universal_robot/ur_gazebo/urdf/ur.xacro
- 注释掉文件末尾的如下代码
<!-- <link name="tool0_controller"/> <joint name="tool_controller_fake_joint" type="fixed"> <parent link="tool0"/> <child link="tool0_controller"/> <origin xyz="0 0 0" rpy="0 0 0"/> </joint> -->
- 然后在文件末尾添加如下代码
最终,ur.xacro文件中的完整代码如下<!--继承robotiq_arg2f_85宏--> <xacro:include filename="$(find robotiq_2f_85_gripper_gazebo)/urdf/robotiq_arg2f_85_macro.xacro"/> <!--定义fixed关节,将robotiq_arg2f_base_link连接到机械臂末端的tool0--> <joint name="ur_robotiq_joint" type="fixed"> <parent link="tool0"/> <child link="robotiq_arg2f_base_link"/> <origin xyz="0 0 0" rpy="0 0 0"/> </joint> <!--调用robotiq_arg2f_85,加入夹爪的link和joint--> <xacro:robotiq_arg2f_85_gazebo prefix="" transmission_hw_interface="$(arg transmission_hw_interface)" />
<?xml version="1.0"?> <robot xmlns:xacro="http://wiki.ros.org/xacro" name="$(arg robot_model)_robot"> <!-- This is a top-level xacro instantiating the Gazebo-specific version of the 'ur_robot' macro (ie: 'ur_robot_gazebo') and passing it values for all its required arguments. This file should be considered the Gazebo-specific variant of the file with the same name in the ur_description package. It accepts the same arguments, but instead of configuring everything for a real robot, will generate a Gazebo-compatible URDF with a ros_control hardware_interface attached to it. Only use this top-level xacro if you plan on spawning the robot in Gazebo 'by itself', without any gripper or any other geometry attached. If you need to attach an end-effector, camera or need to integrate the robot into a larger workcell and want to spawn that as a single entity in Gazebo, DO NOT EDIT THIS FILE. Instead: create a new top-level xacro, give it a proper name, include the required '.xacro' files, instantiate the models (ie: call the macros) and connect everything by adding the appropriate joints. --> <!-- Import main macro. NOTE: this imports the Gazebo-wrapper main macro, NOT the regular xacro macro (which is hosted by ur_description). --> <xacro:include filename="$(find ur_gazebo)/urdf/ur_macro.xacro"/> <!--Declare arguments --> <xacro:arg name="joint_limit_params" default=""/> <xacro:arg name="physical_params" default=""/> <xacro:arg name="kinematics_params" default=""/> <xacro:arg name="visual_params" default=""/> <!-- legal values: - hardware_interface/PositionJointInterface - hardware_interface/VelocityJointInterface - hardware_interface/EffortJointInterface NOTE: this value must correspond to the controller configured in the controller .yaml files in the 'config' directory. --> <xacro:arg name="transmission_hw_interface" default="hardware_interface/EffortJointInterface"/> <xacro:arg name="safety_limits" default="false"/> <xacro:arg name="safety_pos_margin" default="0.15"/> <xacro:arg name="safety_k_position" default="20"/> <!-- Instantiate the Gazebo robot and pass it all the required arguments. --> <xacro:ur_robot_gazebo prefix="" joint_limits_parameters_file="$(arg joint_limit_params)" kinematics_parameters_file="$(arg kinematics_params)" physical_parameters_file="$(arg physical_params)" visual_parameters_file="$(arg visual_params)" transmission_hw_interface="$(arg transmission_hw_interface)" safety_limits="$(arg safety_limits)" safety_pos_margin="$(arg safety_pos_margin)" safety_k_position="$(arg safety_k_position)" /> <!-- Attach the Gazebo model to Gazebo's world frame. Note: if you're looking to integrate a UR into a larger scene and need to add EEFs or other parts, DO NOT change this file or the 'world' link here. Create a NEW xacro instead and decide whether you need to add a 'world' link there. --> <link name="world"/> <joint name="world_joint" type="fixed"> <parent link="world"/> <child link="base_link"/> <origin xyz="0 0 0" rpy="0 0 0"/> </joint> <!--继承robotiq_arg2f_85宏--> <xacro:include filename="$(find robotiq_2f_85_gripper_gazebo)/urdf/robotiq_arg2f_85_macro.xacro"/> <!--定义fixed关节,将robotiq_arg2f_base_link连接到机械臂末端的tool0--> <joint name="ur_robotiq_joint" type="fixed"> <parent link="tool0"/> <child link="robotiq_arg2f_base_link"/> <origin xyz="0 0 0" rpy="0 0 0"/> </joint> <!--调用robotiq_arg2f_85,加入夹爪的link和joint--> <xacro:robotiq_arg2f_85_gazebo prefix="" transmission_hw_interface="$(arg transmission_hw_interface)" /> <!-- <link name="tool0_controller"/> <joint name="tool_controller_fake_joint" type="fixed"> <parent link="tool0"/> <child link="tool0_controller"/> <origin xyz="0 0 0" rpy="0 0 0"/> </joint> --> </robot>
设置夹爪的控制器
-
参考官方给出的夹爪控制器配置文件,其路径如下
catkin_UR5/src/robotiq/robotiq_2f_85_gripper_gazebo/config/robotiq_2f_85_gripper_controllers.yaml
可以看到如下内容:
joint_state_controller: type: joint_state_controller/JointStateController publish_rate: &loop_hz 125 gripper_controller: type: effort_controllers/GripperActionController gains: finger_joint: {p: 10, d: 0.1, i: 1, i_clamp: 1} joint: finger_joint action_monitor_rate: 20 goal_tolerance: 0.002 max_effort: 100 stall_velocity_threshold: 0.001 stall_timeout: 1.0
-
复制“gripper_controller:”及其下方内容到ur5_controllers.yaml中,ur5_controllers.yaml的文件路径如下
catkin_UR5/src/universal_robot/ur_gazebo/config/ur5_controllers.yaml
-
修改gripper_controller的类型和关节,如下
gripper_controller: type: effort_controllers/JointTrajectoryController #修改控制器类型 gains: finger_joint: {p: 10, d: 0.1, i: 1, i_clamp: 1} joints: #修改关节 - finger_joint #修改关节 action_monitor_rate: 20 goal_tolerance: 0.002 max_effort: 100 stall_velocity_threshold: 0.001 stall_timeout: 1.0
-
最终,完整的ur5_controllers.yaml内容如下:
joint_state_controller: type: joint_state_controller/JointStateController publish_rate: &loop_hz 125 eff_joint_traj_controller: type: effort_controllers/JointTrajectoryController joints: &robot_joints - shoulder_pan_joint - shoulder_lift_joint - elbow_joint - wrist_1_joint - wrist_2_joint - wrist_3_joint gains: # Required because we're controlling an effort interface shoulder_pan_joint: {p: 4000, d: 200, i: 1, i_clamp: 1} shoulder_lift_joint: {p: 10000, d: 200, i: 1, i_clamp: 1} elbow_joint: {p: 2000, d: 20, i: 1, i_clamp: 1} wrist_1_joint: {p: 500, d: 1, i: 1, i_clamp: 1} wrist_2_joint: {p: 500, d: 1, i: 1, i_clamp: 1} wrist_3_joint: {p: 10, d: 0.1, i: 0, i_clamp: 1} constraints: goal_time: 0.6 stopped_velocity_tolerance: 0.05 shoulder_pan_joint: {trajectory: 0.1, goal: 0.1} shoulder_lift_joint: {trajectory: 0.1, goal: 0.1} elbow_joint: {trajectory: 0.1, goal: 0.1} wrist_1_joint: {trajectory: 0.1, goal: 0.1} wrist_2_joint: {trajectory: 0.1, goal: 0.1} wrist_3_joint: {trajectory: 0.1, goal: 0.1} stop_trajectory_duration: 0.5 state_publish_rate: *loop_hz action_monitor_rate: 10 gripper_controller: type: effort_controllers/JointTrajectoryController gains: finger_joint: {p: 10, d: 0.1, i: 1, i_clamp: 1} joints: - finger_joint action_monitor_rate: 20 goal_tolerance: 0.002 max_effort: 100 stall_velocity_threshold: 0.001 stall_timeout: 1.0 joint_group_eff_controller: type: effort_controllers/JointGroupEffortController joints: *robot_joints
-
打开ur5_bringup.launch文件,其路径如下
catkin_UR5/src/universal_robot/ur_gazebo/config/ur5_controllers.yaml
-
修改ur5_bringup.launch文件中controllers参数值,启动三个控制器:关节状态发布、机械臂关节控制、夹爪关节控制。
<arg name="controllers" default="joint_state_controller eff_joint_traj_controller gripper_controller" doc="Controllers that are activated by default."/>
-
夹爪防抖(可选)
我在第一次启动仿真的时候抖过一次,但是后来就没再发现抖动,因此个人认为这一步如果出现抖动就可以做一下,如果没有抖动问题就可以忽略。
在catkin_UR5/src/robotiq/robotiq_2f_85_gripper_gazebo/config/文件夹下新建gazebo_controller.yaml,并添加如下内容:
# Note: You MUST load these PID parameters for all joints that are using the # PositionJointInterface, otherwise the arm + gripper will act like a giant # parachute, counteracting gravity, and causing some of the wheels to lose # contact with the ground, so the robot won't be able to properly navigate. See # https://github.com/ros-simulation/gazebo_ros_pkgs/issues/612 ros_control: pid_gains: # these gains are used by the gazebo_ros_control plugin finger_joint: p: 20.0 i: 0.1 d: 0.0 i_clamp: 0.2 antiwindup: false publish_state: true # the following gains are used by the gazebo_mimic_joint plugin left_inner_knuckle_joint: p: 20.0 i: 0.1 d: 0.0 i_clamp: 0.2 antiwindup: false publish_state: true left_inner_finger_joint: p: 20.0 i: 0.1 d: 0.0 i_clamp: 0.2 antiwindup: false publish_state: true right_outer_knuckle_joint: p: 20.0 i: 0.1 d: 0.0 i_clamp: 0.2 antiwindup: false publish_state: true right_inner_knuckle_joint: p: 20.0 i: 0.1 d: 0.0 i_clamp: 0.2 antiwindup: false publish_state: true right_inner_finger_joint: p: 20.0 i: 0.1 d: 0.0 i_clamp: 0.2 antiwindup: false publish_state: true
在ur5_bringup.launch中加载夹爪的pid参数
<rosparam file="$(find robotiq_2f_85_gripper_gazebo)/config/gazebo_controller.yaml" command="load" />
在gazebo中仿真带有夹爪的机械臂
-
为了实现对机械臂和夹爪的控制,使用rqt_joint_trajectory_controller自带的gui_controller控制机械臂关节。在上述ur5_bringup.launch文件中,添加控制节点如下
<node name="gui_controller" pkg="rqt_joint_trajectory_controller" type="rqt_joint_trajectory_controller" />
至此,ur5_bringup.launch文件的完整内容如下
<?xml version="1.0"?> <launch> <!-- Main entry point for loading a single UR5 into Gazebo, in isolation, in the empty world. A set of ros_control controllers similar to those loaded by ur_robot_driver will be loaded by 'ur_control.launch.xml' (note: *similar*, *not* identical). This bringup .launch file is intentionally given the same name as the one in the ur_robot_driver package, as it fulfills a similar role: loading the configuration and starting the necessary ROS nodes which in the end provide a ROS API to a Universal Robots UR5. Only in this case, instead of a real robot, a virtual model in Gazebo is used. NOTE 1: as this is not a real robot, there are limits to the faithfulness of the simulation. Dynamic behaviour will be different from a real robot. Only a subset of topics, actions and services is supported. Specifically, interaction with the Control Box itself is not supported, as Gazebo does not simulate a Control Box. This means: no Dashboard server, no URScript topic and no force-torque sensor among other things. NOTE 2: users wishing to integrate a UR5 with other models into a more complex simulation should NOT modify this file. Instead, if it would be desirable to reuse this file with a custom simulation, they should create a copy and update this copy so as to accomodate required changes. In those cases, treat this file as an example, showing one way how a Gazebo simulation for UR robots *could* be launched. It is not necessary to mimic this setup completely. --> <!--Robot description and related parameter files --> <arg name="robot_description_file" default="$(dirname)/inc/load_ur5.launch.xml" doc="Launch file which populates the 'robot_description' parameter."/> <arg name="joint_limit_params" default="$(find ur_description)/config/ur5/joint_limits.yaml"/> <arg name="kinematics_params" default="$(find ur_description)/config/ur5/default_kinematics.yaml"/> <arg name="physical_params" default="$(find ur_description)/config/ur5/physical_parameters.yaml"/> <arg name="visual_params" default="$(find ur_description)/config/ur5/visual_parameters.yaml"/> <!-- Controller configuration --> <arg name="controller_config_file" default="$(find ur_gazebo)/config/ur5_controllers.yaml" doc="Config file used for defining the ROS-Control controllers."/> <!-- 启动三个控制器:关节状态发布、机械臂关节控制、夹爪关节控制 --> <arg name="controllers" default="joint_state_controller eff_joint_traj_controller gripper_controller" doc="Controllers that are activated by default."/> <arg name="stopped_controllers" default="joint_group_eff_controller" doc="Controllers that are initally loaded, but not started."/> <!-- 加载夹爪的pid参数,如果不添加这一步,仿真环境中夹爪会一直抖动 --> <!-- <rosparam file="$(find robotiq_2f_85_gripper_gazebo)/config/gazebo_controller.yaml" command="load" /> --> <!-- robot_state_publisher configuration --> <arg name="tf_prefix" default="" doc="tf_prefix used for the robot."/> <arg name="tf_pub_rate" default="125" doc="Rate at which robot_state_publisher should publish transforms."/> <!-- Gazebo parameters --> <arg name="paused" default="false" doc="Starts Gazebo in paused mode" /> <arg name="gui" default="true" doc="Starts Gazebo gui" /> <!-- Load urdf on the parameter server --> <include file="$(arg robot_description_file)"> <arg name="joint_limit_params" value="$(arg joint_limit_params)"/> <arg name="kinematics_params" value="$(arg kinematics_params)"/> <arg name="physical_params" value="$(arg physical_params)"/> <arg name="visual_params" value="$(arg visual_params)"/> </include> <!-- Robot state publisher --> <node pkg="robot_state_publisher" type="robot_state_publisher" name="robot_state_publisher"> <param name="publish_frequency" type="double" value="$(arg tf_pub_rate)" /> <param name="tf_prefix" value="$(arg tf_prefix)" /> </node> <!-- 添加控制节点 --> <node name="gui_controller" pkg="rqt_joint_trajectory_controller" type="rqt_joint_trajectory_controller" /> <!-- Start the 'driver' (ie: Gazebo in this case) --> <include file="$(dirname)/inc/ur_control.launch.xml"> <arg name="controller_config_file" value="$(arg controller_config_file)"/> <arg name="controllers" value="$(arg controllers)"/> <arg name="gui" value="$(arg gui)"/> <arg name="paused" value="$(arg paused)"/> <arg name="stopped_controllers" value="$(arg stopped_controllers)"/> </include> </launch>
-
在catkin_UR5工作空间目录下,激活工作空间,并启动相应的launch文件
cd ~/catkin_UR5 source ./devel/setup.bash roslaunch ur_gazebo ur5_bringup.launch
如果出现如下类似报错
ERROR: cannot launch node of type [rqt_joint_trajectory_controller/rqt_joint_trajectory_controller]: rqt_joint_trajectory_controller ROS path [0]=/opt/ros/noetic/share/ros ROS path [1]=/home/tianming/catkin_UR5/src ROS path [2]=/opt/ros/noetic/share
说明没有安装rqt_joint_trajectory_controller,使用如下命令安装即可
sudo apt-get install ros-noetic-rqt-joint-trajectory-controller
gazebo启动后,可以看到机械臂成功添加夹爪,拖动gui的滚动条可以实现对机械臂关节和夹爪的控制。
附录A 查看并修改文件的一些技巧
-
如何知道应该修改哪些文件
首先应该对Universal Robots的相关功能包有一定了解,打开UR功能包的github链接
https://github.com/ros-industrial/universal_robot
可以看到如下的文件结构
从中可以找到用于ur5机械臂gazebo仿真的launch文件,即:
universal_robot/ur_gazebo/launch/ur5_bringup.launch
查看该文件,可以发现它包含了load_ur5.launch.xml:
<!--Robot description and related parameter files --> <arg name="robot_description_file" default="$(dirname)/inc/load_ur5.launch.xml" doc="Launch file which populates the 'robot_description' parameter."/> <!-- Load urdf on the parameter server --> <include file="$(arg robot_description_file)"> <arg name="joint_limit_params" value="$(arg joint_limit_params)"/> <arg name="kinematics_params" value="$(arg kinematics_params)"/> <arg name="physical_params" value="$(arg physical_params)"/> <arg name="visual_params" value="$(arg visual_params)"/> </include>
因此,继续查看load_ur5.launch.xml文件,可以发现它包含了load_ur.launch.xml:
<!-- Use common launch file and pass all arguments to it --> <include file="$(dirname)/load_ur.launch.xml" pass_all_args="true"> <arg name="robot_model" value="ur5" /> </include>
进一步查看load_ur.launch.xml文件,发现其加载的模型为ur_gazebo功能包下的urdf/ur.xacro:
<param name="robot_description" command="$(find xacro)/xacro '$(find ur_gazebo)/urdf/ur.xacro' robot_model:=$(arg robot_model) joint_limit_params:=$(arg joint_limit_params) kinematics_params:=$(arg kinematics_params) physical_params:=$(arg physical_params) visual_params:=$(arg visual_params) transmission_hw_interface:=$(arg transmission_hw_interface) safety_limits:=$(arg safety_limits) safety_pos_margin:=$(arg safety_pos_margin) safety_k_position:=$(arg safety_k_position)" />
查看ur.xacro文件,发现创建用于gazebo仿真的机械臂的代码就在此文件中:
<!-- Import main macro. NOTE: this imports the Gazebo-wrapper main macro, NOT the regular xacro macro (which is hosted by ur_description). --> <xacro:include filename="$(find ur_gazebo)/urdf/ur_macro.xacro"/> <!-- Instantiate the Gazebo robot and pass it all the required arguments. --> <xacro:ur_robot_gazebo prefix="" joint_limits_parameters_file="$(arg joint_limit_params)" kinematics_parameters_file="$(arg kinematics_params)" physical_parameters_file="$(arg physical_params)" visual_parameters_file="$(arg visual_params)" transmission_hw_interface="$(arg transmission_hw_interface)" safety_limits="$(arg safety_limits)" safety_pos_margin="$(arg safety_pos_margin)" safety_k_position="$(arg safety_k_position)" />
此时初步确定,可以在此文件中创建机械臂的代码后面添加夹爪相关代码。进一步查看ur_gazebo功能包下的urdf/ur_macro.xacro文件,可以发现它是先用ur_description功能包下的/urdf/inc/ur_macro.xacro提供的宏函数ur_robot进行机械臂的创建,然后又添加的gazebo相关的代码。因此描述机械臂的代码路径为
universal_robot/ur_description/urdf/inc/ur_macro.xacro
接下来就是夹爪应该添加在机械臂的哪个link上,所以我们需要了解机械臂的结构。机械臂的创建使用了宏函数,而该宏函数来自于ur_description功能包下的/urdf/inc/ur_macro.xacro,通过阅读该宏函数,我们可以发现,夹爪应该添加在名为tool0的link上。此外,还可以在添加夹爪前,通过rviz显示机械臂:
cd ~/catkin_UR5 source ./devel/setup.bash roslaunch ur_description view_ur5.launch
在rviz中可以查看机械臂的层级关系
下图是安装夹爪后的层级关系,可以看到机械臂的末端为tool0
查看夹爪的结构同理。带有夹爪的机械臂结构如下
它们分别对应的link和joint名称为
底座 ----------------> ${prefix}base_link 肩连杆与底座的关节 ---> ${prefix}shoulder_pan_link 肩连杆 --------------> ${prefix}shoulder_link 肩关节 --------------> ${prefix}shoulder_lift_joint 上臂 ----------------> ${prefix}upper_arm_link 肘关节 --------------> ${prefix}elbow_joint 前臂 ----------------> ${prefix}forearm_link 腕关节1 -------------> ${prefix}wrist_1_joint 腕连杆1 -------------> ${prefix}wrist_1_link 腕关节2 -------------> ${prefix}wrist_2_joint 腕连杆2 -------------> ${prefix}wrist_2_link 腕关节3 -------------> ${prefix}wrist_3_joint 腕连杆3 -------------> ${prefix}wrist_3_link 末端:法兰盘和空工具 --> ${prefix}flange和${prefix}tool0(连接夹爪)
最终,我们可以在universal_robot/ur_gazebo/urdf/ur.xacro文件中添加夹爪。
至于控制功能,同样可以从universal_robot/ur_gazebo/launch/ur5_bringup.launch回溯找到;universal_robot/ur_gazebo/config文件夹下的配置文件夹下有ur5_controllers.yaml也很明显是机械臂控制相关的配置,我们可以在里面增加有关夹爪的控制配置。
附录B 关于我参考的一些教程
参考教程里面最多的可能就是robotiq夹爪存在被动关节(Mimic Joint),标准Gazebo不支持Mimic关节仿真, 所以需要安装一个插件。但现在robotiq官方已经解决了这个问题,而且用的是最新的插件,所以不再需要关心这件事情了。
参考
- 本文之后,世上再无rosdep更新失败问题!如果有…小鱼就…
- rosdepc update常见错误及解决方案
- UR5机械臂仿真实例(三)2 末端添加robotiq夹爪[ubuntu20.04+ROSnoetic+gazebo11]
- 【Autolabor初级教程】ROS机器人入门
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?